{"id":4593,"date":"2021-07-30T12:53:29","date_gmt":"2021-07-30T19:53:29","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=4593"},"modified":"2021-09-10T10:25:55","modified_gmt":"2021-09-10T17:25:55","slug":"tutorial-creating-a-service-with-odata-8-0","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/tutorial-creating-a-service-with-odata-8-0\/","title":{"rendered":"Tutorial: Creating a Service with ASP.NET Core OData 8.0 for .NET 5"},"content":{"rendered":"<p>In this document, we will walk through the process of creating a local service using OData 8.0, which is optimized to support ASP.NET Core 5. To learn more about the changes, check out <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-odata-8-0-preview-for-net-5\/\">ASP.NET Core OData 8.0 Preview for .NET 5<\/a>\u00a0(which also references <a href=\"https:\/\/devblogs.microsoft.com\/odata\/asp-net-core-odata-now-available\/\">ASP.NET Core OData now Available<\/a>), written by my colleague, Sam. You&#8217;ll notice that this post has a lot of similarities; here, I&#8217;m aiming to consolidate our documentation and share my experience from the perspective of an intern new to OData.<\/p>\n<h3>Topics Covered<\/h3>\n<ul>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#odata-service-in-practical\">Creating an OData Service<\/a>\n<ul>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#create-application\">Create the Application<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#build-entity-data-model\">Build the Entity Data Model<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#register-the-odata-services\">Register Services<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#test-query-metadata\">Test: Query Metadata<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#run-and-test\">Create the Data Source<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#add-controllers\">Add Controllers<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#test-query-data\">Test: Query Data<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#recap\">Recap<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#additional-features\">Additional Features<\/a>\n<ul>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#query-options\">Query Options<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#batch\">$batch<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#multiple-models\">Multiple Models<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#prefix-template\">Prefix Templates<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#dependency-injection-for-odata-services\">Dependency Injection for OData Services<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#routing\">Routing<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/?p=4593&amp;preview=true#summary\">Conclusion<\/a><\/li>\n<\/ul>\n<h3>Software Used<\/h3>\n<ul>\n<li><a href=\"https:\/\/visualstudio.microsoft.com\/\">Visual Studio 2019<\/a><\/li>\n<li><a href=\"https:\/\/www.postman.com\/\">Postman<\/a><\/li>\n<\/ul>\n<p>As always, please don\u2019t hesitate to file any issues at <a href=\"https:\/\/github.com\/OData\/aspnetcoreodata\" target=\"_blank\" rel=\"noopener\">ASP.NET Core OData Github Repo<\/a>. Let&#8217;s get started!<\/p>\n<h1 id=\"odata-service-in-practical\">Creating an OData Service<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<h2 id=\"create-application\">Create the Application<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>Let&#8217;s start by opening\u00a0<a href=\"https:\/\/visualstudio.microsoft.com\/\">Visual Studio 2019<\/a> and creating a new project. Select the \u201c<strong>ASP.NET Core Web App<\/strong>\u201d project template in the following dialog to create a skeleton of the ASP.NET Core OData service.<\/p>\n<p><a class=\"lightbox-link\" href=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj.png\" aria-label=\"Lightbox image, click or press enter to enlarge\" data-featherlight=\"image\"><img decoding=\"async\" class=\"lazyloaded alignnone wp-image-4609 size-full\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj.png\" alt=\"Create new project\" width=\"1350\" height=\"945\" data-src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2020\/10\/word-image-3.png\" data-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\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj.png 1350w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj-300x210.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj-1024x717.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/create-new-proj-768x538.png 768w\" sizes=\"(max-width: 1350px) 100vw, 1350px\" \/><\/a><\/p>\n<p>On the next page&#8217;s \u201c<b>Configure your new project<\/b>\u201d dialog, fill out your project&#8217;s name (I&#8217;ve chosen &#8220;<strong>BookStore<\/strong>&#8220;) and location.<\/p>\n<p>Next, under &#8220;<strong>Additional information<\/strong>,&#8221; make sure that ASP.NET Core 5.0 is selected as the target platform, choose your preferred authentication type, and un-check \u201c<strong>Configure for HTTPS<\/strong>\u201d (for simplicity)\u00a0to create the application.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-4608\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information.png\" alt=\"additional information\" width=\"1350\" height=\"945\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information.png 1350w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information-300x210.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information-1024x717.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/additional-information-768x538.png 768w\" sizes=\"(max-width: 1350px) 100vw, 1350px\" \/><\/a><\/p>\n<h3 id=\"install-the-nuget-package\">Install NuGet Packages<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h3>\n<p class=\"\">Once the empty application has been created, our next step is to install a couple NuGet packages from <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\" target=\"_blank\" rel=\"noopener\">NuGet.org<\/a>: ASP.NET Core OData and EntityFrameworkCore.InMemory.<\/p>\n<p class=\"\">First, let&#8217;s install the ASP.NET Core OData NuGet package. In the solution explorer, right click on <strong>Dependencies<\/strong>\u00a0in the <strong>BookStore<\/strong>\u00a0project and select &#8220;<strong>Manage NuGet Packages<\/strong>&#8221; to open the NuGet Package Manager dialog. In this dialog, select the latest stable &#8220;<strong>Microsoft.AspNetCore.OData<\/strong>&#8221;\u00a0package and install it (in this case, 8.0.1).<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-4615\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core.png\" alt=\"NuGet install OData\" width=\"1468\" height=\"643\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core.png 1468w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core-300x131.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core-1024x449.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-core-768x336.png 768w\" sizes=\"(max-width: 1468px) 100vw, 1468px\" \/><\/a><\/p>\n<p>We&#8217;ll go through the same process for <a href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/\" target=\"_blank\" rel=\"noopener\">EF Core<\/a> by installing &#8220;<strong>Microsoft.EntityFrameworkCore.InMemory<\/strong>&#8221;\u00a0and its dependencies (for simplicity, we&#8217;re using the version with the <em>In-Memory<\/em>\u00a0data source).<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-4616\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef.png\" alt=\"NuGet install EF\" width=\"1470\" height=\"589\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef.png 1470w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef-300x120.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef-1024x410.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/nuget-install-ef-768x308.png 768w\" sizes=\"(max-width: 1470px) 100vw, 1470px\" \/><\/a><\/p>\n<p>Now, we have the following project configuration:<\/p>\n<p><a href=\"https:\/\/gist.github.com\/xuzhg\/43e2a08244c464804a8dfe5d945b3acf#file-bookstore-csproj\">bookstore.csproj<\/a><\/p>\n<pre>&lt;<span class=\"pl-ent\">Project<\/span> <span class=\"pl-e\">Sdk<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.NET.Sdk.Web<span class=\"pl-pds\">\"<\/span><\/span>&gt;\r\n\r\n\u00a0 \u00a0 &lt;<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n  \u00a0     &lt;<span class=\"pl-ent\">TargetFramework<\/span>&gt;netcoreapp2.1&lt;\/<span class=\"pl-ent\">TargetFramework<\/span>&gt;\r\n    &lt;\/<span class=\"pl-ent\">PropertyGroup<\/span>&gt;\r\n\r\n    &lt;ItemGroup&gt;\r\n        &lt;Folder Include=\"wwwroot\\\" \/&gt;\r\n    &lt;\/ItemGroup&gt;\r\n\r\n    &lt;ItemGroup&gt;\r\n         &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.AspNetCore.App<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n         &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.AspNetCore.OData<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>7.0.0<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n         &lt;<span class=\"pl-ent\">PackageReference<\/span> <span class=\"pl-e\">Include<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>Microsoft.EntityFrameworkCore.InMemory<span class=\"pl-pds\">\"<\/span><\/span> <span class=\"pl-e\">Version<\/span>=<span class=\"pl-s\"><span class=\"pl-pds\">\"<\/span>2.1.0<span class=\"pl-pds\">\"<\/span><\/span> \/&gt;\r\n    &lt;\/ItemGroup&gt;\r\n\r\n&lt;\/Project&gt;<\/pre>\n<h2 id=\"build-entity-data-model\">Build the Entity Data Model<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>We will now add the model classes for our bookstore project and use them to build the Entity Data Model (EDM).<\/p>\n<h3>Add the Model Classes<\/h3>\n<p>A model is an object representing the data in the application. In this tutorial, we use the POCOs (<strong>P<\/strong>lain\u00a0<strong>O<\/strong>ld\u00a0<strong>C<\/strong>LR\u00a0<strong>O<\/strong>bject) classes to represent our book store models.<\/p>\n<p>To organize our models, we will first create a folder to store them. Right click the <strong>BookStore<\/strong> project in the solution explorer, then select <strong>Add<\/strong>\u00a0&gt;\u00a0<strong>New Folder<\/strong> and name the folder &#8220;<strong>Models<\/strong>&#8220;. Now, let&#8217;s add some bookstore-related classes in a new file, &#8220;<strong>DataSource.cs<\/strong>&#8220;, in our <strong>Models<\/strong> folder:<\/p>\n<p><a href=\"https:\/\/gist.github.com\/xuzhg\/871e540a3f7e9634c374398b68655523#file-bookstoremodelclass-cs\">bookstoremodelclass.cs<\/a><\/p>\n<pre>\/\/ Book\r\npublic class Book\r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">int<\/span> <span class=\"pl-en\">Id<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-en\">ISBN<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-en\">Title<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-en\">Author<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">decimal<\/span> <span class=\"pl-en\">Price<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">Address<\/span> <span class=\"pl-en\">Location<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">Press<\/span> <span class=\"pl-en\">Press<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n}\r\n\r\n\/\/ Category\r\n<span class=\"pl-k\">public<\/span> <span class=\"pl-k\">enum<\/span> <span class=\"pl-en\">Category<\/span>\u00a0\r\n{\r\n    Book,\r\n    Magazine,\r\n    EBook\r\n}\r\n\r\n\/\/ Address\r\npublic class Address \r\n{\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-en\">City<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\u00a0\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">string<\/span> <span class=\"pl-en\">Street<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n}<\/pre>\n<p>In this file, we have defined the following classes as these CLR types:<\/p>\n<ul>\n<li><strong>Book, Press<\/strong> &#8211; <em>Entity<\/em> Type<\/li>\n<li><strong>Address<\/strong> &#8211;\u00a0<em>Complex<\/em> Type<\/li>\n<li class=\"\"><strong>Category<\/strong> &#8211; <em>Enum<\/em> Type<\/li>\n<\/ul>\n<h3>Build the Entity Data Model<\/h3>\n<p class=\"\">OData uses the Entity Data Model (EDM) to describe the structure of data. In ASP.NET Core OData, it\u2019s easy to build the EDM based on the above CLR types (<em>Entity<\/em>, <em>Complex<\/em>, <em>Enum<\/em>).<\/p>\n<p class=\"\">With that in mind, let&#8217;s add the following private static method at the end of <strong>Startup.cs<\/strong>, which actually builds a model from the classes we&#8217;ve just defined.<\/p>\n<p><a href=\"https:\/\/gist.github.com\/xuzhg\/2528597c2c9d81ba81a64a6f06d0fd1e#file-startupmodelbuild-cs\">startupModelbuild.cs<\/a><\/p>\n<pre><span class=\"kwd\">public<\/span> <span class=\"kwd\">class<\/span> <span class=\"typ\">Startup<\/span> <span class=\"pun\">{ <\/span>\r\n\r\n    \/\/ ...\r\n\r\n    private static IEdmModel GetEdmModel() \r\n    { \r\n        ODataConventionModelBuilder builder = new ODataConventionModelBuilder(); \r\n        builder.EntitySet&lt;Book&gt;(\"Books\"); \r\n        builder.EntitySet&lt;Press&gt;(\"Presses\"); \r\n        return builder.GetEdmModel(); \r\n    }\r\n}<\/pre>\n<p>Now, we have defined two entity sets named \u201c<strong>Books<\/strong>\u201d and \u201c<strong>Presses<\/strong>\u201d.<\/p>\n<h2 id=\"register-the-odata-services\">Register Services<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<h3>Register the OData Services<\/h3>\n<p>Let&#8217;s register the OData services using OData 8.0. We will add the following <strong>ConfigureServices()<\/strong> method to <strong>Startup.cs<\/strong>, which adds services such as authentication, the models, and querying capabilities:<\/p>\n<pre class=\"lang:c# decode:true prettyprinted\" tabindex=\"0\"><span class=\"kwd\">public<\/span> <span class=\"kwd\">class<\/span> <span class=\"typ\">Startup<\/span>\r\n<span class=\"pun\">{\r\n\r\n<\/span>    \/\/ ...\r\n    \r\n    <span class=\"kwd\">public<\/span> <span class=\"kwd\">void<\/span> <span class=\"typ\">ConfigureServices<\/span><span class=\"pun\">(<\/span><span class=\"typ\">IServiceCollection<\/span><span class=\"pln\"> services<\/span><span class=\"pun\">)<\/span>\r\n    <span class=\"pun\">{<\/span>\r\n<span class=\"pln\">        services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddControllers<\/span><span class=\"pun\">();<\/span><span class=\"pln\">\r\n        services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.AddRouteComponents<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">GetEdmModel<\/span><span class=\"pun\">()));<\/span>\r\n    <span class=\"pun\">}<\/span>\r\n<span class=\"pun\">}<\/span><\/pre>\n<h3>Register the OData Endpoint<\/h3>\n<p class=\"\">We also need to add OData route to register the OData endpoint so we can query our data using URLs. To do so, let&#8217;s add our models and call \u201c<strong>GetEdmModel()<\/strong>\u201d to bind the EDM to the endpoint &#8212; the part of our application that&#8217;s accessible to us. At this point, the <strong>Configure()<\/strong> method in <strong>Startup.cs<\/strong>\u00a0should look like this:<\/p>\n<pre class=\"lang:c# decode:true prettyprinted\" tabindex=\"0\"><span class=\"kwd\">public<\/span> <span class=\"kwd\">void<\/span> <span class=\"typ\">Configure<\/span><span class=\"pun\">(<\/span><span class=\"typ\">IApplicationBuilder<\/span><span class=\"pln\"> app<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">IWebHostEnvironment<\/span><span class=\"pln\"> env<\/span><span class=\"pun\">)<\/span>\r\n<span class=\"pun\">{\r\n<\/span><span class=\"pln\">    app<\/span><span class=\"pun\">.<\/span><span class=\"typ\">UseRouting<\/span><span class=\"pun\">();<\/span><span class=\"pln\">\r\n    app<\/span><span class=\"pun\">.<\/span><span class=\"typ\">UseEndpoints<\/span><span class=\"pun\">(<\/span><span class=\"pln\">endpoints <\/span><span class=\"pun\">=&gt;<\/span>\r\n<span class=\"pun\">    {<\/span><span class=\"pln\">\r\n        endpoints<\/span><span class=\"pun\">.<\/span><span class=\"typ\">MapODataRoute<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span> <span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">GetEdmModel<\/span><span class=\"pun\">());<\/span>\r\n    <span class=\"pun\">});<\/span>\r\n<span class=\"pun\">}<\/span><\/pre>\n<h2>Test: Query Metadata<\/h2>\n<p>The OData service is now ready to run so we can access its basic functionalities, such as querying the <em>metadata<\/em> (XML representation of the EDM).<\/p>\n<p>Remember to use the proper dependencies in your files, then build (<strong>Build<\/strong> &gt; <strong>Build BookStore<\/strong>) and run (<strong>Debug<\/strong> &gt; <strong>Start Without Debugging<\/strong>) in Visual Studio. This should open up a new window that tells you it&#8217;s listening on a localhost URL. Verify that side of things is working by opening that URL (with the OData extension, as in http:\/\/localhost:5000\/odata) in your browser &#8212; it should\u00a0display an overview of your service.<\/p>\n<p>Once it\u2019s running, we can use any client tools (I&#8217;m using <a href=\"https:\/\/www.getpostman.com\/\" target=\"_blank\" rel=\"noopener\">Postman<\/a>) to issue requests. So, let&#8217;s create a new HTTP GET Request in Postman and enter the localhost URL. If you add &#8220;\/odata\/$metadata&#8221; to the end of the URL, you should be able to see the metadata of your newly created service!<\/p>\n<p>Your full HTTP request should look something like this:<\/p>\n<p class=\"\"><em><strong>GET<\/strong>\u00a0http:\/\/localhost:5000\/odata\/$metadata<\/em><\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-4628\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata.png\" alt=\"temporary postman query metadata\" width=\"1706\" height=\"907\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata.png 1706w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata-300x159.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata-1024x544.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata-768x408.png 768w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/07\/temp-postman-query-metadata-1536x817.png 1536w\" sizes=\"(max-width: 1706px) 100vw, 1706px\" \/><\/a><\/p>\n<h2 id=\"run-and-test\">Create the Data Source<\/h2>\n<h3>Create Data Context<\/h3>\n<p>In order to query the actual data we care about &#8212; in this case, books &#8212; we need a database that stores it!<\/p>\n<p>Inside the <strong>Models<\/strong> folder, let&#8217;s create a new class named\u00a0&#8220;<strong>BookStoreContext.cs<\/strong>&#8221; that extends <strong>DbContext<\/strong>.<\/p>\n<p><a href=\"https:\/\/gist.github.com\/xuzhg\/f2e9d6d1f66c4f82be1e4d4824277cd3#file-bookstorecontext-cs\">BookStoreContext.cs<\/a><\/p>\n<pre><span class=\"pl-k\">public<\/span> <span class=\"pl-k\">class<\/span> <span class=\"pl-en\">BookStoreContext<\/span> : <span class=\"pl-k\">DbContext<\/span>\r\n{\r\n\u00a0 \u00a0 <span class=\"pl-k\">public<\/span> <span class=\"pl-en\">BookStoreContext<\/span>(<span class=\"pl-k\">DbContextOptions<\/span>&lt;<span class=\"pl-k\">BookStoreContext<\/span>&gt; <span class=\"pl-en\">options<\/span>)\r\n        : <span class=\"pl-k\">base<\/span>(<span class=\"pl-smi\">options<\/span>)\r\n    {\r\n    }\r\n\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">DbSet<\/span>&lt;<span class=\"pl-k\">Book<\/span>&gt; <span class=\"pl-en\">Books<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n    <span class=\"pl-k\">public<\/span> <span class=\"pl-k\">DbSet<\/span>&lt;<span class=\"pl-k\">Press<\/span>&gt; <span class=\"pl-en\">Presses<\/span> { <span class=\"pl-k\">get<\/span>; <span class=\"pl-k\">set<\/span>; }\r\n\r\n    <span class=\"pl-k\">protected<\/span> <span class=\"pl-k\">override<\/span> <span class=\"pl-k\">void<\/span> <span class=\"pl-en\">OnModelCreating<\/span>(<span class=\"pl-k\">ModelBuilder<\/span> <span class=\"pl-en\">modelBuilder<\/span>)\r\n    {\r\n        <span class=\"pl-smi\">modelBuilder<\/span>.<span class=\"pl-en\">Entity<\/span>&lt;<span class=\"pl-k\">Book<\/span>&gt;().<span class=\"pl-en\">OwnsOne<\/span>(<span class=\"pl-en\">c<\/span> <span class=\"pl-k\">=&gt;<\/span> <span class=\"pl-smi\">c<\/span>.<span class=\"pl-smi\">Location<\/span>);\r\n    }\r\n}\r\n\r\n<\/pre>\n<p>Then, to add this new context to our service, let&#8217;s revise our <strong>Startup.cs<\/strong>\u00a0file so that the <strong>ConfigureServices()<\/strong><span style=\"font-size: 1rem;\">\u00a0method looks like this:<\/span><\/p>\n<pre class=\"lang:c# decode:true prettyprinted\" tabindex=\"0\"><span class=\"kwd\">public<\/span> <span class=\"kwd\">class<\/span> <span class=\"typ\">Startup<\/span>\r\n<span class=\"pun\">{\r\n\r\n<\/span>    \/\/ ...\r\n    \r\n    <span class=\"kwd\">public<\/span> <span class=\"kwd\">void<\/span> <span class=\"typ\">ConfigureServices<\/span><span class=\"pun\">(<\/span><span class=\"typ\">IServiceCollection<\/span><span class=\"pln\"> services<\/span><span class=\"pun\">)<\/span>\r\n    <span class=\"pun\">{\r\n<\/span>        services.AddDbContext&lt;BookStoreContext&gt;(opt =&gt; opt.UseInMemoryDatabase(\"BookLists\")); \/\/ new\r\n<span class=\"pln\">        services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddControllers<\/span><span class=\"pun\">();<\/span><span class=\"pln\">\r\n        services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.AddRouteComponents<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">GetEdmModel<\/span><span class=\"pun\">()));<\/span>\r\n    <span class=\"pun\">}<\/span>\r\n<span class=\"pun\">}<\/span><\/pre>\n<h3>Add Data<\/h3>\n<p class=\"\">We also need the data itself! Let&#8217;s add some inline model data on just a couple books for demonstration purposes in a new file in our <strong>Models<\/strong> folder.<\/p>\n<p><a href=\"https:\/\/gist.github.com\/xuzhg\/d40d70ecdaa9054f0ae8e92ba80746da#file-datasource-cs\" target=\"_blank\" rel=\"noopener\">DataSource.cs<\/a><\/p>\n<pre>public static class DataSource\r\n{\r\n    private static IList&lt;Book&gt; _books { get; set; }\r\n\r\n    public static IList&lt;Book&gt; GetBooks()\r\n    {\r\n        if (_books != null)\r\n        {\r\n            return _books;\r\n        }\r\n\r\n        _books = new List&lt;Book&gt;();\r\n\r\n        \/\/ book #1\r\n        Book book = new Book\r\n        {\r\n            Id = 1,\r\n            ISBN = \"978-0-321-87758-1\",\r\n            Title = \"Essential C#5.0\",\r\n            Author = \"Mark Michaelis\",\r\n            Price = 59.99m,\r\n            Location = new Address { City = \"Redmond\", Street = \"156TH AVE NE\" },\r\n            Press = new Press\r\n            {\r\n                Id = 1,\r\n                Name = \"Addison-Wesley\",\r\n                Category = Category.Book\r\n            }\r\n        };\r\n        _books.Add(book);\r\n\r\n        \/\/ book #2\r\n        book = new Book\r\n        {\r\n            Id = 2,\r\n            ISBN = \"063-6-920-02371-5\",\r\n            Title = \"Enterprise Games\",\r\n            Author = \"Michael Hugos\",\r\n            Price = 49.99m,\r\n            Location = new Address { City = \"Bellevue\", Street = \"Main ST\" },\r\n            Press = new Press\r\n            {\r\n                Id = 2,\r\n                Name = \"O'Reilly\",\r\n                Category = Category.EBook,\r\n            }\r\n        };\r\n        _books.Add(book);\r\n\r\n        return _books;\r\n    }\r\n}<\/pre>\n<h2>Add Controllers<\/h2>\n<p>Let&#8217;s add a way to not just have the data, but be able to access and manipulate it &#8212; a <strong>Controller<\/strong>. We can create a new folder for &#8220;<strong>Controllers<\/strong>,&#8221; and within it, create a &#8220;<strong>BooksController.cs<\/strong>&#8221; class. To start, it&#8217;ll be able to simply keep track of the data:<\/p>\n<pre>public class BooksController : ODataController\r\n{\r\n    private BookStoreContext _db;\r\n\r\n    public BooksController(BookStoreContext context)\r\n    {\r\n        _db = context;\r\n        if (context.Books.Count() == 0)\r\n        {\r\n            foreach (var b in DataSource.GetBooks())\r\n            {\r\n                context.Books.Add(b);\r\n                context.Presses.Add(b.Press);\r\n            }\r\n            context.SaveChanges();\r\n        }\r\n    }        \r\n}<\/pre>\n<p>Now, let&#8217;s make it so we can access our book data through some <strong>GET<\/strong>\u00a0methods:<\/p>\n<pre>public class BooksController : ODataController\r\n{\r\n    \/\/ ...\r\n  \r\n    \/\/ Return all books\r\n    [EnableQuery]\r\n    public IActionResult Get()\r\n    {\r\n        return Ok(_db.Books);\r\n    }\r\n\r\n    \/\/ Returns a specific book given its key\r\n    [EnableQuery]\r\n    public IActionResult Get(int key)\r\n    {\r\n        return Ok(_db.Books.FirstOrDefault(c =&gt; c.Id == key));\r\n    }\r\n}<\/pre>\n<p>We might also want to add some <strong>POST<\/strong>\u00a0methods to not only read the data, but change it:<\/p>\n<pre>public class BooksController : ODataController\r\n{\r\n    \/\/ ...\r\n    \r\n    \/\/ Create a new book\r\n    [EnableQuery]\r\n    public IActionResult Post([FromBody]Book book)\r\n    {\r\n        _db.Books.Add(book);\r\n        _db.SaveChanges();\r\n        return Created(book);\r\n    }\r\n}<\/pre>\n<p>We can continue this process of adding different types of <strong>GET<\/strong>, <strong>POST<\/strong>, and <strong>DELETE<\/strong>\u00a0methods for different situations of dealing with books. Additionally, we can go through a similar process to create Controllers for our other classes whose data we want to access and manipulate &#8212; for example, a <strong>PressesController<\/strong>.<\/p>\n<h2>Test: Query Data<\/h2>\n<p class=\"\">Finally, the OData service is ready to test for greater capabilities.<\/p>\n<p class=\"\">For example, we can query a single book as by sending the following HTTP request:<\/p>\n<p class=\"\"><em><strong>GET<\/strong> http:\/\/localhost:5000\/odata\/Books(1)<\/em><\/p>\n<p class=\"\">This will call our first GET\u00a0method we defined on our <strong>BooksController<\/strong>, and the response should have the following payload:<\/p>\n<pre class=\"lang:js decode:true prettyprinted\" tabindex=\"0\"><span class=\"pun\">{<\/span>\r\n    <span class=\"str\">\"@odata.context\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"http:\/\/localhost:5000\/odata\/$metadata#Books\/$entity\"<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"Id\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"lit\">1<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"ISBN\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"978-0-321-87758-1\"<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"Title\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"Essential\u00a0C#5.0\"<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"Author\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"Mark\u00a0Michaelis\"<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"Price\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"lit\">59.99<\/span><span class=\"pun\">,<\/span>\r\n    <span class=\"str\">\"Location\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"pun\">{<\/span>\r\n        <span class=\"str\">\"City\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"Redmond\"<\/span><span class=\"pun\">,<\/span>\r\n        <span class=\"str\">\"Street\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"156TH\u00a0AVE\u00a0NE\"<\/span>\r\n<span class=\"pun\">    }<\/span>\r\n<span class=\"pun\">}<\/span><\/pre>\n<p>This will call our first <strong>GET<\/strong>\u00a0method we defined on our <strong>BooksController<\/strong>, and the response should have the following payload:<\/p>\n<p>Similarly, we can also test our <strong>POST<\/strong> method that creates a new book by making the following request:<\/p>\n<p><strong><em>POST<\/em><\/strong><em class=\"\">\u00a0http:\/\/localhost:5000\/odata\/Books<\/em><\/p>\n<p><em>Content-Type: application\/json<\/em><\/p>\n<p class=\"\"><em>Content:<\/em><\/p>\n<pre>{\r\n    \"Id\":3,\"ISBN\":\"82-917-7192-5\",\"Title\":\"Hary Potter\",\"Author\":\"J. K. Rowling\",\r\n    \"Price\":199.99,\r\n    \"Location\":{\r\n        \"City\":\"Shanghai\",\r\n        \"Street\":\"Zhongshan RD\"\r\n    }\r\n}<\/pre>\n<p>Our result should be:<\/p>\n<pre>{\r\n    \"Id\":3,\"ISBN\":\"82-917-7192-5\",\"Title\":\"Hary Potter\",\"Author\":\"J. K. Rowling\",\r\n    \"Price\":199.99,\r\n    \"Location\":{\r\n        \"City\":\"Shanghai\",\r\n        \"Street\":\"Zhongshan RD\"\r\n    }\r\n}<\/pre>\n<h2>Recap<\/h2>\n<p>At this point, our solution space looks something like this:<\/p>\n<pre><strong>Solution 'BookStore'<\/strong>\r\n| <strong>BookStore<\/strong>\r\n| | Connected Services\r\n| | <strong>Dependencies<\/strong>\r\n| | Properties\r\n| | wwwroot\r\n| | <strong>Controllers\r\n<\/strong>| | | <strong>BooksController.cs<\/strong>\r\n| | <strong>Models<\/strong>\r\n| | | <strong>BookStoreContext.cs<\/strong>\r\n| | | <strong>bookstoremodelclass.cs<\/strong>\r\n| | | <strong>DataSource.cs<\/strong>\r\n| | Pages\r\n| | appsettings.json\r\n| | Program.cs\r\n| | <strong>Startup.cs<\/strong><\/pre>\n<p>We started by downloading some necessary packages for our dependencies. Then, we created an Entity Data Model by adding some new classes that defined our <em>Entity<\/em>,\u00a0<em>Complex<\/em>, and\u00a0<em>Enum<\/em> types with all of their properties. Our next step was making sure we added all the services that make this whole thing work with querying and so forth, which allowed us to query the metadata of our service. After that, we added some sample data to a database context, plus added some methods to access and modify the data.<\/p>\n<h2>Additional Features<\/h2>\n<p>Now that we&#8217;ve gotten through the basics of setting up a simple service, let&#8217;s talk about some additional features to fully take advantage of OData 8.0.<\/p>\n<h2 id=\"query-options\">Query Options<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>Query options (such as $filter, $count, etc.) allow you to view your data in more interesting ways. By default, they&#8217;re disabled for security reasons. However, it\u2019s easy to enable them after configuring your model by calling their respective methods on the model:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"pln\">services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">GetEdmModel<\/span><span class=\"pun\">()).<\/span><span class=\"typ\">Filter<\/span><span class=\"pun\">().<\/span><span class=\"typ\">Select<\/span><span class=\"pun\">().<\/span><span class=\"typ\">Expand<\/span><span class=\"pun\">());<\/span><\/pre>\n<p>The above code enables $filter, $select, and $expand. Now that we&#8217;ve done this, we can send more complicated request such as the following:<\/p>\n<p><em><strong>GET<\/strong>\u00a0http:\/\/localhost:5000\/odata\/Books?$filter=Price le 50&amp;$expand=Press($select=Name)&amp;$select=Location($select=City)<\/em><\/p>\n<p>These can give us more specific responses, such as this for the above example request:<\/p>\n<pre class=\"lang:js decode:true prettyprinted\" tabindex=\"0\"><span class=\"pun\">{<\/span>\r\n  <span class=\"str\">\"@odata.context\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"http:\/\/localhost:5000\/odata\/$metadata#Books(Location\/City,Press(Name))\"<\/span><span class=\"pun\">,<\/span>\r\n  <span class=\"str\">\"value\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"pun\">[<\/span>\r\n  <span class=\"pun\">{<\/span>\r\n    <span class=\"str\">\"Location\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"pun\">{<\/span>\r\n      <span class=\"str\">\"City\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"Bellevue\"<\/span>\r\n      <span class=\"pun\">},<\/span>\r\n    <span class=\"str\">\"Press\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"pun\">{<\/span>\r\n      <span class=\"str\">\"Name\"<\/span><span class=\"pun\">:<\/span><span class=\"pln\">\u00a0<\/span><span class=\"str\">\"O'Reilly\"<\/span>\r\n      <span class=\"pun\">}<\/span>\r\n    <span class=\"pun\">}<\/span>\r\n  <span class=\"pun\">]<\/span>\r\n<span class=\"pun\">}\r\n<\/span><\/pre>\n<p>If you&#8217;d like to enable them all at once, you can call &#8220;<strong>EnableQueryFeatures()<\/strong>&#8220;:<\/p>\n<pre><span class=\"pln\">services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.<span class=\"typ\">AddModel<\/span>(<span class=\"str\">\"odata\"<\/span>, <span class=\"typ\">GetEdmModel<\/span>()).EnableQueryFeatures()<\/span><span class=\"pun\">);\r\n<\/span><\/pre>\n<h2 id=\"batch\">$batch<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>Sometimes, we like to make multiple requests at once. We can do this by using <strong>$batch<\/strong>, which gives us an array of responses. For performance, though, $batch is disabled by default. In order to enable $batch, you should include following configurations:<\/p>\n<p><strong>1.<\/strong> Configure the model so it takes in a <strong>batch handler<\/strong> by using the following <strong>AddModel()<\/strong> method:<\/p>\n<pre>public <span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">ODataOptions<\/span> <span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">AddModel<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">(<\/span><span class=\"kwd\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">string<\/span><span class=\"pln\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\"> prefix<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">,<\/span> <span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">IEdmModel<\/span><span class=\"pln\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\"> model<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">,<\/span> <strong><span class=\"typ\" style=\"font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">ODataBatchHandler<\/span><span class=\"pln\" style=\"font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\"> batchHandler<\/span><\/strong><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">)<\/span><\/pre>\n<p><strong>2.<\/strong> Enable batching before enabling routing:<\/p>\n<pre>public <span class=\"kwd\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">void<\/span> <span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">Configure<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">(<\/span><span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">IApplicationBuilder<\/span><span class=\"pln\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\"> app<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">,<\/span> <span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">IWebHostEnvironment<\/span><span class=\"pln\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\"> env<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">)\r\n<\/span>{\r\n\u00a0 \u00a0 app<span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">.<\/span><span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">UseODataBatching<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">();<\/span> <span class=\"com\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">\/\/ call before \"UseRouting()\"\r\n<\/span>\u00a0 \u00a0 app<span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">.<\/span><span class=\"typ\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">UseRouting<\/span><span class=\"pun\" style=\"color: #292b2c; font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; font-size: 14.4px;\">();\r\n<\/span>  \u00a0 \/\/ ...\r\n}<\/pre>\n<p>Now, the OData service can handle <strong>$batch<\/strong> requests!<\/p>\n<h2 id=\"multiple-models\">Multiple Models<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>So far, we&#8217;ve been working with a single model. We currently add the one model like this:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"pln\">services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"pln\">model<\/span><span class=\"pun\">));<\/span><\/pre>\n<p>And we make requests like this:<\/p>\n<p><strong><em>GET<\/em><\/strong>\u00a0<em>http:\/\/localhost:5000\/Books(1)<\/em><\/p>\n<p>We can also easily configure <em>multiple<\/em> models within a single OData service. For example, here&#8217;s some sample code that configures two models using different &#8220;<strong>prefixes<\/strong>:&#8221;<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"typ\">IEdmModel<\/span><span class=\"pln\"> model1 <\/span><span class=\"pun\">=<\/span> <span class=\"typ\">GetEdmModel1<\/span><span class=\"pun\">();<\/span>\r\n<span class=\"typ\">IEdmModel<\/span><span class=\"pln\"> model2 <\/span><span class=\"pun\">=<\/span> <span class=\"typ\">GetEdmModel2<\/span><span class=\"pun\">();<\/span><span class=\"pln\">\r\nservices<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"str\" style=\"color: #ff6600;\">\"v1\"<\/span><span class=\"pun\">,<\/span><span class=\"pln\"> model1<\/span><span class=\"pun\">).<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"str\" style=\"color: #ff6600;\">\"v2\"<\/span><span class=\"pun\">,<\/span><span class=\"pln\"> model2<\/span><span class=\"pun\">));<\/span><\/pre>\n<p>Note that here, we can call <strong>AddModel()<\/strong> multiple times. The function also accepts another parameter &#8212; not just the name of the model we&#8217;re adding, but a <strong>prefix<\/strong> we can use to refer to it. The prefixes we see here are &#8220;<strong>v1<\/strong>&#8221; and &#8220;<strong>v2<\/strong>&#8220;, which are used before the OData path in a URI request to identify the specific model we&#8217;re trying to access. (Accordingly, these prefixes should be unique within a specific OData service.)<\/p>\n<p>In this configuration, we can now call the service using request URIs such as the following:<\/p>\n<ul>\n<li><strong><em>GET<\/em><\/strong> <em>http:\/\/localhost:5000\/<span style=\"color: #ff6600;\">v1<\/span>\/Books(1)<\/em> &#8211; Get the first book from v1 (model1)<\/li>\n<li><strong><em>GET<\/em><\/strong> <em>http:\/\/localhost:5000\/<span style=\"color: #ff6600;\">v2<\/span>\/Books(1)<\/em> &#8211; Get the first book from v2 (model2)<\/li>\n<\/ul>\n<h2 id=\"prefix-template\">Prefix Templates<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>We can also call an OData service using different versions by using the <strong>prefix<\/strong>\u00a0parameter in the <strong>AddModel()<\/strong> method. In order to do so, we format the prefix as a template:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"pln\">services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">(<\/span><span class=\"pln\">opt <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> opt<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"v{version}\"<\/span><span class=\"pun\">,<\/span><span class=\"pln\"> model<\/span><span class=\"pun\">));<\/span><\/pre>\n<p>This allows us to call an OData service using different versions. or example:<\/p>\n<ul>\n<li><em><strong>GET<\/strong> http:\/\/localhost:5000\/<span style=\"color: #ff6600;\">v1<\/span>\/Books(1)<\/em> &#8211; Get the first book from version &#8220;1&#8221;<\/li>\n<li><em><strong>GET<\/strong> http:\/\/localhost:5000\/<span style=\"color: #ff6600;\">vbeta<\/span>\/Books(1)<\/em>\u00a0&#8211; Get the first book from version &#8220;beta&#8221;<\/li>\n<\/ul>\n<p>You can add a \u201c<strong>version<\/strong>\u201d parameter in the action of the controller to retrieve the \u201cversion\u201d string:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"kwd\">public<\/span> <span class=\"typ\">IActionResult<\/span> <span class=\"typ\">Get<\/span><span class=\"pun\">(<\/span><span class=\"kwd\">int<\/span><span class=\"pln\"> key<\/span><span class=\"pun\">,<\/span> <span class=\"kwd\">string<\/span><span class=\"pln\"> version<\/span><span class=\"pun\">)<\/span>\r\n<span class=\"pun\">{<\/span>\r\n    <span class=\"com\">\/\/ do something<\/span>\r\n<span class=\"pun\">}<\/span><\/pre>\n<p>This<strong> Get()<\/strong> method will then process version as &#8220;<strong>1<\/strong>&#8221; for the first request above, and &#8220;<strong>beta<\/strong>&#8221; for the second request.<\/p>\n<h2 id=\"dependency-injection-for-odata-services\">Dependency Injection for OData Services<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>If you would like to enable dependency injection of OData services, you can use the following variation of the \u201c<strong>AddModel()<\/strong>\u201d method:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"typ\">ODataOptions<\/span> <span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"kwd\">string<\/span><span class=\"pln\"> prefix<\/span><span class=\"pun\">,<\/span> <span class=\"typ\">IEdmModel<\/span><span class=\"pln\"> model<\/span><span class=\"pun\">,<\/span> <strong><span class=\"typ\">Action<\/span><span class=\"pun\">&lt;<\/span><span class=\"typ\">IContainerBuilder<\/span><span class=\"pun\">&gt;<\/span><span class=\"pln\"> configureAction<\/span><\/strong><span class=\"pun\">);<\/span><\/pre>\n<p>For example, if you&#8217;d like to use your own deserializer provider, you can inject it like this:<\/p>\n<pre class=\"lang:C# decode:true prettyprinted\" tabindex=\"0\"><span class=\"pln\">services<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddOData<\/span><span class=\"pun\">((<\/span><span class=\"pln\">opt) <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> \r\n{\r\n    opt<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddModel<\/span><span class=\"pun\">(<\/span><span class=\"str\">\"odata\"<\/span><span class=\"pun\">,<\/span><span class=\"pln\"> model<\/span><span class=\"pun\">,<\/span><strong><span class=\"pln\"> (builder) <\/span><span class=\"pun\">=&gt;<\/span><span class=\"pln\"> \r\n    {\r\n        builder<\/span><span class=\"pun\">.<\/span><span class=\"typ\">AddService<\/span><span class=\"pun\">&lt;<\/span><span class=\"typ\">ODataDeserializerProvider<\/span><span class=\"pun\">&gt;(<\/span><span class=\"typ\">Microsoft<\/span><span class=\"pun\">.<\/span><span class=\"typ\">OData<\/span><span class=\"pun\">.<\/span><span class=\"typ\">ServiceLifetime<\/span><span class=\"pun\">.<\/span><span class=\"typ\">Singleton<\/span><span class=\"pun\">,<\/span><span class=\"pln\"> (sp) <\/span><span class=\"pun\">=&gt;<\/span> \r\n        {\r\n<span class=\"kwd\">            new<\/span> <span class=\"typ\">MyDeserializerProvider<\/span><span class=\"pun\">(<\/span><span class=\"pln\">sp<\/span><\/strong><span class=\"pun\"><strong>))\r\n        }<\/strong>\r\n    }\r\n});<\/span><\/pre>\n<pre class=\"lang:js decode:true prettyprinted\" tabindex=\"0\"><\/pre>\n<h2 id=\"routing\">Routing<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>OData routing is responsible for matching incoming HTTP requests and dispatching those requests to the app\u2019s executable endpoints, namely the action in the OData controller. For information on more routing features such as bot built-in convention routing, attribute routing, and the path\/segment template, check out <a href=\"https:\/\/devblogs.microsoft.com\/odata\/routing-in-asp-net-core-8-0-preview\/\">Routing in ASP.NET Core OData 8.0 Preview<\/a><span style=\"font-size: 1rem;\">.<\/span><\/p>\n<h1 id=\"summary\" class=\"\">Conclusion<i class=\"fabric-icon fabric-icon--Link\" aria-hidden=\"true\"><\/i><\/h2>\n<p>Thanks for following along! This post is a simple introduction to using the ASP.NET Core OData 8.0 to build a service. If you have any questions or concerns, feel free email Sam at <a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>.<\/p>\n<p>We look forward to seeing you build amazing OData services running on ASP.NET Core 5!<\/p>\n<h3>Additional Resources<\/h3>\n<ul>\n<li><a href=\"https:\/\/www.odata.org\/documentation\/\" target=\"_blank\" rel=\"noopener\">Open Data Protocol (OData) Specification<\/a><\/li>\n<li><a class=\"\" href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/?view=aspnetcore-2.1\" target=\"_blank\" rel=\"noopener\">ASP.NET Core<\/a>\u00a0&amp;\u00a0<a class=\"\" href=\"https:\/\/docs.microsoft.com\/en-us\/ef\/core\/\" target=\"_blank\" rel=\"noopener\">EF Core<\/a><\/li>\n<li>OData .NET Open Source (<a href=\"https:\/\/github.com\/OData\/odata.net\" target=\"_blank\" rel=\"noopener\">ODL<\/a>\u00a0&amp;\u00a0<a href=\"https:\/\/github.com\/OData\/WebApi\" target=\"_blank\" rel=\"noopener\">Web API<\/a>)<\/li>\n<li>OData\u00a0<a href=\"http:\/\/odata.github.io\/\" target=\"_blank\" rel=\"noopener\">Tutorials<\/a>\u00a0&amp;\u00a0<a href=\"https:\/\/github.com\/OData\/ODataSamples\" target=\"_blank\" rel=\"noopener\">Samples<\/a><\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this document, we will walk through the process of creating a local service using OData 8.0, which is optimized to support ASP.NET Core 5. To learn more about the changes, check out ASP.NET Core OData 8.0 Preview for .NET 5\u00a0(which also references ASP.NET Core OData now Available), written by my colleague, Sam. You&#8217;ll notice [&hellip;]<\/p>\n","protected":false},"author":67450,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4593","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>In this document, we will walk through the process of creating a local service using OData 8.0, which is optimized to support ASP.NET Core 5. To learn more about the changes, check out ASP.NET Core OData 8.0 Preview for .NET 5\u00a0(which also references ASP.NET Core OData now Available), written by my colleague, Sam. You&#8217;ll notice [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4593","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\/67450"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=4593"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4593\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=4593"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=4593"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=4593"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}