Getting started with OData v4 in ASP.NET Web API

Β· 747 words Β· 4 minutes to read

Since yesterday, the ASP.NET Web stack nightly feed contains the packages supporting OData v4. The package is called Microsoft.AspNet.OData and has a working version 5.2.0 - so I’m guessing this is intended to ship with Web API 2.

It relies on the latest beta of Microsoft.OData.Core. OData v4 is a massive changed compared to v3 - you can read about all of them [here][1].

Adding OData v4 to your Web API πŸ”—

You need to use the Web stack nightlies which is available at http://www.myget.org/F/aspnetwebstacknightly/ and needs to be added to your Nuget repositories.

[nightlies][2]

Once added, you can install the new OData nightly packages using:

install-package Microsoft.AspNet.OData -pre  

If your Web API has a reference to Microsoft.AspNet.WebApi.OData (the old OData support package), it should be removed. Also, you may have to add assembly binding redirects - since Microsoft.AspNet.OData will pull latest nightlies of Microsoft.AspNet.WebApi.Core and Microsoft.AspNet.WebApi.Client, which will replace your current references to those packages.

Below are the necessary redirects:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">  
<dependentAssembly>  
<assemblyIdentity name="System.Web.Http" publicKeyToken="31BF3856AD364E35" culture="neutral"/>  
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0"/>  
</dependentAssembly>  
<dependentAssembly>  
<assemblyIdentity name="System.Net.Http.Formatting" publicKeyToken="31BF3856AD364E35" culture="neutral"/>  
<bindingRedirect oldVersion="0.0.0.0-5.2.0.0" newVersion="5.2.0.0"/>  
</dependentAssembly>  
</assemblyBinding>  

Getting started with OData v4 in Web API πŸ”—

I’ll use a simple model and a DbContext for this exercise:

public class Player  
{  
public int Id { get; set; }  
public string Name { get; set; }  
public int Age { get; set; }  
}

public class PlayerAppContext : DbContext  
{  
public PlayerAppContext() : base("name=PlayerAppContext")  
{}

public DbSet<Player> Players { get; set; }  
}  

The controller is the same one as you’d get using scaffolding, with some small changes done to cater for the new OData package. Notice that we now use [EnableQuery] instead of [Queryable]. Also, with OData v4 we could (should?) support upsert (insert with PUT), but I will not do that.

public class PlayerController : ODataController  
{  
private PlayerAppContext db = new PlayerAppContext();

[EnableQuery]  
public IQueryable<Player> GetPlayer()  
{  
return db.Players;  
}

[EnableQuery]  
public SingleResult<Player> GetPlayer([FromODataUri] int key)  
{  
return SingleResult.Create(db.Players.Where(player => player.Id == key));  
}

public async Task<IHttpActionResult> Put([FromODataUri] int key, Player player)  
{  
if (!ModelState.IsValid)  
{  
return BadRequest(ModelState);  
}

if (key != player.Id)  
{  
return BadRequest();  
}

db.Entry(player).State = EntityState.Modified;

try  
{  
await db.SaveChangesAsync();  
}  
catch (DbUpdateConcurrencyException)  
{  
if (!PlayerExists(key))  
{  
return NotFound();  
}

throw;  
}

return Updated(player);  
}

public async Task<IHttpActionResult> Post(Player player)  
{  
if (!ModelState.IsValid)  
{  
return BadRequest(ModelState);  
}

db.Players.Add(player);  
await db.SaveChangesAsync();

return Created(player);  
}

[AcceptVerbs("PATCH", "MERGE")]  
public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Player> patch)  
{  
if (!ModelState.IsValid)  
{  
return BadRequest(ModelState);  
}

Player player = await db.Players.FindAsync(key);  
if (player == null)  
{  
return NotFound();  
}

patch.Patch(player);

try  
{  
await db.SaveChangesAsync();  
}  
catch (DbUpdateConcurrencyException)  
{  
if (!PlayerExists(key))  
{  
return NotFound();  
}  
else  
{  
throw;  
}  
}

return Updated(player);  
}

public async Task<IHttpActionResult> Delete([FromODataUri] int key)  
{  
Player player = await db.Players.FindAsync(key);  
if (player == null)  
{  
return NotFound();  
}

db.Players.Remove(player);  
await db.SaveChangesAsync();

return StatusCode(HttpStatusCode.NoContent);  
}

protected override void Dispose(bool disposing)  
{  
if (disposing)  
{  
db.Dispose();  
}  
base.Dispose(disposing);  
}

private bool PlayerExists(int key)  
{  
return db.Players.Count(e => e.Id == key) > 0;  
}  
}  

Registration is done in the same way as before, except the MapODataRoute is replaced with MapODataServiceRoute method.

var builder = new ODataConventionModelBuilder();  
builder.EntitySet<Player>("Player");  
config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());  

With these in place, I also added DB migrations using the following steps:

    1. run enable-migrations from the package manager console
    1. updated the generated Configuration.cs with my sample data
    protected override void Seed(WebApplication3.Models.PlayerAppContext context)  
    {  
    context.Players.AddOrUpdate(  
    new Player { Name = "Phil Kessel", Age = 26},  
    new Player { Name = "Dion Phaneuf", Age = 28 },  
    new Player { Name = "Nazem Kadri", Age = 23 }  
    );  
    }  
    ``` 
  3. 3. added initial migration with _add-migration Initial_ command 
  4. 4. executed the DB update using _update-database_

Next step is to run the whole solution and navigate to the $metadata to see what version of OData we are running - and indeed it is v4.

[<img src="/images/2014/02/odata.png" alt="odata" width="620" height="398" class="aligncenter size-full wp-image-1093" />][3]

In OData v4, $inlinecount has been replaced with $count.

[<img src="/images/2014/02/count.png" alt="count" width="606" height="383" class="aligncenter size-full wp-image-1089" srcset="/images/2014/02/count.png 606w, /images/2014/02/count-300x190.png 300w" sizes="(max-width: 606px) 100vw, 606px" />][4]

[<img src="/images/2014/02/getall.png" alt="getall" width="584" height="393" class="aligncenter size-large wp-image-1090" srcset="/images/2014/02/getall.png 617w, /images/2014/02/getall-300x202.png 300w" sizes="(max-width: 584px) 100vw, 584px" />][5]

[<img src="/images/2014/02/getplayer.png" alt="getplayer" width="584" height="271" class="aligncenter size-large wp-image-1091" />][6]

While these are only nightlies, it's great to see the progress on the OData v4 front. So go ahead and start playing with the next generation web services built on top of OData v4!

 [1]: http://www.infoq.com/news/2013/05/OData-4
 [2]: /images/2014/02/nightlies.png
 [3]: /images/2014/02/odata.png
 [4]: /images/2014/02/count.png
 [5]: /images/2014/02/getall.png
 [6]: /images/2014/02/getplayer.png

About


Hi! I'm Filip W., a cloud architect from ZΓΌrich πŸ‡¨πŸ‡­. I like Toronto Maple Leafs πŸ‡¨πŸ‡¦, Rancid and quantum computing. Oh, and I love the Lowlands 🏴󠁧󠁒󠁳󠁣󠁴󠁿.

You can find me on Github, on Mastodon and on Bluesky.

My Introduction to Quantum Computing with Q# and QDK book
Microsoft MVP