This article is part of the .NET Core Series. Go have a look at the other articles of this series, and run through the previous topics if not done already!
What is Web API in .NET?
Web API in classic .NET was the framework for building modern RESTful web services. In ASP.NET Core
The MVC
web application framework and Web API
framework are unified, creating a single framework for both.
So, What is the difference between MVC
and Web API
?
The difference is pretty much same like the difference between a web site and a RESTful service! A web gives a nicely formatted graphical user interface, whereas a web service provides plain data as requested. So, the main distinguishing points are:
- Web API provides data than user interface. Code wise, it does not return a
View
to the end user. So, it has controller but no view. - It follows the
REST
principles. But, that responsibility mostly lies with the developer. With wrong implementation, it can easily violate the REST pattern. - It respects the general http standards like http verbs, content negotiation, headers and stuffs.
Since we have already covered ASP.NET Core web (MVC) development in last article, here we’ll see just the Web API specific parts.
[1] A simple Web API Controller
A Web API Controller
is very similar to a MVC
controller. Most significant differences are
- Returns plain data or
http
code specific results, but notView
- Specifies
http verb
& attribute route for each action, which should be in line with REST principles
In the sample below, DataStore
is some virtual source of Item
data. Where Item
is a simple class with an integer Id
and other properties.
[Route("api/Items")] //base route for all actions in this controller
public class ItemController : Controller
{
private readonly ItemContext DataStore; //some source of data
//GET http://example.com/api/items
[HttpGet]
public IEnumerable<Item> GetAll() //return type is data type
{
return DataStore.Items.ToList();
}
//GET http://example.com/api/items/10
[HttpGet("{id}")] //uri part after base route will map to id parameter
public IActionResult GetById(long id)
{
var item = DataStore.Items.FirstOrDefault(t => t.Id == id);
if (item == null)
return NotFound(); //http 404
return new ObjectResult(item); //this returns with 200 OK
}
//return type is IActionResult as there are different return items
//POST http://example.com/api/items, item in request body
[HttpPost]
public IActionResult Create([FromBody] Item item)
{
if (item == null)
return BadRequest(); //http 400
DataStore.Items.Add(item);
DataStore.SaveChanges();
//http 201 created //also adds location header to get the newly created item
return CreatedAtRoute("GetTodo", new { id = item.Id }, item);
}
}
[2] Result types in ASP.NET Core 2.0 MVC
Web API supports many different result types to format and send data in different forms and with different http codes.
JsonResult
returnsapplication/json
ContentResult
returnstext/plain
ObjectResult
supports content negotiation. When anyobject
type is returned from API, they are wrapped inside an ObjectResult.StatusCode
can be specified too.StatusCodeResult
for returning a plain status code.
Following code shows different ways of returning data
with status code
.
return new OkObjectResult(new Item { Id = 123, Name = "Hero" });
//following two are funtionally same, but has contrasting semantics
return new ObjectResult(new Item { Id = 123, Name = "Hero" })
{ StatusCode = StatusCodes.Status200OK }; //status 200 is default
return StatusCode(200, new Item { Id = 123, Name = "Hero" });
//returns JSON, no content negotiation
return Json(new Item { Id = 123, Name = "Hero" });
public Item Get()
{
return new Item { Id = 123, Name = "Hero" };
}
[3] There are lot of short-hand methods and types for http-code-specific result
- 200 -
OkResult
orOkObjectResult
to include object data - 404 -
NotFoundResult
orNotFoundObjectResult
- 201 -
CreatedAtRoute
, with route to newly created object in header - 400 -
BadRequest
- 204 -
NoContent
- etc. Read more about them in the docs.
[4] Adding media type formatters
By default, ASP.NET Core 2.0 (Web API too) supports only JSON
as output formatter. If XML
support is required, add in Startup
. Same should be followed for any other media types as well.
//using using Microsoft.AspNetCore.Mvc.Formatters;
services.AddMvc(options =>
{
//to use XmlSerializer, which you should
options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
//OR, to use DataContractSerializer instead
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
//use only one
});
[5] The Produces filter
A Produces
ResultFilter can be added at action, controller or global level to force output to be specific format, ignoring content negotiation. Following code forces a XML
formatted result
[Produces("application/xml")] //will force to send XML ignoring ConNeg
public IActionResult GetById(long id)
{
var item = _context.Items.FirstOrDefault(t => t.Id == id);
if (item == null)
{
return NotFound(); //http 404
}
return new ObjectResult(item); //200 OK
}
To enforce JSON
, there is also the JsonResult
return type and Json(object)
shorthand method.
[6] The cool uri format trick
In some environments (especially development & testing), it’s very helpful to be able to specify the expected mime type without modifying http headers. For that, ASP.Net Core provides a in-built ResultFilter
, Produces
. When this is used, the mime type can be specified in the request URI as an extension, like .json
or .xml
.
- OLD ASP.NET Web API :: type as query string
- http://myapp.com/api/SomeEntity?format=xml
//public static class WebApiConfig
public static void Register(HttpConfiguration config)
{
config.Formatters.JsonFormatter.MediaTypeMappings
.Add(new QueryStringMapping("format", "json", "application/json"));
config.Formatters.XmlFormatter.MediaTypeMappings
.Add(new QueryStringMapping("format", "xml", "application/xml"));
}
- New ASP.NET Core 2.0 :: type as extension
- http://myapp.com/api/SomeEntity/5.xml
//Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
//json is available by default
options.FormatterMappings
.SetMediaTypeMappingForFormat("xml", "application/xml");
});
}
//Controller
[FormatFilter] //added here at action level
[HttpGet("{id}.{format?}")] //OPTIONAL format like .json or .xml
public IActionResult GetByIdWithFormat(long id)
{
var item = _context.TodoItems.FirstOrDefault(t => t.Id == id);
return new ObjectResult(item);
}
[7] Migrating Web API to ASP.NET Core, the good boy way
The basic way to migrating old Web API code to ASP.NET Core
is to convert the old project to new project, and make necssary adjustments to comply with the new conventions of ASP.NET Core MVC
. See the Porting ASP.NET MVC apps to ASP.NET Core 2.0 MVC section first.
So, the general process for migrating Web API project would be
- Create a new
ASP.NET Core 2.0
application and choose theWeb API
template (it’s just a template) - Copy over all your existing classes
- Enable MVC in
Startup
. Addservices.AddMvc()
in ConfigureServices(), andapp.UseMvc()
in Configure(). AddingUseMvc()
by default enables attribute routing. - Change all your
ApiController
toController
- Change using directive from
System.Web.Http
toMicrosoft.AspNetCore.Mvc
- Change return type from
IHttpActionResult
toIActionResult
, if any - Add http verb e.g.
[HttpGet]
to all controller actions - Add attribute routing at controller level
[Route("api/[controller]")]
or at action level - Build. Fix any additional error, add missing NuGet if any. Change return type if required.
- Run your migrated Web API and do all the tests.
[8] Migrating Web API to ASP.NET Core, the bad-ass way
The magical WebApiCompatShim for migrating old Web API code.
Microsoft has shipped a WebApiCompatShim to help migrate existing Web API
projects to AP.NET Core
. The NuGet package has all the necessary code to make your old Web API code work as if nothing has happened.
Some of the benefits of using this shim - all the old conventions basically!
- UseWebApiActionConventions attribute - to use old cenventions of choosing action method by matching http verb and action name. Like,
Get()
method gets called forhttp get
call. - UseWebApiParameterConventions attribute - old convention of binding simple types from
query string
and complex types fromrequest body
. - The classic
ApiController
class!! Which comes with all the necessary attributes auto-applied! This class is underSystem.Web.Http
namespace! - The old style “api/{controller}/{id?}” default API route can be applied globally.
- Support for old style
HttpResponseMessage
. - Still everything is 100% .NET Core!
Note: The (current) stable version of Microsoft.AspNet.WebApi.Client (which the shim depends on) v5.2.3 is not fully compatible with .NET Standard. But v5.2.4-preview1 is available on NuGet which is fully compatible with .NET Standard 2.0 .
Easily migrate old Web API to .NET Core with WebApiCompatShim
- Create a new
ASP.NET Core 2.0
application and choose theWeb API
template (well, the template doesn’t do much) - Copy over all your existing classes
- Install the WebApiCompatShim NuGet package
- Enable the shim in
Startup
with.AddWebApiConventions()
as shown below. - Add default API route in
Startup
as shown below. TheUseMvc()
by default enables attribute routing. - If the Web API controllers already inherit
ApiController
, then great. Else make them inherit that, or use the Web API shim attributes - Build. Fix any additional error, add missing NuGet if any. Change return type if required.
- Run your migrated Web API and do all the tests.
//Startup
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc() //enables attribute routing too
.AddWebApiConventions(); //handle WebAPI with shim
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//no default route provided by default for Web API
//This MapWebApiRoute is part of the shim
app.UseMvc(routes =>
routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}")
);
}
This article covered the process of porting existing ASP.NET Web API
REST services .NET Core 2.0
services. This concludes the .NET Core Series.
Bonus read - Practical configuration & DI in ASP.NET Core 2.0 .
comments powered by Disqus