{"id":3298,"date":"2019-05-19T04:57:11","date_gmt":"2019-05-19T11:57:11","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=3298"},"modified":"2019-05-19T05:16:45","modified_gmt":"2019-05-19T12:16:45","slug":"simplifying-edm-with-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/simplifying-edm-with-odata\/","title":{"rendered":"Simplifying EDM with OData"},"content":{"rendered":"<h3>Summary<\/h3>\n<p>In a previous <a href=\"https:\/\/devblogs.microsoft.com\/odata\/supercharging-asp-net-core-api-with-odata\/\">article<\/a>, I talked about how you can leverage the power of OData with your existing ASP.NET Core API to bring in more features to your API consumers.<\/p>\n<p>But there are different ways you could enable OData on your existing API that are just as simple but offers more powerful features than overriding your existing routes and enabling dependency injection.<\/p>\n<p>For instance, if you\u2019ve tried to perform a count operation using our previous method you will notice it doesn\u2019t really return or perform anything, the same thing goes with many other features that we will talk about extensively in future articles.<\/p>\n<p>In this article, however, I\u2019m going to show you how you can enable OData on your existing ASP.NET Core API using EDM.<\/p>\n<p>&nbsp;<\/p>\n<h3>What is EDM?<\/h3>\n<p>EDM is short for Entity Data Model, it plays the role of a mapper between whatever data source and format you have and the OData engine.<\/p>\n<p>In other words, whether your source of data is SQL, Cosmos DB or just plain text files, and whether your format is XML, Json or raw text or any other type out there, What the entity data model does is to turn that raw data into entities that allow functionality like count, select, filter and expand to be performed seamlessly through your API.<\/p>\n<p>&nbsp;<\/p>\n<h3>Setting Things up<\/h3>\n<p>Let\u2019s set our existing API up with OData using EDM.<\/p>\n<p>First and foremost, add in <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\">Microsoft.AspNetCore.OData<\/a> nuget package to your ASP.NET Core project.<\/p>\n<p>Once the nuget package is installed, let\u2019s setup the configuration to utilize that package.<\/p>\n<p>In your <em>Startup.cs<\/em> file, in your <em>ConfigureServices<\/em> function, add in the following line:<\/p>\n<pre class=\"lang:c# decode:true\">    services.AddOData();<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Important Note:<\/strong> This will work with ASP.NET Core 2.1, if you are trying to set this up with ASP.NET Core 2.2, then you must add another line of code as follows:<\/p>\n<pre class=\"lang:c# decode:true\">    services.AddMvcCore(action =&gt; action.EnableEndpointRouting = false);<\/pre>\n<p>OData doesn\u2019t yet support .NET Core 3.0 \u2013 at the time of this article, .NET Core 3.0 is still in preview, OData support will extend to 3.0 once it\u2019s production ready.<\/p>\n<p>Once that part is done, let\u2019s build a private method to do a handshake between your existing data models (Students in this case) and EDM, as follows:<\/p>\n<pre class=\"lang:c# decode:true\">    private IEdmModel GetEdmModel()\r\n    {\r\n        var builder = new ODataConventionModelBuilder();\r\n        builder.EntitySet&lt;Student&gt;(\"Students\");\r\n        return builder.GetEdmModel();\r\n    }<\/pre>\n<p>The student model we are using here is the same model we used in our previous article, as a reminder here\u2019s how the model looks like:<\/p>\n<pre class=\"lang:c# decode:true\">    public class Student\r\n    {\r\n        public Guid Id { get; set; }\r\n        public string Name { get; set; }\r\n        public int Score { get; set; }\r\n    }<\/pre>\n<p>Now that we have created our EDM method, now let\u2019s do one last configuration change in the <em>Configure<\/em> method in our <em>Startup.cs<\/em> file as follows:<\/p>\n<pre class=\"lang:c# decode:true\">    app.UseMvc(routeBuilder =&gt;\r\n    {\r\n        routeBuilder.Select().Filter().OrderBy().Expand().Count().MaxTop(10);\r\n        routeBuilder.MapODataServiceRoute(\"api\", \"api\", GetEdmModel());\r\n    });<\/pre>\n<p>&nbsp;<\/p>\n<p>Just like our last article, we enabled the functionality we needed such as select, filter and order by then we used the MapODataServiceRoute method to utilize our EDM method.<\/p>\n<p>We used \u201capi\u201d instead of \u201codata\u201d as our first and second parameters as a route name and a route prefix to continue to support our existing APIs endpoints, but there\u2019s a catch to that.<\/p>\n<p>Your contract in this case will change, if your API returns a list of students like this:<\/p>\n<pre class=\"lang:js decode:true \">[\r\n  {\r\n    \"id\": \"acc25b4f-c53d-4363-ad33-e0c860a83a1b\",\r\n    \"name\": \"Hassan Habib\",\r\n    \"score\": 100\r\n  },\r\n  {\r\n    \"id\": \"d42daeb4-37d7-4a20-9e9b-7f7a60f27ff6\",\r\n    \"name\": \"Cody Allen\",\r\n    \"score\": 90\r\n  },\r\n  {\r\n    \"id\": \"db246814-d34e-40e4-aa00-b9192cec447b\",\r\n    \"name\": \"Sandeep Pal\",\r\n    \"score\": 120\r\n  },\r\n  {\r\n    \"id\": \"c4e9efc9-40b7-4a85-b000-ce9c076fcd57\",\r\n    \"name\": \"David Pullara\",\r\n    \"score\": 50\r\n  }\r\n]<\/pre>\n<p>&nbsp;<\/p>\n<p>With the EDM method, your contract will change a bit, your response will have some helpful metadata that we are going to talk about, and it will look like this:<\/p>\n<pre class=\"lang:js decode:true\">{\r\n\u00a0 \"@odata.context\": \"https:\/\/localhost:44374\/api\/$metadata#Students\",\r\n\u00a0 \"value\": [\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Id\": \"9cef40f6-db31-4d4c-997d-8b802156dd4c\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Name\": \"Hassan Habib\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Score\": 100\r\n\u00a0\u00a0\u00a0 },\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Id\": \"282be5ea-231b-4a59-8250-1247695f16c3\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Name\": \"Cody Allen\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Score\": 90\r\n\u00a0\u00a0\u00a0 },\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Id\": \"b3b06596-729b-4c6f-b337-7ad11b01371b\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Name\": \"Sandeep Pal\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Score\": 120\r\n\u00a0\u00a0\u00a0 },\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Id\": \"084bd81e-b8a2-471d-8396-ace675f73688\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Name\": \"David Pullara\",\r\n\u00a0\u00a0\u00a0\u00a0\u00a0 \"Score\": 50\r\n\u00a0\u00a0\u00a0 }\r\n\u00a0 ]\r\n}<\/pre>\n<p>That extra metadata is going to help us perform more operations than the old method.<\/p>\n<p>In that case if you have existing consumers for your API, I recommend introducing a new endpoint, version or informing them to change their contracts, otherwise this will be a breaking change.<\/p>\n<p>The other option is to change your route name and route prefix parameters to say &#8220;odata&#8221; instead, which is the standard way to implement OData.<\/p>\n<p>The last thing we need to do to make this work for us is removing the notations on top of your existing API controller class, in our case we will remove these two lines:<\/p>\n<pre class=\"lang:c# decode:true\">\u00a0\u00a0\u00a0 [Route(\"api\/[controller]\")]\r\n\u00a0\u00a0\u00a0 [ApiController]<\/pre>\n<p>And don&#8217;t forget to add the enabling querying annotation on top of your API method:<\/p>\n<pre class=\"lang:c# decode:true  \">    [EnableQuery()]<\/pre>\n<p>&nbsp;<\/p>\n<h3>Putting OData into Action<\/h3>\n<p>Once that\u2019s done, now you can try to perform higher operations using OData like Count for instance, you can call your endpoint with <em>\/api\/students?$count=true<\/em> and you should get:<\/p>\n<pre class=\"lang:js decode:true\">{\r\n  \"@odata.context\": \"https:\/\/localhost:44374\/api\/$metadata#Students\",\r\n  \"@odata.count\": 4,\r\n  \"value\": [\r\n    {\r\n      \"Id\": \"6a7e60b8-cea9-4132-aac7-be9995e8e048\",\r\n      \"Name\": \"Hassan Habib\",\r\n      \"Score\": 100\r\n    },\r\n    {\r\n      \"Id\": \"d6661173-4370-4781-b016-a311b0e96f14\",\r\n      \"Name\": \"Cody Allen\",\r\n      \"Score\": 90\r\n    },\r\n    {\r\n      \"Id\": \"caad33c3-d2bf-443e-8623-4a033ca77de2\",\r\n      \"Name\": \"Sandeep Pal\",\r\n      \"Score\": 120\r\n    },\r\n    {\r\n      \"Id\": \"eee8bb79-df81-4cc8-b03f-a3887ecabb50\",\r\n      \"Name\": \"David Pullara\",\r\n      \"Score\": 50\r\n    }\r\n  ]\r\n}<\/pre>\n<p>&nbsp;<\/p>\n<p>As you can see here, you have a new property <em>@odata.count<\/em> that shows you the count of the items in your list.<\/p>\n<p>&nbsp;<\/p>\n<h3>Final Notes<\/h3>\n<p>Now that you\u2019ve learned about the simplest way (8 lines of code) to create a handshake between ASP.NET Core, OData and EDM here\u2019s few notes:<\/p>\n<ol>\n<li>EDM doesn\u2019t have any dependency on the Entity Framework, in fact the whole purpose of creating an EDM is to link whatever data you have in any format it may be to the OData engine and serialize the results through an API endpoint.<\/li>\n<li>EDM can only be useful if you need some specific OData features such as count and nextlink and so many other features that we will explore in future articles.<\/li>\n<li>There\u2019s more to learn about EDM, I encourage you to check all about EDM in this extensive, comprehensive <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/framework\/data\/adonet\/entity-data-model\">documentation<\/a>.<\/li>\n<li>OData is an open-source <a href=\"https:\/\/github.com\/odata\">project<\/a>, I encourage you as you benefit from it\u2019s amazing features to contribute to the project, suggest new features and participate with documentation and your experiences to keep the community active and useful for everyone.<\/li>\n<li>You can clone the project I built and try things out from this <a href=\"https:\/\/github.com\/hassanhabib\/ODataWithEDM\">github repo<\/a>.<\/li>\n<\/ol>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary In a previous article, I talked about how you can leverage the power of OData with your existing ASP.NET Core API to bring in more features to your API consumers. But there are different ways you could enable OData on your existing API that are just as simple but offers more powerful features than [&hellip;]<\/p>\n","protected":false},"author":3348,"featured_media":3305,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-3298","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>Summary In a previous article, I talked about how you can leverage the power of OData with your existing ASP.NET Core API to bring in more features to your API consumers. But there are different ways you could enable OData on your existing API that are just as simple but offers more powerful features than [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/3298","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=3298"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/3298\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3305"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=3298"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=3298"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=3298"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}