{"id":1108,"date":"2017-05-04T13:02:47","date_gmt":"2017-05-04T13:02:47","guid":{"rendered":"http:\/\/tpodolak.com\/blog\/?p=1108"},"modified":"2017-05-04T13:02:47","modified_gmt":"2017-05-04T13:02:47","slug":"asp-net-core-api-versioning-convention","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2017\/05\/04\/asp-net-core-api-versioning-convention\/","title":{"rendered":"ASP.NET Core &#8211; API versioning by convention"},"content":{"rendered":"<h3>1. Introduction<\/h3>\n<p>If you were looking for information about <i>API<\/i> versioning support for <i>ASP.NET Core<\/i> you&#8217;ve probably come across <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.Mvc.Versioning\"> Microsoft.AspNetCore.Mvc.Versioning<\/a> library. The library itself allows you to set up versioning in a couple of different ways, for instance, via <a href=\"https:\/\/github.com\/Microsoft\/aspnet-api-versioning\/wiki\/How-to-Version-Your-Service\">attributes<\/a> or via <a href=\"https:\/\/github.com\/Microsoft\/aspnet-api-versioning\/wiki\/API-Version-Conventions\">manual convention setup<\/a>. All of this options are really nice but unfortunately, they have one significant drawback, namely, you have to set them up manually. As I tend to forget about this kind of things I wanted to automate that process as much as possible. Thankfully <i>ASP.NET Core<\/i> has a very nice feature called <i>ApplicationModelConventions<\/i> which allows you to add some metadata(like routing information, <i>API<\/i> versioning ) to your controller, actions etc.<\/p>\n<h3>2. Convention<\/h3>\n<p>I&#8217;ve decided to implement versioning by routing which means the <i>API<\/i> will be accessible via this template<\/p>\n<pre lang=\"bash\">\r\n\"v{version:apiVersion}\/[controller]\"\r\n<\/pre>\n<p>The template itself doesn\u2019t give you much, as you also have to add information to the controllers which version of <i>API<\/i> they support. In my case, I decided to go with a version by namespace convention, which means that supported version will be deduced from namespace in which given controller resides. So, in fact, there will be two conventions, one for applying routing and a second one for applying versioning<\/p>\n<h3>3. Creating conventions.<\/h3>\n<p>In order to write an <i>ASP.NET Core<\/i> convention, we have to implement <i>IApplicationModelConvention<\/i> interface. This interface has a single method <i>Apply<\/i> which takes <i>ApplicationModel<\/i> class as an argument. This class gives us access to all of our controllers, actions and other parts of the application. So if we want to apply a routing with versioning for all of the controllers, we can do it in following way<\/p>\n<pre lang=\"csharp>\r\npublic class ApiVersionRoutePrefixConvention : IApplicationModelConvention\r\n{\r\n    private readonly string _versionConstraintTemplate;\r\n    private readonly string _versionedControllerTemplate;\r\n\r\n    public ApiVersionRoutePrefixConvention()\r\n    {\r\n        _versionConstraintTemplate = \"v{version:apiVersion}\";\r\n        _versionedControllerTemplate = $\"{_versionConstraintTemplate}\/[controller]\";\r\n    }\r\n\r\n    public void Apply(ApplicationModel application)\r\n    {\r\n        foreach (var applicationController in application.Controllers)\r\n        {\r\n            foreach (var applicationControllerSelector in applicationController.Selectors)\r\n            {\r\n                if (applicationControllerSelector.AttributeRouteModel != null)\r\n                {\r\n                    var versionedConstraintRouteModel = new AttributeRouteModel\r\n                    {\r\n                        Template = _versionConstraintTemplate\r\n                    };\r\n\r\n                    applicationControllerSelector.AttributeRouteModel =\r\n                            AttributeRouteModel.CombineAttributeRouteModel(versionedConstraintRouteModel,\r\n                                applicationControllerSelector.AttributeRouteModel);\r\n                }\r\n                else\r\n                {\r\n                    applicationControllerSelector.AttributeRouteModel = new AttributeRouteModel\r\n                    {\r\n                        Template = _versionedControllerTemplate\r\n                    };\r\n                }\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>As you can see we iterate through all of the <i>Selectors<\/i> and then apply new route model to them. If selector already had a route attribute applied we combine two attributes together. Once our convention is applied our actions will be accessible with following routing prefix<\/p>\n<pre lang=\"csharp\">\r\n\"v{version:apiVersion}\/[controller]\" \r\n<\/pre>\n<p>or via <\/p>\n<pre lang=\"csharp\">\r\n\"v{version:apiVersion}\/previous_route_for_controller\"\r\n<\/pre>\n<p>Having our routing ready now it is time to add versioning information to our controllers. Once again it will be done via application model convention. The version of the controller will be deduced from the namespace given controller is located in. <\/p>\n<pre lang=\"csharp\">\r\npublic class ApiVersionByNamespaceConvention : IApplicationModelConvention\r\n{\r\n    public void Apply(ApplicationModel application)\r\n    {\r\n        foreach (var controllerModel in application.Controllers)\r\n        {\r\n            var version = DeduceControllerVersion(controllerModel);\r\n            var builder = new ControllerApiVersionConventionBuilder<ControllerModel>();\r\n            builder.HasApiVersion(new ApiVersion(version, 0));\r\n            builder.ApplyTo(controllerModel);\r\n        }\r\n    }\r\n\r\n    private int DeduceControllerVersion(ControllerModel model)\r\n    {\r\n        \/\/ super trivial way of retrieving version number from namespace\r\n        if (!int.TryParse(model.ControllerType.Namespace.Last().ToString(), out var version))\r\n        {\r\n            throw new InvalidOperationException(\"Unable to retrieve version information from namespace\");\r\n        }\r\n        \r\n        return version;\r\n     }\r\n}\r\n<\/pre>\n<p>As you can see I iterate through all of the controller models of our application, then check the version of controller based on its namespace and finally with <\/p>\n<pre lang=\"csharp\">\r\nvar builder = new ControllerApiVersionConventionBuilder<ControllerModel>();\r\nbuilder.HasApiVersion(new ApiVersion(version, 0));\r\nbuilder.ApplyTo(controllerModel);\r\n<\/pre>\n<p>I am able to inform the framework which version of <i>API<\/i> given controller supports.<\/p>\n<h3>4. Adding convention to application<\/h3>\n<p>Having all of our conventions prepared now we can enable versioning support<\/p>\n<pre lang=\"csharp\">\r\n\/\/ Add framework services.\r\nservices.AddApiVersioning(options =>\r\n{\r\n    options.ReportApiVersions = true;\r\n});\r\n<\/pre>\n<p>And of course, we have to add newly created conventions to list of application conventions<\/p>\n<pre lagn=\"csharp\">\r\nservices.AddMvc(options =>\r\n{\r\n    options.Conventions.Remove<ApiVersionConvention>();\r\n    options.Conventions.Remove<ImplicitControllerVersionConvention>();\r\n    options.Conventions.Add(new ApiVersionRoutePrefixConvention());\r\n    options.Conventions.Add(new ApiVersionByNamespaceConvention());\r\n});\r\n<\/pre>\n<p>Note that I removed <i>ApiVersionConvention<\/i> and <i>ImplicitControllerVersionConvention<\/i> with simple extension method <i>Remove&lt;T&gt;<\/i><\/p>\n<pre lang=\"csharp\">\r\npublic static class CollectionExtensions\r\n{\r\n    public static void Remove<T>(this IList<IApplicationModelConvention> conventionsCollection)\r\n            where T : IApplicationModelConvention\r\n    {\r\n        var conventions = conventionsCollection.OfType<T>().ToList();\r\n\r\n        foreach (var convention in conventions)\r\n        {\r\n            conventionsCollection.Remove(convention);\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>to make sure that only my convention is responsible for API versioning.<\/p>\n<p>Source code for this post can be found <a href=\"https:\/\/github.com\/tpodolak\/Blog\/tree\/master\/AspNetCoreDefaultApiVersionWithUrlPathVersioning\">here<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>1. Introduction If you were looking for information about API versioning support for ASP.NET Core you&#8217;ve probably come across Microsoft.AspNetCore.Mvc.Versioning library. The library itself allows you to set up versioning in a couple of different ways, for instance, via attributes or via manual convention setup. All of this options are really nice but unfortunately, they [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[317],"tags":[318,323],"class_list":["post-1108","post","type-post","status-publish","format-standard","hentry","category-asp-net-core","tag-asp-net-core","tag-versioning"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1108","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=1108"}],"version-history":[{"count":14,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1108\/revisions"}],"predecessor-version":[{"id":1137,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/1108\/revisions\/1137"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=1108"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=1108"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=1108"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}