ASP.NET Core: Tạo API web với ASP.NET Core dựa trên controller
ASP.NET Core hỗ trợ tạo API web bằng controller hoặc sử dụng API tối thiểu. Controller trong API web là các lớp bắt nguồn từ ControllerBase. Controller được kích hoạt và xử lý trên cơ sở mỗi yêu cầu.
Bài viết này cho biết cách sử dụng controller để xử lý các yêu cầu API web. Để biết thông tin về cách tạo API web không có controller, hãy xem Hướng dẫn tạo API tối thiểu với ASP.NET Core.
Lớp ControllerBase
API web dựa trên controller bao gồm một hoặc nhiều lớp controller xuất phát từ ControllerBase. Mẫu dự án API web cung cấp controller khởi động:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Controller API Web thường phải xuất phát từ ControllerBase chứ không phải từ Controller. Controller
xuất phát từ ControllerBase và thêm hỗ trợ cho view, do đó, nó dùng để xử lý các trang web chứ không phải các yêu cầu API web. Nếu cùng một controller phải hỗ trợ chế độ xem và API web, hãy lấy từ Controller
.
Lớp ControllerBase
cung cấp nhiều property và phương thức hữu ích để xử lý các yêu cầu HTTP. Ví dụ: CreatedAtAction trả về mã trạng thái 201:
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);
return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}
Bảng sau đây chứa các ví dụ về các phương thức trong ControllerBase
.
Phương thức | Ghi chú |
---|---|
Badrequest | Trả về mã trạng thái 400. |
Notfound | Trả về mã trạng thái 404. |
Tệp vật lý | Trả về một tập tin. |
TryUpdateModelAsync | Gọi liên kết mô hình. |
TryValidateModel | Gọi xác thực mô hình. |
Để biết danh sách tất cả các phương thức và property khả dụng, hãy xem ControllerBase.
Attribute
Không gian tên Microsoft.AspNetCore.Mvc cung cấp các thuộc tính có thể được sử dụng để định cấu hình hành vi của bộ điều khiển API web và phương thức hành động. Ví dụ sau sử dụng các thuộc tính để chỉ định động từ hành động HTTP được hỗ trợ và bất kỳ mã trạng thái HTTP đã biết nào có thể được trả về:
[HttpPost]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<Pet> Create(Pet pet)
{
pet.Id = _petsInMemoryStore.Any() ?
_petsInMemoryStore.Max(p => p.Id) + 1 : 1;
_petsInMemoryStore.Add(pet);
return CreatedAtAction(nameof(GetById), new { id = pet.Id }, pet);
}
Dưới đây là một số ví dụ khác về các attribute có sẵn.
Attribute | Ghi chú |
---|---|
[Route] | Chỉ định mẫu URL cho controller hoặc action. |
[Bind] | Chỉ định tiền tố và property để bao gồm cho liên kết model. |
[HttpGet] | Xác định một action hỗ trợ phương thức hành động HTTP GET. |
[Consumes] | Chỉ định các loại dữ liệu mà một action chấp nhận. |
[Produces] | Chỉ định các loại dữ liệu mà một action trả về. |
Để biết danh sách bao gồm các attribute khả dụng, hãy xem namespace Microsoft.AspNetCore.Mvc.
Attribute ApiController
Attribute [ApiController] có thể được áp dụng cho một lớp controller để kích hoạt các hành vi dành riêng cho API, được đánh giá cao sau đây:
- Yêu cầu định tuyến attribute
- Phản hồi HTTP 400 tự động
- Ràng buộc tham số nguồn suy luận
- Suy luận yêu cầu nhiều phần/biểu mẫu dữ liệu
- Chi tiết vấn đề cho mã trạng thái lỗi
Attribute trên controller cụ thể
Attribute [ApiController]
có thể được áp dụng cho các controller cụ thể, như trong ví dụ sau từ mẫu dự án:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Attribute trên nhiều controller
Một cách tiếp cận để sử dụng attribute trên nhiều controller là tạo một lớp controller cơ sở tùy chỉnh được chú thích bằng attribute [ApiController]. Ví dụ sau đây cho thấy một lớp cơ sở tùy chỉnh và một controller bắt nguồn từ nó:
[ApiController]
public class MyControllerBase : ControllerBase
{
}
[Produces(MediaTypeNames.Application.Json)]
[Route("[controller]")]
public class PetsController : MyControllerBase
Attribute trên một assembly
Attribute [ApiController]
có thể được áp dụng cho một assembly. Khi attribute [ApiController]
được áp dụng cho một assembly, tất cả các controller trong assembly đều có attribute [ApiController]
được áp dụng. Không có cách nào để chọn không tham gia các controller riêng lẻ. Áp dụng attribute mức assembly cho file Program.cs
:
using Microsoft.AspNetCore.Mvc;
[assembly: ApiController]
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Yêu cầu định tuyến attribute
Attribute [ApiController]
làm cho định tuyến attribute trở thành một yêu cầu. Ví dụ:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
Không thể truy cập các action thông qua các route thông thường được xác định bởi UseEndpoints, UseMvc hoặc UseMvcWithDefaultRoute.
Phản hồi HTTP 400 tự động
Attribute [ApiController]
làm cho lỗi xác thực mô hình tự động kích hoạt phản hồi HTTP 400. Do đó, đoạn mã sau là không cần thiết trong một phương thức action:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
ASP.NET Core MVC sử dụng bộ lọc action ModelStateInvalidFilter để thực hiện kiểm tra trước đó.
Phản hồi BadRequest mặc định
Loại phản hồi mặc định cho phản hồi HTTP 400 là ValidationProblemDetails. Nội dung phản hồi sau đây là một ví dụ về loại được tuần tự hóa:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "|7fb5e16a-4c8f23bbfc974667.",
"errors": {
"": [
"A non-empty request body is required."
]
}
}
Loại ValidationProblemDetails
:
- Cung cấp định dạng mà máy có thể đọc được để chỉ định lỗi trong phản hồi API web.
- Tuân thủ thông số kỹ thuật RFC 7807.
Để làm cho các phản hồi tự động và tùy chỉnh nhất quán, hãy gọi phương thức ValidationProblem thay vì BadRequest. ValidationProblem
trả về một đối tượng ValidationProblemDetails cũng như phản hồi tự động.
Đăng nhập 400 phản hồi tự động
Để ghi nhật ký 400 phản hồi tự động, hãy đặt property delegate InvalidModelStateResponseFactory để thực hiện xử lý tùy chỉnh. Theo mặc định, InvalidModelStateResponseFactory
sử dụng ProblemDetailsFactory để tạo một thể hiện của ValidationProblemDetails.
Ví dụ sau đây cho thấy cách truy xuất phiên bản ILogger<TCategoryName> để ghi thông tin về phản hồi 400 tự động:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
// To preserve the default behavior, capture the original delegate to call later.
var builtInFactory = options.InvalidModelStateResponseFactory;
options.InvalidModelStateResponseFactory = context =>
{
var logger = context.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>();
// Perform logging here.
// ...
// Invoke the default behavior, which produces a ValidationProblemDetails
// response.
// To produce a custom response, return a different implementation of
// IActionResult instead.
return builtInFactory(context);
};
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Vô hiệu hóa phản hồi 400 tự động
Để tắt hành vi 400 tự động, hãy đặt property SuppressModelStateInvalidFilter thành true
. Thêm code được đánh dấu sau:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Ràng buộc tham số nguồn suy luận
Attribute nguồn liên kết xác định vị trí mà tại đó giá trị của thông số hành động được tìm thấy. Các attribute nguồn ràng buộc sau tồn tại:
Attribute | Nguồn ràng buộc |
---|---|
[FromBody] | Request body |
[FromForm] | Dữ liệu form trong request body |
[FromHeader] | Request header |
[FromQuery] | Yêu cầu tham số chuỗi truy vấn |
[FromRoute] | Định tuyến dữ liệu từ yêu cầu hiện tại |
[FromServices] | Dịch vụ yêu cầu được đưa vào làm tham số action |
[AsParameters] | Tham số phương thức |
Cảnh báo
Không sử dụng
[FromRoute]
khi các giá trị có thể chứa%2f
(nghĩa là/
).%2f
sẽ không thoát khỏi/
. Sử dụng[FromQuery]
nếu giá trị có thể chứa%2f
.
Không có attribute [ApiController]
hoặc attribute nguồn liên kết như [FromQuery]
, thời gian chạy ASP.NET Core cố gắng sử dụng trình liên kết model đối tượng phức tạp. Trình liên kết model đối tượng phức tạp lấy dữ liệu từ các nhà cung cấp giá trị theo một thứ tự đã xác định.
Trong ví dụ sau, attribute [FromQuery]
chỉ ra rằng giá trị tham số discontinuedOnly
được cung cấp trong chuỗi truy vấn của URL yêu cầu:
[HttpGet]
public ActionResult<List<Product>> Get(
[FromQuery] bool discontinuedOnly = false)
{
List<Product> products = null;
if (discontinuedOnly)
{
products = _productsInMemoryStore.Where(p => p.IsDiscontinued).ToList();
}
else
{
products = _productsInMemoryStore;
}
return products;
}
Attribute [ApiController]
áp dụng quy tắc suy luận cho các nguồn dữ liệu mặc định của tham số hành động. Các quy tắc này giúp bạn không phải xác định các nguồn liên kết theo cách thủ công bằng cách áp dụng các attribute cho các tham số action. Các quy tắc suy luận nguồn ràng buộc hoạt động như sau:
[FromServices]
được suy ra cho các tham số loại phức tạp được đăng ký trong DI Container.[FromBody]
được suy ra cho các tham số loại phức tạp không được đăng ký trong DI Container. Một ngoại lệ đối với quy tắc suy luận[FromBody]
là bất kỳ loại tích hợp, phức tạp nào có ý nghĩa đặc biệt, chẳng hạn như IFormCollection và CancellationToken. Mã suy luận nguồn ràng buộc bỏ qua các kiểu đặc biệt đó.[FromForm]
được suy ra cho các tham số hành động thuộc kiểu IFormFile và IFormFileCollection. Nó không được suy ra cho bất kỳ loại đơn giản hoặc do người dùng xác định nào.[FromRoute]
được suy ra cho bất kỳ tên tham số action nào khớp với một tham số trong mẫu route. Khi có nhiều route khớp với một tham số action, bất kỳ giá trị route nào cũng được xem xét[FromRoute]
.[FromQuery]
được suy ra cho bất kỳ tham số action nào khác.
Ghi chú suy luận FromBody
[FromBody]
không được suy ra cho các kiểu đơn giản như string
hoặc int
. Do đó, attribute [FromBody]
nên được sử dụng cho các kiểu đơn giản khi chức năng đó là cần thiết.
Khi một action có nhiều tham số bị ràng buộc từ phần thân yêu cầu, một ngoại lệ sẽ được đưa ra. Ví dụ: tất cả các chữ ký phương thức action sau gây ra ngoại lệ:
[FromBody]
suy ra trên cả hai vì chúng là những kiểu phức tạp.
[HttpPost]
public IActionResult Action1(Product product, Order order)
- Attribute
[FromBody]
trên cái này, sẽ suy ra trên cái kia vì nó là một kiểu phức tạp.
[HttpPost]
public IActionResult Action2(Product product, [FromBody] Order order)
- Attribute
[FromBody]
trên cả hai.
[HttpPost]
public IActionResult Action3([FromBody] Product product, [FromBody] Order order)
Ghi chú suy luận FromServices
Liên kết tham số sẽ thực hiện liên kết các tham số thông qua phép nội xạ phụ thuộc khi loại được định cấu hình là một dịch vụ. Điều này có nghĩa là không bắt buộc phải áp dụng rõ ràng attribute [FromServices] cho một tham số. Trong đoạn code sau, cả hai action đều trả về thời gian:
[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
public ActionResult GetWithAttribute([FromServices] IDateTime dateTime)
=> Ok(dateTime.Now);
[Route("noAttribute")]
public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}
Trong một số ít trường hợp, DI tự động có thể phá vỡ các ứng dụng có kiểu DI cũng được chấp nhận trong các phương thức action của controller API. Không phổ biến khi có một kiểu trong DI và làm đối số trong một action của controller API.
Để tắt tính năng suy luận [FromServices]
cho một tham số action, hãy áp dụng attribute nguồn liên kết mong muốn cho tham số. Ví dụ: áp dụng attribute [FromBody]
cho tham số action cần được liên kết từ phần nội dung của yêu cầu.
Để tắt suy luận global [FromServices]
, hãy đặt DisableImplicitFromServicesParameters thành true
:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();
builder.Services.Configure<ApiBehaviorOptions>(options =>
{
options.DisableImplicitFromServicesParameters = true;
});
var app = builder.Build();
app.MapControllers();
app.Run();
Các kiểu được kiểm tra khi khởi động ứng dụng với IServiceProviderIsService để xác định xem một đối số trong action của controller API đến từ DI hay từ các nguồn khác.
Cơ chế suy ra nguồn ràng buộc của các tham số action của Controller API sử dụng các quy tắc sau:
- BindingInfo.BindingSource được chỉ định trước đó không bao giờ bị ghi đè.
- Một tham số kiểu phức tạp, được đăng ký trong vùng chứa DI, được gán BindingSource.Services.
- Một tham số kiểu phức tạp, không được đăng ký trong vùng chứa DI, được gán BindingSource.Body.
- Một tham số có tên xuất hiện dưới dạng giá trị route trong bất kỳ mẫu route nào được chỉ định BindingSource.Path.
- Tất cả các tham số khác là BindingSource.Query.
Vô hiệu hóa quy tắc suy luận
Để tắt suy luận nguồn ràng buộc, hãy đặt SuppressInferBindingSourcesForParameters thành true
:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
options.DisableImplicitFromServicesParameters = true;
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Suy luận yêu cầu multipart/form-data
Attribute [ApiController]
áp dụng quy tắc suy luận cho các tham số action thuộc kiểu IFormFile và IFormFileCollection. Kiểu nội dung yêu cầu multipart/form-data
được suy ra cho các kiểu này.
Để tắt hành vi mặc định, hãy đặt property SuppressConsumesConstraintForFormFileParameters thành true
:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Chi tiết vấn đề cho mã trạng thái lỗi
MVC chuyển đổi kết quả lỗi (kết quả có mã trạng thái 400 hoặc cao hơn) thành kết quả có ProblemDetails. Kiểu ProblemDetails
dựa trên đặc tả RFC 7807 để cung cấp chi tiết lỗi mà máy có thể đọc được trong phản hồi HTTP.
Hãy xem xét đoạn mã sau trong một action của controller:
if (pet == null)
{
return NotFound();
}
Phương thức NotFound
tạo mã trạng thái HTTP 404 có phần body là ProblemDetails
. Ví dụ:
{
type: "https://tools.ietf.org/html/rfc7231#section-6.5.4",
title: "Not Found",
status: 404,
traceId: "0HLHLV31KRN83:00000001"
}
Tắt phản hồi ProblemDetails
Tự động tạo ProblemDetails
cho mã trạng thái lỗi bị tắt khi property SuppressMapClientErrors được đặt thành true
. Thêm đoạn mã sau:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.ConfigureApiBehaviorOptions(options =>
{
options.SuppressConsumesConstraintForFormFileParameters = true;
options.SuppressInferBindingSourcesForParameters = true;
options.SuppressModelStateInvalidFilter = true;
options.SuppressMapClientErrors = true;
options.ClientErrorMapping[StatusCodes.Status404NotFound].Link =
"https://httpstatuses.com/404";
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Xác định loại nội dung yêu cầu được hỗ trợ với attribute [Consumes]
Theo mặc định, một action hỗ trợ tất cả các loại nội dung yêu cầu có sẵn. Ví dụ: nếu một ứng dụng được định cấu hình để hỗ trợ cả trình định dạng đầu vào JSON và XML, thì một hành động sẽ hỗ trợ nhiều loại nội dung, bao gồm application/json
và application/xml
.
Attribute [Consumes] cho phép một action giới hạn các loại nội dung yêu cầu được hỗ trợ. Áp dụng attribute [Consumes]
cho một action hoặc controller, chỉ định một hoặc nhiều loại nội dung:
[HttpPost]
[Consumes("application/xml")]
public IActionResult CreateProduct(Product product)
Trong đoạn code trên, action CreateProduct
chỉ định loại nội dung application/xml
. Các yêu cầu được chuyển đến action này phải chỉ định header Content-Type
là application/xml
. Các yêu cầu không chỉ định header Content-Type
của kết quả application/xml
trong phản hồi 415 Unsupported Media Type.
Attribute [Consumes]
cũng cho phép một action tác động đến lựa chọn của nó dựa trên loại nội dung của yêu cầu đến bằng cách áp dụng một ràng buộc kiểu. Hãy xem xét ví dụ sau:
[ApiController]
[Route("api/[controller]")]
public class ConsumesController : ControllerBase
{
[HttpPost]
[Consumes("application/json")]
public IActionResult PostJson(IEnumerable<int> values) =>
Ok(new { Consumes = "application/json", Values = values });
[HttpPost]
[Consumes("application/x-www-form-urlencoded")]
public IActionResult PostForm([FromForm] IEnumerable<int> values) =>
Ok(new { Consumes = "application/x-www-form-urlencoded", Values = values });
}
Trong đoạn code trên, ConsumesController
được định cấu hình để xử lý các yêu cầu được gửi tới URL https://localhost:5001/api/Consumes
. Cả hai action của controller PostJson
và PostForm
, đều xử lý các yêu cầu POST có cùng một URL. Nếu không có attribute [Consumes]
áp dụng ràng buộc kiểu, thì một ngoại lệ khớp không rõ ràng sẽ được đưa ra.
Attribute [Consumes]
được áp dụng cho cả hai action. Action PostJson
xử lý các yêu cầu được gửi với header Content-Type
là application/json
. Action PostForm
xử lý các yêu cầu được gửi với header Content-Type
là application/x-www-form-urlencoded
.
Tài nguyên bổ sung
- Xem hoặc tải xuống code mẫu. (Cách tải xuống).
- Các kiểu trả về action của controller trong API web ASP.NET Core
- Xử lý lỗi trong API web ASP.NET Core
- Trình định dạng tùy chỉnh trong ASP.NET Core Web API
- Định dạng dữ liệu phản hồi trong ASP.NET Core Web API
- Tài liệu API web ASP.NET Core với Swagger/OpenAPI
- Định tuyến đến các action của controller trong ASP.NET Core
- Sử dụng Visual Studio tạo tunnel cổng để gỡ lỗi API web
- Tạo API web với ASP.NET Core