{"id":5995,"date":"2025-11-27T23:53:44","date_gmt":"2025-11-28T06:53:44","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5995"},"modified":"2025-12-02T12:13:21","modified_gmt":"2025-12-02T19:13:21","slug":"announcing-asp-net-core-odata-10-0-0-preview-1","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/announcing-asp-net-core-odata-10-0-0-preview-1\/","title":{"rendered":"Announcing ASP.NET Core OData 10.0.0 Preview 1"},"content":{"rendered":"<p>We&#8217;re thrilled to announce the release of <strong>ASP.NET Core OData 10.0.0 Preview 1<\/strong>, a major modernization update that embraces .NET&#8217;s native <code>System.DateOnly<\/code> and <code>System.TimeOnly<\/code> types! This release upgrades to .NET 10.0 and replaces OData&#8217;s proprietary <code>Edm.Date<\/code> and <code>Edm.TimeOfDay<\/code> CLR wrapper types with .NET&#8217;s native structs.<\/p>\n<ul>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\/10.0.0-preview.1\">Microsoft.AspNetCore.OData 10.0.0-preview.1<\/a><\/li>\n<\/ul>\n<h2 id=\"whats-changed\">What&#8217;s Changed<\/h2>\n<h3>Framework &amp; Dependency Updates<\/h3>\n<h4>.NET 10.0 Support<\/h4>\n<p>ASP.NET Core OData now targets <strong>.NET 10.0<\/strong>, taking advantage of the latest runtime performance improvements and language features.<\/p>\n<h4>OData Library 9.x and Model Builder 3.x Integration<\/h4>\n<p>This release updates OData dependencies to align with the latest OData .NET ecosystem:<\/p>\n<ul>\n<li><strong>OData Library (ODL)<\/strong>: <code>8.4.0<\/code> \u2192 <code>9.0.0-preview.3<\/code><\/li>\n<li><strong>OData Model Builder<\/strong>: <code>2.0.0<\/code> \u2192 <code>3.0.0-preview.1<\/code><\/li>\n<\/ul>\n<p>This coordinated release ensures seamless integration between ASP.NET Core OData and OData Model Builder, providing a unified and consistent experience when building EDM models with modern .NET types.<\/p>\n<h3>Native .NET Type Support<\/h3>\n<p>The most significant change is the migration from legacy EDM date\/timeOfDay wrapper types to .NET&#8217;s native date and time types:<\/p>\n<p><strong>What&#8217;s Changed:<\/strong><\/p>\n<ul>\n<li>\u274c <strong>Removed<\/strong>: Support for <code>Microsoft.OData.Edm.Date<\/code> and <code>Microsoft.OData.Edm.TimeOfDay<\/code><\/li>\n<li>\u2705 <strong>Added<\/strong>: Native support for <code>System.DateOnly<\/code> and <code>System.TimeOnly<\/code><\/li>\n<\/ul>\n<p><strong>Important Note:<\/strong> While the CLR types have been changed to <code>System.DateOnly<\/code> and <code>System.TimeOnly<\/code>, the CSDL metadata continues to use <code>Edm.Date<\/code> and <code>Edm.TimeOfDay<\/code> primitive type kinds for OData protocol compatibility. Future releases will address complete replacement in the metadata layer.<\/p>\n<h2 id=\"why-this-matters\">Why This Matters<\/h2>\n<h3>Alignment with Modern .NET<\/h3>\n<p>By adopting .NET 10.0 and native <code>DateOnly<\/code>\/<code>TimeOnly<\/code> types, ASP.NET Core OData now leverages capabilities that are:<\/p>\n<ul>\n<li><strong>Native to .NET<\/strong>: Eliminates the need for custom EDM-specific wrapper types<\/li>\n<li><strong>Consistent<\/strong>: Same types used across .NET applications, and ASP.NET Core<\/li>\n<li><strong>Performant<\/strong>: Optimized by the .NET runtime as value types, benefiting from .NET 10.0&#8217;s performance improvements<\/li>\n<\/ul>\n<h3>OData Serialization Format Compliance<\/h3>\n<p>While <code>DateOnly<\/code> and <code>TimeOnly<\/code> do not directly align with the OData v4.01 specification&#8217;s <code>Edm.Date<\/code> and <code>Edm.TimeOfDay<\/code> type definitions (which are defined as distinct EDM primitive types), ASP.NET Core OData provides serialization and deserialization to ensure wire-format compatibility:<\/p>\n<ul>\n<li><strong>DateOnly<\/strong> serializes to OData <a href=\"https:\/\/docs.oasis-open.org\/odata\/odata-csdl-json\/v4.01\/odata-csdl-json-v4.01.html#sec_Date\">Date format<\/a>: <code>yyyy-MM-dd<\/code> (e.g., <code>2025-11-27<\/code>)<\/li>\n<li><strong>TimeOnly<\/strong> serializes to OData <a href=\"https:\/\/docs.oasis-open.org\/odata\/odata-csdl-json\/v4.01\/odata-csdl-json-v4.01.html#sec_TimeofDay\">TimeOfDay format<\/a>: <code>HH:mm:ss.fffffff<\/code> (e.g., <code>14:30:45.1234567<\/code>)<\/li>\n<\/ul>\n<p><strong>Developer Note:<\/strong> The default <code>.ToString()<\/code> methods produce culture-dependent output (<code>MM\/dd\/yyyy<\/code> for DateOnly and short time format for TimeOnly). The OData serializers handle correct formatting, but if you need to manually format these types, use the <code>ToODataString()<\/code> extension methods to ensure compliance with <a href=\"https:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/os\/abnf\/odata-abnf-construction-rules.txt\">ABNF construction rules<\/a>.<\/p>\n<h2 id=\"breaking-changes\">Breaking Changes<\/h2>\n<p>\u26a0\ufe0f <strong>This is a breaking change for applications using <code>Microsoft.OData.Edm.Date<\/code> or <code>Microsoft.OData.Edm.TimeOfDay<\/code>.<\/strong><\/p>\n<p>To upgrade to ASP.NET Core OData 10.0.0 Preview 1, you will need to:<\/p>\n<ol>\n<li><strong>Upgrade to .NET 10.0<\/strong>: Update your project&#8217;s target framework to <code>net10.0<\/code><\/li>\n<li><strong>Update package dependencies<\/strong>:\n<ul>\n<li><code>Microsoft.AspNetCore.OData<\/code> -&gt; <code>10.0.0-preview.1<\/code><\/li>\n<li><code>Microsoft.OData.ModelBuilder<\/code> -&gt; <code>3.0.0-preview.1<\/code><\/li>\n<\/ul>\n<\/li>\n<li><strong>Replace types in your models<\/strong>:\n<ul>\n<li><code>Date<\/code> -&gt; <code>DateOnly<\/code><\/li>\n<li><code>TimeOfDay<\/code> -&gt; <code>TimeOnly<\/code><\/li>\n<\/ul>\n<\/li>\n<li><strong>Update models<\/strong> to use native types<\/li>\n<\/ol>\n<h2 id=\"migration-guide\">Migration Guide<\/h2>\n<h3>Before (ASP.NET Core OData 9.x)<\/h3>\n<pre><code>using Microsoft.OData.Edm;\r\n\r\npublic class Event\r\n{\r\n    public int Id { get; set; }\r\n    public string Title { get; set; }\r\n    public Date EventDate { get; set; }\r\n    public TimeOfDay StartTime { get; set; }\r\n    public TimeOfDay EndTime { get; set; }\r\n}\r\n<\/code><\/pre>\n<h3>After (ASP.NET Core OData 10.x)<\/h3>\n<pre><code>using System;\r\n\r\npublic class Event\r\n{\r\n    public int Id { get; set; }\r\n    public string Title { get; set; }\r\n    public DateOnly EventDate { get; set; }\r\n    public TimeOnly StartTime { get; set; }\r\n    public TimeOnly EndTime { get; set; }\r\n}\r\n<\/code><\/pre>\n<h3>Model Configuration<\/h3>\n<p>All model configurations using <code>Date<\/code> or <code>TimeOfDay<\/code> properties must be updated to use <code>DateOnly<\/code> and <code>TimeOnly<\/code>. The good news is that the API surface remains otherwise identical &#8211; simply replace the types and you&#8217;re ready to go!<\/p>\n<h2 id=\"getting-started\">Getting Started<\/h2>\n<h3>Prerequisites<\/h3>\n<p>Ensure you have the .NET 10.0 SDK installed:<\/p>\n<pre><code>dotnet --version\r\n# Should output 10.0.x or higher\r\n<\/code><\/pre>\n<h3>Installation<\/h3>\n<p>Install the preview packages via the .NET CLI:<\/p>\n<pre><code>dotnet add package Microsoft.AspNetCore.OData --version 10.0.0-preview.1\r\n<\/code><\/pre>\n<p>Or update your <code>.csproj<\/code> file directly:<\/p>\n<pre><code>&lt;Project Sdk=\"Microsoft.NET.Sdk.Web\"&gt;\r\n  &lt;PropertyGroup&gt;\r\n    &lt;TargetFramework&gt;net10.0&lt;\/TargetFramework&gt;\r\n  &lt;\/PropertyGroup&gt;\r\n\r\n  &lt;ItemGroup&gt;\r\n    &lt;PackageReference Include=\"Microsoft.AspNetCore.OData\" Version=\"10.0.0-preview.1\" \/&gt;\r\n  &lt;\/ItemGroup&gt;\r\n&lt;\/Project&gt;\r\n<\/code><\/pre>\n<h3>Complete Example<\/h3>\n<p>Here&#8217;s a comprehensive example demonstrating <code>DateOnly<\/code> and <code>TimeOnly<\/code> support in ASP.NET Core OData:<\/p>\n<h4>Step 1: Define Your Entity Model<\/h4>\n<pre><code>using System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace ODataDateTimeSample.Models;\r\n\r\npublic class Event\r\n{\r\n   public int Id { get; set; }\r\n   public string Title { get; set; }\r\n   public string Location { get; set; }\r\n        \r\n   \/\/ Non-nullable DateOnly and TimeOnly\r\n   public DateOnly EventDate { get; set; }\r\n   public TimeOnly StartTime { get; set; }\r\n   public TimeOnly EndTime { get; set; }\r\n        \r\n   \/\/ Nullable DateOnly and TimeOnly\r\n   public DateOnly? RegistrationDeadline { get; set; }\r\n   public TimeOnly? DoorOpenTime { get; set; }\r\n        \r\n   \/\/ Collections of DateOnly and TimeOnly\r\n   public IList&lt;DateOnly&gt; AlternateDates { get; set; }\r\n   public IList&lt;TimeOnly&gt; SessionTimes { get; set; }\r\n}\r\n<\/code><\/pre>\n<h4>Step 2: Build the EDM Model<\/h4>\n<p><strong>Convention-Based Model<\/strong><\/p>\n<pre><code>using Microsoft.AspNetCore.OData;\r\nusing Microsoft.OData.ModelBuilder;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\n\/\/ Build the EDM model using conventions\r\nvar modelBuilder = new ODataConventionModelBuilder();\r\nmodelBuilder.EntitySet&lt;Event&gt;(\"Events\");\r\nvar edmModel = modelBuilder.GetEdmModel();\r\n\r\n\/\/ Add OData services with query features\r\nbuilder.Services.AddControllers()\r\n    .AddOData(options =&gt; options\r\n        .EnableQueryFeatures(100)\r\n        .AddRouteComponents(\"odata\", edmModel));\r\n\r\nvar app = builder.Build();\r\napp.MapControllers();\r\napp.Run();<\/code><code>\r\n<\/code><\/pre>\n<h4>Step 3: Create the OData Controller<\/h4>\n<pre><code>using System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing Microsoft.AspNetCore.Mvc;\r\nusing Microsoft.AspNetCore.OData.Query;\r\nusing Microsoft.AspNetCore.OData.Routing.Controllers;\r\nusing ODataDateTimeSample.Models;\r\n\r\nnamespace ODataDateTimeSample.Controllers;\r\n\r\npublic class EventsController : ODataController\r\n{\r\n    private static readonly List&lt;Event&gt; _events = new()\r\n    {\r\n        new Event\r\n        {\r\n            Id = 1,\r\n            Title = \".NET Conference 2025\",\r\n            Location = \"Seattle Convention Center\",\r\n            EventDate = new DateOnly(2025, 11, 15),\r\n            StartTime = new TimeOnly(9, 0, 0),\r\n            EndTime = new TimeOnly(17, 30, 0),\r\n            RegistrationDeadline = new DateOnly(2025, 11, 1),\r\n            DoorOpenTime = new TimeOnly(8, 30, 0),\r\n            AlternateDates = new List\r\n            {\r\n                new DateOnly(2025, 11, 16),\r\n                new DateOnly(2025, 11, 17)\r\n            },\r\n            SessionTimes = new List\r\n            {\r\n                new TimeOnly(10, 0, 0),\r\n                new TimeOnly(14, 0, 0),\r\n                new TimeOnly(16, 0, 0)\r\n            }\r\n        },\r\n        new Event\r\n        {\r\n            Id = 2,\r\n            Title = \"Azure Workshop\",\r\n            Location = \"Online\",\r\n            EventDate = new DateOnly(2025, 12, 5),\r\n            StartTime = new TimeOnly(13, 0, 0),\r\n            EndTime = new TimeOnly(16, 0, 0)\r\n        }\r\n    };\r\n\r\n    \/\/ GET: odata\/Events\r\n    [EnableQuery]\r\n    public IActionResult Get()\r\n    {\r\n        return Ok(_events);\r\n    }\r\n\r\n    \/\/ GET: odata\/Events(1)\r\n    [EnableQuery]\r\n    public IActionResult Get(int key)\r\n    {\r\n        var evt = _events.FirstOrDefault(e =&gt; e.Id == key);\r\n        if (evt == null)\r\n        {\r\n            return NotFound();\r\n        }\r\n        return Ok(evt);\r\n    }\r\n\r\n    \/\/ POST: odata\/Events\r\n    [HttpPost]\r\n    public IActionResult Post([FromBody] Event evt)\r\n    {\r\n        evt.Id = _events.Max(e =&gt; e.Id) + 1;\r\n        _events.Add(evt);\r\n        return Created(evt);\r\n    }\r\n}\r\n<\/code><\/pre>\n<h4>Step 4: Add OData Functions with DateOnly\/TimeOnly Parameters<\/h4>\n<pre><code>\/\/ In your model builder\r\n\/\/ Define the Bound function to collection\r\nvar function = modelBuilder.EntityType()\r\n    .Collection\r\n    .Function(\"GetEventsInDateRange\")\r\n    .ReturnsFromEntitySet(\"Events\");\r\nfunction.Parameter(\"startDate\");\r\nfunction.Parameter(\"endDate\");\r\nfunction.Parameter&lt;TimeOnly?&gt;(\"preferredTime\");\r\n\r\n\/\/ In your controller\r\n[EnableQuery]\r\n[HttpGet]\r\npublic IActionResult GetEventsInDateRange([FromODataUri] DateOnly startDate, [FromODataUri] DateOnly endDate, [FromODataUri] TimeOnly? preferredTime)\r\n{\r\n    var filtered = _events.Where(e =&gt;\r\n        e.EventDate &gt;= startDate &amp;&amp;\r\n        e.EventDate &lt;= endDate); if (preferredTime.HasValue) { filtered = filtered.Where(e =&gt;\r\n            e.StartTime &lt;= preferredTime.Value &amp;&amp; e.EndTime &gt;= preferredTime.Value);\r\n    }\r\n\r\n    return Ok(filtered);\r\n}\r\n<\/code><\/pre>\n<h4>Step 5: Test Your OData API<\/h4>\n<p>Once your application is running, test it with these OData queries:<\/p>\n<p><strong>Query all events:<\/strong><\/p>\n<pre><code>GET \/odata\/Events<\/code><\/pre>\n<p><strong>Filter by EventDate:<\/strong><\/p>\n<pre><code>GET \/odata\/Events?$filter=EventDate eq 2025-12-05<\/code> \r\n<code>GET \/odata\/Events?$filter=EventDate ge 2025-11-01<\/code> \r\n<code>GET \/odata\/Events?$filter=month(EventDate) eq 12<\/code><\/pre>\n<p><strong>Filter by StartTime:<\/strong><\/p>\n<pre><code>GET \/odata\/Events?$filter=StartTime eq 13:00:00<\/code> <code>\r\nGET \/odata\/Events?$filter=StartTime lt 10:00:00\r\nGET \/odata\/Events?$filter=minute(EndTime) eq 30<\/code><\/pre>\n<p><strong>Order by EventDate and StartTime:<\/strong><\/p>\n<pre><code>GET \/odata\/Events?$orderby=EventDate desc, StartTime<\/code><\/pre>\n<p><strong>Add a new Event:<\/strong><\/p>\n<pre><code>\r\nPOST \/odata\/Events\r\nContent-Type: application\/json\r\n\r\n{\r\n    \"Id\": 23,\r\n    \"Title\": \"Test title\",\r\n    \"Location\": \"Online\",\r\n    \"EventDate\": \"2023-05-15\",\r\n    \"StartTime\": \"20:30:59.0010100\",\r\n    \"EndTime\": \"08:30:59.0010000\",\r\n    \"AlternateDates\": [\"2023-05-15\", \"2024-10-24\"],\r\n    \"SessionTimes\": [\"20:30:59.0010100\", \"08:30:59.0010000\", \"21:31:59\"]\r\n}\r\n<\/code><\/pre>\n<p><strong>Call a custom function:<\/strong><\/p>\n<pre><code>GET \/odata\/Events\/GetEventsInDateRange(startDate=2025-11-01,endDate=2025-12-31,preferredTime=10:00:00)<\/code><code><\/code><\/pre>\n<h2 id=\"feedback\">Feedback<\/h2>\n<p>This is a preview release, and <strong>we want to hear from you!<\/strong> Please try ASP.NET Core OData 10.0.0 Preview 1 in your projects and share your experiences, issues, or suggestions on the <a href=\"https:\/\/github.com\/OData\/AspNetCoreOData\/issues\">GitHub repository<\/a>.<\/p>\n<p>Your feedback is invaluable as we work toward the final 10.0.0 release and continue to modernize the library.<\/p>\n<hr \/>\n<h3>Related Resources<\/h3>\n<ul>\n<li style=\"list-style-type: none;\">\n<ul>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/announcing-odata-model-builder-3-0-0-preview-1-release\/\">OData Model Builder 3.0.0 Preview 1 Release Announcement<\/a><\/li>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/odata\/announcing-odata-net-odl-9-preview-3-release\/\">OData .NET (ODL) 9.0 Preview 3 Release<\/a><\/li>\n<li><a href=\"https:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part1-protocol.html\">OData v4.01 Specification<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;re thrilled to announce the release of ASP.NET Core OData 10.0.0 Preview 1, a major modernization update that embraces .NET&#8217;s native System.DateOnly and System.TimeOnly types! This release upgrades to .NET 10.0 and replaces OData&#8217;s proprietary Edm.Date and Edm.TimeOfDay CLR wrapper types with .NET&#8217;s native structs. Microsoft.AspNetCore.OData 10.0.0-preview.1 What&#8217;s Changed Framework &amp; Dependency Updates .NET 10.0 [&hellip;]<\/p>\n","protected":false},"author":169392,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1472,1,116,117],"tags":[],"class_list":["post-5995","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","category-odl","category-webapi"],"acf":[],"blog_post_summary":"<p>We&#8217;re thrilled to announce the release of ASP.NET Core OData 10.0.0 Preview 1, a major modernization update that embraces .NET&#8217;s native System.DateOnly and System.TimeOnly types! This release upgrades to .NET 10.0 and replaces OData&#8217;s proprietary Edm.Date and Edm.TimeOfDay CLR wrapper types with .NET&#8217;s native structs. Microsoft.AspNetCore.OData 10.0.0-preview.1 What&#8217;s Changed Framework &amp; Dependency Updates .NET 10.0 [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5995","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\/169392"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5995"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5995\/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=5995"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5995"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5995"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}