October 7th, 2025
like1 reaction

Enable OData functionalities on ASP.NET Core Minimal API

Sam Xu
Senior Software Engineer

Introduction

Minimal API is a simplified approach for building HTTP APIs fast within ASP.NET Core, compared to the controller-based APIs. Developers can build fully functioning REST endpoints with minimal code and configuration, especially without controller, action and even the formatters. See ASP.NET Core Minimal API details at here.

Since Microsoft.AspNetCore.OData version 9.4.0, it enables developers to achieve OData functionalities on ASP.NET Core minimal APIs. In this post, I’d like to go through the basics of enabling OData functionalities on a Minimal API application. Let’s get started.

Prerequisites

First of first, let’s create an ASP.NET Core Empty application named ODataMinimalAPI with following Nuget packages installed.

<ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OData" Version="9.4.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="9.0.11" />
</ItemGroup>

The Program.cs contains the following codes:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();
app.MapGet("/", () => "Hello World!");

app.Run();

The lambda expression in preceding MapGet is called Route Handler. They are methods that execute when the route matches. Route handlers can be:

  • A lambda expression,
  • A local function,
  • An instance method
  • A static method
  • Or a RequestDelegate

Basically, ASP.NET Core OData decorates the endpoint with metadata or even changes the route handler to enable OData functionalities.

Models and database context

In the project, Let’s create the following classes to represent data managed in the application:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Location { get; set; }
    public Info Info { get; set; }
    public List<Order> Orders { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public int Amount { get; set; }
}

public class Address
{
    public string City { get; set; }
    public string Street { get; set; }
}

public class Info
{
    public string Email { get; set; }
    public string Phone { get; set; }
}

Let’s also create a DbContext as below:

using Microsoft.EntityFrameworkCore;

public class ApplicationDb : DbContext
{
    public ApplicationDb(DbContextOptions<ApplicationDb> options) : base(options) { }

    public DbSet<Customer> Customers=> Set<Customer>();

    public DbSet<Order> Orders => Set<Order>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Customer>().HasKey(x => x.Id);
        modelBuilder.Entity<Order>().HasKey(x => x.Id);
        modelBuilder.Entity<Customer>().OwnsOne(x => x.Info);
        modelBuilder.Entity<Customer>().OwnsOne(x => x.Location);
    }
}

Then, update Program.cs as below:

using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<ApplicationDb>(options => options.UseInMemoryDatabase("CustomerOrderList"));

var app = builder.Build();

app.MakeSureDbCreated(); // Create sample data ahead

