{"id":4241,"date":"2020-10-19T21:58:36","date_gmt":"2020-10-20T04:58:36","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=4241"},"modified":"2021-09-29T13:51:47","modified_gmt":"2021-09-29T20:51:47","slug":"asp-net-odata-8-0-preview-for-net-5","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/asp-net-odata-8-0-preview-for-net-5\/","title":{"rendered":"ASP.NET Core OData 8.0 Preview for .NET 5"},"content":{"rendered":"<p>Recently, OData team released the 8.0.0 preview version of ASP.NET Core OData on <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\/8.0.0-preview\">nuget.org<\/a>. It is the first version of OData supporting .NET 5, especially for ASP.NET Core 5. This version includes some breaking changes, such as model builder separation, namespace changes and configuration changes etc. Most importantly, the routing mechanism is changed a lot comparing to the previous 7.x version.<\/p>\n<p>This post is intended to introduce such changes using a basic tutorial about how to build OData service through ASP.NET Core OData 8.0 preview package. Besides, we are looking for requirements of features and reports of any bugs, issues that we can fix before the general availability. Please don&#8217;t hesitate to file any kind of issues at <a href=\"https:\/\/github.com\/OData\/aspnetcoreodata\">ASP.NET Core OData Github Repo<\/a>.<\/p>\n<p>Let us get started.<\/p>\n<h2>OData Service in practical<\/h2>\n<p>The basic flow to create OData service using ASP.NET Core OData 8.0 preview is same as the flow introduced in <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-core-odata-now-available\/\">ASP.NET Core OData now Available<\/a>. Please refer to that post for other details not listed in this post.<\/p>\n<h3>Create application<\/h3>\n<p>Visual Studio 2019 preview is necessary to create the ASP.NET Core 5.0 web application. So, let&#8217;s select &#8220;<strong>ASP.NET Core Web Application<\/strong>&#8221; project template in the following dialog to create the skeleton of the ASP.NET Core OData service.<\/p>\n<p><img decoding=\"async\" width=\"1024\" height=\"683\" class=\"wp-image-4246\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-3.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-3.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-3-300x200.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-3-768x512.png 768w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/p>\n<p>In the following &#8220;<strong>Create a new ASP.NET Core web application<\/strong>&#8221; dialog, select &#8220;<strong>API<\/strong>&#8221; and un-check the \u201c<strong>Configure for HTTPs<\/strong>\u201d for simplicity to create the application.\nBe noticed, ASP.NET Core 5.0 is selected as the target platform.<\/p>\n<p><img decoding=\"async\" width=\"983\" height=\"634\" class=\"wp-image-4247\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-4.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-4.png 983w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-4-300x193.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-4-768x495.png 768w\" sizes=\"(max-width: 983px) 100vw, 983px\" \/><\/p>\n<p>As mentioned, ASP.NET Core OData 8.0 is a Nuget package. Let&#8217;s use <strong>Nuget Package Manager<\/strong> to install it. In the below dialog, use &#8220;<strong>nuget.org<\/strong>&#8221; as package source, search and select &#8220;<strong>Microsoft.AspNetCore.OData<\/strong>&#8221; package, check &#8220;<strong>Include prerelease<\/strong>&#8221; and select 8.0.0-preview version to install.<\/p>\n<p><img decoding=\"async\" width=\"815\" height=\"458\" class=\"wp-image-4248\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-5.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-5.png 815w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-5-300x169.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-5-768x432.png 768w\" sizes=\"(max-width: 815px) 100vw, 815px\" \/><\/p>\n<p><a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/\" target=\"_blank\" rel=\"noopener noreferrer\">EF Core<\/a> is also used in this post, so do the same process as above to install &#8220;<strong>Microsoft.EntityFrameworkCore.InMemory<\/strong>&#8221; (so far, it&#8217;s 5.0.0-rc.2) and its dependencies (for simplicity, we use In Memory data source also).<\/p>\n<h3>Build Entity Data Model<\/h3>\n<p>We re-use the C# class models in <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-core-odata-now-available\/#add-the-model-classes\">Add the Model class section<\/a> of ASP.NET Core OData now Available to build the Entity Data Model (EDM).<\/p>\n<p>We can copy\/paste the &#8220;<strong>GetEdmModel()<\/strong>&#8221; method at <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-core-odata-now-available\/#build-the-edm-model\">Build the Edm Model<\/a> into &#8220;<strong>Startup<\/strong>&#8221; class in the new project.<\/p>\n<p>Be noted, the ASP.NET Core OData 8 preview depends on an individual OData model builder nuget package. The individual OData model builder has the same functionality as it in the existing Web API OData except the namespace. Please find more details about the OData model builder from <a href=\"https:\/\/devblogs.microsoft.com\/odata\/odata-model-builder-now-available\/\">OData Model Builder now Available<\/a>.<\/p>\n<h3><strong>Register the OData Services<\/strong><\/h3>\n<p>There is a big configuration change comparing to the previous ASP.NET Core OData version. In the previous 7.5 Web API OData version, we have &#8220;<strong>AddOData()<\/strong>&#8221; to register the mandatory services ahead to provide OData functionality and &#8220;<strong>MapODataRoute(&#8230;)<\/strong>&#8221; to register the single OData route based on the given OData Edm model. For example:<\/p>\n<pre class=\"lang:c# decode:true\">public class Startup\r\n{\r\n    public void ConfigureServices(IServiceCollection services)\r\n    {\r\n        services.AddRouting();\r\n        services.AddOData();\r\n    }\r\n\r\n    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n    {\r\n        app.UseRouting();\r\n        app.UseEndpoints(endpoints =&gt;\r\n        {\r\n            endpoints.MapODataRoute(\"odata\", \"odata\", GetEdmModel());\r\n        });\r\n    }\r\n\r\n    private static IEdmModel GetEdmModel()\r\n    {\r\n        \/\/ \u2026\r\n    }\r\n}\r\n<\/pre>\n<p>In the 8.0 preview, the configuration is changed. Where, &#8220;<strong>AddOData()<\/strong>&#8221; is kept, meanwhile &#8220;<strong>MapODataRoute(&#8230;)<\/strong>&#8221; is gone. &#8220;<strong>AddOData()<\/strong>&#8221; accepts a delegate to do the configuration on <strong>ODataOptions<\/strong>. For example, We can register Edm model, setup the query configuration by calling methods on <strong>ODataOptions<\/strong>. Below is the new configuration in 8.0 preview: [<strong>AddModel<\/strong> is renamed as <strong>AddRouteComponents<\/strong> in 8.0.]<\/p>\n<pre class=\"lang:c# decode:true\">public class Startup\r\n{\r\n    public void ConfigureServices(IServiceCollection services)\r\n    {\r\n        services.AddDbContext&lt;BookStoreContext&gt;(opt =&gt; opt.UseInMemoryDatabase(\"BookLists\"));\r\n        services.AddControllers();\r\n        services.AddOData(opt =&gt; opt.AddModel(\"odata\", GetEdmModel()));\r\n    }\r\n\r\n    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n    {\r\n        app.UseRouting();\r\n        app.UseEndpoints(endpoints =&gt;\r\n        {\r\n            endpoints.MapControllers();\r\n        });\r\n    }\r\n\r\n    private static IEdmModel GetEdmModel()\r\n    {\r\n        \/\/ \u2026\r\n    }\r\n}\r\n<\/pre>\n<h3>Run and test<\/h3>\n<p>Let&#8217;s re-use the &#8220;<strong>BooksController<\/strong>&#8221; and &#8220;<strong>PressesController<\/strong>&#8221; from the <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-core-odata-now-available\/\">ASP.NET Core OData now Available<\/a> and put them into the project. Now, the OData service is ready to test.<\/p>\n<p>For example, we can query a single book as by sending <strong><em>HTTP GET<\/em><\/strong> request at\u00a0&#8220;<strong>http:\/\/localhost:5000\/odata\/Books(1)<\/strong>&#8220;, the response has the following payload:<\/p>\n<pre class=\"lang:js decode:true \">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5000\/odata\/$metadata#Books\/$entity\",\r\n  \"Id\":\u00a01,\r\n  \"ISBN\":\u00a0\"978-0-321-87758-1\",\r\n  \"Title\":\u00a0\"Essential\u00a0C#5.0\",\r\n  \"Author\":\u00a0\"Mark\u00a0Michaelis\",\r\n  \"Price\":\u00a059.99,\r\n  \"Location\":\u00a0{\r\n    \"City\":\u00a0\"Redmond\",\r\n    \"Street\":\u00a0\"156TH\u00a0AVE\u00a0NE\"\r\n  }\r\n}\r\n<\/pre>\n<p>That&#8217;s it.<\/p>\n<h2>Multiple models<\/h2>\n<p>It&#8217;s easy to config multiple models in one OData service. Below is the sample codes that configures two models using different prefixes.<\/p>\n<pre class=\"lang:C# decode:true \">IEdmModel model1 = GetEdmModel1();\r\nIEdmModel model2 = GetEdmModel2();\r\nservices.AddOData(opt =&gt; opt.AddModel(\"v1\", model1).AddModel(\"v2\", model2));\r\n<\/pre>\n<p>Where, &#8220;<strong>v1<\/strong>&#8221; and &#8220;<strong>v2<\/strong>&#8221; are the prefixes used before the OData path. It should be unique in one OData service.<\/p>\n<p>In this configuration, we can call the service using following request Uri:<\/p>\n<ul>\n<li>&#8220;<strong><em>GET<\/em><\/strong> http:\/\/localhost:5000\/v1\/Books(1)&#8221; or<\/li>\n<li>&#8220;<strong><em>GET<\/em><\/strong> http:\/\/localhost:5000\/v2\/Books(1)&#8221;<\/li>\n<\/ul>\n<p>There&#8217;s another overload of &#8220;<strong>AddModel()<\/strong>&#8221; method without &#8220;<em>prefix<\/em>&#8221; parameter as below. It means there&#8217;s no prefix before the OData path.<\/p>\n<pre class=\"lang:C# decode:true \">services.AddOData(opt =&gt; opt.AddModel(model));\r\n<\/pre>\n<p>The request Uri should look like:<strong><em>GET<\/em><\/strong> <em>http:\/\/localhost:5000\/Books(1)<\/em>&#8221;<\/p>\n<h2>Prefix template<\/h2>\n<p>The &#8220;<strong>prefix<\/strong>&#8221; parameter in the method &#8220;<strong>AddModel<\/strong>&#8221; could be a template. For example<\/p>\n<pre class=\"lang:C# decode:true \">services.AddOData(opt =&gt; opt.AddModel(\"v{version}\", model));\r\n<\/pre>\n<p>In the case, we can call OData service using different version. for example:<\/p>\n<ul>\n<li>http:\/\/localhost:5000\/v1\/Books(1) #1<\/li>\n<li>http:\/\/localhost:5000\/vbeta\/Books(1) #2<\/li>\n<\/ul>\n<p>Where, the version in #1 is &#8220;1&#8221;, meanwhile the version in #2 is &#8220;beta&#8221;.<\/p>\n<p>You can inject a &#8220;<strong>version<\/strong>&#8221; parameter in the action of the controller to retrieve the &#8220;version&#8221; string. for example:<\/p>\n<pre class=\"lang:C# decode:true \">public IActionResult Get(int key, string version)\r\n{\r\n    \/\/ do something\r\n}\r\n<\/pre>\n<p>where, version has &#8220;1&#8221; for #1 request and &#8220;beta&#8221; for #2 request.<\/p>\n<h2>$batch<\/h2>\n<p>For performance, $batch is disabled by default. In order to enable $batch, you should do following configurations:<\/p>\n<p>1. Config the model using a batch handler. That is, you can call the following overload version to set up a batch handler.<\/p>\n<pre class=\"lang:C# decode:true \">public ODataOptions AddModel(string prefix, IEdmModel model, ODataBatchHandler batchHandler)\r\n<\/pre>\n<p>2. Enable the batch middleware before &#8220;UseRouting()&#8221; middleware.<\/p>\n<pre class=\"lang:C# decode:true \">public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n{\r\n    app.UseODataBatching(); \/\/ call before \"UseRouting\"\r\n    app.UseRouting();\r\n    \/\/ ...\r\n}\r\n<\/pre>\n<p>Now, the OData service can handle $batch request.<\/p>\n<h2>Dependency Injection for OData services<\/h2>\n<p>There&#8217;s another overload of &#8220;<strong>AddModel<\/strong>&#8221; to enable dependency injection of OData services as:<\/p>\n<pre class=\"lang:C# decode:true \">ODataOptions AddModel(string prefix, IEdmModel model, Action&lt;IContainerBuilder&gt; configureAction);\r\n<\/pre>\n<p>For example, customer can inject his own deserializer provider using the following codes:<\/p>\n<pre class=\"lang:C# decode:true \">services.AddOData(opt =&gt; opt.AddModel(\"odata\", model, builder =&gt; builder.AddService&lt;ODataDeserializerProvider&gt;(Microsoft.OData.ServiceLifetime.Singleton, sp =&gt; new MyDeserializerProvider(sp)));\r\n<\/pre>\n<h2>Query options<\/h2>\n<p>Query options (such as $fitler, $count, etc.) are disabled by default for security. However, it&#8217;s easy to enable it by calling method on the <strong>ODataOptions<\/strong>.<\/p>\n<p>For example, we can call query options related methods after model configuration as:<\/p>\n<pre class=\"lang:C# decode:true \">services.AddOData(opt =&gt; opt.AddModel(\"odata\", GetEdmModel()).Filter().Select().Expand());\r\n<\/pre>\n<p>The above codes enable $filter, $select and $expand.\nThen, we can send request <strong><em>GET<\/em> http:\/\/localhost:5000\/odata\/Books?$filter=Price le 50&amp;$expand=Press($select=Name)&amp;$select=Location($select=City)<\/strong><\/p>\n<p>We can get:<\/p>\n<pre class=\"lang:js decode:true \">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5000\/odata\/$metadata#Books(Location\/City,Press(Name))\",\r\n  \"value\":\u00a0[\r\n  {\r\n    \"Location\":\u00a0{\r\n      \"City\":\u00a0\"Bellevue\"\r\n      },\r\n    \"Press\":\u00a0{\r\n      \"Name\":\u00a0\"O'Reilly\"\r\n      }\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>Routing<\/h2>\n<p>OData routing takes the responsibility to match the incoming HTTP requests and dispatch those requests to the app&#8217;s executable endpoints, especially the action in the OData controller.<\/p>\n<p>This section, a test middleware is used to introduce some basic ideas of the routing in ASP.NET Core OData 8.0. For more detail routing, please looking forward to the next post called <strong>routing in ASP.NET Core OData 8.0<\/strong>.<\/p>\n<p>So, Let&#8217;s create a simple middleware between &#8220;UseRouting()&#8221; and &#8220;UseEndpoints()&#8221; as:<\/p>\n<pre class=\"lang:C# decode:true \">public class Startup\r\n{   \r\n    public void ConfigureServices(IServiceCollection services)\r\n    {\r\n        services.AddControllers();\r\n        services.AddOData(opt =&gt; opt.AddModel(\"odata\", GetEdmModel())\r\n            .AddModel(\"v{version}\", GetEdmModel())\r\n            .Filter().Select().Expand());\r\n    }\r\n\r\n    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\r\n    {\r\n        app.UseRouting();\r\n\r\n        \/\/ a test middleware\r\n        app.Use(next =&gt; context =&gt;\r\n        {\r\n            var endpoint = context.GetEndpoint();\r\n            if (endpoint == null)\r\n            {\r\n             return next(context);\r\n            }\r\n\r\n            IEnumerable templates;\r\n            IODataRoutingMetadata metadata = endpoint.Metadata.GetMetadata&lt;IODataRoutingMetadata&gt;();\r\n            if (metadata != null)\r\n            {\r\n                templates = metadata.Template.GetTemplates();\r\n            }\r\n\r\n            return next(context); \/\/ put a breaking point here\r\n        });\r\n\r\n        app.UseEndpoints(endpoints =&gt;\r\n        {\r\n            endpoints.MapODataRoute(\"odata1\", \"v1\", GetEdmModel());\r\n        });\r\n    }\r\n}\r\n<\/pre>\n<p>The middleware simply retrieves the selected endpoint from the <strong>HttpContext<\/strong>, then retrieves the OData routing metadata defined on the endpoint.<\/p>\n<p>Let&#8217;s run it as debug, send request as <strong><em>GET<\/em><\/strong><strong> &#8220;<strong>http:\/\/localhost:5000\/v3\/Books(1)<\/strong>&#8220;<\/strong>, then stop at a breaking point in this middleware.<\/p>\n<p>Below is the debug information on the Endpoint.\n<img decoding=\"async\" class=\"AspnetCoreodata80_debug1\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/AspnetCoreodata80_debug1.png\" \/><\/p>\n<p>We can find that there&#8217;s an endpoint (<strong>BookStore.Controllers.BooksController.Get(&#8230;)<\/strong> action) selected.\nThe &#8220;RoutePattern&#8221; property of this endpoint has &#8220;<strong>v{version}\/Books({key})<\/strong>&#8221; template associated. This is created by built-in convention routing.<\/p>\n<p>Besides, the endpoint has &#8220;<strong>IODataRoutingMetadata<\/strong>&#8221; associated. Below is the debug information on OData routing metadata.\n<img decoding=\"async\" class=\"AspnetCoreodata80_debug2\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/AspnetCoreodata80_debug2.png\" \/><\/p>\n<p>We can find that the &#8220;<strong>Prefix<\/strong>&#8221; is &#8220;v{version}&#8221;. Importantly, it has an OData path template with 2 template segments associated.\nOne is entity set segment template, the other is key segment template.\nThe OData template is used to generate the route pattern and the real OData path for a certain request.\nBot built-in convention routing, attribute routing and path\/segment template will be discussed in next post.<\/p>\n<h1 id=\"summary\" class=\"\">Summary<\/h2>\n<p>Thanks for reading and trying. This post is a simple introduction about the ASP.NET Core OData 8.0. We encourage you to download it and start building amazing OData service running on ASP.NET Core 5.<\/p>\n<p>You can refer to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/BookStoreAspNetCoreOData8Preview\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>\u00a0for the sample project created in this blog. Any questions or concerns, feel free email to\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, OData team released the 8.0.0 preview version of ASP.NET Core OData on nuget.org. It is the first version of OData supporting .NET 5, especially for ASP.NET Core 5. This version includes some breaking changes, such as model builder separation, namespace changes and configuration changes etc. Most importantly, the routing mechanism is changed a lot [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":3286,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[117],"tags":[502,48,80,81],"class_list":["post-4241","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-webapi","tag-asp-net-core","tag-odata","tag-web-api","tag-web-api-odata"],"acf":[],"blog_post_summary":"<p>Recently, OData team released the 8.0.0 preview version of ASP.NET Core OData on nuget.org. It is the first version of OData supporting .NET 5, especially for ASP.NET Core 5. This version includes some breaking changes, such as model builder separation, namespace changes and configuration changes etc. Most importantly, the routing mechanism is changed a lot [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4241","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\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=4241"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4241\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3286"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=4241"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=4241"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=4241"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}