Bài viết mới:

Hiển thị các bài đăng có nhãn C#. Hiển thị tất cả bài đăng
Hiển thị các bài đăng có nhãn C#. Hiển thị tất cả bài đăng

Chủ Nhật, 14 tháng 5, 2017

Tìm hiểu LINQ và sử dụng LINQ trên C#


1.  Giới thiệu về LINQ


Language-Integrated Query (LINQ) là một sự đổi mới được giới thiệu trong Visual Studio 2008 và .NET Framework phiên bản 3.5.

Theo truyền thống, các truy vấn dữ liệu được thể hiện dưới dạng các chuỗi mà không cần kiểm tra kiểu tại thời gian biên dịch hoặc hỗ trợ IntelliSense và bạn phải học một ngôn ngữ truy vấn khác nhau cho từng loại nguồn dữ liệu ví dụ như: cơ sở dữ liệu SQL, tài liệu XML, dịch vụ web khác nhau, ... LINQ giúp nhúng truy vấn vào ngôn ngữ lập trình (đúng như tên gọi của nó). Tức là bạn có thể sử dụng C# hay Visual Basic để có thể truy vấn đến mọi nguồn dữ liệu mà không nhất thiết phải biết các loại ngôn ngữ truy vấn riêng biệt để có thể thao tác với chúng. Bạn sẽ viết truy vấn sử dụng từ khóa và toán tử quen thuộc của ngôn ngữ lập trình. Hình minh họa dưới đây cho thấy một truy vấn LINQ kết nối đến 1 cơ sở dữ liệu SQL Server trong C# và Visual Basic:




Trong Visual Studio, bạn có thể viết các truy vấn LINQ trong Visual Basic hoặc C# đến cơ sở dữ liệu SQL Server, các tài liệu XML, tập dữ liệu ADO.NET, và bất kỳ collections của của đối tượng có hỗ trợ interface IEnumerable hoặc IEnumerable<T>. LINQ cũng hỗ trợ cho ADO.NET Entity Framework, và các LINQ provider đang được viết bởi bên thứ ba cho nhiều dịch vụ Web và triển khai cơ sở dữ liệu khác.

Để sử dụng LINQ thì dự án của bạn phải đang chạy trên nền tảng .NET Framework 3.5 hoặc cao hơn.

2.  Sử dụng LINQ trên C#


