{"id":3693,"date":"2020-03-04T00:18:30","date_gmt":"2020-03-04T07:18:30","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=3693"},"modified":"2020-03-08T19:32:22","modified_gmt":"2020-03-09T02:32:22","slug":"enabling-endpoint-routing-in-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/enabling-endpoint-routing-in-odata\/","title":{"rendered":"Enabling Endpoint Routing in OData"},"content":{"rendered":"<p>Few months ago we <a href=\"https:\/\/devblogs.microsoft.com\/odata\/experimenting-with-odata-in-asp-net-core-3-1\/\">announced<\/a> an experimental release of OData for ASP.NET Core 3.1, and for those who could move forward with their applications without leveraging endpoint routing, the release was considered final, although not ideal.<\/p>\n<p>But for those who have existing APIs or were planning to develop new APIs leveraging endpoint routing, the OData\u00a07.3.0 release didn&#8217;t quiet meet their expectations without having to disable endpoint routing.<\/p>\n<p>Understandably this was quite a trade off between leveraging the capabilities of endpoints routing versus being able to use OData. Therefore in the past couple of months the OData team in coordination with ASP.NET team have worked together to achieve the desired compatibility between OData and Endpoint Routing to work seamlessly and offer the best capabilities of both worlds to our libraries consumers.<\/p>\n<p>Today, we announce that this effort is over! OData release of 7.4.0 now allows using Endpoint Routing, which brings in a whole new spectrum of capabilities to take your APIs to the next level with the least amount of effort possible.<\/p>\n<p>&nbsp;<\/p>\n<h3>Getting Started<\/h3>\n<p>To fully bring this into action, we are going to follow the Entity Data Model (EDM) approach, which we have explored <a href=\"https:\/\/devblogs.microsoft.com\/odata\/simplifying-edm-with-odata\/\">previously\u00a0<\/a>by disabling Endpoint Routing, so let&#8217;s get started.<\/p>\n<p>We are going to create an ASP.NET Core Application from scratch as follows:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-3708\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-Web-Application.png\" alt=\"Image New Web Application\" width=\"865\" height=\"612\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-Web-Application.png 865w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-Web-Application-300x212.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-Web-Application-768x543.png 768w\" sizes=\"(max-width: 865px) 100vw, 865px\" \/><\/p>\n<p>Since the API template we are going to select already comes with an endpoint to return a list of weather forecasts, let&#8217;s name our project <em>WeatherAPI<\/em>, with ASP.NET Core 3.1 as a project configuration as follows:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter wp-image-3712\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-API-1.png\" alt=\"Image New API\" width=\"1174\" height=\"778\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-API-1.png 1382w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-API-1-300x199.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-API-1-1024x679.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/New-API-1-768x509.png 768w\" sizes=\"(max-width: 1174px) 100vw, 1174px\" \/><\/p>\n<p>&nbsp;<\/p>\n<h3>Installing OData 7.4.0 (Beta)<\/h3>\n<p>Now that we have created a new project, let&#8217;s go ahead and install the latest release of OData with version 7.4.0 by either using PowerShell command as follows:<\/p>\n<pre class=\"install-script\" id=\"package-manager-text\">Install-Package Microsoft.AspNetCore.OData -Version 7.4.0-beta<\/pre>\n<p>You can also navigate to the Nuget Package Manager as follows:<\/p>\n<p><img decoding=\"async\" class=\"aligncenter size-full wp-image-3743\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/ODataWithContextBeta.png\" alt=\"Image ODataWithContextBeta\" width=\"1470\" height=\"799\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/ODataWithContextBeta.png 1470w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/ODataWithContextBeta-300x163.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/ODataWithContextBeta-1024x557.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/03\/ODataWithContextBeta-768x417.png 768w\" sizes=\"(max-width: 1470px) 100vw, 1470px\" \/><\/p>\n<p>&nbsp;<\/p>\n<h3>Startup Setup<\/h3>\n<p>Now that we have the latest version of OData installed, and an existing controller for weather forecasts, let&#8217;s go ahead and setup our <em>startup.cs\u00a0<\/em>file as follows:<\/p>\n<pre class=\"lang:c# decode:true\">using System.Linq;\r\nusing Microsoft.AspNet.OData.Builder;\r\nusing Microsoft.AspNet.OData.Extensions;\r\nusing Microsoft.AspNetCore.Builder;\r\nusing Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing Microsoft.Extensions.Hosting;\r\nusing Microsoft.OData.Edm;\r\n\r\nnamespace WeatherAPI\r\n{\r\n    public class Startup\r\n    {\r\n        public Startup(IConfiguration configuration)\r\n        {\r\n            Configuration = configuration;\r\n        }\r\n\r\n        public IConfiguration Configuration { get; }\r\n\r\n        public void ConfigureServices(IServiceCollection services)\r\n        {\r\n            services.AddControllers();\r\n            services.AddOData();\r\n        }\r\n\r\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n        {\r\n            if (env.IsDevelopment())\r\n            {\r\n                app.UseDeveloperExceptionPage();\r\n            }\r\n\r\n            app.UseHttpsRedirection();\r\n\r\n            app.UseRouting();\r\n\r\n            app.UseAuthorization();\r\n\r\n            app.UseEndpoints(endpoints =&gt;\r\n            {\r\n                endpoints.MapControllers();\r\n                endpoints.Select().Filter().OrderBy().Count().MaxTop(10);\r\n                endpoints.MapODataRoute(\"odata\", \"odata\", GetEdmModel());\r\n            });\r\n        }\r\n\r\n        private IEdmModel GetEdmModel()\r\n        {\r\n            var odataBuilder = new ODataConventionModelBuilder();\r\n            odataBuilder.EntitySet&lt;WeatherForecast&gt;(\"WeatherForecast\");\r\n\r\n            return odataBuilder.GetEdmModel();\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>As you can see in the code above, we didn&#8217;t have to disable EndpointRouting as we used to do in the past in the <em>ConfigureServices\u00a0<\/em>method, you will also notice in the\u00a0<em>Configure\u00a0<\/em>method has all OData configurations as usual referencing creating an entity data model with whatever prefix we choose, in our case here we set it to\u00a0<em>odata\u00a0<\/em>but you can change that to virtually anything you want, including <em>api.<\/em><\/p>\n<p>&nbsp;<\/p>\n<h3>Weather Forecast Model<\/h3>\n<p>Before you run your API, you will need to do a slight change to the demo <em>WeatherForecast\u00a0<\/em>model that comes in with the API template, which is adding a key to it, otherwise OData wouldn&#8217;t know how to operate on a keyless model, so we are going to add an <em>Id\u00a0<\/em>of type GUID to the model, and this is how the WeatherForecast model would look like:<\/p>\n<pre class=\"lang:c# decode:true \">    public class WeatherForecast\r\n    {\r\n        public Guid Id { get; set; }\r\n        public DateTime Date { get; set; }\r\n        public int TemperatureC { get; set; }\r\n        public int TemperatureF =&gt; 32 + (int)(TemperatureC \/ 0.5556);\r\n        public string Summary { get; set; }\r\n    }<\/pre>\n<h3><\/h3>\n<p>&nbsp;<\/p>\n<h3>Weather Forecast Controller<\/h3>\n<p>We had to enable OData querying on the weather forecast endpoint while removing all the other unnecessary annotations, this is how our controller looks like:<\/p>\n<pre class=\"lang:c# decode:true\">using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Microsoft.AspNet.OData;\r\nusing Microsoft.AspNetCore.Mvc;\r\nusing Microsoft.Extensions.Logging;\r\n\r\nnamespace WeatherAPI.Controllers\r\n{\r\n    public class WeatherForecastController : ControllerBase\r\n    {\r\n        private static readonly string[] Summaries = new[]\r\n        {\r\n            \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\r\n        };\r\n\r\n        [EnableQuery]\r\n        public IEnumerable&lt;WeatherForecast&gt; Get()\r\n        {\r\n            var rng = new Random();\r\n            return Enumerable.Range(1, 5).Select(index =&gt; new WeatherForecast\r\n            {\r\n                Id = Guid.NewGuid(),\r\n                Date = DateTime.Now.AddDays(index),\r\n                TemperatureC = rng.Next(-20, 55),\r\n                Summary = Summaries[rng.Next(Summaries.Length)]\r\n            })\r\n            .ToArray();\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3>Hit Run<\/h3>\n<p>Now that we have everything in place, let&#8217;s run our API and hit our OData endpoint with an HTTP GET request as follows:<\/p>\n<pre class=\"lang:default decode:true\">https:\/\/localhost:44344\/odata\/weatherforecast<\/pre>\n<p>The following result should be returned:<\/p>\n<pre class=\"lang:js decode:true \">{\r\n  \"@odata.context\": \"https:\/\/localhost:44344\/odata\/$metadata#WeatherForecast\",\r\n  \"value\": [\r\n    {\r\n      \"Id\": \"66b86d0d-375f-4133-afb4-82b44f7f2e79\",\r\n      \"Date\": \"2020-03-02T23:07:52.4084956-08:00\",\r\n      \"TemperatureC\": 23,\r\n      \"Summary\": \"Mild\"\r\n    },\r\n    {\r\n      \"Id\": \"d534a764-4fb8-4f49-96c5-8f09987a61d8\",\r\n      \"Date\": \"2020-03-03T23:07:52.4085408-08:00\",\r\n      \"TemperatureC\": 9,\r\n      \"Summary\": \"Balmy\"\r\n    },\r\n    {\r\n      \"Id\": \"07583c78-b2f5-4119-acdb-50511ac02e8a\",\r\n      \"Date\": \"2020-03-04T23:07:52.4085416-08:00\",\r\n      \"TemperatureC\": -15,\r\n      \"Summary\": \"Hot\"\r\n    },\r\n    {\r\n      \"Id\": \"05810360-d1fb-4f89-be18-2b8ddc75beff\",\r\n      \"Date\": \"2020-03-05T23:07:52.4085421-08:00\",\r\n      \"TemperatureC\": 9,\r\n      \"Summary\": \"Hot\"\r\n    },\r\n    {\r\n      \"Id\": \"35b23b1a-4803-4c3e-aebc-ced17807b1e1\",\r\n      \"Date\": \"2020-03-06T23:07:52.4085426-08:00\",\r\n      \"TemperatureC\": 16,\r\n      \"Summary\": \"Hot\"\r\n    }\r\n  ]\r\n}<\/pre>\n<p>You can now try the regular operations of <strong>$select<\/strong>, <strong>$orderby<\/strong>, <strong>$filte<\/strong>r, <strong>$count<\/strong> and <strong>$top<\/strong> on your data and examine the functionality yourself.<\/p>\n<p>&nbsp;<\/p>\n<h3>Non-Edm Approach<\/h3>\n<p>If you decide to go the non-Edm route, you will need to install an additional Nuget package to resolve a Json formatting issue as follows:<\/p>\n<p>First of all install <strong>Microsoft.AspNetCore.Mvc.NewtonsoftJson\u00a0<\/strong>package by running the following PowerShell command:<\/p>\n<pre class=\"lang:default decode:true \">Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.2<\/pre>\n<p>You can also navigate for the package using Nuget Package manager as we did above.<\/p>\n<p>Secondly, you will need to modify your\u00a0<em>ConfigureService\u00a0<\/em>in your <em>Startup.cs\u00a0<\/em>file to enable the Json formatting extension method as follows:<\/p>\n<pre class=\"lang:c# decode:true \">using System.Linq;\r\nusing Microsoft.AspNet.OData.Extensions;\r\nusing Microsoft.AspNetCore.Builder;\r\nusing Microsoft.AspNetCore.Hosting;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing Microsoft.Extensions.Hosting;\r\n\r\nnamespace WeatherAPI\r\n{\r\n    public class Startup\r\n    {\r\n        public Startup(IConfiguration configuration)\r\n        {\r\n            Configuration = configuration;\r\n        }\r\n\r\n        public IConfiguration Configuration { get; }\r\n\r\n        public void ConfigureServices(IServiceCollection services)\r\n        {\r\n            services.AddControllers().AddNewtonsoftJson();\r\n            services.AddOData();\r\n        }\r\n\r\n        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n        {\r\n            if (env.IsDevelopment())\r\n            {\r\n                app.UseDeveloperExceptionPage();\r\n            }\r\n\r\n            app.UseHttpsRedirection();\r\n\r\n            app.UseRouting();\r\n\r\n            app.UseAuthorization();\r\n\r\n            app.UseEndpoints(endpoints =&gt;\r\n            {\r\n                endpoints.MapControllers();\r\n                endpoints.EnableDependencyInjection();\r\n                endpoints.Select().Filter().OrderBy().Count().MaxTop(10);\r\n            });\r\n        }\r\n    }\r\n}<\/pre>\n<p>Notice that we added\u00a0<em>AddNewtonsoftJson()\u00a0<\/em>to resolve the formatting issue with <strong>$select,\u00a0<\/strong>we have also removed the <em>MapODataRoute(..)\u00a0<\/em>and added\u00a0<em>EnableDependencyInjection()\u00a0<\/em>instead.<\/p>\n<p>With that, we have added back the weather forecast controller<strong> [ApiController]<\/strong> and <strong>[Route]<\/strong> annotations in addition to<strong> [HttpGet]<\/strong> as follows:<\/p>\n<pre class=\"lang:c# decode:true \">using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Microsoft.AspNet.OData;\r\nusing Microsoft.AspNetCore.Mvc;\r\n\r\nnamespace WeatherAPI.Controllers\r\n{\r\n    [ApiController]\r\n    [Route(\"api\/[controller]\")]\r\n    public class WeatherForecastController : ControllerBase\r\n    {\r\n        private static readonly string[] Summaries = new[]\r\n        {\r\n            \"Freezing\", \"Bracing\", \"Chilly\", \"Cool\", \"Mild\", \"Warm\", \"Balmy\", \"Hot\", \"Sweltering\", \"Scorching\"\r\n        };\r\n\r\n        [HttpGet]\r\n        [EnableQuery]\r\n        public IEnumerable&lt;WeatherForecast&gt; Get()\r\n        {\r\n            var rng = new Random();\r\n            return Enumerable.Range(1, 5).Select(index =&gt; new WeatherForecast\r\n            {\r\n                Id = Guid.NewGuid(),\r\n                Date = DateTime.Now.AddDays(index),\r\n                TemperatureC = rng.Next(-20, 55),\r\n                Summary = Summaries[rng.Next(Summaries.Length)]\r\n            })\r\n            .ToArray();\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Now, run your API and try all the capabilities of OData with your ASP.NET Core 3.1 API including Endpoint Routing.<\/p>\n<p>&nbsp;<\/p>\n<h3>Final Notes<\/h3>\n<ol>\n<li>The OData team will continue to address all the issues opened on the public github repo based on their priority.<\/li>\n<li>We are always open to feedback from the community, and we hope to get the community&#8217;s support on some of our public repos to keep OData and it&#8217;s client libraries running.<\/li>\n<li>With this current implementation of OData you can now enable <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/tutorials\/getting-started-with-swashbuckle?view=aspnetcore-3.1&amp;tabs=visual-studio\">Swagger<\/a> easily on your API without any issues, here&#8217;s an <a href=\"https:\/\/github.com\/hassanhabib\/OData3.1WithSwagger\">example<\/a><\/li>\n<li>You can clone the example we used in this article from this <a href=\"https:\/\/github.com\/hassanhabib\/ODataWithEndpointRouting3.1\">repo<\/a> here to try it for yourself.<\/li>\n<li>The final release of OData 7.4.0 library should be released within two weeks from the time this article was published.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Few months ago we announced an experimental release of OData for ASP.NET Core 3.1, and for those who could move forward with their applications without leveraging endpoint routing, the release was considered final, although not ideal. But for those who have existing APIs or were planning to develop new APIs leveraging endpoint routing, the OData\u00a07.3.0 [&hellip;]<\/p>\n","protected":false},"author":3348,"featured_media":3529,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-3693","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>Few months ago we announced an experimental release of OData for ASP.NET Core 3.1, and for those who could move forward with their applications without leveraging endpoint routing, the release was considered final, although not ideal. But for those who have existing APIs or were planning to develop new APIs leveraging endpoint routing, the OData\u00a07.3.0 [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/3693","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/3348"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=3693"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/3693\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3529"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=3693"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=3693"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=3693"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}