If you are writing .NET Core REST services to expose data entities, I would recommend using OData now that ASP.NET Core support has been added. You need to add the Nuget package – Microsoft.AspNetCore.OData (at the time of writing, the latest stable version is 7.0.1). One caveat is that Swagger and Swashbuckle will not work with your OData controllers. The issue has been reported on the Swashbuckle git forums, and hopefully it will be resolved in the not too far future. I quickly tested out a demo app and it seemed to work as expected. Here are the model classes.
public class Client
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[Key]
public int Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Here’s what you add to ConfigureServices. Add it before the call to AddMvc.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ClientContext>(op =>
{
op.UseInMemoryDatabase("clientsDatabase");
});
services.AddOData();
And here are the changes made to the Configure method. Those are extension methods added by the OData package, and essentially turns on those OData features on your service. Note that I’ve added two entity sets as well as a function on one of the entities.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(rb =>
{
rb.Expand().OrderBy().Select().Filter();
rb.MapODataServiceRoute("odata", "odata", BuildEdmModel());
});
}
public static IEdmModel BuildEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Client>("Clients");
builder.EntitySet<Address>("Addresses");
builder.EntityType<Client>()
.Function("NameLength")
.Returns<string>();
return builder.GetEdmModel();
}
And this is my mocked up data context class (based off EF Core).
public class ClientContext : DbContext
{
public ClientContext(DbContextOptions<ClientContext> options) : base(options)
{
}
public DbSet<Client> Clients { get; set; }
public DbSet<Address> Addresses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
public void InitializeIfEmpty()
{
if (this.Clients.Count() == 0)
{
var address = new Address()
{
Id = 25,
Street = "100 Main St.",
City = "Columbus",
State = "OH"
};
this.Addresses.Add(address);
this.Clients.Add(new Client()
{
Id = 100,
Name = "Sam Slick",
Address = address
});
this.Clients.Add(new Client()
{
Id = 105,
Name = "Janet Slick",
Address = address
});
this.SaveChanges();
}
}
}
This is how we setup a Clients endpoint.
public class ClientsController : ODataController
{
private ClientContext _dbContext;
public ClientsController(ClientContext context)
{
_dbContext = context;
_dbContext.InitializeIfEmpty();
}
Adding methods to pull all clients, a specific client, and a specific client’s address.
[EnableQuery]
public IActionResult Get()
{
return Ok(_dbContext.Clients);
}
[EnableQuery]
public IActionResult Get(int key)
{
Client client = _dbContext.Clients
.Include(c => c.Address)
.FirstOrDefault(it => it.Id == key);
return Ok(client);
}
[EnableQuery]
public IActionResult GetAddress([FromODataUri]int key)
{
Client client = _dbContext.Clients
.Include(c => c.Address)
.FirstOrDefault(it => it.Id == key);
return base.Ok(client.Address);
}
Here’s how you would add a Post action.
[EnableQuery]
public IActionResult Post([FromBody] Client client)
{
_dbContext.Add(client);
_dbContext.SaveChanges();
return Created(client);
}
And here’s how you expose the OData action.
public IActionResult NameLength([FromODataUri]int key)
{
var client = _dbContext.Clients.FirstOrDefault(it => it.Id == key);
return Ok($"{client.Name} -> {client.Name.Length}");
}
That’s all for the demo. Now you can make standard OData queries against this API.
http://localhost:52913/odata/clients
http://localhost:52913/odata/clients?$expand=Address
http://localhost:52913/odata/clients/105/address
http://localhost:52913/odata/clients/105/NameLength