Cơ chế Routing trong ASP.NET Core

Một trong các thành phần quan trọng nhất của kiến trúc MVC là cơ chế routing (định tuyến). Nó là cơ chế quyết định xem Controller nào sẽ được xử lý request nào. Bài này chúng ta sẽ tìm hiểu Routing làm việc ra sao trong ứng dụng ASP.NET Core.

Routing là gì?

Routing là một quá trình khi ASP.NET Core xem xét các URL request gửi đến và "chỉ đường" cho nó đến Controller Actions. Nó cũng được sử dụng để tạo ra URL đầu ra. Quá trình này được đảm nhiệm bởi Routing Middleware. Routing Middleware có sẵn trong thư viện Microsoft.AspNetCore.Routing.

Routing có 2 trách nhiệm chính:

  • Nó map request đến vào Controller Action.
  • Tạo ra URL đầu ra tương ứng với Controller action.

 

Routing làm việc ra sao?

Mô hình dưới đây mô tả cách làm việc của cơ chế Routing trong ASP.NET Core:

 

Khi request đến thì Routing Middleware sẽ làm những việc sau đây:

  1. Phân tích URL
  2. Tìm kiếm xem có cái Route nào match trong RouteCollection
  3. Nếu Route tìm thấy thì đẩy nó sang RouteHandler
  4. Nếu không tìm thấy Route nào thì bỏ qua và gọi middleware tiếp theo

 Route là gì?

Route tương tự như bản đồ. Chúng ta sử dụng bản đồ để đi đến điểm đích. Tương tự như thế, ứng dụng ASP.NET Core sử dụng Route để đến controller action.

Mỗi Route bao gồm các thông tin như tên, mẫu URL (URL pattern) hay còn gọi là template url, thông tin controller action mặc định và ràng buộc (constraints). URL pattern được so sánh với URL đến xem có đúng mẫu không. Một ví dụ của URL pattern là: {controller=Home}/{action=Index}/{id?}

Route được định nghĩa trong Microsoft.AspNetCore.Routing.

Route Collection là gì?

Route Collection là một tập hợp tất cả ác Route trong ứng dụng. Một ứng dụng sẽ lưu một tập hợp các route ở một nơi duy nhất trong bộ nhớ. Các Route này sẽ thêm vào collection khi ứng dụng khởi động. Route Module sẽ tìm kiếm một Route match với URL request đến trong mỗi một Route của Route Collection. Route Collection được định nghĩa trong Microsoft.AspNetCore.Routing.

Route Handler là gì?

Route Handler là một thành phần quyết định sẽ làm gì với Route. Khi cơ chế routing tìm được một Route thích hợp cho một request đến, nó sẽ gọi đến RouteHandler và gửi route đó cho RouteHandler xử lý. Route Handler là class triển khai từ interface IRouteHandler. Trong ASP.NET Core thì Route được xử lý bởi MvcRouteHandler.

MVCRouteHandler

Đây là Route Handler mặc định của ASP.NET Core MVC Middleware. MVCRouteHandler được đăng ký khi đăng ký MVC Middleware. Bạn có thể ghi đè việc này bằng cách tự tạo cho mình một custom implementation của Route Handler.

MVCRouteHandler được định nghĩa trong namespace: Microsoft.AspnetCore.Mvc

MVCRouteHandler có trách nhiệm gọi Controller Factory, sau đó nó sẽ tạo ra một thể hiện của Controller được ghi trong Route. Controller sẽ được nhận và nó sẽ gọi một Action Memthod và tạo ra View. Vậy là hoàn thành request.

Làm sao để cài đặt Routes

Có hai cách khách nhau để cài đặt route:

  1. Convention-based routing
  2. Attribute routing

 Convention-based routing

Convention based routing tạo ra Route dựa trên một loạt các quy tắc được định nghĩa trong file Startup.cs

Attribute routing

Tạo các Route dựa trên các attribute đặt trong Controller action. 2 hệ thống routing này có thể cùng tồn tại trong một hệ thống. Đầu tiên chúng ta sẽ tìm hiểu về Convention-based routing, còn Attribute based routing sẽ tìm hiểu sau.

Convention Based Routing

