{"id":5003,"date":"2022-09-07T16:03:21","date_gmt":"2022-09-07T23:03:21","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5003"},"modified":"2022-09-07T16:03:21","modified_gmt":"2022-09-07T23:03:21","slug":"extension-omit-null-value-properties-in-asp-net-core-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/extension-omit-null-value-properties-in-asp-net-core-odata\/","title":{"rendered":"Extension: Omit null value properties in ASP.NET Core OData"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>By default, ASP.NET Core OData serializes a single value property as \u201cnull\u201d, and a collection value property as an empty array if its value is <strong>null<\/strong> as such:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n   \"SingleValueProperty\": null\r\n   \"CollectionValueProperty\": []\r\n}\r\n<\/pre>\n<p>It\u2019s good for most scenarios. However, omitting those &#8216;<em>annoying<\/em>&#8216; null-value properties from the OData response gets more and more attention. Along with the latest ASP.NET Core OData, it\u2019s very easy to omit such null-value properties by extending the OData resource serializer. The approach is so common that it\u2019s better to create a post and value more customers\/developers. Let\u2019s get started.<\/p>\n<h2>Prerequisites<\/h2>\n<p>As usual, my post starts with building an ASP.NET Core Web API application with <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\" target=\"_blank\" rel=\"noopener\">Microsoft.AspNetCore.OData<\/a> (version-8.0.11) installed. You can follow up on my previous post to build the project.<\/p>\n<p>In the project, I create the following C# classes as the data model:<\/p>\n<p><img decoding=\"async\" class=\"center\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-data-model.png\" \/><\/p>\n<p>Where:<\/p>\n<ol>\n<li><strong>School <\/strong>and <strong>Student <\/strong>are two entity types<\/li>\n<li><strong>Address<\/strong> is a complex type<\/li>\n<li><strong>Color<\/strong> is an enum type<\/li>\n<\/ol>\n<p>The Edm model builder is as simple as the codes below:<\/p>\n<pre class=\"lang:c# decode:true\">public static IEdmModel GetEdmModel()\r\n{\r\n    var builder = new ODataConventionModelBuilder();\r\n    builder.EntitySet&lt;School&gt;(\"Schools\");\r\n    builder.EntitySet&lt;Student&gt;(\"Students\");\r\n    return builder.GetEdmModel();\r\n}\r\n<\/pre>\n<p>I build the controller as below. As you can see, we can use attribute routing to specify multiple routes on one action.<\/p>\n<pre class=\"lang:c# decode:true\">[Route(\"odata\")]\r\npublic class SchoolStudentController : ODataController\r\n{\r\n    private readonly ISchoolStudentRepository _repo;\r\n\r\n    public SchoolStudentController(ISchoolStudentRepository repo)\r\n    {\r\n        _repo = repo;\r\n    }\r\n\r\n    [HttpGet(\"Schools\")]\r\n    [HttpGet(\"Schools\/{key}\")]\r\n    [EnableQuery]\r\n    public IActionResult GetSchool(int? key)\r\n    {\r\n        if (key != null)\r\n        {\r\n            return Ok(_repo.Schools.FirstOrDefault(s =&gt; s.ID == key.Value));\r\n        }\r\n        else\r\n        {\r\n            return Ok(_repo.Schools);\r\n        }\r\n    }\r\n\r\n    [HttpGet(\"Students\")]\r\n    [HttpGet(\"Students\/{key}\")]\r\n    [EnableQuery]\r\n    public IActionResult GetStudent(int? key)\r\n    {\r\n        \/\/ omit for similar codes\r\n    }\r\n<\/pre>\n<p>Where:<\/p>\n<ol>\n<li><strong>GetSchool <\/strong>has the following two endpoints:\n<ul>\n<li><em>~\/odata\/schools<\/em><\/li>\n<li><em>~\/odata\/schools\/{key}<\/em><\/li>\n<\/ul>\n<\/li>\n<li><strong>GetStudent <\/strong>has the following two endpoints:\n<ul>\n<li><em>~\/odata\/students<\/em><\/li>\n<li><em>~\/odata\/students\/{key}<\/em><\/li>\n<\/ul>\n<\/li>\n<li><code>ISchoolStudentRepository<\/code> is a sample data repository. You can find a simple in-memory data repository implementation <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/OmitNullPropertySample\/OmitNullPropertySample\/Models\/SchoolStudentRepositoryInMemory.cs\">here<\/a>.<\/li>\n<\/ol>\n<h2>Customize OData resource serializer<\/h2>\n<p>The easy way to change default resource serialization is to customize OData resource serializer. In this project, I derive from <code>ODataResourceSerializer<\/code> again to change the default null value properties serialization. There are three virtual methods that you can override to change three types of property serialization:<\/p>\n<ol>\n<li><code>CreateStructuralProperty <\/code>is for single value and collection value primitive property (Enum included)<\/li>\n<li><code>CreateComplexNestedResourceInfo <\/code>is for single value and collection value complex property<\/li>\n<li><code>CreateNavigationLink <\/code>is for single value and collection value navigation property<\/li>\n<\/ol>\n<p>Here\u2019s my <code><a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/OmitNullPropertySample\/OmitNullPropertySample\/Extensions\/OmitNullResourceSerializer.cs\">OmitNullResourceSerializer<\/a><\/code> sample codes:<\/p>\n<pre class=\"lang:c# decode:true\">public class OmitNullResourceSerializer : ODataResourceSerializer\r\n{\r\n    public OmitNullResourceSerializer(IODataSerializerProvider serializerProvider) : base(serializerProvider)\r\n    {\r\n    }\r\n\r\n    public override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)\r\n    {\r\n        \/\/ for primitive property, enum property, and collection of them\r\n    }\r\n\r\n    public override ODataNestedResourceInfo CreateComplexNestedResourceInfo(IEdmStructuralProperty complexProperty, PathSelectItem pathSelectItem, ResourceContext resourceContext)\r\n    {\r\n        \/\/ for complex property and collection of it\r\n    }\r\n\r\n    public override ODataNestedResourceInfo CreateNavigationLink(IEdmNavigationProperty navigationProperty, ResourceContext resourceContext)\r\n    {\r\n        \/\/ for single value and collection value navigation property\r\n    }\r\n}\r\n<\/pre>\n<h2>Omit null-value properties<\/h2>\n<p>Let\u2019s implement the above methods to omit null-value properties. The code could be as simple as below:<\/p>\n<pre class=\"lang:c# decode:true\">  \r\npublic override ODataProperty CreateStructuralProperty(IEdmStructuralProperty structuralProperty, ResourceContext resourceContext)\r\n{\r\n    object propertyValue = resourceContext.GetPropertyValue(structuralProperty.Name);\r\n    if (propertyValue == null)\r\n    {\r\n        return null;\r\n    }\r\n\r\n    return base.CreateStructuralProperty(structuralProperty, resourceContext);\r\n}\r\n\r\npublic override ODataNestedResourceInfo CreateComplexNestedResourceInfo(IEdmStructuralProperty complexProperty, PathSelectItem pathSelectItem, ResourceContext resourceContext)\r\n{\r\n    \/\/ similar codes as above, omit it.\r\n}\r\n\r\npublic override ODataNestedResourceInfo CreateNavigationLink(IEdmNavigationProperty navigationProperty, ResourceContext resourceContext)\r\n{\r\n    \/\/ similar codes as above, omit it.\r\n}\r\n<\/pre>\n<h2>Run and test<\/h2>\n<p>Since we have everything ready, we can config the ASP.NET core web application to run and test. Let\u2019s register OData services into the service container in the <strong>Program.cs<\/strong> as follows: (Be noted, I also register the data repository as a transient service. It\u2019s better to use transient for the data repository and make sure we get the update-to-date repository every time.):<\/p>\n<pre class=\"lang:c# decode:true\">var builder = WebApplication.CreateBuilder(args);\r\n\r\n\/\/ Add services to the container.\r\n\r\nbuilder.Services.AddControllers()\r\n\r\n.AddOData(opt =&gt; opt.AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel(),\r\n\r\nservices =&gt; services.AddSingleton&lt;ODataResourceSerializer, OmitNullResourceSerializer&gt;()).EnableQueryFeatures());\r\n\r\nbuilder.Services.AddTransient&lt;ISchoolStudentRepository, SchoolStudentRepositoryInMemory&gt;();\r\n<\/pre>\n<p>Now, we can send a HTTP request as &#8220;<strong>GET <\/strong><a href=\"https:\/\/localhost:7282\/odata\/$metadata\">https:\/\/localhost:7282\/odata\/$metadata<\/a>&#8220;, and get the OData metadata schema as:<\/p>\n<pre class=\"lang:xml decode:true\">&lt;?xml\u00a0version=\"1.0\"\u00a0encoding=\"utf-8\"?&gt;\r\n&lt;edmx:Edmx\u00a0Version=\"4.0\"\u00a0xmlns:edmx=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edmx\"&gt;\r\n  &lt;edmx:DataServices&gt;\r\n    &lt;Schema\u00a0Namespace=\"OmitNullPropertySample.Models\"\u00a0xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;EntityType\u00a0Name=\"School\"&gt;\r\n        &lt;Key&gt;\r\n          &lt;PropertyRef\u00a0Name=\"ID\"\u00a0\/&gt;\r\n        &lt;\/Key&gt;\r\n        &lt;Property\u00a0Name=\"ID\"\u00a0Type=\"Edm.Int32\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Name\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Emails\"\u00a0Type=\"Collection(Edm.String)\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"HeadQuarter\"\u00a0Type=\"OmitNullPropertySample.Models.Address\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Addresses\"\u00a0Type=\"Collection(OmitNullPropertySample.Models.Address)\"\u00a0\/&gt;\r\n        &lt;NavigationProperty\u00a0Name=\"Students\"\u00a0Type=\"Collection(OmitNullPropertySample.Models.Student)\"\u00a0\/&gt;\r\n      &lt;\/EntityType&gt;\r\n      &lt;EntityType\u00a0Name=\"Student\"&gt;\r\n        &lt;Key&gt;\r\n          &lt;PropertyRef\u00a0Name=\"ID\"\u00a0\/&gt;\r\n        &lt;\/Key&gt;\r\n        &lt;Property\u00a0Name=\"ID\"\u00a0Type=\"Edm.Int32\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Name\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Age\"\u00a0Type=\"Edm.Int32\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"FavoriteColor\"\u00a0Type=\"OmitNullPropertySample.Models.Color\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"HomeLocation\"\u00a0Type=\"OmitNullPropertySample.Models.Address\"\u00a0\/&gt;\r\n      &lt;\/EntityType&gt;\r\n      &lt;ComplexType\u00a0Name=\"Address\"&gt;\r\n        &lt;Property\u00a0Name=\"City\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"Street\"\u00a0Type=\"Edm.String\"\u00a0\/&gt;\r\n        &lt;Property\u00a0Name=\"ZipCode\"\u00a0Type=\"Edm.Int32\"\u00a0Nullable=\"false\"\u00a0\/&gt;\r\n      &lt;\/ComplexType&gt;\r\n      &lt;EnumType\u00a0Name=\"Color\"&gt;\r\n        &lt;Member\u00a0Name=\"Red\"\u00a0Value=\"0\"\u00a0\/&gt;\r\n        &lt;Member\u00a0Name=\"Green\"\u00a0Value=\"1\"\u00a0\/&gt;\r\n        &lt;Member\u00a0Name=\"Blue\"\u00a0Value=\"2\"\u00a0\/&gt;\r\n        &lt;Member\u00a0Name=\"Black\"\u00a0Value=\"3\"\u00a0\/&gt;\r\n        &lt;Member\u00a0Name=\"Yellow\"\u00a0Value=\"4\"\u00a0\/&gt;\r\n      &lt;\/EnumType&gt;\r\n    &lt;\/Schema&gt;\r\n    &lt;Schema\u00a0Namespace=\"Default\"\u00a0xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n      &lt;EntityContainer\u00a0Name=\"Container\"&gt;\r\n        &lt;EntitySet\u00a0Name=\"Schools\"\u00a0EntityType=\"OmitNullPropertySample.Models.School\"&gt;\r\n          &lt;NavigationPropertyBinding\u00a0Path=\"Students\"\u00a0Target=\"Students\"\u00a0\/&gt;\r\n        &lt;\/EntitySet&gt;\r\n        &lt;EntitySet\u00a0Name=\"Students\"\u00a0EntityType=\"OmitNullPropertySample.Models.Student\"\u00a0\/&gt;\r\n      &lt;\/EntityContainer&gt;\r\n    &lt;\/Schema&gt;\r\n  &lt;\/edmx:DataServices&gt;\r\n&lt;\/edmx:Edmx&gt;\r\n<\/pre>\n<p>Send a request &#8220;<strong>GET <\/strong><a href=\"https:\/\/localhost:7282\/odata\/schools\">https:\/\/localhost:7282\/odata\/schools<\/a>&#8221; to query school data as:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"https:\/\/localhost:7282\/odata\/$metadata#Schools\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"ID\":\u00a01,\r\n      \"Name\":\u00a0\"Moon\u00a0Middle\u00a0School\",\r\n      \"Emails\":\u00a0[\r\n        \"efg@efg.com\"\r\n      ],\r\n      \"Addresses\":\u00a0[\r\n        {\r\n          \"City\":\u00a0\"Moon\u00a0City\",\r\n          \"Street\":\u00a0\"145TH\u00a0AVE\",\r\n          \"ZipCode\":\u00a00\r\n        },\r\n        {\r\n          \"City\":\u00a0\"Sun\u00a0City\",\r\n          \"Street\":\u00a0\"24TH\u00a0ST\",\r\n          \"ZipCode\":\u00a00\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"ID\":\u00a02,\r\n      \"Name\":\u00a0\"Jupiter\u00a0Middle\u00a0School\",\r\n      \"HeadQuarter\":\u00a0{\r\n        \"City\":\u00a0\"Jupiter\u00a0City\",\r\n        \"Street\":\u00a0\"1110\u00a0AVE\",\r\n        \"ZipCode\":\u00a00\r\n      }\r\n    },\r\n    {\r\n      \"ID\":\u00a03,\r\n      \"Name\":\u00a0\"Mars\u00a0High\u00a0School\",\r\n      \"Emails\":\u00a0[\r\n        \"abc@abc.com\"\r\n      ],\r\n      \"HeadQuarter\":\u00a0{\r\n        \"City\":\u00a0\"Mars\u00a0City\",\r\n        \"Street\":\u00a0\"Space\u00a0Rd\",\r\n        \"ZipCode\":\u00a00\r\n      }\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>Where you can see:<\/p>\n<ol>\n<li><strong>Schools(#1)<\/strong> doesn\u2019t have &#8216;<em>HeadQuarter<\/em>&#8216; complex type property.<\/li>\n<li><strong>Schools(#2)<\/strong> doesn\u2019t have &#8216;<em>Emails<\/em>&#8216; collection primitive property and &#8216;<em>Addresses<\/em>&#8216; collection complex property.<\/li>\n<li><strong>Schools(#3)<\/strong> doesn\u2019t have &#8216;<em>Addresses<\/em>&#8216; collection complex property.<\/li>\n<\/ol>\n<h2>Client-side omit null-value properties<\/h2>\n<p>Most of the time, we\u2019d like to let the client tell the server whether or not to omit null-value properties. Basically, you can achieve it using any technique. In this post, I use OData prefer header called <strong>omit-values<\/strong>. You can find more details about prefer header in OData <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part1-protocol.html#sec_Preferenceomitvalues\">here<\/a>.<\/p>\n<p>Ok, first, we should check the request prefer header on the server side to understand what the client needs. Let&#8217;s create a static class (any class) and add an extension method as follows:<\/p>\n<pre class=\"lang:c# decode:true\">public static bool ShouldOmitNullValues(this HttpRequest request)\r\n{\r\n    string preferHeader = null;\r\n    StringValues values;\r\n    if (request.Headers.TryGetValue(\"Prefer\", out values))\r\n    {\r\n        \/\/ If there are many \"Prefer\" headers, pick up the first one.\r\n        preferHeader = values.FirstOrDefault();\r\n    }\r\n\r\n    \/\/ use case insensitive string comparison for simplicity\r\n    if (preferHeader != null &amp;&amp; preferHeader.Contains(\"omit-values=nulls\", StringComparison.OrdinalIgnoreCase))\r\n    {\r\n        return true;\r\n    }\r\n\r\n    return false;\r\n}\r\n<\/pre>\n<p>Then, we call the above extension method in our <code>OmitNullResourceSerializer<\/code> to make a decision on whether to omit null-value properties. Here are the changes for complex type property (similar codes for the other two methods)<\/p>\n<pre class=\"lang:c# decode:true\">public override ODataNestedResourceInfo CreateComplexNestedResourceInfo(IEdmStructuralProperty complexProperty, PathSelectItem pathSelectItem, ResourceContext resourceContext)\r\n{\r\n    bool isOmitNulls = resourceContext.Request.ShouldOmitNullValues ();\r\n    if (isOmitNulls)\r\n    {\r\n        object propertyValue = resourceContext.GetPropertyValue(complexProperty.Name);\r\n        if (propertyValue == null || propertyValue is NullEdmComplexObject)\r\n        {\r\n            return null;\r\n        }\r\n    }\r\n\r\n    return base.CreateComplexNestedResourceInfo(complexProperty, pathSelectItem, resourceContext);\r\n}\r\n<\/pre>\n<p>Let\u2019s run the project again and send the following requests separately (left one without request prefer header, right one with request prefer header):<\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre class=\"lang:txt decode:true\"><strong>GET <\/strong>https:\/\/localhost:7282\/odata\/schools<\/pre>\n<\/td>\n<td>\n<pre class=\"lang:txt decode:true\"><strong>GET<\/strong> https:\/\/localhost:7282\/odata\/schools\r\n<strong>Prefer<\/strong>: omit-values=nulls<\/pre>\n<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\" colspan=\"2\">Here\u2019s the difference between the two responses:<\/td>\n<\/tr>\n<tr>\n<td><img decoding=\"async\" class=\"wp-image-4922\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-school-diff1_left-1.png\" \/><\/td>\n<td><img decoding=\"async\" class=\"wp-image-4922\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-school-diff1_right-1.png\" \/><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Where you can see:<\/p>\n<ol>\n<li>Left contains &#8216;<strong>full<\/strong>&#8216; properties for each school entity<\/li>\n<li>Right omits <em>HeadQuarter<\/em>, <em>Emails<\/em>, <em>Addresses <\/em>for certain school if its property value is null<\/li>\n<\/ol>\n<p>Let\u2019s run the sample and send the following requests to verify the navigation property serialization:<\/p>\n<table>\n<tbody>\n<tr>\n<td>\n<pre class=\"lang:txt decode:true\"><strong>GET<\/strong> ~\/odata\/schools\/3?$expand=students<\/pre>\n<\/td>\n<td>\n<pre class=\"lang:txt decode:true\"><strong>GET<\/strong> ~\/odata\/schools\/3?$expand=students\r\n<strong>Prefer<\/strong>: omit-values=nulls<\/pre>\n<\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\" colspan=\"2\">Here\u2019s the difference between the two responses:<\/td>\n<\/tr>\n<tr>\n<td><img decoding=\"async\" class=\"wp-image-4922\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-school-diff2_left.png\" \/><\/td>\n<td><img decoding=\"async\" class=\"wp-image-4922\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-school-diff2_right.png\" \/><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>As shown, it omits for null-value navigation property if we specify the prefer header for the request.<\/p>\n<h2>Omit null vs $select<\/h2>\n<p>The\u00a0<strong>$select<\/strong>\u00a0system query option requests that the service return only the selected properties. In other words, <strong>$select<\/strong> <em>omits<\/em> the properties which are not included in the select clause. Sometimes, it could be confusing at the client side if we combine <strong>$select<\/strong> and <strong>omit-values=nulls<\/strong> together. For example:<\/p>\n<pre class=\"lang:txt decode:true\"><strong>Get <\/strong><a href=\"https:\/\/localhost:7282\/odata\/schools?$select=Name,Emails\">https:\/\/localhost:7282\/odata\/schools?$select=Name,Emails<\/a>\r\n<strong>Prefer<\/strong>: omit-values=nulls\r\n<\/pre>\n<p>We can get the following result:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"https:\/\/localhost:7282\/odata\/$metadata#Schools(Name,Emails)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Name\":\u00a0\"Moon\u00a0Middle\u00a0School\",\r\n      \"Emails\":\u00a0[\r\n        \"efg@efg.com\"\r\n      ]\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Jupiter\u00a0Middle\u00a0School\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Mars\u00a0High\u00a0School\",\r\n      \"Emails\":\u00a0[\r\n        \"abc@abc.com\"\r\n      ]\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>Where, the second school entity doesn\u2019t have the &#8216;<em>Emails<\/em>&#8216; property. It might be confusing that &#8216;<em>Emails<\/em>&#8216; is excluded from <strong>$select<\/strong> or is omitted by null-value.<\/p>\n<p>The good news is that it\u2019s OData response payload, it contains the <strong>metadata <\/strong>(aka, control information) to help the client understand the payload. From the above, you can see a context URI metadata (<code>@odata.context<\/code> in the response payload) included in the payload.<\/p>\n<p>The context URI &#8220;<strong><code>https:\/\/localhost:7282\/odata\/$metadata#Schools(Name,Emails)<\/code><\/strong>&#8221; shows:<\/p>\n<ul>\n<li>The data is from <strong>Schools<\/strong> entity set<\/li>\n<li>Each school include and only include &#8216;<em>Name<\/em>&#8216; and &#8216;<em>Emails<\/em>&#8216; property<\/li>\n<\/ul>\n<p>In order to let the client understand that &#8216;<em>Emails<\/em>&#8216; is omitted by a null value, we can specify the\u00a0<strong>Preference-Applied<\/strong>\u00a0response header with\u00a0<strong>omit-values=nulls<\/strong> based on OData spec. Then, the client can combine the context Uri information and the response header together to parse the response payload correctly.<\/p>\n<p>In this project, I build a simple method to specify the <strong>Preference-Applied<\/strong>\u00a0response header at <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/OmitNullPropertySample\/OmitNullPropertySample\/Extensions\/RequestExtensions.cs#L38\">here<\/a> for your reference. Now, if you run the project and send the following request:<\/p>\n<pre class=\"lang:txt decode:true\"><strong>Get <\/strong><a href=\"https:\/\/localhost:7282\/odata\/schools?$select=Name,Emails\">https:\/\/localhost:7282\/odata\/schools?$select=Name,Emails<\/a>\r\n<strong>Prefer<\/strong>: omit-values=nulls\r\n<\/pre>\n<p>You can see a header added in the response as:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/09\/omit-null-response-header.png\" \/><\/p>\n<p>That\u2019s it.<\/p>\n<h2>Summary<\/h2>\n<p>This post introduced the extension to omit null value properties in the OData response payload. Using a similar approach, I think you can achieve more. Try and let us know your stories.<\/p>\n<p>Again, please do not hesitate to leave your comments below or let me know your thoughts through\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>I uploaded the whole project to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/OmitNullPropertySample\">this<\/a>\u00a0repository.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction By default, ASP.NET Core OData serializes a single value property as \u201cnull\u201d, and a collection value property as an empty array if its value is null as such: { &#8220;SingleValueProperty&#8221;: null &#8220;CollectionValueProperty&#8221;: [] } It\u2019s good for most scenarios. However, omitting those &#8216;annoying&#8216; null-value properties from the OData response gets more and more attention. [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":4144,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1472,1,117],"tags":[1474,48],"class_list":["post-5003","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","category-webapi","tag-asp-net-core-odata","tag-odata"],"acf":[],"blog_post_summary":"<p>Introduction By default, ASP.NET Core OData serializes a single value property as \u201cnull\u201d, and a collection value property as an empty array if its value is null as such: { &#8220;SingleValueProperty&#8221;: null &#8220;CollectionValueProperty&#8221;: [] } It\u2019s good for most scenarios. However, omitting those &#8216;annoying&#8216; null-value properties from the OData response gets more and more attention. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5003","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=5003"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5003\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/4144"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=5003"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5003"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}