{"id":5716,"date":"2024-08-12T14:13:25","date_gmt":"2024-08-12T21:13:25","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5716"},"modified":"2025-10-08T01:18:15","modified_gmt":"2025-10-08T08:18:15","slug":"announcing-odata-net-8-official-release","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/announcing-odata-net-8-official-release\/","title":{"rendered":"Announcing OData .NET 8 Official Release"},"content":{"rendered":"<p>We&#8217;re happy to announce that OData .NET 8 has been officially released and is available on NuGet:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Core\/8.0.0\">Microsoft.OData.Core 8.0.0<\/a><\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Edm\/8.0.0\">Microsoft.OData.Edm 8.0.0<\/a><\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Client\/8.0.0\">Microsoft.OData.Client 8.0.0<\/a><\/li>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Spatial\/8.0.0\">Microsoft.Spatial 8.0.0<\/a><\/li>\n<\/ul>\n<p>We would like to thank the community for trying out the pre-release versions and sharing feedback with us following our announcements of the <a href=\"https:\/\/devblogs.microsoft.com\/odata\/odata-net-8-preview-release\/\">previews<\/a> and <a href=\"https:\/\/devblogs.microsoft.com\/odata\/announcing-odata-net-8-release-candidate-1\/\">release candidates<\/a>. This release presents an opportunity for us to modernize our stack, address some technical debt and take better advantage of investments in .NET. OData .NET 8.0.0 contains changes to improve <span class=\"TextRun SCXP26228504 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-usefontface=\"false\" data-contrast=\"none\"><span class=\"NormalTextRun SCXP26228504 BCX8\">maintainability, user experience, <\/span><\/span><span class=\"TextRun SCXP26228504 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-usefontface=\"false\" data-contrast=\"none\"><span class=\"NormalTextRun SCXP26228504 BCX8\">performance and add new capabilities.<\/span><\/span><span class=\"EOP SCXP26228504 BCX8\">\u200b<\/span>To make adoption and upgrading to the new version smooth, we have opted to limit the number of breaking changes.<\/p>\n<h2>Notable changes in OData .NET 8.0.0<\/h2>\n<p>A full list of changes is also available in the <a href=\"https:\/\/learn.microsoft.com\/odata\/odatalib\/release-notes\/odatalib-8\">release notes<\/a> in the official documentation.<\/p>\n<div>\n<div>\n<h2 id=\"target-framework\" class=\"code-line\" dir=\"auto\" data-line=\"12\">Target Framework<\/h2>\n<p class=\"code-line\" dir=\"auto\" data-line=\"14\">The most disruptive change we are making in this release is dropping support for .NET Framework. <strong>The new release will only support .NET 8 and later<\/strong>. To learn more about supported OData libraries and .NET frameworks, visit out <a title=\"\/odata\/support\/support-policy\" href=\"https:\/\/learn.microsoft.com\/odata\/support\/support-policy\" data-href=\"\/odata\/support\/support-policy\">support policy<\/a>.<\/p>\n<h2 id=\"changes-in-microsoftodatacore\" class=\"code-line\" dir=\"auto\" data-line=\"17\">Changes in\u00a0<code>Microsoft.OData.Core<\/code><\/h2>\n<h3 id=\"changed-default-json-writer\" class=\"code-line\" dir=\"auto\" data-line=\"19\">Changed default JSON writer<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"21\">OData .NET 8 default JSON writer is now based on\u00a0<code>Utf8JsonWriter<\/code>. This JSON writer was already available as an option in OData .NET 7 but was not the default. This writer improves performance and reduces memory usage compared to the previous default, especially when performing serialization using the async API.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"24\">in OData .NET 8 the\u00a0<code>JavaScriptEncoder.UnsafeRelaxedJsonEscaping<\/code>\u00a0option is used by default. This option provides better consistency with ASP.NET Core applications and can result in better performance since fewer characters need to be escaped.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"27\">This writer has some notable changes in how some values are handled during serialization:<\/p>\n<h4 id=\"string-escaping\" class=\"code-line\" dir=\"auto\" data-line=\"29\">String Escaping<\/h4>\n<p class=\"code-line code-active-line\" dir=\"auto\" data-line=\"31\">The default writer in version 8 uses uppercase letters for unicode code points, the default in version 7 uses lowercase letters<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"33\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"33\">OData.NET v7 default writer: &#8220;Cust1 \\ud800\\udc05 \\u00e4&#8221;<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"34\">OData .NET v8 default writer: &#8220;Cust1 \\uD800\\uDC05 \\u00E4&#8221;<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"36\">Both versions are valid and equivalent, compliant clients should handle them the same way.<\/p>\n<h4 id=\"number-handling\" class=\"code-line\" dir=\"auto\" data-line=\"38\">Number handling<\/h4>\n<p class=\"code-line\" dir=\"auto\" data-line=\"40\"><code>float.PositiveInfinity<\/code>\u00a0and\u00a0<code>double.PositiveInfinity<\/code>\u00a0are written as &#8220;INF&#8221;.\u00a0<code>float.NegativeInfinity<\/code>\u00a0and\u00a0<code>double.NegativeInfinity<\/code>\u00a0are written as\u00a0<code>\"-INF\"<\/code>. This is consistent with OData .NET 7, but is different from how the values are handled by\u00a0<code>Utf8JsonWriter<\/code>\u00a0or\u00a0<code>JsonSerializer<\/code>\u00a0by default.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"43\">OData .NET 8 serializes a decimal value like\u00a0<code>1.0M<\/code>\u00a0as\u00a0<code>1.0<\/code>, but OData .NET 7 serializes it as\u00a0<code>1<\/code>.<\/p>\n<h4 id=\"datetimeoffset\" class=\"code-line\" dir=\"auto\" data-line=\"45\">DateTimeOffset<\/h4>\n<p class=\"code-line\" dir=\"auto\" data-line=\"47\"><code>DateTimeOffset<\/code>\u00a0values where the offset is 0 use\u00a0<code>Z<\/code>\u00a0to indicate the timezone, e.g.\u00a0<code>2022-11-09T09:42:30Z<\/code>. This is consistent with OData .NET 7, but is different from the default behaviour of\u00a0<code>Utf8JsonWriter<\/code>\u00a0which uses\u00a0<code>+00:00<\/code>\u00a0like\u00a0<code>2022-11-09T09:42:30+00:00<\/code>.<\/p>\n<h4 id=\"switching-to-the-old-json-writer\" class=\"code-line\" dir=\"auto\" data-line=\"50\">Switching to the old JSON writer<\/h4>\n<p class=\"code-line\" dir=\"auto\" data-line=\"52\">You can change the JSON writer used by\u00a0<code>ODataMessageWriter<\/code>\u00a0and by injecting an implementation of\u00a0<code>IJsonWriterFactory<\/code>\u00a0to the service provider attached to the\u00a0<code>IODataRequestMessage<\/code>\u00a0or\u00a0<code>IODataResponseMessage<\/code>. The implementation that corresponds to the old default writer is called\u00a0<code>ODataJsonWriterFactory<\/code>:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"54\">services.AddSingleton&lt;IJsonWriterFactory&gt;(new ODataJsonWriterFactory());\r\n<\/code><\/pre>\n<h4 id=\"changing-javascriptencoder\" class=\"code-line\" dir=\"auto\" data-line=\"58\">Changing\u00a0<code>JavaScriptEncoder<\/code><\/h4>\n<p class=\"code-line\" dir=\"auto\" data-line=\"60\">If you want to change which characters are escaped or encoded you can change the\u00a0<code>JavaScriptEncoder<\/code>\u00a0used by the underlying\u00a0<code>Utf8JsonWriter<\/code>. You can do so by injecting an instance of\u00a0<code>ODataUtf8JsonWriter<\/code>\u00a0to the service provider and specifying the instance of\u00a0<code>JavaScriptEncoder<\/code>\u00a0to be used:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"63\">services.AddSingleton&lt;IJsonWriterFactory&gt;(<span class=\"hljs-keyword\">new<\/span> ODataUtf8JsonWriterFactory(JavaScriptEncoder.Default));\r\n<\/code><\/pre>\n<h3 id=\"changed-odataresourceproperties-property-type\" class=\"code-line\" dir=\"auto\" data-line=\"67\">Changed\u00a0<code>ODataResource.Properties<\/code>\u00a0property type<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"69\">In OData .NET 7, the\u00a0<code>ODataResource.Properties<\/code>\u00a0property is of type\u00a0<code>IEnumerable&lt;ODataProperty&gt;<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"71\">In OData .NET 8, the\u00a0<code>ODataResource.Properties<\/code>&#8216;s type has been changed to\u00a0<code>IEnumerable&lt;ODataPropertyInfo&gt;<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"73\"><code>ODataPropertyInfo<\/code>\u00a0is the base class of\u00a0<code>ODataProperty<\/code>. The main difference between the two is that\u00a0<code>ODataProperty<\/code>\u00a0has a value. The purpose of this change was to make it easier to represent properties in the payloads that don&#8217;t have a value. Sometimes we need to write metadata about a property that is not included in the payload. For example, in the following payload, the\u00a0<code>password<\/code>\u00a0property is missing but the payload includes metadata about the property in the form of\u00a0<code>security.omitted<\/code>\u00a0annotation.<\/p>\n<pre><code class=\"code-line language-json\" dir=\"auto\" data-line=\"75\"><span class=\"hljs-punctuation\">{<\/span>\r\n    <span class=\"hljs-attr\">\"@context\"<\/span><span class=\"hljs-punctuation\">:<\/span><span class=\"hljs-string\">\"users\/$entity\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"id\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-number\">1<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    \u2026\r\n    <span class=\"hljs-attr\">\"password@security.omitted\"<\/span><span class=\"hljs-punctuation\">:<\/span> \u201cpassword is read-only\u201d<span class=\"hljs-punctuation\">,<\/span>\r\n    \u2026\r\n    <span class=\"hljs-attr\">\"hobbies@count\"<\/span><span class=\"hljs-punctuation\">:<\/span><span class=\"hljs-number\">10<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n<span class=\"hljs-punctuation\">}<\/span>\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"86\">The liberty to add\u00a0<code>ODataPropertyInfo<\/code>\u00a0instances in the\u00a0<code>ODataResource.Properties<\/code>\u00a0collection makes it possible to read and write metadata of properties that should be exluded from the payload.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"88\">One consequence of this change is that in cases where we want to write properties with values, we should filter out properties that don&#8217;t have value. We can do this by attempting a cast to\u00a0<code>ODataProperty<\/code>\u00a0before writing the value:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"90\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">void<\/span> <span class=\"hljs-title\">WriteProperties<\/span>(<span class=\"hljs-params\">ODataResource resource<\/span>)<\/span>\r\n{\r\n    <span class=\"hljs-keyword\">foreach<\/span> (ODataPropertyInfo propertyInfo <span class=\"hljs-keyword\">in<\/span> resource.Properties)\r\n    {\r\n        <span class=\"hljs-keyword\">if<\/span> (propertyInfo <span class=\"hljs-keyword\">is<\/span> ODataProperty property)\r\n        {\r\n            WriteValue(property.Value)\r\n        }\r\n    }\r\n}<\/code><\/pre>\n<\/div>\n<div><\/div>\n<div>\n<h3 id=\"removed-odatasimplifiedoptions-class\" class=\"code-line\" dir=\"auto\" data-line=\"103\">Removed\u00a0<code>ODataSimplifiedOptions<\/code>\u00a0class<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"105\">In OData .NET 7, an instance of\u00a0<code>ODataSimplifiedOptions<\/code>\u00a0could be injected in the service provider to configure certain behaviour when parsing URLs, and when reading and writing payloads. This led to confusion because we already have other settings classes like\u00a0<code>ODataMessageReaderSettings<\/code>,\u00a0<code>ODataMessageWriterSettings<\/code>\u00a0and\u00a0<code>ODataUriParsing<\/code>. In OData .NET 8, the\u00a0<code>ODataSimplifiedOptions<\/code> class has been removed and its settings properties and methods have been moved to the other classes:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"108\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"108\"><code>EnableReadingKeyAsSegment<\/code>\u00a0and\u00a0<code>EnableReadingODataAnnotationWithoutPrefix<\/code>\u00a0properties moved to\u00a0<code>ODataMessageReaderSettings<\/code>\u00a0class.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"109\"><code>EnableWritingKeyAsSegment<\/code>\u00a0property moved to\u00a0<code>ODataMessageWriterSettings<\/code>\u00a0class.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"110\"><code>SetOmitODataPrefix(bool)<\/code>,\u00a0<code>SetOmitODataPrefix(bool, ODataVersion)<\/code>,\u00a0<code>GetOmitODataPrefix()<\/code>, and\u00a0<code>GetOmitODataPrefix(ODataVersion)<\/code>\u00a0methods moved to\u00a0<code>ODataMessageWriterSettings<\/code>\u00a0class.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"111\"><code>EnableParsingKeyAsSegment<\/code>\u00a0property moved to\u00a0<code>ODataUriParserSettings<\/code>\u00a0class.<\/li>\n<\/ul>\n<\/div>\n<div>\n<h3 id=\"removed-icontainerbuilder-and-replaced-with-iserviceprovider\" class=\"code-line\" dir=\"auto\" data-line=\"113\">Removed\u00a0<code>IContainerBuilder<\/code>\u00a0and replaced with\u00a0<code>IServiceProvider<\/code><\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"115\">OData .NET 7 used a custom interface called\u00a0<code>IContainerBuilder<\/code>\u00a0for dependency injection. This meant that developers who would need to inject custom services would have to create a custom implementation. Most custom implementations, including the one used in ASP.NET Core OData were basic wrappers around\u00a0<code>Microsoft.Extensions.DepencyInjection<\/code>&#8216;s\u00a0<code>IServiceProvider<\/code>\u00a0and\u00a0<code>ServiceCollection<\/code>\u00a0APIs.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"117\">OData .NET 8 has removed the\u00a0<code>IContainerBuilder<\/code>\u00a0interface and now uses\u00a0<code>IServiceProvider<\/code>\u00a0for dependency injection:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"119\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"119\"><code>AddDefaultODataServices(IServiceCollection, ODataVersion, Action&lt;ODataMessageReaderSettings&gt;, Action&lt;ODataMessageWriterSettings&gt;, Action&lt;ODataUriParserSettings&gt;)<\/code>\u00a0extension method introduced for the purpose of registering OData services.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"120\"><code>IContainerProvider<\/code>\u00a0interface replaced by\u00a0<code>IServiceCollectionProvider<\/code>\u00a0interface. It&#8217;s a provider for the\u00a0<code>IServiceProvider<\/code>\u00a0IoC container.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"121\"><code>ODataBatchOperationRequestMessage<\/code>\u00a0now implements\u00a0<code>IServiceCollectionProvider<\/code>\u00a0instead of\u00a0<code>IContainerProvider<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"122\"><code>ODataBatchOperationResponseMessage<\/code>\u00a0now implements\u00a0<code>IServiceCollectionProvider<\/code>\u00a0instead of\u00a0<code>IContainerProvider<\/code>.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"124\">For example, if you want to add custom configurations to OData services, you&#8217;d follow these steps:<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"126\">Create implementation of\u00a0<code>IODataRequestMessage<\/code>\u00a0or\u00a0<code>IODataResponseMessage<\/code>\u00a0that also implements\u00a0<code>IServiceCollectionProvider<\/code><\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"128\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ODataMessage<\/span> : <span class=\"hljs-title\">IODataRequestMessage<\/span>, <span class=\"hljs-title\">IODataResponseMessage<\/span>, <span class=\"hljs-title\">IServiceCollectionProvider<\/span>\r\n{\r\n\r\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-title\">ODataMessage<\/span>(<span class=\"hljs-params\">..., IServiceProvider services<\/span>)<\/span>\r\n    {\r\n        ServiceProvider = services;\r\n    }\r\n\r\n    <span class=\"hljs-comment\">\/\/ redacted ...<\/span>\r\n    \r\n    <span class=\"hljs-keyword\">public<\/span> IServiceProvider ServiceProvider { <span class=\"hljs-keyword\">get<\/span>; <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">set<\/span>; }\r\n}\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"143\">Create service collection and add default OData services:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"145\"><span class=\"hljs-keyword\">using<\/span> Microsoft.Extensions.DependencyInjection;\r\n\r\n<span class=\"hljs-keyword\">var<\/span> serviceCollection = <span class=\"hljs-keyword\">new<\/span> ServiceCollection();\r\nserviceCollection.AddDefaultODataServices();\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"152\">Configure custom services:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"154\">serviceCollecton.AddSingleton&lt;IJsonWriterFactory&gt;(ODataJsonWriterFactory.Default);\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"158\">Build the service provider:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"160\">IServiceProvider services = serviceCollection.BuildServiceProvider();\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"164\">Add service provider to message writer or reader:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"166\"><span class=\"hljs-keyword\">var<\/span> message = <span class=\"hljs-keyword\">new<\/span> ODataMessage(..., services);\r\n<span class=\"hljs-keyword\">var<\/span> messageWriter = <span class=\"hljs-keyword\">new<\/span> ODataMessageWriter(message, messageWriterSettings, model);\r\n<\/code><\/pre>\n<h3 id=\"changed-odataerror-and-related-classes\" class=\"code-line\" dir=\"auto\" data-line=\"171\">Changed\u00a0<code>ODataError<\/code>\u00a0and related classes<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"173\">The implementation of\u00a0<code>ODataError<\/code>\u00a0in OData .NET 7 did not adhere to\u00a0<a title=\"https:\/\/github.com\/OData\/odata.net\/issues\/1421#issuecomment-1309673556\" href=\"https:\/\/github.com\/OData\/odata.net\/issues\/1421#issuecomment-1309673556\" data-href=\"https:\/\/github.com\/OData\/odata.net\/issues\/1421#issuecomment-1309673556\">OData specification and guidelines<\/a>, for example it exposed an\u00a0<code>ODataError.ErrorCode<\/code>\u00a0property instead of\u00a0<code>ODataError.Code<\/code>\u00a0and other issues.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"175\">In OData .NET 8, changes have been made to\u00a0<code>ODataError<\/code>\u00a0to better align with the specification and developer expectation:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"177\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"177\"><code>ODataError.ErrorCode<\/code>\u00a0property has been replaced with\u00a0<code>ODataError.Code<\/code><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"178\">In OData .NET 8, empty fields (e.g.\u00a0<code>code<\/code>,\u00a0<code>type<\/code>,\u00a0<code>stacktrace<\/code>) are not skipped, but serialized as fields with empty string values.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"179\">In OData .NET 7,\u00a0<code>ODataInnerError<\/code>&#8216;s collection of key-value pairs could be incorrectly serialized into invalid JSON if it containted dates, byte arrays, GUIDs, etc. This has been fixed in OData .NET 8.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"180\">In OData .NET 8, nested inner errors are correctly serialized.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"181\">OData .NET 8 renamed\u00a0<code>ToJson()<\/code>\u00a0method to\u00a0<code>ToJsonString()<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"182\">When serializing an\u00a0<code>ODataErrorDetail<\/code>\u00a0instance, the OData serializers would write the optional target property ahead of the required message property. In OData .NET 8, we switched the order such that the required message property is written first.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"183\">In OData .NET 7,\u00a0<code>ODataError.ToString()<\/code>\u00a0and\u00a0<code>ODataJsonWriterUtils.WriteError(ODataError)<\/code>\u00a0could produce different outputs in some cases. In OData .NET 8, they return the same output.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"184\">In OData .NET 8, if a value cannot be serialized, an error is written in place of value for the corresponding property, instead of throwing an exception.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"186\">Here&#8217;s a sample\u00a0<code>ODataError<\/code>\u00a0instance:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"188\"><span class=\"hljs-keyword\">var<\/span> odataError = <span class=\"hljs-keyword\">new<\/span> ODataError\r\n{\r\n    Details = <span class=\"hljs-keyword\">new<\/span> Collection&lt;ODataErrorDetail&gt;\r\n    {\r\n        <span class=\"hljs-keyword\">new<\/span> ODataErrorDetail { Code = <span class=\"hljs-string\">\"OEDC1\"<\/span>, Message = <span class=\"hljs-string\">\"OEDM1\"<\/span>, Target = <span class=\"hljs-string\">\"OEDT1\"<\/span> },\r\n        <span class=\"hljs-keyword\">new<\/span> ODataErrorDetail { Code = <span class=\"hljs-string\">\"OEDC1\"<\/span>, Message = <span class=\"hljs-string\">\"OEDM1\"<\/span> },\r\n        <span class=\"hljs-keyword\">new<\/span> ODataErrorDetail {}\r\n    }\r\n};\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"200\">Here&#8217;s how it would be written in OData .NET 7 with\u00a0<code>ODataJsonWriterUtils.WriteError(odataError, \/* other parameters *\/)<\/code>:<\/p>\n<pre><code class=\"code-line language-json\" dir=\"auto\" data-line=\"202\"><span class=\"hljs-punctuation\">{<\/span>\r\n    <span class=\"hljs-attr\">\"error\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"details\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">[<\/span>\r\n            <span class=\"hljs-punctuation\">{<\/span>\r\n                <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n                <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDT1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n                <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span>\r\n            <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n            <span class=\"hljs-punctuation\">{<\/span>\r\n                <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n                <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span>\r\n            <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n            <span class=\"hljs-punctuation\">{<\/span>\r\n                <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n                <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span>\r\n            <span class=\"hljs-punctuation\">}<\/span>\r\n        <span class=\"hljs-punctuation\">]<\/span>\r\n    <span class=\"hljs-punctuation\">}<\/span>\r\n<span class=\"hljs-punctuation\">}<\/span>\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"226\">Here&#8217;s how it would be written in OData .NET 7 with\u00a0<code>odataError.ToString()<\/code>:<\/p>\n<pre><code class=\"code-line language-json\" dir=\"auto\" data-line=\"228\"><span class=\"hljs-punctuation\">{<\/span>\r\n  <span class=\"hljs-attr\">\"error\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">{<\/span>\r\n    <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"details\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">[<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"errorcode\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDT1\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"errorcode\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"errorcode\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span>\r\n    <span class=\"hljs-punctuation\">]<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"innererror\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">{<\/span><span class=\"hljs-punctuation\">}<\/span>\r\n  <span class=\"hljs-punctuation\">}<\/span>\r\n<span class=\"hljs-punctuation\">}<\/span>\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"256\">Here&#8217;s how it would be serialized in OData .NET 8 with either\u00a0<code>ODataJsonWriterUtils.WriteError(odataError, \/* other parameters *\/)<\/code>\u00a0and\u00a0<code>odataError.ToString()<\/code>:<\/p>\n<pre><code class=\"code-line language-json\" dir=\"auto\" data-line=\"258\"><span class=\"hljs-punctuation\">{<\/span>\r\n  <span class=\"hljs-attr\">\"error\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">{<\/span>\r\n    <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"details\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-punctuation\">[<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"target\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDT1\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDC1\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"OEDM1\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n      <span class=\"hljs-punctuation\">{<\/span>\r\n        <span class=\"hljs-attr\">\"code\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n        <span class=\"hljs-attr\">\"message\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"\"<\/span>\r\n      <span class=\"hljs-punctuation\">}<\/span>\r\n    <span class=\"hljs-punctuation\">]<\/span>\r\n  <span class=\"hljs-punctuation\">}<\/span>\r\n<span class=\"hljs-punctuation\">}<\/span>\r\n<\/code><\/pre>\n<h3 id=\"changed-odatalibrarycompatibility-into-a-flags-enum\" class=\"code-line\" dir=\"auto\" data-line=\"282\">Changed\u00a0<code>ODataLibraryCompatibility<\/code>\u00a0into a flags enum<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"284\">OData .NET 8 changed the\u00a0<code>ODataLibraryCompatibility<\/code>\u00a0enum into a flags enum, where each bit represents a different compatibility setting that can enable some legacy serialization behavior.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"286\">In OData .NET 7, the enum had values like\u00a0<code>ODataLibraryCompatibility.Version6<\/code>,\u00a0<code>Version7<\/code>\u00a0and\u00a0<code>Latest<\/code>. These values were used to enable or disable certain behaviour based on desired compatibility with a certain library version after migration to a new version. In OData .NET 8, we moved to a more granular option where you can set various compatibility options individually using bit flags, for example\u00a0<code>ODataLibraryCompatibility.UseLegacyVariableCasing<\/code>,\u00a0<code>WriteTopLevelODataNullAnnotation<\/code>, etc. This allows you to retain some legacy behaviour in the serializers when migrating to new versions. The enum still has\u00a0<code>Version6<\/code>\u00a0and\u00a0<code>Version7<\/code>\u00a0values, which now combine specific flags that replicate the behaviour that was on by default for that specific version.<\/p>\n<h3 id=\"serialize-variable-scale-in-lowercase\" class=\"code-line\" dir=\"auto\" data-line=\"288\">Serialize\u00a0<code>variable<\/code>\u00a0scale in lowercase<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"290\">When writing the\u00a0<code>Scale<\/code>\u00a0attribute in XML CSDL, use\u00a0<code>variable<\/code>\u00a0in lowercase instead of\u00a0<code>Variable<\/code>. An enum flag,\u00a0<code>ODataLibraryCompatibility.UseLegacyVariableCasing<\/code>, was added to support the legacy behavior if desired.<\/p>\n<h3 id=\"changed-json-reader-and-writer-interfaces\" class=\"code-line\" dir=\"auto\" data-line=\"292\">Changed JSON reader and writer interfaces<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"294\">This is relevant if you implemented custom JSON reader or JSON writer interfaces.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"296\">In OData .NET 7, there were many interfaces required to implement a custom JSON reader or writer because the synchronous, asynchronous and streaming APIs were split. In OData .NET 8 these different APIs have been consolidated. There&#8217;s now only one\u00a0<code>IJsonWriter<\/code>\u00a0interface and only one\u00a0<code>IJsonReader<\/code>\u00a0interface that a custom writer or reader would need to implement.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"300\">OData .NET 7 also had two interfaces for JsonWriter factories,\u00a0<code>IJsonWriterFactory<\/code>\u00a0which accepted a\u00a0<code>TextWriter<\/code>\u00a0and\u00a0<code>IStreamBasedJsonWriterFactory<\/code>\u00a0accepted a\u00a0<code>Stream<\/code>. OData .NET 8 exposes a single factory interface,\u00a0<code>IJsonWriterFactory<\/code>\u00a0which accepts a\u00a0<code>Stream<\/code>.<\/p>\n<h3 id=\"added-odatamessagewritersettingsshouldincludeannotation-for-custom-instance-annotations\" class=\"code-line\" dir=\"auto\" data-line=\"303\">Added\u00a0<code>ODataMessageWriterSettings.ShouldIncludeAnnotation<\/code>\u00a0for custom instance annotations<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"305\">In OData .NET 7, custom instance annotations would not be included in the written output if they were not inluded in the\u00a0<code>@odata.include-annotations<\/code>\u00a0preference token in preference header. However, in some cases, customers want to add custom instance annotations to the payload that should always be written regardless of end-user preference.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"308\">In OData .NET 8, we introduced a\u00a0<code>ShouldIncludeAnnotation<\/code>\u00a0property to the\u00a0<code>ODataMessageWriterSettings<\/code>\u00a0that allows developers to force a custom instance annotation to be written. It works as follows:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"310\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"310\">The property is a\u00a0<code>Func&lt;string, bool&gt;<\/code>\u00a0that receives an annotation name and returns true if the annotation should be written<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"311\">If the writer was going to skip a certain annotation (e.g. because it was not part of the annotations filter), it will check this delegate before skipping the annotation. If the delegate returns\u00a0<code>true<\/code>, then the annotation will not be skipped.<\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"313\">It is important to note that returning false from the delegate does not guarantee that the annotation will not be written. For example, if an annotation was included in the preference header annotations filter, it may still be written even when the delegate returns false.<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"315\">writerSettings.ShouldIncludeAnnotation == (name) =&gt; name.StartsWith(<span class=\"hljs-string\">\"important.\"<\/span>);\r\n<\/code><\/pre>\n<h3 id=\"changed-odatabinarystreamvalue-to-close-underlying-stream-by-default\" class=\"code-line\" dir=\"auto\" data-line=\"319\">Changed\u00a0<code>ODataBinaryStreamValue<\/code>\u00a0to close underlying stream by default<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"321\">In OData .NET 7, when\u00a0<code>ODataBinaryStreamValue<\/code>\u00a0class is initialized using the\u00a0<code>ODataBinaryStreamValue(Stream)<\/code>\u00a0constructor, the stream is left open by default upon the object being disposed.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"323\">In OData .NET 8, the stream is closed by default when the object is disposed.\u00a0<code>The ODataBinaryStreamValue(Stream, bool)<\/code>\u00a0constructor overload may be used when leaving the stream open is intended.<\/p>\n<h3 id=\"added-inavigationsourcesegment-interface\" class=\"code-line\" dir=\"auto\" data-line=\"325\">Added\u00a0<code>INavigationSourceSegment<\/code>\u00a0interface<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"327\"><code>INavigationSourceSegment<\/code>\u00a0interface introduced. The purpose of this new interface is to reduce casting when determining the navigation source associated with the segment.\u00a0<code>EntitySetSegment<\/code>,\u00a0<code>SingletonSegment<\/code>\u00a0and\u00a0<code>NavigationPropertySegment<\/code>\u00a0implement this new interface.<\/p>\n<h3 id=\"changed-odataurlvalidationcontextmessages-property-type\" class=\"code-line\" dir=\"auto\" data-line=\"329\">Changed\u00a0<code>ODataUrlValidationContext.Messages<\/code>\u00a0property type<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"331\">In OData .NET 7, the\u00a0<code>ODataUrlValidationContext.Messages<\/code>\u00a0property is of type\u00a0<code>List&lt;ODataUrlValidationMessage&gt;<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"333\">In OData .NET 8, the property&#8217;s type has been chaged to\u00a0<code>IReadOnlyList&lt;ODataUrlValidationMessage&gt;<\/code>\u00a0to discourage undesired changes to the list. An\u00a0<code>AddMessage(ODataUrlValidationMessage)<\/code>\u00a0overload has been added to the\u00a0<code>ODataUrlValidationContext<\/code>\u00a0class to make it possible to add validation messages.<\/p>\n<h3 id=\"deprecated-support-for-jsonp\" class=\"code-line\" dir=\"auto\" data-line=\"336\">Deprecated support for JSONP<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"338\"><a title=\"https:\/\/en.wikipedia.org\/wiki\/JSONP\" href=\"https:\/\/en.wikipedia.org\/wiki\/JSONP\" data-href=\"https:\/\/en.wikipedia.org\/wiki\/JSONP\">JSONP<\/a>\u00a0is a legacy technique for requesting data using JavaScript and bypass the same-origin policy in web applications. This technique has long been superseded by the adoption of CORS (cross-origin resource sharing), It also poses security concerns. In OData .NET 8, JSONP support in is marked as deprecated, all methods and properties related to JSONP have been marked as obselete and will be removed in the next major version.<\/p>\n<h2 id=\"changes-in-microsoftodataclient\" class=\"code-line\" dir=\"auto\" data-line=\"340\">Changes in\u00a0<code>Microsoft.OData.Client<\/code><\/h2>\n<h3 id=\"added-support-for-ihttpclientfactory\" class=\"code-line\" dir=\"auto\" data-line=\"342\">Added support for\u00a0<code>IHttpClientFactory<\/code><\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"344\">In OData .NET 7, the\u00a0<code>DataServiceContext<\/code>\u00a0class had a\u00a0<code>HttpClientHandlerProvider<\/code>\u00a0that allowed developer to provide custom instances of\u00a0<code>HttpClientHandler<\/code>\u00a0for making requests. The\u00a0<code>HttpClientHandlerProvider<\/code>\u00a0was based on a custom\u00a0<code>IHttpClientHandlerProvider<\/code>\u00a0interface. Customers requested support for the familiar\u00a0<code>IHttpClientFactory<\/code>\u00a0interface, but we could not support because it would result in breaking changes.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"346\">In OData .NET 8, we introduced support for\u00a0<a title=\"\/dotnet\/core\/extensions\/httpclient-factory\" href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/httpclient-factory\" data-href=\"\/dotnet\/core\/extensions\/httpclient-factory\"><code>IHttpClientFactory<\/code><\/a>\u00a0<code>DataServiceContext<\/code>\u00a0now has a\u00a0<code>HttpClientFactory<\/code>\u00a0property of type\u00a0<code>IHttpClientFactory<\/code>. This allows developers to provide custom\u00a0<code>HttpClient<\/code>\u00a0instances and control their lifetimes. Subsequently, we have removed the\u00a0<code>IHttpClientHandlerProvider<\/code>\u00a0interface and the\u00a0<code>DataServiceContext.HttpClientProvider<\/code>\u00a0property.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"348\">Here&#8217;s a basic code sample of using injecting an\u00a0<code>IHttpClientFactory<\/code>\u00a0instance to OData Client&#8217;s\u00a0<code>DataServiceContext<\/code>. In the following examples, we make use of the following packages:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"350\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"350\"><a title=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.DependencyInjection\" href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.DependencyInjection\" data-href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.DependencyInjection\"><code>Microsoft.Extensions.DependencyInjection<\/code><\/a><\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"351\"><a title=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Http\" href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Http\" data-href=\"https:\/\/www.nuget.org\/packages\/Microsoft.Extensions.Http\"><code>Microsoft.Extension.Http<\/code><\/a><\/li>\n<\/ul>\n<p class=\"code-line\" dir=\"auto\" data-line=\"353\">We also assume the following\u00a0<code>using<\/code>\u00a0statement exists at the top of the soure code:<\/p>\n<pre><code class=\"code-line language-c#\" dir=\"auto\" data-line=\"355\"><span class=\"hljs-keyword\">using<\/span> Microsoft.Extensions.DependencyInjection\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"359\">We can obtain an instance of\u00a0<code>IHttpClientFactory<\/code>\u00a0by injecting in a\u00a0<code>ServiceCollection<\/code>\u00a0instance:<\/p>\n<pre><code class=\"code-line language-c#\" dir=\"auto\" data-line=\"361\"><span class=\"hljs-keyword\">using<\/span> Microsoft.Extensions.DependencyInjection\r\n\r\nServiceCollection serviceCollection = <span class=\"hljs-keyword\">new<\/span> ServiceCollection();\r\nserviceCollection.AddHttpClient();\r\nIServiceProvider services = serviceCollection.BuildServiceProvider();\r\n\r\n<span class=\"hljs-comment\">\/\/ Create a new DataServiceContext instance that points to our OData service<\/span>\r\n<span class=\"hljs-keyword\">var<\/span> client = <span class=\"hljs-keyword\">new<\/span> DefaultContainer(<span class=\"hljs-keyword\">new<\/span> Uri(<span class=\"hljs-string\">\"https:\/\/services.odata.org\/V4\/TripPinServiceRW\/\"<\/span>));\r\n\r\n<span class=\"hljs-comment\">\/\/ Configure te IHttpClientFactory to use<\/span>\r\nclient.HttpClientFactory = services.GetRequiredService&lt;IHttpClientFactory&gt;();\r\n\r\n<span class=\"hljs-comment\">\/\/ Make a request<\/span>\r\n<span class=\"hljs-keyword\">var<\/span> people = <span class=\"hljs-keyword\">await<\/span> client.People.ExecuteAsync();\r\n\r\n<span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> p <span class=\"hljs-keyword\">in<\/span> people)\r\n{\r\n    Console.WriteLine(<span class=\"hljs-string\">$\"<span class=\"hljs-subst\">{p.FirstName}<\/span> <span class=\"hljs-subst\">{p.LastName}<\/span>\"<\/span>);\r\n}\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"383\">Let&#8217;s look at another example. This time we&#8217;ll configure the underlying\u00a0<code>HttpMessageHandler<\/code>\u00a0with custom credentials and lifetime. To configure the handler, we need to use a named client by using the\u00a0<code>AddHttpClient<\/code>\u00a0overload that accepts a string. Since OData Client isn&#8217;t aware of custom client names, we&#8217;ll use an empty string as the name.<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"386\"><span class=\"hljs-keyword\">using<\/span> Microsoft.Extensions.DependencyInjection\r\n\r\nServiceCollection serviceCollection = <span class=\"hljs-keyword\">new<\/span> ServiceCollection();\r\n\r\nserviceCollection.AddHttpClient(<span class=\"hljs-string\">\"\"<\/span>)\r\n.ConfigurePrimaryHttpMessageHandler(_ =&gt;\r\n{\r\n    <span class=\"hljs-comment\">\/\/ configure credentials<\/span>\r\n    <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">new<\/span> HttpClientHandler\r\n    {\r\n        Credentials = <span class=\"hljs-keyword\">new<\/span> NetworkCredential(secureUsername, securePassword)\r\n    };\r\n})\r\n<span class=\"hljs-comment\">\/\/ configure lifetime<\/span>\r\n.SetHandlerLifetime(TimeSpan.FromMinutes(<span class=\"hljs-number\">5<\/span>));\r\n\r\nclient.HttpClientFactory = services.GetRequiredService&lt;IHttpClientFactory&gt;();\r\n\r\n<span class=\"hljs-comment\">\/\/ Make a request<\/span>\r\n<span class=\"hljs-keyword\">var<\/span> people = <span class=\"hljs-keyword\">await<\/span> client.People.ExecuteAsync();\r\n\r\n<span class=\"hljs-keyword\">foreach<\/span> (<span class=\"hljs-keyword\">var<\/span> p <span class=\"hljs-keyword\">in<\/span> people)\r\n{\r\n    Console.WriteLine(<span class=\"hljs-string\">$\"<span class=\"hljs-subst\">{p.FirstName}<\/span> <span class=\"hljs-subst\">{p.LastName}<\/span>\"<\/span>);\r\n}\r\n<\/code><\/pre>\n<h3 id=\"renamed-ibaseentitytypecontext-to-dataservicecontext\" class=\"code-line\" dir=\"auto\" data-line=\"414\">Renamed\u00a0<code>IBaseEntityType.Context<\/code>\u00a0to\u00a0<code>DataServiceContext<\/code><\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"416\">In OData .NET 7, the\u00a0<code>IBaseEntityType<\/code>\u00a0has property called\u00a0<code>Context<\/code>\u00a0of type\u00a0<code>DataServiceContext<\/code>. The\u00a0<code>IBaseEntityType<\/code>\u00a0is a base type used for all entity type classes generated by our code-generation tools (OData Connected Service and OData CLI). We had many reports the inherited\u00a0<code>Context<\/code>\u00a0properties would conflict with entity types which also had a property called\u00a0<code>Context<\/code>. To avoid this occurrence, OData .NET 8 has renamed the property to\u00a0<code>DataServiceContext<\/code>, which has fewer chances of collisions.<\/p>\n<h3 id=\"changed-key-lookup-in-where-clause-to-use-filter-by-default\" class=\"code-line\" dir=\"auto\" data-line=\"418\">Changed key lookup in\u00a0<code>Where<\/code>\u00a0clause to use\u00a0<code>$filter<\/code>\u00a0by default<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"420\">In OData .NET 7, running a LINQ query that performed a key look up, like\u00a0<code>context.People.Where(p =&gt; p.Id == 10)<\/code>\u00a0would result in a request URL that fetches an entity by ID, i.e.\u00a0<code>\/People(10)<\/code>. In all other cases, the LINQ\u00a0<code>Where<\/code>\u00a0clause maps to a\u00a0<code>$filter<\/code>\u00a0query. This inconsistent handling of the\u00a0<code>Where<\/code>\u00a0clause would sometimes cause confusions and unexpected results. OData .NET 7 introduced a setting\u00a0<code>DataServiceContext.KeyComparisonGeneratesFilterQuery<\/code>\u00a0to configure this behavior. When set to true,\u00a0<code>Where<\/code>\u00a0clause that performs a key lookup would generated a\u00a0<code>$filter<\/code>\u00a0query that performs a key lookup, i.e.\u00a0<code>$filter=Id eq 10<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"422\">In OData .NET 8,\u00a0<code>Where<\/code>\u00a0clause generates a\u00a0<code>$filter<\/code>\u00a0query even for key lookups by default. The\u00a0<code>DataServiceContext.KeyComparisonGeneratesFilterQuery<\/code>\u00a0setting is still available. It now defaults to\u00a0<code>true<\/code>. You can restore the old behaviour by setting this flag to\u00a0<code>false<\/code>. However, this setting has been marked as deprecated and could be removed in a future major version.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"424\">The recommended way to fetch an entity by ID is to use the\u00a0<code>ByKey<\/code>\u00a0method, e.g.\u00a0<code>context.People.ByKey(10)<\/code>. This generates a URL to retrieve the entity by id (<code>\/People(10)<\/code>). We encourage developers that use\u00a0<code>Where<\/code>\u00a0for key-lookups to switch to using\u00a0<code>ByKey()<\/code>\u00a0instead.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"426\">Using\u00a0<code>$filter<\/code>\u00a0when you intend to fetch an entity by key can lead to surprising results. For example, consider the following snippet of code:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"428\"><span class=\"hljs-keyword\">var<\/span> context = <span class=\"hljs-keyword\">new<\/span> Container(<span class=\"hljs-keyword\">new<\/span> Uri(<span class=\"hljs-string\">\"https:\/\/services.odata.org\/TripPinRESTierService\/\"<\/span>));\r\n<span class=\"hljs-keyword\">var<\/span> friend = context.People.Where(p =&gt; p.UserName == <span class=\"hljs-string\">\"russellwhyte\"<\/span>).Select(p =&gt; p.BestFriend);\r\n\r\nConsole.WriteLine(friend.FirstName);\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"435\">In OData .NET 7, the following OData URL would be generated by default:\u00a0<code>https:\/\/services.odata.org\/TripPinRESTierService\/People('russellwhyte')\/BestFriend<\/code>. Notice that the selected\u00a0<code>BestFriend<\/code>\u00a0is added as a navigation property segment to the URL. This request returns the entity corresponding to the best friend at the top level of the response:<\/p>\n<pre><code class=\"code-line language-json\" dir=\"auto\" data-line=\"437\"><span class=\"hljs-punctuation\">{<\/span>\r\n    <span class=\"hljs-attr\">\"@odata.context\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"https:\/\/services.odata.org\/TripPinRESTierService\/(S(q5f0myjzjtfondm5l1a5qsbw))\/$metadata#People\/$entity\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"UserName\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"scottketchum\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"FirstName\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"Scott\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    <span class=\"hljs-attr\">\"LastName\"<\/span><span class=\"hljs-punctuation\">:<\/span> <span class=\"hljs-string\">\"Ketchum\"<\/span><span class=\"hljs-punctuation\">,<\/span>\r\n    ...\r\n<span class=\"hljs-punctuation\">}<\/span>\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"447\">However, in OData .NET 8, this code would generate the following URL:\u00a0<code>https:\/\/services.odata.org\/TripPinRESTierService\/People?$filter=UserName eq 'russellwhyte'&amp;$expand=BestFriend<\/code>. Notice that the request filters the entity set collection and also includes the navigation property using the\u00a0<code>$expand<\/code>\u00a0query. This means the result will be a collection containing a single entity, which has an expanded\u00a0<code>BestFriend<\/code>\u00a0property:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"449\">{\r\n    <span class=\"hljs-string\">\"@odata.context\"<\/span>: <span class=\"hljs-string\">\"https:\/\/services.odata.org\/TripPinRESTierService\/(S(pbicrumqvat4igher2mbadb3))\/$metadata#People(BestFriend())\"<\/span>,\r\n    <span class=\"hljs-string\">\"value\"<\/span>: [\r\n        {\r\n            <span class=\"hljs-string\">\"UserName\"<\/span>: <span class=\"hljs-string\">\"russellwhyte\"<\/span>,\r\n            <span class=\"hljs-string\">\"FirstName\"<\/span>: <span class=\"hljs-string\">\"Russell\"<\/span>,\r\n            <span class=\"hljs-string\">\"LastName\"<\/span>: <span class=\"hljs-string\">\"Whyte\"<\/span>,\r\n            ...\r\n            <span class=\"hljs-string\">\"BestFriend\"<\/span>: {\r\n                <span class=\"hljs-string\">\"UserName\"<\/span>: <span class=\"hljs-string\">\"scottketchum\"<\/span>,\r\n                <span class=\"hljs-string\">\"FirstName\"<\/span>: <span class=\"hljs-string\">\"Scott\"<\/span>,\r\n                <span class=\"hljs-string\">\"LastName\"<\/span>: <span class=\"hljs-string\">\"Ketchum\"<\/span>,\r\n                ...\r\n            }\r\n        }\r\n    ]\r\n}\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"469\">This means that trying to access the result with\u00a0<code>friend.FirstName<\/code>\u00a0will fail and throw an exception, because the result is not the friend entity, but a collection that contains the filtered entity with the\u00a0<code>BestFriend<\/code>\u00a0property attached to it. Therefore in OData .NET 8 we would have to adjust the code as follows:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"471\"><span class=\"hljs-comment\">\/\/ fetch people with expanded navigation property using OData URI People?$expand=BestFriend<\/span>\r\n<span class=\"hljs-keyword\">var<\/span> people = <span class=\"hljs-keyword\">await<\/span> context.People.Expand(<span class=\"hljs-string\">'BestFriend'<\/span>).ExecuteAsync();\r\n<span class=\"hljs-comment\">\/\/ perform a local LINQ transform on the result data<\/span>\r\n<span class=\"hljs-keyword\">var<\/span> bestFriends = people.Select(p =&gt; p.BestFriend);\r\n\r\nConsole.WriteLine(bestFriends.First().FirstName);\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"480\">Alternatively we could use the\u00a0<code>ByKey<\/code>\u00a0method:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"482\"><span class=\"hljs-keyword\">var<\/span> friend = <span class=\"hljs-keyword\">await<\/span> ctx.People.ByKey(<span class=\"hljs-string\">\"russelwhyte\"<\/span>).Select(p =&gt; p.BestFriend).GetValueAsync();\r\nConsole.WriteLine(friend.FirstName);\r\n<\/code><\/pre>\n<p class=\"code-line\" dir=\"auto\" data-line=\"487\">Alternatively, we could set\u00a0<code>DataServiceContext.KeyComparisonGeneratesFilterQuery<\/code>\u00a0to false, but it&#8217;s no longer recommended:<\/p>\n<pre><code class=\"code-line language-csharp\" dir=\"auto\" data-line=\"489\"><span class=\"hljs-keyword\">var<\/span> context = <span class=\"hljs-keyword\">new<\/span> Container(<span class=\"hljs-keyword\">new<\/span> Uri(<span class=\"hljs-string\">\"https:\/\/services.odata.org\/TripPinRESTierService\/\"<\/span>));\r\ncontext.KeyComparisonGeneratesFilterQuery = <span class=\"hljs-literal\">false<\/span>;\r\n\r\n<span class=\"hljs-keyword\">var<\/span> friend = context.People.Where(p =&gt; p.UserName == <span class=\"hljs-string\">\"russellwhyte\"<\/span>).Select(p =&gt; p.BestFriend);\r\n\r\nConsole.WriteLine(friend.FirstName);\r\n<\/code><\/pre>\n<h3 id=\"dropped-support-for-httpwebrequest\" class=\"code-line\" dir=\"auto\" data-line=\"498\">Dropped support for\u00a0<code>HttpWebRequest<\/code><\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"500\">OData .NET 8 removed support for\u00a0<code>HttpWebRequest<\/code>,\u00a0<code>HttpClient<\/code>\u00a0should be used instead.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"502\">Subsequently the following related APIs have also been removed:<\/p>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"504\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"504\">The\u00a0<code>HttpWebRequestMessage<\/code>\u00a0class was removed.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"505\">The\u00a0<code>HttpRequestTransportMode<\/code>\u00a0enum was removed.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"506\">The\u00a0<code>DataServiceContext.HttpRequestTransportMode<\/code>\u00a0property was removed.<\/li>\n<\/ul>\n<h3 id=\"other-obsolete-apis-that-have-been-removed\" class=\"code-line\" dir=\"auto\" data-line=\"508\">Other obsolete APIs that have been removed<\/h3>\n<ul class=\"code-line\" dir=\"auto\" data-line=\"510\">\n<li class=\"code-line\" dir=\"auto\" data-line=\"510\">Obsolete\u00a0<code>Credentials<\/code>\u00a0property dropped from\u00a0<code>DataServiceClientRequestMessage<\/code>\u00a0abstract class. The recommended way to configure credentials is through\u00a0<code>HttpClientHandler<\/code>\u00a0that can be provided using\u00a0<code>IHttpClientFactory<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"511\">Obsolete\u00a0<code>Credentials<\/code>\u00a0property dropped from\u00a0<code>HttpClientRequestMessage<\/code>\u00a0class. The recommended way to configure credentials is through\u00a0<code>HttpClientHandler<\/code>\u00a0that can be provided using\u00a0<code>IHttpClientFactory<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"512\">Obsolete\u00a0<code>Credentials<\/code>\u00a0property dropped from\u00a0<code>DataServiceContext<\/code>\u00a0class. The recommended way to configure credentials is through\u00a0<code>HttpClientHandler<\/code>\u00a0that can be provided using\u00a0<code>IHttpClientFactory<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"513\">Obsolete\u00a0<code>ReadWriteTimeout<\/code>\u00a0property dropped from\u00a0<code>DataServiceClientRequestMessage<\/code>\u00a0abstract class. This property would be used with\u00a0<code>HttpWebRequestMessage<\/code>. The\u00a0<code>Timeout<\/code>\u00a0property should be used instead.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"514\">Obsolete\u00a0<code>ReadWriteTimeout<\/code>\u00a0property dropped from\u00a0<code>HttpClientRequestMessage<\/code>\u00a0class. This property would be used with\u00a0<code>HttpWebRequestMessage<\/code>. The\u00a0<code>Timeout<\/code>\u00a0property should be used instead.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"515\">In\u00a0<code>DataServiceClientRequestMessageArgs<\/code>\u00a0class, the\u00a0<code>DataServiceClientRequestMessageArgs(string, Uri, bool, bool, IDictionary&lt;string, string&gt;)<\/code>\u00a0constructor has changed to\u00a0<code>DataServiceClientRequestMessageArgs(string, Uri, bool, IDictionary&lt;string, string&gt;)<\/code>. The boolean\u00a0<code>useDefaultCredentials<\/code>\u00a0parameter is no longer supported.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"516\">In\u00a0<code>DataServiceClientRequestMessageArgs<\/code>\u00a0class, the\u00a0<code>DataServiceClientRequestMessageArgs(string, Uri, bool, bool, IDictionary&lt;string, string&gt;, IHttpClientHandlerProvider)<\/code>\u00a0constructor has changed to\u00a0<code>DataServiceClientRequestMessageArgs(string, Uri, bool, IDictionary&lt;string, string&gt;, IHttpClientFactory)<\/code>. The boolean\u00a0<code>useDefaultCredentials<\/code>\u00a0parameter is no longer supported.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"517\">In\u00a0<code>DataServiceClientRequestMessageArgs<\/code>\u00a0class, the\u00a0<code>UseDefaultCredentials<\/code>\u00a0property dropped from\u00a0<code>DataServiceClientRequestMessageArgs<\/code>\u00a0class. The recommended way to configure credentials is through\u00a0<code>HttpClientHandler<\/code>\u00a0that can be provided using\u00a0<code>IHttpClientFactory<\/code>.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"518\">Obsolete\u00a0<code>IncludeTotalCount()<\/code>\u00a0method was dropped from\u00a0<code>DataServiceQuery&lt;TElement&gt;<\/code>\u00a0class. Use\u00a0<code>IncludeCount()<\/code>\u00a0method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"519\">Obsolete\u00a0<code>IncludeTotalCount(bool)<\/code>\u00a0method was dropped from\u00a0<code>DataServiceQuery&lt;TElement&gt;<\/code>\u00a0class: Use\u00a0<code>IncludeCount(bool)<\/code>\u00a0the method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"520\">Obsolete\u00a0<code>TotalCount<\/code>\u00a0property was dropped from\u00a0<code>QueryOperationResponse<\/code>\u00a0class. Use\u00a0<code>Count<\/code>\u00a0property.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"521\">Obsolete\u00a0<code>TotalCount<\/code>\u00a0property was dropped from\u00a0<code>QueryOperationResponse&lt;T&gt;<\/code>\u00a0class. Use\u00a0<code>Count<\/code>\u00a0property.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"522\">Obsolete\u00a0<code>CreateODataDeltaReader(IEdmEntitySetBase, IEdmEntityType)<\/code>\u00a0method dropped from\u00a0<code>ODataMessageReader<\/code>\u00a0class. Use\u00a0<code>CreateODataDeltaResourceSetReader(IEdmEntitySetBase, IEdmStructuredType)<\/code>\u00a0method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"523\">Obsolete\u00a0<code>CreateODataDeltaReaderAsync(IEdmEntitySetBase, IEdmEntityType)<\/code>\u00a0method dropped from\u00a0<code>ODataMessageReader<\/code>\u00a0class. Use\u00a0<code>CreateODataDeltaResourceSetReader(IEdmEntitySetBase, IEdmStructuredType)<\/code>\u00a0method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"524\">Obsolete\u00a0<code>CreateODataDeltaWriter(IEdmEntitySetBase, IEdmEntityType)<\/code>\u00a0method dropped from\u00a0<code>ODataMessageReader<\/code>\u00a0class. Use\u00a0<code>CreateODataDeltaResourceSetWriter(IEdmEntitySetBase, IEdmStructuredType)<\/code>\u00a0method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"525\">Obsolete\u00a0<code>CreateODataDeltaWriterAsync(IEdmEntitySetBase, IEdmEntityType)<\/code>\u00a0method dropped from\u00a0<code>ODataMessageReader<\/code>\u00a0class. Use\u00a0<code>CreateODataDeltaResourceSetWriterAsync(IEdmEntitySetBase, IEdmStructuredType)<\/code>\u00a0method.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"526\">Obsolete\u00a0<code>Expressions<\/code>\u00a0property dropped from\u00a0<code>AggregateToken<\/code>\u00a0class. Use\u00a0<code>AggregateExpressions<\/code>\u00a0property.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"527\">Obsolete\u00a0<code>Expressions<\/code>\u00a0property dropped from\u00a0<code>AggregateTransformationNode<\/code>\u00a0class. Use\u00a0<code>AggregateExpressions<\/code>\u00a0property.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"528\">Obsolete\u00a0<code>EntityTypeInvalidKeyKeyDefinedInBaseClass<\/code>\u00a0validation rule dropped from\u00a0<code>ValidationRules<\/code>\u00a0class. Use\u00a0<code>EntityTypeInvalidKeyKeyDefinedInAncestor<\/code>\u00a0validation rule.<\/li>\n<li class=\"code-line\" dir=\"auto\" data-line=\"529\">Obsolete\u00a0<code>EntityTypeKeyMissingOnEntityType<\/code>\u00a0validation rule dropped from\u00a0<code>ValidationRules<\/code>\u00a0class. Use\u00a0<code>NavigationSourceTypeHasNoKeys<\/code>\u00a0validation rule.<\/li>\n<\/ul>\n<h2 id=\"changes-in-microsoftodataedm\" class=\"code-line\" dir=\"auto\" data-line=\"531\">Changes in\u00a0<code>Microsoft.OData.Edm<\/code><\/h2>\n<h3 id=\"added-usedefault-property-to-iedmvocabularyannotation-interface\" class=\"code-line\" dir=\"auto\" data-line=\"533\">Added\u00a0<code>UseDefault<\/code>\u00a0property to\u00a0<code>IEdmVocabularyAnnotation<\/code>\u00a0interface<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"535\">Added\u00a0<code>UsesDefault<\/code>\u00a0property to\u00a0<code>IEdmVocabularyAnnotation<\/code>\u00a0to support creating vocabulary annotations with default values. We do not write an\u00a0<code>EdmVocabularyAnnotation<\/code>&#8216;s value to the serialized CSDL if it has a default value. When creating an\u00a0<code>EdmVocabularyAnnotation<\/code>\u00a0if the value is not provided, we check whether the\u00a0<code>EdmTerm<\/code>\u00a0has a default value or not. If the term has a default value and no value was provided, we set the\u00a0<code>UsesDefault<\/code>\u00a0to\u00a0<code>true<\/code>, otherwise\u00a0<code>false<\/code>.<\/p>\n<p class=\"code-line\" dir=\"auto\" data-line=\"537\">We also added new extension method,\u00a0<code>CreateVocabularyAnnotation<\/code>\u00a0for creating an\u00a0<code>EdmVocabularyAnnotation<\/code>\u00a0when a value is provided. OData .NET 7 already had an extension method for creating an\u00a0<code>EdmVocabularyAnnotation<\/code>\u00a0when a value is not provided.<\/p>\n<h3 id=\"added-entitytype-property-to-iedmnavigationsource-interface\" class=\"code-line\" dir=\"auto\" data-line=\"539\">Added\u00a0<code>EntityType<\/code>\u00a0property to\u00a0<code>IEdmNavigationSource<\/code>\u00a0interface<\/h3>\n<p class=\"code-line\" dir=\"auto\" data-line=\"541\">OData .NET 8 added a property called\u00a0<code>EntityType<\/code>\u00a0of type\u00a0<code>IEdmEntityType<\/code>\u00a0to the\u00a0<code>IEdmNavigationSource<\/code>\u00a0interface. Existing implementation of that interface have been updated to implement that property. This makes it possible to get the entity type of a navigation source without having to perform a cast. Subsequently, the\u00a0<code>EntityType(IEdmNavigationSource)<\/code>\u00a0static helper method is no longer recommended. It has been marked as obsolete and may be removed in a future major version.<\/p>\n<\/div>\n<div><\/div>\n<\/div>\n<h2>OData .NET v7 and .NET Framework support<\/h2>\n<p>We understand that there are still a lot of people using .NET Framework. For this reason, we will continue to maintain OData .NET v7 suite of libraries for the foreseeable future. We currently have no plans to drop support for OData .NET 7.x. However, OData .NET 7 will soon enter in maintenance mode after it might not receive new features outside security patches and critical bug fixes. We have a published a <a href=\"https:\/\/learn.microsoft.com\/odata\/support\/support-policy\">support policy<\/a> that provides more information about which library versions we support and what support phases they are currently in. We will also publish information about future support cycles to help customers plan for migration ahead time.<\/p>\n<p>&nbsp;<\/p>\n<p>We invite you to try out OData .NET 8 and share your feedback with us. Thank you for your continued support and contributions to the OData ecosystem. Stay tuned for future updates.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;re happy to announce that OData .NET 8 has been officially released and is available on NuGet: Microsoft.OData.Core 8.0.0 Microsoft.OData.Edm 8.0.0 Microsoft.OData.Client 8.0.0 Microsoft.Spatial 8.0.0 We would like to thank the community for trying out the pre-release versions and sharing feedback with us following our announcements of the previews and release candidates. This release presents [&hellip;]<\/p>\n","protected":false},"author":20326,"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-5716","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>We&#8217;re happy to announce that OData .NET 8 has been officially released and is available on NuGet: Microsoft.OData.Core 8.0.0 Microsoft.OData.Edm 8.0.0 Microsoft.OData.Client 8.0.0 Microsoft.Spatial 8.0.0 We would like to thank the community for trying out the pre-release versions and sharing feedback with us following our announcements of the previews and release candidates. This release presents [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5716","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\/20326"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5716"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5716\/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=5716"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5716"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5716"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}