May 01, 2019

tl;dr: Odata provides a nice, conventional, powerful and easy-to-setup api. This post is a walk-through to expose a readonly model.

We will

Initialize a new web api project Add Odata for ASP.NET Core and also Microsofts api versioning library In the Startup.cs class configure services and the middleware pipeline to enable Odata Add a sample model and Odata controller Implement IModelConfiguration to configure the Odata model Execute some queries against the Odata controller

The resulting source code is available on github:

git clone https://github.com/jannikbuschke/pragmatic-cqrs-with-asp-net-core-for-spas

Introduction

This is the first post of a mini series about creating an opinionated ASP.NET Core api with the help of Odata (among others).

Odata is a conventional restful api, meaning that some query operations like top, skip, select, order by and filter (typical sql-like operations) are standardized as part of the api. Odata also provides conventions for commands (if we think in terms of CQRS ), which I will leave out, as in my experience this part is overly complex to use and the normal webapi is powerful and easily adjusted/modified to provide custom conventions.

This post is a walk-through to setup a simple queryable (readonly) api which in my opinion gives the most “bang for your buck”.

Pragmatic CQRS with ASP.NET Core and SPAs - Series overview

Project setup

Lets create our project and add Odata as well as mvc- and odata-versioning.

dotnet new webapi --name MyApp cd MyApp dotnet package add Microsoft.AspNetCore.Odata dotnet package add Microsoft.AspNetCore.Mvc.Versioning dotnet package add Microsoft.AspNetCore.OData.Versioning

Our .csproj file now should look something like this:

< Project Sdk = " Microsoft.NET.Sdk.Web " > < PropertyGroup > < TargetFramework > netcoreapp2.2 </ TargetFramework > < AspNetCoreHostingModel > InProcess </ AspNetCoreHostingModel > </ PropertyGroup > < ItemGroup > < PackageReference Include = " Microsoft.AspNetCore.App " /> < PackageReference Include = " Microsoft.AspNetCore.Odata " Version = " 7.1.0 " /> < PackageReference Include = " Microsoft.AspNetCore.Mvc.Versioning " Version = " 3.1.2 " /> < PackageReference Include = " Microsoft.AspNetCore.OData.Versioning " Version = " 3.1.0 " /> < PackageReference Include = " Microsoft.AspNetCore.Razor.Design " Version = " 2.2.0 " PrivateAssets = " All " /> </ ItemGroup > </ Project >

Add Odata and ApiVersioning services in the ConfigureServices method (Startup.cs):

public void ConfigureServices ( IServiceCollection services ) { services . AddMvcCore ( options => { options . EnableEndpointRouting = false ; } ) ; services . AddMvc ( ) . SetCompatibilityVersion ( CompatibilityVersion . Version_2_2 ) ; services . AddApiVersioning ( ) ; services . AddOData ( ) . EnableApiVersioning ( ) ; }

Also add Odata routes in the Configure method (Startup.cs). The Configure method needs a VersionedODataModelBuilder as an an additional parameter:

public void Configure ( IApplicationBuilder app , IHostingEnvironment env , VersionedODataModelBuilder modelBuilder ) { if ( env . IsDevelopment ( ) ) { app . UseDeveloperExceptionPage ( ) ; } else { app . UseHsts ( ) ; } app . UseHttpsRedirection ( ) ; app . UseMvc ( builder => { builder . Select ( ) . Expand ( ) . Filter ( ) . OrderBy ( ) . Count ( ) ; builder . MapVersionedODataRoutes ( "odata" , "odata" , modelBuilder . GetEdmModels ( ) ) ; } ) ; }

Adding a Model, Configuration and Controller

Next create a model (Person.cs):

public class Person { public Guid Id { get ; set ; } public string FirstName { get ; set ; } public string LastName { get ; set ; } }

and the Odata configuration (OdataModelConfigurations.cs)

public class OdataModelConfigurations : IModelConfiguration { public void Apply ( ODataModelBuilder builder , ApiVersion apiVersion ) { builder . EntitySet < Person > ( "Persons" ) ; } }

Finally let`s create a controller with sample data created on the fly (PersonsController.cs):

public class PersonsController : ODataController { [ EnableQuery ] public IQueryable < Person > Get ( ) { return new string [ ] { "Alice" , "Bob" , "Chloe" , "Dorothy" , "Emma" , "Fabian" , "Garry" , "Hannah" , "Julian" } . Select ( v => new Person { FirstName = v , Id = Guid . NewGuid ( ) , Age = new Random ( ) . Next ( 80 ) } ) . AsQueryable ( ) ; } }

If you have experience with Entity Framework Core you could just return a DbSet<T> (or DbQuery<T> ) property of your DbContext implementation in above Get() method.

Run and test

Now that we have a controller we can run the project and execute queries in a browser:

dotnet run

open https://localhost:5001/odata/Persons?api-version=1.0 in a browser (or http://localhost:5000/... ).

You should get the full resultset in json format.

Some parameters that can be tried now: