title | date | lastmod |
---|---|---|
ASP.NET Web API |
2022-11-08 |
2022-11-21 |
Routing uses APIController
Add an attribute to a controller class to tell the compiler that this is an APIController
[APIController]
public class TicketsController: ControllerBase{}
IActionResult
is a generic interface to encapsulate all the return types such as XML, JSON.- Use the route method attribute to set the endpoint
[HTTPGet]
[Route("api/tickets")]
public IActionResult GetTickets(){
return Ok("Reading tickets");
}
//with interpolation
[HTTPGet]
[Route("api/tickets/{id}")]
public IActionResult GetTicket(int id){
return Ok($"Reading tickets #{id}");
}
Can also define the route based on the controller name at the class level
[APIController]
[Route("api/[controller]")]
public class TicketsController: ControllerBase{
[HTTPGet]
public IActionResult GetTickets(){
return Ok("Reading tickets");
}
[HTTPGet("{id}")]
public IActionResult GetTicket(int id){
return Ok($"Reading tickets #{id}");
}
}
FromRoute
FromQuery
: specifies that this attribute must come from the query string
[HTTPGet]
[Route("/api/projects/{pid}/tickets")] //slash at the start indicates from root rather than the controller route defined in the class-level
public IActionResult GetTicketFromProject(int pid, [FromQuery] int tid){
if (tid == 0){
return Ok($"Reading all tickets belonging to project #{pid}");
}
else {
return Ok($"Reading project {pid}, tickets #{id}");
}
}
Using a complex type:
public class Ticket{
[FromQuery(Name="tid")]
public int TicketId {get; set;}
[FromRoute(Name="pid")]
public int ProjectId {get; set;}
}
[HTTPGet("{id}")]
[Route("/api/projects/{pid}/tickets")]
public IActionResult GetTicketFromProject(Ticket ticket){
if (ticket.TicketId == 0){
return Ok($"Reading all tickets belonging to project #{ticket.ProjectId}");
}
else {
return Ok($"Reading project {ticket.ProjectId}, tickets #{ticket.TickedId}");
}
}
[HttpPost]
public IActionResult Post([FromBody] Ticket ticket){
return Ok(ticket); //automatically serializes the body into JSON
}
Data Annotations Place validation on the mode attributes
public class Ticket{
[Required]
public int TicketId {get; set;}
[Required]
public int ProjectId {get; set;}
}
Custom validation attributes
public class Ticket_EnsureDueDateForTicketOwner: ValidationAttribute{
protected override ValidationResult IsValid(Object value, ValidationContext
validationContext){
var ticket = validationContext.ObjectInstance as Ticket;
if (ticket != null && !string.IsNullOrWhiteSpace(ticket.Owner)){
if (!ticket.DueDate.HasValue){
return new ValidationResult("Due date is required when ticket has owner");
}
}
return ValidationResult.Success;
}
}
/**some code **/
public string Owner {get; set;}
[Ticket_EnsureDueDateForTicketOwner]
public DateTime? DueDate {get; set;}
How the filter pipeline works:
Place validation on endpoint routes.
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
//custom validation on the endpoint
if (!context.ModelState.IsValid)
{
context.ModelState.AddModelError("SomeKey", "Key is missing");
//short circuit the request
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
[HttpPost]
[ValidateModelAttribute]
public IActionResult Post([FromBody] Ticket ticket){
return Ok(ticket); //automatically serializes the body into JSON
}
Useful to short-circuit the rest of the pipeline such as during versioning and caching
public class Version1DiscontinueResourceFilter : Attribute, IResourceFilter{
public void OnResourceExecuting(ResourceExecutingContext context){
if (path contains v1){
contex.Result = new BadRequestObjectResut(
new {
Versioning = new[] {"This API Version is discontinued"}
}
);
}
}
}