Skip to content

Latest commit

 

History

History
178 lines (156 loc) · 4.74 KB

ASP.NET Web API.md

File metadata and controls

178 lines (156 loc) · 4.74 KB
title date lastmod
ASP.NET Web API
2022-11-08
2022-11-21

ASP.NET Web API

Routing

Routing uses APIController Add an attribute to a controller class to tell the compiler that this is an APIController

[APIController]
public class TicketsController: ControllerBase{}

Route pattern using attribute binding

  • 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}");
	}
}

Route pattern using model binding

Primitive type binding

  • 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}");
	}
}

Post Routes

[HttpPost]
public IActionResult Post([FromBody] Ticket ticket){
	return Ok(ticket); //automatically serializes the body into JSON
}

Validation

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;}

Filters

How the filter pipeline works:

Action Filters

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"}
								}
							);
		}
	}
}