Convention based Routes được cấu hình trong phương thức Configure của Startup class. Routing được xử lý bởi Router Middleware. ASP.NET MVC thêm Routing Middleware vào request pipeline khi sử dụng app.UseMvc() hoặc app.UseMvcWithDefaultRoute().

Phương thức app.UseMvc tạo ra một thể hiện của class RouteBuilder. RouteBuilder có một extension method là MapRoute cho phép chúng ta thêm Route vào Route Collection.

Routing engine được nhận một Route sử dụng API routes.MapRoute:

 

Trong ví dụ trên, MapRoute tạo một route đơn lẻ nó có tên là default và với URL Pattern của route là {controller=Home}/{action=Index}/{id?}

URL Patterns

Mỗi Route phải chứa một URL Pattern. Pattern này sẽ được so sánh với URL requét. Nếu pattern đúng với URL thì nó sẽ được sử dụng bởi hệ thống routing để xử lý URL đó. Mỗi một URL Pattern bao gồm một hoặc nhiều phần. Các phần chia tách bởi dấu gạch chéo.

Mỗi phần có thể là một hằng số (constant) hoặc một Route Parameter.

Route Parameter được bao gọc bởi một cặp dấu ngoặc nhọn ví dụ {controller}, {action}.

Route Parameter có thể có giá trị mặc định như {controller=Home} khi Home là giá trị mặc định của controller. Một dấu = sẽ gán giá trị cho tên parameter.

Bạn có thể có một thành phần dạng hằng số. Ví dụ: admin/{controller=Home}/{action=Index}/{id?}. Ở đây thì "admin" là một hằng tức là một chuỗi cố định phải tồn tại trên URL.

Dấu ? trong {id?} chỉ ra là tham số này không bắt buộc. Một dấu ? sau tên tham số chỉ ra tham số đó không yêu cầu phải có giá trị.

URL Pattern {controller=Home}/{action=Index}/{id?}. Đăng ký một route có thành phần đầu tiên trên URL là một controller, phần thứ 2 là Action method trong controller đó. Và phần cuối là dữ liệu thêm vào tên là id.

 

URL Matching

Mỗi phần trong URL request đến sẽ match tương ứng với thành phần của URL Pattern. Route {controller=Home}/{action=Index}/{id?} có 3 thành phần. Phần cuối là tùy chọn. Xem xét ví dụ URL www.example.com/product/list thì URL này có 2 thành phần. URL này vẫn match với pattern ở trên vì phần thứ 3 không yêu cầu.

Routing Engine sẽ nhận diện {controller}= Product & {action}= List

 

URL www.example.com/product cũng match với URL pattern ở trên mặc dù nó chỉ có một thành phần. Vì phần cho action có giá trị mặc định là Index. Nếu không có thành phần tương ứng trong URL và thành phần đó có giá trị mặc định trong Pattern thì giá trị mặc định sẽ được chọn bởi Routing Engine.

Vì thế mà URL được nhận diện như là {controller}=Product and {action}=Index

Ví dụ www.example.com cũng được match với URL Pattern ở trên vì là thành phần đầu tiên controller cũng có giá trị mặc định là Home. URL này được nhận diện: {controller}=Home và {action}=Index

URL www.example.com/product/list/10 được nhận diện như là {controller}=Home,{action}=Index và{id}=10.

URL www.example.com/product/list/10/detail thì không đúng vì URL này có 4 thành phần trong khi URL Pattern lại chỉ có 3 thành phần.

Phiên bản trước của ASP.NET thì lại là match ngay cả nếu Controller Action method không tồn tại và ứng dụng trả về lỗi 404.

ASP.NET Core routing engine kiểm tra sự tồn tại của Controller và Action method cho mỗi route. Nếu không có controller và action method tương ứng trong ứng dụng thì cũng không được match ngay cả khi Route tồn tại.

Routeing trong Action

Hãy xây dựng một ứng dụng ASP.NET Core kiểm trả xem routing làm việc ra sao nhé.

Tạo một ứng dụng ASP.NET Core sử dụng .NET Core 2.2. Chọn Empty Project và đặt tên là MVCController.

Mở Startup.cs và mở phương thức ConfigureServices ra sau đó thêm MVC service như dưới:

 public void ConfigureServices(IServiceCollection services){  
         services.AddMvc();       
      }

