Table of Contents
In case you're using the (at the time of writing) brand-new ASP.NET 5 with ASP.NET MVC 6 frameworks, you'll have already witness the fact that the MVC pipeline has been completely rewritten in order to merge the MVC and WebAPI modules into a single, lightweight framework able to handle both worlds. The advantages are many, and I will cover most of them in a series of dedicated post. The major disadvantage is that we need to learn a lot of new stuff, even if we're MVC and/or WebAPI/WebAPI2 experts, simply because the new approach is quite different from the previous ones.
One of the first thing you'll notice, which is most likely the reason you're reading this post, is that the Routes.MapRoute method is gone. You won't find it in your Global.asax file, as there's no such thing anymore, nor in the new Startup.cs file which now contains a very small amount of code. And you'll also notice that there is no ad-hoc configuration file to take care of it such as RouteConfig.cs , WebApiConfig.cs or other intermediate handlers frequently shipped with the previous ASP.NET versions and MVC project templates.
The reason behind the MapRoute disappearance is related to the fact that there's no need for it anymore. Routing is handled by the two brand-new services.AddMvc() and services.UseMvc() methods called within the Startup.cs file, which respectively register MVC using the Dependency Injection framework built into ASP.NET 5 and add a set of default routes to our app. We can take a look of what happens behind the hood by looking at the current implementation of the UseMvc() method in the framework code (relevant lines in yellow):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static IApplicationBuilder UseMvc( [NotNull] this IApplicationBuilder app, [NotNull] Action<IRouteBuilder> configureRoutes) { // Verify if AddMvc was done before calling UseMvc // We use the MvcMarkerService to make sure if all the services were added. MvcServicesHelper.ThrowIfMvcNotRegistered(app.ApplicationServices); var routes = new RouteBuilder { DefaultHandler = new MvcRouteHandler(), ServiceProvider = app.ApplicationServices }; configureRoutes(routes); // Adding the attribute route comes after running the user-code because // we want to respect any changes to the DefaultHandler. routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute( routes.DefaultHandler, app.ApplicationServices)); return app.UseRouter(routes.Build()); } |
The good thing about this is that the framework now handles all the hard work, iterating through all the Controller's Actions and setting up their default routes, thus saving you some redundant work.
The bad thing is, there's little or no documentation about how you could add your own routes. Luckily enough, you can easily do that by using either a Convention-Based and/or an Attribute-Based approach, which also goes by the name of Attribute Routing.
Convention-Based Approach
In your Startup.cs class, replace this:
1 |
app.UseMvc(); |
with this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
app.UseMvc(routes => { // Route Sample A routes.MapRoute( name: "RouteSampleA", template: "MyOwnGet", defaults: new { controller = "Items", action = "Get" } ); // Route Sample B routes.MapRoute( name: "RouteSampleB", template: "MyOwnPost", defaults: new { controller = "Items", action = "Post" } ); }); |
Attribute-Based Approach
A great thing about MVC6 is that you can also define routes on a per-controller basis by decorating either the Controller class and/or the Action methods with the appropriate [RouteAttribute] and/or [HttpGet] / [HttpPost] template parameters, such as the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc; namespace MyNamespace.Controllers { [Route("api/[controller]")] public class ItemsController : Controller { // GET: api/items [HttpGet()] public IEnumerable<string> Get() { return GetLatestItems(); } // GET: api/items/5 [HttpGet("{num}")] public IEnumerable<string> Get(int num) { return GetLatestItems(5); } // GET: api/items/GetLatestItems [HttpGet("GetLatestItems")] public IEnumerable<string> GetLatestItems() { return GetLatestItems(5); } // GET api/items/GetLatestItems/5 [HttpGet("GetLatestItems/{num}")] public IEnumerable<string> GetLatestItems(int num) { return new string[] { "test", "test2" }; } // POST: /api/items/PostSomething [HttpPost("PostSomething")] public IActionResult Post([FromBody]string someData) { return Content("OK, got it!"); } } } |
This controller will handle the following requests:
- [GET] api/items
- [GET] api/items/5
- [GET] api/items/GetLatestItems
- [GET] api/items/GetLatestItems/5
- [POST] api/items/PostSomething
Also notice that if you use the two approaches togheter, Attribute-based routes (if/when defined) would override Convention-based ones, and both of them would override the default routes defined by the built-in UseMvc() method.
That's it for now: happy coding!