April 07, 2017

Hi dotnet - Phần 3 - MVC và WebAPI

Hôm nay chúng ta tiếp tục với ASP.net thêm MVC và WebAPI. Mã nguồn của phần này được đặt trong thư mục 3-ASP trên github. Với phiên bản .NET Core thì MVC và WebAPI đã được hợp nhất, không có sự phân biệt trong package Microsoft.AspNetCore.Mvc do đó trong phần này mình chỉ gọi là MVC ngắn gọn. Bạn dùng lệnh để thêm package này vào project:

dotnet add package Microsoft.AspNetCore.Mvc

Để sử dụng được MVC thì cần thêm một class Startup cho project:

Startup.cs

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace MvcWebApplication {
  public class Startup {
    public void ConfigureServices(IServiceCollection services)
    {
      // MVC service: MVC Core, View, Razor, JsonFormatter, CORS...
      services.AddMvc();
    }
    public void Configure(IApplicationBuilder app) {
      // Default MVC route to controller/action
      app.UseMvc(routes => {
        routes.MapRoute(
          name: "default",
          template: "{controller=Home}/{action=Index}/{id?}");
      });
    }
  }
}

ASP.net Core sử dụng dependency injection, bạn có thể dễ dàng sử dụng các đối tượng thông qua việc khai báo tham số. Trong class Startup chúng ta có 2 hàm:

  • ConfigureServices dùng để khai báo thêm các service mà chúng ta có sử dụng trong project như MVC hay Database (Entity Framework), Logging…

  • Configure cấu hình các middleware xử lý request từ client, tương tự như app.use của express.js ở đâu UseMvc sẽ gọi thêm các middleware khác như View, Razor, Helper, JsonFormatter… tương tự như EJS, bodyParser… nếu bạn quen thuộc với Node.

Khai báo middleware MVC sử dụng routing mặc định mà chúng ta vẫn thường thấy trong các framework mvc khác controller/action/[id] với controller mặc định là Home và action mặc định là Index. Ví dụ:

  • /, /Home, /Home/Index: Gọi HomeController và phương thức Index

  • /Foo: Gọi FooController và phương thức Index

  • /Foo/Update/3: Gọi FooController và phương thức Index với tham số là 3

Chỉnh sửa class Program để gọi Startup:

Program.cs

using System;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Builder;

namespace MvcWebApplication
{
  public class Program
  {
    public static void Main(string[] args)
    {
      var host = new WebHostBuilder()
        .UseKestrel()
        .UseStartup<Startup>()
        .UseUrls("http://*:3000")
        .Build();

      host.Run();
    }
  }
}

Nói chung vì C# class là bắt buộc nên khai báo hơi phức tạp hơn express.js một tí. Nhưng được cái là có khung để tách bạch rõ ràng, dùng Node mình lại có xu hướng gom lại thành một cục, sau này bị phình quá lại phải tách ra.

Tiếp theo tạo class HomeController trong thư mục Controllers, thực chất thì bạn có thể tạo thư mục tên gì cũng được, nhưng theo convention có sẵn của MVC trước giờ cho dễ thôi, mặc dù cá nhân mình muố đặt tên viết thường. Khổ nổi C# dùng namespace dạng CamelCase nên đành theo vậy:

HomeController.cs

using System;
using Microsoft.AspNetCore.Mvc;

namespace MvcWebApplication.Controllers {
  public class HomeController : Controller {
    public string Index()
    {
        return "Hello world from ASP.net MVC controller";
    }
  }
}

Đến đây thì có thể sử dụng dotnet run để kiểm tra xem HomeController có chạy được chưa:

HomeController

Nếu bạn chú ý thì mình chỉ mới có phần routing đến controller thôi nghĩa là controller trả về dữ liệu trực tiếp. Tiếp theo chúng ta sẽ bổ sung view cho project. Đầu tiên cần bổ sung khai báo thư mục gốc của project, để khi thực thi MVC có thể tìm được thư mục chứa các view.

Program.cs

var host = new WebHostBuilder()
  .UseKestrel()
  .UseContentRoot(Directory.GetCurrentDirectory())
  .UseStartup<Startup>()
  .UseUrls("http://*:3000")
  .Build();

Tạo view Index.cshtml trong thư mục Views/Home ngang cấp với Controller:

Index.cshtml

<h1>Hello world from ASP.net MVC view</h1>

Sửa Index trong HomeController để render view:

HomeController.cs

public ActionResult Index()
{
  return View();
}

Và được kết quả:

HomeController với View

Đến đây thì bạn đã cơ bản hoàn thành xong phần controller và view, phần model mình sẽ tiếp tục bổ sung trong phần tiếp theo về Entity Framework. Còn lại một nội dung nhỏ trong phần này là WebAPI, khá đơn giản vì nó gần như tương đồng với MVC bình thường. Chúng ta sẽ tạo thử một API controller mới tên Number:

NumberController.cs

using System;
using Microsoft.AspNetCore.Mvc;

namespace MvcWebApplication.Controllers {
  [Route("api/[controller]")]
  public class NumberController : Controller {
    [HttpGet]
    public int[] Get()
    {
        return new int[] { 0, 1, 2, 3 };
    }
    [HttpGet("{id}")]
    public int Get(int id) {
      return id;
    }
  }
}

Controller này không sử dụng routing dạng MVC mà cấu hình trực tiếp trong khai báo class sử dụng attribute Route, và các phương thức được xác định cũng bằng các thuộc tính HttpGet, tương tự với POST, PUT, DELETE…

NumberController

NumberController với get id

Trong trường hợp project của bạn chỉ bao bao gồm API thì bạn có thể sử dụng .AddMvcCore().AddJsonFormatters() thay cho .AddMvc() để lược bỏ các middleware không cần thiết cũng như dùng package Microsoft.AspNetCore.Mvc.Core thay cho Microsoft.AspNetCore.Mvc, tuy nhiên bạn phải nhớ rõ các package đã bao gồm sẵn trong MVC nên cũng hơi phức tạp.

Cuối cùng của phần này là thêm hỗ trợ static file, mình sẽ tạo thư mục public và thêm vào middleware chỉ định thư mục này chứa các tập tin js, css, img… Bạn phải cài thêm package để hỗ trợ:

dotnet add package Microsoft.AspNetCore.StaticFiles

Startup.cs

public void Configure(IApplicationBuilder app) {
  // Serve static files
  app.UseStaticFiles(new StaticFileOptions() {
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), @"public")),
    RequestPath = "/"
  });
}

Nhìn có vẻ phức tạp nếu bạn không sử dụng thư mục wwwroot, nhớ thêm các using namespace cho các class này.

Tạm xong phần này, hơi dài, nhưng chỉ thêm vài tập tin thôi. Hẹn gặp lại phần tiếp theo về Entity Framework!