a. Giới thiệu về LINQ Queries (C#)


Một truy vấn là một biểu thức để lấy dữ liệu từ một nguồn dữ liệu. Các truy vấn thường được biểu thị bằng một ngôn ngữ truy vấn cụ thể. Trước đây với mỗi nguồn dữ liệu thì sẽ đi kèm với ngôn ngữ riêng để truy vấn trên nguồn dữ liệu đó, ví dụ SQL cho cơ sở dữ liệu quan hệ và XQuery cho XML. Do đó, các nhà phát triển đã phải học một ngôn ngữ truy vấn mới cho mỗi loại nguồn dữ liệu. LINQ đơn giản hoá vấn đề này bằng cách tích hợp truy vấn vào trong ngôn ngữ lập. Trong một truy vấn LINQ, bạn luôn làm việc với các đối tượng (object). Bạn sử dụng cùng một truy vấn và chuyển đổi dữ liệu trong các tài liệu XML, cơ sở dữ liệu SQL, ADO.NET Datasets, .NET collections và bất kỳ định dạng nào khác mà LINQ provider có sẵn. 

Truy vấn LINQ chia thành 3 thành phần sau:
  1. Chuẩn bị nguồn dữ liệu
  2. Tạo câu lệnh truy vấn LINQ
  3. Thực thi truy vấn để lấy kết quả

Ví dụ sau đây cho thấy cách hoạt động của 3 thành phần trên. Ví dụ sử dụng một mảng số nguyên như là một nguồn dữ liệu. Ví dụ này được sử dụng trong suốt phần còn lại của chủ đề này.

      
        //  1. Nguồn dữ liệu.
        int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

        // 2. Tạo câu lệnh truy vấn:
        // Lấy ra các số chẵn trong mảng numbers
        var numQuery =
            from num in numbers
            where (num % 2) == 0
            select num;

        // 3. Thực thi truy vấn.
        foreach (int num in numQuery)
        {
            Console.Write("{0,1} ", num);
        }

Hình minh họa dưới đây cho thấy cách hoạt động của LINQ.



Các biểu thức truy vấn có chứa ba mệnh đề: from, where, select. (Nếu bạn đã quen thuộc với SQL, bạn sẽ nhận thấy rằng cách sắp xếp trật tự của các mệnh đề ngược lại với trật tự trong SQL). Mệnh đề from chỉ ra nguồn dữ liệu, mệnh đề where áp dụng để lọc dữ liệu theo 1 điều kiện nào đó, và mệnh đề select chỉ ra kiểu của kết quả trả về. Những mệnh đề này và một số mệnh đề khác được thảo luận chi tiết trong phần LINQ Query Expressions (C# Programming Guide).

Chú ý: Truy vấn LINQ cũng có thể được xây dựng bằng cách sử dụng phương thức mở rộng (method systax) thay vì  dùng theo kiểu Query syntax như ví dụ. Để biết thêm thông tin, xem Query Syntax and Method Syntax in LINQ (C#).

Trì hoãn thực thi


Các biến truy vấn mặc định chỉ lưu trữ các lệnh truy vấn. Các hoạt động truy vấn được hoãn lại cho đến khi bạn lặp qua các biến truy vấn trong một câu lệnh foreach. Khái niệm này được gọi là deferred execution (trì hoãn thực thi) và được thể hiện trong ví dụ sau:

//Khi vòng lặp được chạy thì truy vấn mới được thực thi và gán kết quả truy vấn qua biến num.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}


Câu lệnh foreach cũng là nơi mà các kết quả truy vấn được lấy ra. Ví dụ, trong truy vấn trên, mỗi lần lặp thì biến num giữ mỗi giá trị khác nhau (tại mỗi thời điểm) bởi vì mỗi lần lặp là mỗi lần trình biên dịch thực thi query và lấy dữ liệu từ CSDL.

Buộc thực thi ngay


Truy vấn thực hiện chức năng thống kê phải duyệt qua những phần tử. Ví dụ như các truy vấn Count, Max, Min, Sum, Average, First. Đó là những truy vấn mà sự thực thi truy vấn đó mà không có một câu lệnh foreach rõ ràng vì các truy vấn tự nó phải sử dụng ngầm foreach để trả lại kết quả. Ví dụ truy vấn dùng mệnh đề Sum sẽ ngầm dùng vòng lặp foreach để duyệt qua tất cả các phần tử để thực hiện tính tổng. Lưu ý rằng các loại truy vấn thống kê sẽ trả về 1 giá trị duy nhất do đó kiểu trả về không phải là 1 queryable. Các truy vấn sau đây trả về số lượng các số chẵn trong mảng numbers:

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Buộc thực thi ngay và lưu lại kết quả vào 1 collection hoặc generic collection, bạn có thể sử dụng phương thức ToList<TSource> hoặc ToArray<TSource> . Ví dụ:

List<int> numQuery2 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToList();

// hoặc dùng cách này:
// numQuery3 sẽ có kiểu là 1 mảng int[]

var numQuery3 =
    (from num in numbers
     where (num % 2) == 0
     select num).ToArray();


Bằng cách gọi ToList hoặc ToArray bạn sẽ lưu trữ tất cả các dữ liệu trong một đối tượng collection nằm trong bộ nhớ để sử dụng dữ liệu đó và truy vấn tiếp về sau trên collection thay vì cứ phải truy xuất đến dữ liệu mỗi khi thực thi truy vấn.

b. LINQ và kiểu Generic


Truy vấn LINQ dựa trên các kiểu generic, mà được giới thiệu trong .NET Framework 2.0. Bạn không cần 1 kiến thức chuyên sâu về Generic để có thể bắt đầu viết truy vấn LINQ. Tuy nhiên, bạn cần phải biết 2 khái niệm cơ bản sau đây:
  1. Khi bạn tạo một đối tượng của một 1 class Generic Collection như List<T>, bạn sẽ thay thế chữ “T” với chính xác kiểu dữ liệu của đối tượng mà danh sách giữ. Ví dụ, List<string> đại diện cho một danh sách chuỗi hoặc 1 danh sách các Customer có thể được khai báo là List<Customer>. 
  2. IEnumerable<T> là interface cho phép các lớp Collection Generic có thể dùng câu lệnh lặp foreach. Các lớp Generic Collection hỗ trợ IEnumerable<T> cũng giống như các lớp non-generic như ArrayList hỗ trợ IEnumerable .
Để biết thêm thông tin về generics, xem Generics (C# Programming Guide).

c. Sử dụng kiểu var


Nếu bạn thích, bạn có thể tránh được cú pháp generic bằng từ khóa var. Từ khóa var chỉ thị trình biên dịch tự định kiểu theo giá trị được gán. Tuy nhiên nếu truy vấn LINQ trả về 1 kiểu vô danh (anonymos) thì bạn bắt buộc phải dùng kiểu var.

Ví dụ 1 (có thể dùng var):


    var customerQuery =

    from cust in customers

    where cust.City == "London"
    select cust;

Ví dụ 2 (bắt buộc dùng var):


    var customerQuery =

    from cust in customers

    where cust.City == "London"
    selet new { ID = cust.ID, CityName = cust.City };   // truy vấn trả về kiểu vô danh

3. Truy vấn cơ bản LINQ (C#)


Chủ đề này giới thiệu ngắn gọn các biểu thức truy vấn LINQ và một số mệnh đề điển hình mà bạn thực hiện trong một truy vấn. thông tin chi tiết hơn có trong các chủ đề sau:


Lấy 1 nguồn dữ liệu 


Trong một truy vấn LINQ, bước đầu tiên là xác định nguồn dữ liệu. Trong C# và hầu hết các ngôn ngữ lập trình khác một biến phải được khai báo trước khi nó có thể được sử dụng. Trong một truy vấn LINQ, mệnh đề from được viết đầu tiên để khai báo nguồn dữ liệu, đi kèm là biến phạm vi.

Trong ví dụ dưới đây nguồn dữ liệu là customers và biến phạm vi là cust: 

//queryAllCustomers is an IEnumerable<Customer>
var queryAllCustomers = from cust in customers
                        select cust;


Biến phạm vi giống như các biến lặp trong một vòng lặp foreach ngoại trừ việc không có 1 sự lặp lại thực sự xảy ra trong một biểu thức truy vấn. Khi truy vấn được thực thi, biến phạm vi sẽ có nhiệm vụ như là một tham chiếu đến từng phần tử kế tiếp trong customers. Bởi vì trình biên dịch có thể suy ra kiểu của biến cust , bạn không cần phải xác định kiểu tường minh.

Lọc (Filter)


Có lẽ 1 trong các hoạt động truy vấn phổ biến nhất là áp dụng một bộ lọc. Bộ lọc khiến cho truy vấn trả về chỉ những phần tử thỏa yêu cầu. Sử dụng mệnh đề where kèm theo một biểu thức boolean để thực hiện lọc. Trong ví dụ sau đây, chỉ những custommers có địa chỉ ở London được trả về.

var queryLondonCustomers = from cust in customers
                           where cust.City == "London"
                           select cust;

Bạn có thể sử dụng toán tử AND (&&) và OR (||) quen thuộc trong C# để áp dụng nhiều biểu thức lọc khi cần thiết. Ví dụ, để trả về chỉ có khách hàng từ "London" và có tên là "Devon" bạn sẽ viết đoạn code sau:

var queryLondonCustomers = from cust in customers
                           where cust.City=="London" && cust.Name == "Devon"
                           select cust;

Trả về chỉ những khách hàng đến từ London hoặc Pari bạn sẽ viết đoạn code sau:

var queryLondonCustomers = from cust in customers
                                                      where cust.City == "London" || cust.City == "Pari"                               select cust;

Để biết thêm thông tin, xem where clause (C# Reference)

Sắp xếp (Sorting)


Các mệnh đề orderby mặc định sẽ sắp xếp phần tử theo thứ tự tăng dần . Ví dụ, truy vấn sau đây thực hiện sắp xếp danh sách customers theo thứ tự tăng dần theo Name.

    var queryLondonCustomers3 =
    from cust in customers
    where cust.City == "London"
    orderby cust.Name ascending
    select cust;

Ngược lại, để sắp xếp giảm dần sử dụng mệnh đề orderby…descending

    var queryLondonCustomers3 =
    from cust in customers
    where cust.City == "London"
    orderby cust.Name descending
    select cust;

Gom nhóm (Group)


Mệnh đề group cho phép bạn nhóm kết quả trả về dựa trên một khóa (key) mà bạn chỉ định. Ví dụ bạn có thể chỉ định kết quả trả về sẽ được nhóm theo City để có thể phân các khách hàng ở London và Paris thành các nhóm riêng. Trong trường hợp này Cust.City là 1 khóa (key).

  // queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>>
  var queryCustomersByCity =
      from cust in customers
      group cust by cust.City;

  // customerGroup is an IGrouping<string, Customer>
  foreach (var customerGroup in queryCustomersByCity)
  {
      Console.WriteLine(customerGroup.Key);
      foreach (Customer customer in customerGroup)
      {
          Console.WriteLine("    {0}", customer.Name);
      }
  }


Khi bạn kết thúc truy vấn với mệnh đề group, kết quả trả về của bạn mang hình thức như một danh sách của danh sách.

Nếu bạn cần tham chiếu các kết quả của một group operation, bạn có thể sử dụng từ khóa into để tạo ra một định danh có thể được truy vấn thêm. Các chỉ truy vấn sau trả về những nhóm có chứa nhiều hơn hai khách hàng.

// custQuery is an IEnumerable<IGrouping<string, Customer>>
  var custQuery =
    from cust in customers
    group cust by cust.City into custGroup
    where custGroup.Count() > 2
    orderby custGroup.Key
    select custGroup;
  }

Để biết thêm thông tin, xem group clause (C# Reference).

Nối (Joining)


Sử dụng mệnh đề join để có thể trả về 1 kết quả được tổng hợp từ nhiều nguồn lại với nhau. Ví dụ, bạn có thể sử dụng join để tìm tất cả các khách hàng và nhà phân phối có cùng một vị trí.

    var innerJoinQuery =
    from cust in customers
    join dist in distributors on cust.City equals dist.City
    select new { CustomerName = cust.Name, DistributorName = dist.Name };


Để biết thêm thông tin, xem join clause (C# Reference).

Chọn (Selection)


Mệnh đề select xác định kiểu trả về của truy vấn LINQ. Ví dụ, bạn có thể xác định xem kết quả của bạn sẽ trả về 1 đối tượng Customer hoàn chỉnh với đầy đủ các thuộc tính hoặc một số loại kết quả hoàn toàn khác nhau dựa trên tính toán hay tạo đối tượng mới.

Khi bạn dùng mệnh select để trả về 1 kiểu dữ liệu mới thì đó được gọi là chiếu( projection). Việc sử dụng projection để chuyển đổi dữ liệu là một khả năng mạnh mẽ của các biểu thức truy vấn LINQ. 

Để biết thêm thông tin, xem Data Transformations with LINQ (C#) và select clause (C# Reference).

4. Lời kết


LINQ là 1 công nghệ rất mạnh trong .NET Framework, nhờ LINQ thì việc truy vấn dữ liệu từ bất cứ nguồn dữ liệu nào đều dễ dàng và thống nhất 1 cú pháp chung. Người ta ví rằng 1 developer học C# nhưng không biết đến LINQ cũng giống như khi bạn sang Trung Quốc nhưng không đến Vạn Lý Trường Thành :)). Hi vọng qua biết viết này đã giúp bạn hiểu rõ về LINQ và sử dụng thành thạo nó trong nhiều dự án sắp tới. 

Chủ Nhật, 9 tháng 4, 2017

Đăng nhập bằng tài khoản Google Plus trong ASP.NET MVC

Mình đã có bài viết hướng dẫn đăng nhập bằng mạng xã hội Facebook, Google Plus, Twitter trên series "Hướng dẫn sử dụng ASP.NET Identity", tuy nhiên công nghệ ASP.NET Identity chỉ hỗ trợ phiên bản MVC 5 trở lên, còn ở bài viết này mình sẽ hướng dẫn bạn đăng nhập bằng tài khoản Google Plus trên website ASP.NET MVC bằng cách sử dụng thư viện ASPSnippets.GoogleAPI. Cách làm này thích hợp cho cả phiên bản MVC 3 và 4.

Hình demo



Bước 1: Bạn cần tạo 1 ứng dụng trên trang https://console.developers.google.com để có thể nhận 2 giá trị Client ID và Client Secret vì method API của Google yêu cầu bạn truyền 2 giá trị này. Chi tiết cách tạo ứng dụng bạn truy cập địa chỉ https://developers.google.com/identity/sign-in/web/devconsole-project.

Trong phần cấu hình Credentials, bạn thêm giá trị của thuộc tính Authorized JavaScript origins là diachiwebcuaban, giá trị của thuộc tính Authorized redirect URIs là diachiawebcuaban/LoginSocial/LoginWithGooglePlus Xem ví dụ hình dưới mình cấu hình với url là http://localhost:50077.



Có thể bạn sẽ thắc mắc vì sao giá trị của Authorized redirect URIs lại là vậy? Câu trả lời là bởi vì khi người dùng chấp nhận cung cấp quyền cho phép ứng dụng xem thông tin tiểu sử và email của họ thì ứng dụng sẽ chuyển hướng về URL như trên để xử lý. Chắc chắn chúng ta sẽ phải tạo LoginSocialController và action method LoginWithGooglePlus trong LoginSocialController để xử lý lưu thông tin người dùng lấy được từ Google API.

Bước 2: Tải source demo tại địa chỉ https://goo.gl/ZtmiBm

Bước 3: Giải nén file vừa tải ra bạn sẽ thấy file ASPSnippets.GoogleAPI.dll trong thư mục Bin. Bạn tiến hành Add Reference file .dll này vào project của bạn như hình dưới.






Bước 4: Tiến hành tạo các class sau để chứa thông tin user lấy được từ Google API sau khi xác thực thành công.

public class GoogleProfile
{
    public string Id { get; set; }
    public string DisplayName { get; set; }
    public ImageProfile Image { get; set; }
    public List<Email> Emails { get; set; }
    public string Gender { get; set; }
    public string ObjectType { get; set; }
}
public class Email
{
    public string Value { get; set; }
    public string Type { get; set; }
}
public class ImageProfile
{
    public string Url { get; set; }
}

Bước 5: Tạo LoginSocialController và khai báo thêm 3 namespace sau.

using ASPSnippets.GoogleAPI;
using System.Web.Script.Serialization;
using System.Net;

Bước 6: Tạo action method LoginWithGooglePlus trong LoginSocialController. Nội dung bên trong như sau:

[HttpPost]
[ValidateAntiForgeryToken]
public void LoginWithGooglePlus()
{
            GoogleConnect.ClientId = "Client-ID-Here";
            GoogleConnect.ClientSecret = "Client-Secret-Here";
            GoogleConnect.RedirectUri = Request.Url.AbsoluteUri.Split('?')[0];
            GoogleConnect.Authorize("profile", "email");
}

Bạn thay Client-ID-Here và Client-Secret-Here thành giá trị Client ID và Client Secret từ ứng dụng Google API bạn vừa tạo.

Ý nghĩa đoạn lệnh trên như sau:

- 2 dòng đầu gán giá trị Client ID và Client Secret qua 2 thuộc tính ClientID và ClientSecret của class GoogleConnect.

- Dòng thứ 3 chính là gán URL chuyển hướng qua thuộc tính RedirectUri. Giá trị này chắc chắn sẽ có giá trị giống như giá trị chúng ta đã thiết lập cho Authorized redirect URIs ở trên thông qua method Split. Mình viết như vậy vì muốn lấy giá trị động thay vì truyền giá trị tĩnh thì sẽ không hay.

- 3 dòng đầu cần phải có là bởi vì ở dòng cuối chúng ta gọi method Authorize(),  method này yêu cầu chúng ta phải gửi kèm giá trị ClientID, ClientSecret, RedirectUri ở trên để yêu cầu quyền lấy thông tin tiểu sử (profile) và email từ người dùng.

Bước 7: Tạo thêm action method LoginWithGooglePlusConfirmed() trong LoginSocialController như sau:

  [ActionName("LoginWithGooglePlus")]
        public ActionResult LoginWithGooglePlusConfirmed()
        {
            if (!string.IsNullOrEmpty(Request.QueryString["code"]))
            {
                string code = Request.QueryString["code"];
                string json = GoogleConnect.Fetch("me", code);
                GoogleProfile profile = new JavaScriptSerializer().Deserialize<GoogleProfile>(json);
               
                BlogITEntities db = new BlogITEntities();

                if(db.KhachHangs.Find(profile.Id) != null)
                {
                    Session["khachhang"] = db.KhachHangs.Find(profile.Id);
                    return RedirectToAction("Index", "Home");
                }
                KhachHang khachHang = new KhachHang()
                {
                    ID = profile.Id,
                    Name = profile.DisplayName,
                    Email = profile.Emails.Find(email => email.Type == "account").Value,
                    Gender = profile.Gender,
                    Type = profile.ObjectType,
                    ImageURL = profile.Id + System.IO.Path.GetExtension(profile.Image.Url.Split('?')[0])
                };
                using (var client = new WebClient())
                {
                    client.DownloadFile(profile.Image.Url, Server.MapPath("~/Photos/Users/" + khachHang.ImageURL));
                }
                Session["khachhang"] = khachHang;
                db.KhachHangs.Add(khachHang);
                db.SaveChanges();
            }
            if (Request.QueryString["error"] == "access_denied")
            {
                return Content("access_denied");
            }
            return RedirectToAction("Index", "Home");

        }

Đây là  action sẽ xử lý lưu thông tin người dùng từ Google API truyền về (bởi vì URL của action này trùng với RedirectUri ). 

Chú ý: đoạn lệnh trên mình dùng Linq to Entity Framework để lưu thông tin người dùng vào bảng KhachHang trong CSDL của mình. Bạn phải chỉnh sửa lại đoạn lưu thông tin cho đúng với công nghệ kết nối CSDL bạn đang dùng (có thể là ADO.NET, Linq to SQL, ...). Việc này không khó nếu bạn rành C#, quan trọng là chúng ta đã lấy được thông tin người dùng từ biến profile.

Lệnh "using (var client = new WebClient()){ client.DownloadFile(profile.Image.Url, Server.MapPath("~/Photos/Users/" + khachHang.ImageURL)); }" để tải hình đại diện người dùng vào thư mục Photos/Users mình đã tạo sẵn.

Và khi đăng nhập thành công thì chúng ta cần tạo 1 biến Session, mình đặt là Session["khachhang"] và gán giá trị cho Session này bằng đúng đối tượng khachHang. Bạn chú ý rằng lệnh "if(db.KhachHangs.Find(profile.Id) != null)" để kiểm tra xem trong CSDL đã có user với Id lấy từ Google API chưa nếu có rồi thì không add thêm record vào csdl nữa.

Tiếp theo là lệnh "if (Request.QueryString["error"] == "access_denied")" nhằm kiểm tra nếu người dùng không cho phép ứng dụng truy cập thông tin của họ thì chúng ta sẽ trả về dòng báo lỗi access_denied.

Bước 8: Dán đoạn lệnh sau vào view bạn muốn hiển thị nút Login.

<form action="@Url.Action("LoginWithGooglePlus", "LoginSocial")" method="post">
   @Html.AntiForgeryToken()
   <button type="submit" class="btn btn-danger"><i class="fa fa-google-plus"></i>  Login with Google + </button>

</form>

Form có action gọi đến action method LoginWithGooglePlus có attribute [HttpPost] của chúng ta đã tạo trước đó.

Vậy là đã xong. Đây là kết quả của mình, bạn thử test xem có thành công như mình không nhé.

Click button login







Đăng nhập thành công




Lưu vào CSDL thành công.

Chúc bạn cài đặt thành công. Mọi thắc mắc bạn comment dưới bài viết nhé.