Giờ hãy mở phương thức Configure ra trong Startup.cs và đăng ký MVC Middleware sử dụng app.UseMvc.

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();            
    }
    app.UseMvc();
    app.Run(async (context) => { 
         await context.Response.WriteAsync("Failed to Find Route");            
    });
}

Phương thức app.UseMvc không được đăng ký bất cứ route nào. Mà app.Run lại là một Terminating Middleware (middleware ngắt) thì ví dụ trên sẽ hiển thị dòng "Failed to Find Route". Middleware này sẽ chỉ chạy khi app.UseMvc không tìm thấy bất cứ route nào.

Giờ hãy tạo thư mục Controllers trong thư mục gốc của project. Chuột phải vào thư mục Controllers chọn Add Controller. Chọn "MVC - Controller Emtpy". Đặt tên là HomeController.

Giờ hãy mở HomeController ra và thay đổi phương thức Index như sau:

public string Index() {
     return "Hello from Index method of Home Controller";        
}

Bạn có thể tham khảo bài viết Xây dựng ứng dụng ASP.NET Core MVC đầu tiên để tạo ứng dụng.

Chạy ứng dụng và gọi đến các URL như: /, /Home, /Home/Index.

Tất cả các trường hợp trên sẽ nhận dòng chữ "Fails to Find a Route". Vì nó không được đăng ký bất cứ route nào.

Đăng ký Route

Vào phương thức Configure của class Startup và thay đổi app.UseMvc như sau:

app.UseMvc(routes =>  {
      routes.MapRoute("default",
                      "{controller}/{action}");            
});

Chúng ta không chỉ ra bất cứ mặc định nào. Vì cả Controller và Action đều có trên URL:

URL Đúng? Nhận điện
/ Không  
/Home Không  
/Home/Index Controller=Home
Action=Index

Giờ hãy thử route này:

app.UseMvc(routes => {   
       routes.MapRoute("default",  
                       "{controller}/{action=index}");            
});

Giờ chúng ta có giá trị mặc định trên URL Parameter:

URL Đúng? Nhận diện
/ Không  
/Home Controller=Home
Action=Index
/Home/Index Controller=Home
Action=Index

Giờ hãy thêm mặc định cho Controller 

routes.MapRoute("default",   "{controller=Home}/{action=Index}");
URL Có đúng? Nhận diện
/ Controller=Home
Action=Index
/Home Controller=Home
Action=Index
/Home/Index Controller=Home
Action=Index

Giờ hãy thử route này:

routes.MapRoute("default", "{admin}/{controller=Home}/{action=Index}");

Chúng ta thêm Route Parameter admin cho route. Chú ý là Route Parameter được đóng bởi cặp ngoặc nhọn. Giờ hãy test lại URL:

URL Có đúng? Nhận diện
/ Không
Không có mặc định cho admin. Vì thế phần này bắt buộc
 
/Home

Thành phần đầu tiên match với admin
Admin=Home
Controller=Home
Action=Index
/Abc Admin=Abc
Controller=Home
Action=Index
/Home/Index Không

Admin=Home
Controller=Index

Không có controller nào là IndexController,vì thế không đúng
 
/Xyz/Home Admin=Xyz
Controller=Home
Action=Index
/Admin/Home Admin=Admin
Controller=Home
Action=Index

Giờ hãy thử:

routes.MapRoute("default",
                  "admin/{controller=Home}/{action=Index}");            
});

Sự khác nhau giữa các route ở trên và route này là admin được định nghĩa dạng Constant (không có dấu ngoặc nhọn). Nghĩa là phần đầu tiên phải là từ "admin"

URL Có đúng? Nhận diện
/ Không, vì thành phần đầu tiên bắt buộc  
/Home Không, vì thành phần đầu tiên phải chứa từ admin  
/Abc Không, vì thành phần đầu tiên phải chứa từ admin  
/Admin Controller=Home
Action=Index
/Admin/Home Controller=Home
Action=Index

Các tham số cho Controller Action Method 

Giờ hãy xem xét Route dưới đây. Đây là Route mặc định được đăng ký khi chúng ta sử dụng app.UseMvcWithDefaultRoute.

app.UseMvc(routes =>  {
     routes.MapRoute("default",
                     "{controller=Home}/{action=Index}/{id?}");            
});

