Projeto de estudo para utilização de OData com ASP.Net Core Web API, que utiliza um controller de Games para exemplo. Possui uma coleção do Postman que exemplifica o teste de chamadas OData. Utiliza o pacote NuGet Microsoft.AspNetCore.OData (versão 7.0.0-beta2) para implementação do OData.
public void ConfigureServices(IServiceCollection services)
{
services.AddOData();
services.AddSingleton(sp => new ODataUriResolver { EnableCaseInsensitive = true });
services.AddMvc().AddJsonOptions(opt =>
{
if (opt.SerializerSettings.ContractResolver != null)
{
var resolver = opt.SerializerSettings.ContractResolver as DefaultContractResolver;
resolver.NamingStrategy = null;
}
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMvc(ConfigureODataRoutes);
}
private static void ConfigureODataRoutes(IRouteBuilder routes)
{
var model = GetEdmModel();
routes.MapODataServiceRoute("ODataRoute", "odata", model);
routes.Filter(QueryOptionSetting.Allowed);
routes.OrderBy();
routes.Count();
routes.Select();
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
var games = builder.EntitySet<Game>("Games").EntityType;
games.ComplexProperty(y => y.Platform);
return builder.GetEdmModel();
}
No arquivo Startup.cs, registramos o OData logo no começo, seguido do Singleton para ODataUriResolver, definindo que teremos um tratamento Case Insensitive para URLs.
AddJsonOptions é necessário para que o JsonSerializer não altere o nome das propriedades de retorno e o mantenha em Upper Case.
No método Configure, é chamada a Action ConfigureODataRoutes, onde definimos a rota ODataRoute, com prefixo "odata" (o prefixo que será chamado na URL). Essa rota é baseada em um Model pré definido, sendo criado no método GetEdmModel. Tal método informa quais conjuntos de entidade (EntitySet) serão disponibilizados. Caso haja uma propriedade complexa (um subtipo agregado, como Platform, por exemplo), é necessário definir que ela também será retornada.
As outras definições (Filter, OrderBy, Count e Select) são informadas para que as respectivas operações sejam suportadas.
[EnableQuery(PageSize = 20)]
[Route("api/[controller]")]
public class GamesController : Controller
{
// GET odata/Games
[HttpGet]
public IQueryable<Game> Get()
{
return Games.AsQueryable();
}
// GET odata/Games(5)
[HttpGet]
public Game Get(int key)
{
return Games.SingleOrDefault(x => x.Id == key);
}
// POST odata/Games
[HttpPost]
public IActionResult Post([FromBody]Game value)
{
value.Id = Games.Max(g => g.Id) + 1;
value.Platform = Platforms.SingleOrDefault(p => p.Id == value.Platform.Id);
Games.Add(value);
return Created($"?key={value.Id}", value);
}
// PUT odata/Games(5)
[HttpPut]
public IActionResult Put(int key, [FromBody]Game value)
{
var existingGame = Games.SingleOrDefault(x => x.Id == key);
Games.Remove(existingGame);
value.Id = key;
value.Platform = Platforms.SingleOrDefault(p => p.Id == value.Platform.Id);
Games.Add(value);
return Ok(existingGame);
}
[HttpPatch]
public IActionResult Patch(int key, [FromBody]JsonPatchDocument<Game> valuePatch)
{
var existingGame = Games.SingleOrDefault(x => x.Id == key);
Games.Remove(existingGame);
valuePatch.ApplyTo(existingGame);
Games.Add(existingGame);
return Ok(existingGame);
}
// DELETE odata/Games(5)
[HttpDelete]
public IActionResult Delete(int key)
{
var existingGame = Games.SingleOrDefault(x => x.Id == key);
Games.Remove(existingGame);
return Ok(key);
}
private static Platform GetPlatform(int id)
{
return Platforms.SingleOrDefault(x => x.Id == id);
}
}
No arquivo GamesController.cs temos as actions que serão disponibilizadas pela API. Vale notar o atributo [EnableQuery], definindo o PageSize = 20. Isso permite que a paginação já venha habilitada por padrão.
Os métodos que utilizam Id (GET/Id, PUT e PATCH) possuem uma peculiaridade. É necessário substituir o nome de parâmetro 'id' (padrão do WebAPI) por 'key', caso contrário o OData não reconhece a Action.
http://localhost:53760/odata/Games
http://localhost:53760/odata/Games?$select=Name
http://localhost:53760/odata/Games/$count
http://localhost:53760/odata/Games?$filter=Name eq 'Persona 4 Golden'
http://localhost:53760/odata/Games?$filter=contains(Name, 'Need')
http://localhost:53760/odata/Games?$filter=Genre eq WebAPIOData.Genre'FPS'
Retornar apenas o nome de todos os Games em que o nome contenha a letra 'a', ordenando de maneira crescente pelo nome da plataforma
http://localhost:53760/odata/Games?$filter=contains(Name, 'a')&$select=Name&$orderby=Platform/Name asc
http://localhost:53760/odata/Games(2)
Para demais operações (POST, PUT, PATCH e DELETE), vide coleção do Postman