app.MapGet("/json/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))

app.Run();

Run the application. Open ‘Endpoints Explorer’ by clicking [View]-> [Other Windows]-> [Endpoints Explorer] menu, you may see an endpoint listed as:

Endpoints

Right-click on the /json/customers and select [Generate Request] menu, a ODataMinimalApi.http is created with a Http Request. We can send a request by clicking [Send request] on the endpoint in the http file and check the response. Below is the sample data created ahead by sending request to /json/customers.

[
  {
    "id": 1,
    "name": "Alice",
    "location": {
      "city": "Redmond",
      "street": "123 Main St"
    },
    "info": {
      "email": "[email protected]",
      "phone": "123-456-7819"
    },
    "orders": [
      {
        "id": 11,
        "amount": 9
      },
      {
        "id": 12,
        "amount": 19
      }
    ]
  },
  {
    "id": 2,
    "name": "Johnson",
    "location": {
      "city": "Sammamish",
      "street": "228 Ave NE"
    },
    "info": {
      "email": "[email protected]",
      "phone": "233-468-7289"
    },
    "orders": [
      {
        "id": 21,
        "amount": 8
      },
      {
        "id": 22,
        "amount": 76
      }
    ]
  }
]

OData Edm Model

Let’s create the following OData Edm model ahead which is used in some scenarios below. Be noted, I mark Address as entity type, and Info as complex type explicitly.

public class EdmModelBuilder
{
    public static IEdmModel GetEdmModel()
    {
        var builder = new ODataConventionModelBuilder();

        builder.EntitySet<Customer>("Customers");
        builder.EntitySet<Order>("Orders");

        builder.EntityType<Address>(); // Intentionally built it as entity type
        builder.ComplexType<Info>(); // Intentionally built it as complex type

        return builder.GetEdmModel();
    }
}

OData basic scenarios

WithODataResult()

We enable developers to call an extension method named WithODataResult() on the endpoint to get ‘OData-formatted‘ response. Let’s add a new endpoint as below:

app.MapGet("odata/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .WithODataResult();

Then, generate a request in ODataMinimalApi.http as GET {{ODataMinimalApi_HostAddress}}/odata/customers. Run the application, click the [Send request], (I will omit these steps in the following section), we can get the OData response payload as below:

{
  "@odata.context": "http://localhost:5134/$metadata#Customer",
  "value": [
    {
      "Id": 1,
      "Name": "Alice",
      "Location": {
        "City": "Redmond",
        "Street": "123 Main St"
      },
      "Info": {
        "Email": "[email protected]",
        "Phone": "123-456-7819"
      }
    },
    {
      "Id": 2,
      "Name": "Johnson",
      "Location": {
        "City": "Sammamish",
        "Street": "228 Ave NE"
      },
      "Info": {
        "Email": "[email protected]",
        "Phone": "233-468-7289"
      }
    }
  ]
}

Be noted, Location and Info are returned as complex type properties (without an explicit key property), meanwhile Orders is not presented by default since it’s a navigation property (with a key property).

IODataModelConfiguration

One way to change the model is to provide an implementation of IODataModelConfiguration interface. For example, we can create a class MyODataModelConfiguration implemented IODataModelConfiguration interface to config the Info type as an entity type. As a result, the Info property on Customer is built as a navigation property.

public class MyODataModelConfiguration : IODataModelConfiguration
{
    public ODataModelBuilder Apply(HttpContext context, ODataModelBuilder builder, Type clrType)
    {
        if (context.Request.Path.StartsWithSegments("/modelconfig/customers", StringComparison.OrdinalIgnoreCase))
        {
            builder.EntityType<Info>();
        }

        return builder;
    }
}

Register the configuration as a global service into the Dependency Injection container as below:

builder.Services.AddSingleton<IODataModelConfiguration, MyODataModelConfiguration>();

Meanwhile, add a new endpoint as:

app.MapGet("modelconfig/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .WithODataResult();

Then, run the application and send request to GET {{ODataMinimalApi_HostAddress}}/modelconfig/customers, we can get a different payload as:

{
  "@odata.context": "http://localhost:5134/$metadata#Customer",
  "value": [
    {
      "Id": 1,
      "Name": "Alice",
      "Location": {
        "City": "Redmond",
        "Street": "123 Main St"
      }
    },
    {
      "Id": 2,
      "Name": "Johnson",
      "Location": {
        "City": "Sammamish",
        "Street": "228 Ave NE"
      }
    }
  ]
}

Be noted, Info property is not presented by default since it’s a navigation property.

WithODataModel()

Another way to configure the model for an endpoint to call WithODataModel(model) extension method as below example:

IEdmModel model = EdmModelBuilder.GetEdmModel();
app.MapGet("/withmodel/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .WithODataResult()
    .WithODataModel(model);

Then, run and send request to GET {{ODataMinimalApi_HostAddress}}/withmodel/customers, we can get the following payload:

{
  "@odata.context": "http://localhost:5134/$metadata#Customers",
  "value": [
    {
      "Id": 1,
      "Name": "Alice",
      "Info": {
        "Email": "[email protected]",
        "Phone": "123-456-7819"
      }
    },
    {
      "Id": 2,
      "Name": "Johnson",
      "Info": {
        "Email": "[email protected]",
        "Phone": "233-468-7289"
      }
    }
  ]
}

Be noted, in the given model, Info is built as complex type meanwhile Address is built as entity type explicitly.

WithODataVersion()

We can change the OData spec version by calling WithODataVersion(version) extension method as:

app.MapGet("/v401/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .WithODataResult()
    .WithODataModel(model)
    .WithODataVersion(ODataVersion.V401);

Then, run and send request to GET {{ODataMinimalApi_HostAddress}}/v401/customers, we can get an OData v4.01 payload:

{
  "@context": "http://localhost:5134/$metadata#Customers",
  "value": [
    {
       ...
    },
    {
       ...
    }
  ]
}

We can see the odata. prefix is omitted in the context URL by default in OData v4.01 version.

WithODataBaseAddressFactory()

We can change the base address in the payload by calling WithODataBaseAddressFactory(lambda). For example:

app.MapGet("/baseaddress/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .WithODataResult()
    .WithODataModel(model)
    .WithODataBaseAddressFactory(c => new Uri("http://abc.com"));

Then, run and send request to GET {{ODataMinimalApi_HostAddress}}/baseaddress/customers, we can get the following payload:

{
  "@odata.context": "http://abc.com/$metadata#Customers",
  "value": [
    {
       ...
    },
    {
      ...
    }
  ]
}

Be noted, the context URL is changed using the URL specified in the extension method.

OData query option scenarios

OData is so powerful owing to its richful querying capabilities. Let’s see how to enable OData query options on the Minimal API endpoint.

Configure OData query options

For security reason, OData query options are disabled globally by default. If we want to enable them, just call the following method:

builder.Services.AddOData(q => q.EnableAll());

It enables all query options. Of course, we can enable a certain query option by calling its corresponding method only, for example, Expand() only enables $expand, etc.

ODataQueryOptions<T>

One way to enable OData query functionalities on an endpoint is to add a ODataQueryOptions<T> parameter explicitly on the route handler. Here’s the example:

app.MapGet("/queryoptions/customers", (ApplicationDb db, ODataQueryOptions<Customer> queryOptions) =>
    queryOptions.ApplyTo(db.Customers.Include(s => s.Orders)))
    .WithODataResult();

Then run and send request to GET {{ODataMinimalApi_HostAddress}}/queryoptions/customers, we can get:

{
  "@odata.context": "http://localhost:5134/$metadata#Customer",
  "value": [
    {
      "Id": 1,
      "Name": "Alice"
    },
    {
      "Id": 2,
      "Name": "Johnson"
    }
  ]
}

Be noted, in query option pattern, the Address and Info are built as entity type by default. It’s a little bit different compared to the non-query option scenario mentioned above.

We can change the model by implementing the IODataModelConfiguration interface or calling WithODataModel(model) extension method. If we mark the model configuration class as attribute, we can also decorate the configuration on the parameter as below.

(ApplicationDb db, [MyModelConfiguration] ODataQueryOptions<Customer> queryOptions) => {}

Now, let’s run and test the query options on this endpoint as:GET {{ODataMinimalApi_HostAddress}}/queryoptions/customers?$expand=info($select=Phone)&$filter=startswith(Name,’A’)&$select=Name, we can get the following payload:

{
  "@odata.context": "http://localhost:5134/$metadata#Customer(Name,Info(Phone))",
  "value": [
    {
      "Name": "Alice",
      "Info": {
        "Phone": "123-456-7819"
      }
    }
  ]
}

ODataQueryEndpointFilter

The other way to enable OData query on an endpoint is to apply ODataQueryEndpointFilter on the endpoint.

Here’s an example:

app.MapGet("queryfilter/customers", (ApplicationDb db) => db.Customers.Include(s => s.Orders))
    .AddODataQueryEndpointFilter()
    .WithODataResult();

We can test this endpoint using the following request:

GET {{ODataMinimalApi_HostAddress}}/queryfilter/customers?$select=location($select=street)&$orderby=Id&$select=Name

Send the request and we can get the following payload:

{
  "@odata.context": "http://localhost:5134/$metadata#Customer(Location/Street,Name)",
  "value": [
    {
      "Name": "Alice",
      "Location": {
        "Street": "123 Main St"
      }
    },
    {
      "Name": "Johnson",
      "Location": {
        "Street": "228 Ave NE"
      }
    }
  ]
}

Be noted, the Address and Info are built as complex type again in this scenario. You can use the IODataModelConfiguration or WithODataModel(model) to specify the model.

OData advanced scenarios

Service document and Metadata

We can call MapODataServiceDocument() extension method to register an endpoint which returns the service document of an Edm model.

For example:

app.MapODataServiceDocument("/$document", model);

Run the application and send request to GET {{ODataMinimalApi_HostAddress}}/$document, we can get the result as:

{
  "@odata.context": "http://localhost:5134/$metadata",
  "value": [
    {
      "name": "Customers",
      "kind": "EntitySet",
      "url": "Customers"
    },
    {
      "name": "Orders",
      "kind": "EntitySet",
      "url": "Orders"
    }
  ]
}

Meanwhile, we can call MapODataMetadata() extension method to register an endpoint which returns the schema document of an Edm model.

For example:

app.MapODataMetadata("/$metadata", model);

Run the application and send reqquest to GET {{ODataMinimalApi_HostAddress}}/$metadata, we can get the result as:

<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
  <edmx:DataServices>
    <Schema Namespace="ODataMinimalApi.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityType Name="Customer">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Name" Type="Edm.String" />
        <Property Name="Info" Type="ODataMinimalApi.Models.Info" />
        <NavigationProperty Name="Location" Type="ODataMinimalApi.Models.Address" />
        <NavigationProperty Name="Orders" Type="Collection(ODataMinimalApi.Models.Order)" />
      </EntityType>
      <EntityType Name="Order">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Amount" Type="Edm.Int32" Nullable="false" />
      </EntityType>
      <EntityType Name="Address">
        <Property Name="City" Type="Edm.String" />
        <Property Name="Street" Type="Edm.String" />
      </EntityType>
      <ComplexType Name="Info">
        <Property Name="Email" Type="Edm.String" />
        <Property Name="Phone" Type="Edm.String" />
      </ComplexType>
    </Schema>
    <Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
      <EntityContainer Name="Container">
        <EntitySet Name="Customers" EntityType="ODataMinimalApi.Models.Customer">
          <NavigationPropertyBinding Path="Orders" Target="Orders" />
        </EntitySet>
        <EntitySet Name="Orders" EntityType="ODataMinimalApi.Models.Order" />
      </EntityContainer>
    </Schema>
  </edmx:DataServices>
</edmx:Edmx>

Function and action

We can build endpoint to handle OData function and action. Let’s define the following edm function and action in the model builder as examples:

public static IEdmModel GetEdmModel()
{
    // …

    var function = builder.EntityType<Customer>().Collection.Function("AddNameSuffixForAllCustomer");
    function.Parameter<string>("suffix");
    function.ReturnsCollectionFromEntitySet<Customer>("Customers");

    var action = builder.EntityType<Customer>().Action("RateByName");
    action.Parameter<string>("name");
    action.Parameter<int>("age");
    action.Returns<string>();

    return builder.GetEdmModel();
}
  • Edm Function

We can define the following endpoint to handle the edm function:

app.MapGet("function/customers/addsuffix(suffix={suffix})", (ApplicationDb db, string suffix) =>
{
    var customers = db.Customers.ToList();
    foreach (var customer in customers)
    {
        customer.Name += suffix;
    }

    return customers;
})
    .WithODataResult()
    .WithODataModel(model)
    .AddODataQueryEndpointFilter()
    .WithODataPathFactory(
        (h, t) =>
        {
            string suffix = h.GetRouteValue("suffix") as string;
            IEdmEntitySet customers = model.FindDeclaredEntitySet("Customers");
            IEdmOperation function = model.FindDeclaredOperations("Default.AddNameSuffixForAllCustomer").Single();
            IList<OperationSegmentParameter> parameters = new List<OperationSegmentParameter>
            {
                new OperationSegmentParameter("suffix", suffix)
            };

            return new ODataPath(new EntitySetSegment(customers), new OperationSegment(function, parameters, customers));
});

Then generate the following request and send request to:

GET {{ODataMinimalApi_HostAddress}}/function/customers/addsuffix(suffix=’%20Sr’)?$select=name

We can get the following payload:

{
  "@odata.context": "http://localhost:5134/$metadata#Customers(Name)",
  "value": [
    {
      "Name": "Johnson' Sr'"
    },
    {
      "Name": "Alice' Sr'"
    }
  ]
}

Be noted, the parameter suffix value is binded directly from the route data. Therefore, developers should take responsibility to convert the raw value to OData value, for example, to remove the single quote for the string value. We’ll figure out a better solution to improve this scenario in the next version.

  • Edm Action

We can add ODataActionParameters as a parameter the route handler to access the Edm action parameters as follows:

app.MapPost("action/customers/{id}/rateByName", (ApplicationDb db, int id, ODataActionParameters parameters) =>
{
    Customer customer = db.Customers.FirstOrDefault(s => s.Id == id);
    if (customer == null)
    {
        return null;
    }

    return $"{customer.Name}: {System.Text.Json.JsonSerializer.Serialize(parameters)}"; // for test only
})
    .WithODataResult()
    .WithODataModel(model)
    .WithODataPathFactory(
        (h, t) =>
        {
            string idStr = h.GetRouteValue("id") as string;
            int id = int.Parse(idStr);
            IEdmEntitySet customers = model.FindDeclaredEntitySet("Customers");
            IDictionary<string, object> keysValues = new Dictionary<string, object>();
            keysValues["Id"] = id;

            IEdmAction action = model.SchemaElements.OfType<IEdmAction>().First(a => a.Name == "RateByName");

            return new ODataPath(new EntitySetSegment(customers), 
                        new KeySegment(keysValues, customers.EntityType, customers), 
                       new OperationSegment(action, null)
        );
    });

Then generate the following POST request to test:

@id=1

POST {{ODataMinimalApi_HostAddress}}/action/customers/{{id}}/rateByName

Content-Type: application/json

{
  "name": "kerry",
  "age":16
}

We can get the following response:

{
  "@odata.context": "http://localhost:5134/$metadata#Edm.String",
  "value": "Alice: {\"name\":\"kerry\",\"age\":16}"
}

Be noted, WithODataPathFactory() is necessary so far to provide an OData path to get the response from edm function and action.

Delta request

We can add Detal<T> parameter on a router handler to change the single entity. For example:

app.MapPatch("delta/customers/{id}", (ApplicationDb db, int id, Delta<Customer> delta) =>
{
    Customer customer = db.Customers.FirstOrDefault(s => s.Id == id);
    if (customer == null)
    {
        return null;
    }
    delta.Patch(customer);
    return customer;
})
    .WithODataResult()
    .WithODataModel(model)
    .AddODataQueryEndpointFilter()
    .WithODataPathFactory(
        (h, t) =>
        {
            string idStr = h.GetRouteValue("id") as string;
            int id = int.Parse(idStr);
            IEdmEntitySet customers = model.FindDeclaredEntitySet("Customers");
            IDictionary<string, object> keysValues = new Dictionary<string, object>();
            keysValues["Id"] = id;
            return new ODataPath(new EntitySetSegment(customers), new KeySegment(keysValues, customers.EntityType, customers));

        });

Generate the patch request as:

PATCH {{ODataMinimalApi_HostAddress}}/delta/customers/1?$expand=location;$select=id,name,location
Content-Type: application/json

 
{
  "Name": "kerry",
  "[email protected]": {
    "street": "new street"
  }
}

Then run the application and send the request, we can get an updated customer as:

{
  "@odata.context": "http://localhost:5134/$metadata#Customers(Id,Name,Location,Location())/$entity",
  "Id": 1,
  "Name": "kerry",
  "Location": {
    "City": "Redmond",
    "Street": "new street"
  }
}

We can also use DeltaSet<T> to handle the changes for an entity set as:

app.MapPatch("deltaset/customers", (ApplicationDb  db, DeltaSet<Customer> changes) =>
{
    // omit others …
})

I’d omit codes here for simplicity.

Batch request

We can call UseODataMiniBatching() to enable the batch request on the Minimal API. For example:

app.UseODataMiniBatching("odata/$batch", model);

Be noted, we should call this method as early as possible to support OData batching.

Let’s use the following request to test the $batch:

POST {{ODataMinimalApi_HostAddress}}/odata/$batch
Content-Type: application/json
Accept: application/json

 
{
  "requests": [
  {
    "id": "1",
    "atomicityGroup": "f7de7314-2f3d-4422-b840-ada6d6de0f18",
    "method": "POST",
    "url": "http://localhost:5134/action/customers/2/rateByName",
    "headers": {
      "OData-Version": "4.0",
      "Content-Type": "application/json",
      "Accept": "application/json"
    },
    "body": {
      "name": "wu",
      "age": 20
    }
  },
  {
    "id": "2",
    "atomicityGroup": "f7de7314-2f3d-4422-b840-ada6d6de0f18",
    "method": "GET",
    "url": "http://localhost:5134/queryoptions/customers?$expand=info($select=Phone)&$filter=startswith(Name,'A')&$select=Name",
    "headers": {
      "OData-Version": "4.0",
      "Content-Type": "application/json;odata.metadata=minimal",
      "Accept": "application/json;odata.metadata=minimal"
      }
    }
  ]
}

Run the application and send the request, we can get the following response:

{
  "responses": [
    {
      "id": "1",
      "atomicityGroup": "fd7fb5df-a27f-4dde-a72e-71c142028037",
      "status": 200,
      "headers": {
        "content-type": "application/json",
        "odata-version": "4.0"
      },
      "body": {
        "@odata.context": "http://localhost:5134/$metadata#Edm.String",
        "value": "Johnson: {\"name\":\"wu\",\"age\":20}"
      }
    },
    {
      "id": "2",
      "atomicityGroup": "fd7fb5df-a27f-4dde-a72e-71c142028037",
      "status": 200,
      "headers": {
        "content-type": "application/json",
        "odata-version": "4.0"
      },
      "body": {
        "@odata.context": "http://localhost:5134/$metadata#Customer(Name,Info(Phone))",
        "value": [
          {
            "Name": "Alice",
            "Info": {
              "Phone": "123-456-7819"
             }
          }
        ]
      }
    }
  ]
}

That’s all.

Summary

This post went throw the scenarios to enable OData functionalities on ASP.NET Core Minimal API. For new simple projects, OData recommend using Minimal APIs to build the OData endpoints as they provide a simplified, high-performance approach for building APIs with minimal code and configuration. Of course, please do not hesitate to leave your comments below or let me know your thoughts through [email protected]. Thanks.

I uploaded the whole project to this repository.


        

Author

Sam Xu
Senior Software Engineer

Sam is a Senior software engineer at Microsoft with over than 10 years of software developement experience. He's worked on a wide variety of platforms such as (C++, C#, etc.) and currently works on the OData team to design and implement features in the .NET stack of Microsoft's OData libraries. OData (Open Data Protocol) is an ISO/IEC approved, OASIS standard that defines a set of best practices for building and consuming RESTful APIs. You can find more information about OData at ...

More about author

0 comments