Nó có 3 thành phần và phần thứ 3 có tên là id, nó không bắt buộc. Phần id này có thể được gán như một tham số vào Controller action method.

Bất cứ tham số trên route nào trừ {controller} và {action} có thể được gán như các tham số vào action method.

Thay đổi phương thức Index của HomeController:

public string Index(string id) { 
    if (id !=null) {
        return "Received " + id.ToString();
    } else {
         return "Received nothing";            
    }                    
}

Một request cho "/Home/Index/10" sẽ match với rouet trên và giá trị 10 sẽ được gán vào tham số id của action Index.

Route mặc định

Route mặc định có thể được chỉ ra bằng 2 cách. Cách đầu tiên là sử dụng dấu bằng (=)  ({controller=Home})  như là các ví dụ trên.

Cách khác là sử dụng tham số thứ 3 của phương thức MapRoute.

routes.MapRoute("default", 
                "{controller}/{action}",                                
                new { controller = "Home", action = "Index" });

Chúng ta tạo ra một thể hiện của một kiểu nặc danh, nó chứa các thuộc tính được trình bày trên URL. Các giá trị đó trở thành giá trị mặc định của URL Parameter.

Route trên tương tự như route {controller=Home}/{action=Index}.

Multiple Route

Trong ví dụ trên chúng ta chỉ sử dụng có 1 route. Chúng ta có thể cấu hình ASP.NET Core xử lý bất cứ route nào:

app.UseMvc(routes => {
     routes.MapRoute("secure",
                      "secure",
                      new { Controller = "Admin", Action = "Index" });
    
     routes.MapRoute("default",
                     "{controller=Home}/{action=Index}");
});

Ví dụ trên có 2 route. Mỗi route phải có một tên duy nhất. Chúng ta đặt là "secure"và "default".

Route đầu tiên rất thú vị. Nó chỉ có một thành phần. Chúng ta cài đặt giá trị mặc định cho Controller và Action method trên route này. Controller và Action mặc định là phương thức Index của AdminController.

Tạo một AdminController.cs trong thư mục Controllers. Thay đổi nội dung phương thức Index:

 public string Index() {
      return "Hello from Index method of Admin Controller";        
}

Giờ chúng ta hãy thử chạy các URL sau:

URL Có đúng? Nhận diện
/ Controller=Home
Action=Index
/Secure Controller=Admin
Action=Index
/Secure/Test Không  
/Admin

Sẽ đến AdminController không qua rouet đầu tiên, những sẽ là route thứ 2
Controller=Admin
Action=Index

Vấn đề thứ tự, Route trước sẽ được dùng

Thứ tự route nào được đăng ký rất quan trọng. URL Matching bắt đầu chạy từ trên xuống của tập Route Collection và tìm xem có Route nào match với URL không. Nó sẽ dừng lại khi tìm thấy Route match đầu tiên.

routes.MapRoute("Home",
  "{home}",
   new { Controller = "Home", Action = "Index" });
 
routes.MapRoute("secure",
  "secure",
  new { Controller = "Admin", Action = "Index" });

Route đầu tiên có URL Parameter {home} và match với tất cả. Route thứ 2 có chứa từ "secure" là một route cụ thể hơn.

Khi bạn dùng URL "/secure" , nó không gọi đến AdminController nhưng thay vào đó lại là HomeController. Điều này bởi vì URL /secure match với route đầu tiên mất rồi.

Để làm cho route này chạy, hãy chuyển route secure lên trên Home Route.

routes.MapRoute("secure",
  "secure",
  new { Controller = "Admin", Action = "Index" });
 
routes.MapRoute("Home",
  "{home}",
   new { Controller = "Home", Action = "Index" });

Tổng kết

Bài viết này chúng ta đã học về cơ chế routing trong ASP.NET MVC Core. Chúng ta cần đăng ký Rouet sử dụng phương thức MapRoute của Routing Middleware. Routing Engine sau đó sẽ match URL đến với Route Collection và tìm ra một Route match với nó. Nó sẽ gọi RouteHandler (mặc định là MvcRouteHandler). MvcRouteHandler sau đó gọi Controller tương ứng trong Route.


Trích nguồn từ: (https://www.tektutorialshub.com/)

Lên trên