OData API, EF Core, and Blazor

WORKING WITH ODATA API, EF CORE, AND BLAZOR

by

In this tutorial, you will learn how to configure an OData API, integrate it with Entity Framework Core, and call the API from a server-side Blazor front-end targeting .NET Core 3.1.

An OData API allows you to easily and efficiently query, sort, filter, and paginate data from a database. Whereas a JavaScript-only solution in the DOM operating on a standard API would require you to first fetch all the data in order to sort or filter, OData ensures that only the requested data is fetched, thus saving resources.

Set up EF Core

We will continue the Contact Management solution we have been working on. You should already have a Contacts model created in the BlazorContacts.Shared project. The next step is to create a database context for your contacts. In the BlazorContacts.API project, create a Data folder and add a class called ContactsContext.cs.

Start by referencing the EF Core namespace by adding the following using directive. You may have to install the package if you have not already done so.

using Microsoft.EntityFrameworkCore;

Use the following class constructor to inherit from DbContext.

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

    public DbSet<Contact> Contacts { get; set; }
}

With this context defined, you must now register it in your API’s Startup.cs file. I will use an in-memory database for this example, but feel free to use whatever database your project requires. Again, this will use the Microsoft.EntityFrameworkCore namespace, as well as the BlazorContacts.API.Data namespace you just created. For an in-memory DB, add the following code within your ConfigureServices() method:

services.AddDbContext<ContactsContext>(options =>
    options.UseInMemoryDatabase("Contacts"));

Configure OData API

To create an OData API, first install version 7.3.0 or greater of the Microsoft.AspNetCore.OData NuGet package.

Install-Package Microsoft.AspNetCore.OData

Next, co configure your OData API, you must make some more changes to the Startup.cs file of your BlazorContacts.API project. With version 7.3.0, you cannot use endpoint routing on the API project. This is not a problem with our application structure, because you can still use endpoint routing on your Blazor front-end!

Update 9 March: With the release of OData 7.4.0, it is no longer necessary to disable endpoint routing.

Disable endpoint routing and add the OData service to your project’s service collection in ConfigureServices().

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvcCore(options =>
    {
        options.EnableEndpointRouting = false;
        ...
    });

    ...

    services.AddOData();

    ...

}

Then, in the Configure() method, remove UseEndpoints and configure MVC routing for your OData project.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    /*
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
    */

    app.UseMvc(routeBuilder =>
    {
        routeBuilder.Select().Filter().OrderBy().Expand().Count().MaxTop(50);
        routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
    });
}

The first line of routeBuilder parameters adds the OData functionality you intend to use in your project. Feel free to remove any functionality you will not need. The second defines a default route prefix api for this API. For example, if you configure a contacts controller (which is the next step), you will access it by calling the endpoint located at api/contacts, for example http://localhost:5001/api/contacts

Finally, define the EDM model to be used for parsing your API. In this case, that will be the Contacts model located in BlazorContacts.Shared.Models.

private IEdmModel GetEdmModel()
{
    var builder = new ODataConventionModelBuilder();
    builder.EntitySet<Contact>("Contacts");
    return builder.GetEdmModel();
}

Create OData API Controller

Rather than scaffolding a standard API controller based on the model and database context, simply create your own OData API controller by adding a class to the Controllers folder of BlazorContacts.API. Name the class ContactsController.cs.

Add the following using directives.

using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNet.OData;
using BlazorContacts.API.Data;
using Microsoft.EntityFrameworkCore;

Next, your ContactsController class should inherit from ODataController and inject the Contacts database context as a dependency.

public class ContactsController : ODataController
{
    private readonly ContactsContext _context;

    public ContactsController (ContactsContext context)
    {
        _context = context;
    }
}

Add the following annotation above the ContactsController class definition to define the route prefix for this controller.

[ODataRoutePrefix("contacts")]

Remember, you previously defined api as the route prefix for the entire API. In this line, you are now defining contacts as the route prefix for this particular controller. OData API methods in this controller will thus be accessible from api/contacts.

Now it is time to add GET, POST, PATCH, and DELETE routes for the API’s ContactsController. When configuring an OData controller, there are other annotations that can be helpful. First, the [EnableQuery] annotation enables querying for the API method. You can use this to enable sorting and filtering for your API’s GET methods, for example. Second, the [ODataRoute] annotation defines the route for the API method. Without optional parameters, it uses the default route of api/contacts in this case. You can also use parameters from the OData Uri; for example, you may get a specific contact by ID with the following route [ODataRoute("({id})")] accessible via api/contacts(5).

Consider the following example methods.

// GET: api/contacts
[EnableQuery(PageSize = 50)]
[ODataRoute]
public IQueryable<Contact> Get()
{
    return _context.Contacts;
}

// GET: api/contacts(5)
[EnableQuery]
[ODataRoute("({id})")]
public async Task<IActionResult> GetContact([FromODataUri] long id)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var contact = await _context.Contacts.FindAsync(id);

    if (contact == null)
    {
        return NotFound();
    }

    return Ok(contact);
}

For other Entity Framework Core compatible OData API methods, check out this controller’s source code on GitHub.

OData API and Blazor

You will work with an OData API the same way you would any other web API, by using HttpClientFactory, registering an instance of HttpClient, and injecting that service instance into your application. Now, however, you have access to extra features available with OData. You can get a count of matching items, modify the sorting order, limit the number of results, filter contacts, and even get paginated results by simply changing the query string.

var response = await _httpClient.GetAsync($”api/contacts?$count=true&$orderby=&$skip=&$top=”);

For a complete example of a server-side Blazor front-end interacting with an OData API and EF Core backend, check out the BlazorContacts.Web project on GitHub. The complete solution also includes source code for the API, the shared models library, and the authentication server. Feel free to use it as a template for your real-world Blazor applications. If you have any questions, let me know in the comments!


Don't stop learning!

There is so much to discover about C#. That's why I am making my favorite tips and tricks available for free. Enter your email address below to become a better .NET developer.


Did you know?

Our beautiful, multi-column C# reference guides contain more than 150 tips and examples to make it even easier to write better code.

Get your cheat sheets