Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly integrate OData with ASP.net Core

I'm trying to create a new ASP.NET Core project with a "simple" web api using OData and EntityFramework. I have previously used OData with older versions of ASP.NET.

I have set up a controller with only a simple get function. I've managed to get it working with basic OData commands as filter and top, but I can't get the expand command working. I think it's because I can't quite figure out how to set it up in Startup.cs. I have tried a lot of things including following some odata samples from Github:

https://github.com/OData/WebApi/tree/vNext/vNext/samples/ODataSample.Web https://github.com/bigfont/WebApi/tree/master/vNext/samples/ODataSample.Web

In my startup file I try to exclude some properties from the Service class which has no effect at all. So the problem may lie in the way I'm using the IDataService interface. (The ApplicationContext implements it like in the samples)

To be clear I'm creating a ASP.NET Core web api with the full .NET Framework and not only .Core framework. My current code is a mix of the best/worst of both samples and work in the sense that I can filter the WebAPI but can't get it to expand or hide properties.

Can anyone see what I'm missing og have a working ASP.NET Odata sample. I'm new to the whole setup in startup.cs? Guess I'm looking for someone who have made this work.

Controller

[EnableQuery] [Route("odata/Services")] public class ServicesController : Controller {     private IGenericRepository<Service> _serviceRepo;     private IUnitOfWork _unitOfWork;      public ServicesController(IGenericRepository<Service> serviceRepo, IUnitOfWork unitOfWork)     {         _serviceRepo = serviceRepo;         _unitOfWork = unitOfWork;     }      [HttpGet]     public IQueryable<Service> Get()     {         var services = _serviceRepo.AsQueryable();         return services;     } } 

startup

using Core.DomainModel; using Core.DomainServices; using Infrastructure.DataAccess; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.AspNetCore.OData.Extensions;  namespace Web { public class Startup {     public Startup(IHostingEnvironment env)     {         var builder = new ConfigurationBuilder()             .SetBasePath(env.ContentRootPath)             .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)             .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)             .AddEnvironmentVariables();          if (env.IsDevelopment())         {             // This will push telemetry data through Application Insights pipeline faster, allowing you to view results immediately.             builder.AddApplicationInsightsSettings(developerMode: true);         }         Configuration = builder.Build();     }      public IConfigurationRoot Configuration { get; }      // This method gets called by the runtime. Use this method to add services to the container.     public void ConfigureServices(IServiceCollection services)     {         // Add framework services.         services.AddApplicationInsightsTelemetry(Configuration);         services.AddMvc().AddWebApiConventions();          services.AddSingleton<ApplicationContext>(_ => ApplicationContext.Create());          services.AddSingleton<IDataService, ApplicationContext>();          services.AddOData<IDataService>(builder =>         {             //builder.EnableLowerCamelCase();             var service = builder.EntitySet<Service>("Services");             service.EntityType.RemoveProperty(x => x.CategoryId);             service.EntityType.RemoveProperty(x => x.PreRequisiteses);         });           services.AddSingleton<IGenericRepository<Service>, GenericRepository<Service>>();         services.AddSingleton<IUnitOfWork, UnitOfWork>();     }      // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.     public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)     {         loggerFactory.AddConsole(Configuration.GetSection("Logging"));         loggerFactory.AddDebug();          //ODataConventionModelBuilder builder = new ODataConventionModelBuilder();          app.UseApplicationInsightsRequestTelemetry();          //var builder = new ODataConventionModelBuilder(app.ApplicationServices.GetRequiredService<AssembliesResolver>());         //var serviceCtrl = nameof(ServicesController).Replace("Controller", string.Empty);         //var service = builder.EntitySet<Service>(serviceCtrl);         //service.EntityType.RemoveProperty(x => x.CategoryId);          app.UseOData("odata");          if (env.IsDevelopment())         {             app.UseDeveloperExceptionPage();             app.UseBrowserLink();         }         else         {             app.UseExceptionHandler("/Home/Error");         }          app.UseApplicationInsightsExceptionTelemetry();          app.UseStaticFiles();          app.UseMvc(routes =>         {             routes.MapRoute(                 name: "default",                 template: "{controller=Home}/{action=Index}/{id?}");         });     } } 

Project.json dependencies

  "dependencies": {     "Microsoft.ApplicationInsights.AspNetCore": "1.0.2",     "Microsoft.AspNet.Identity.EntityFramework": "2.2.1",     "Microsoft.AspNetCore.Diagnostics": "1.0.0",     "Microsoft.AspNetCore.Identity": "1.0.0",     "Microsoft.AspNetCore.Mvc": "1.0.1",     "Microsoft.AspNetCore.Razor.Tools": {       "version": "1.0.0-preview2-final",       "type": "build"     },     "Microsoft.AspNetCore.Routing": "1.0.1",     "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",     "Microsoft.AspNetCore.Server.Kestrel": "1.0.1",     "Microsoft.AspNetCore.StaticFiles": "1.0.0",     "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",     "Microsoft.Extensions.Configuration.Json": "1.0.0",     "Microsoft.Extensions.Logging": "1.0.0",     "Microsoft.Extensions.Logging.Console": "1.0.0",     "Microsoft.Extensions.Logging.Debug": "1.0.0",     "Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",     "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0",     "Microsoft.AspNetCore.OData": "1.0.0-rtm-00015",     "dnx-clr-win-x86": "1.0.0-rc1-update2",     "Microsoft.OData.Core": "7.0.0",     "Microsoft.OData.Edm": "7.0.0",     "Microsoft.Spatial": "7.0.0" } 
like image 870
Sli Avatar asked Oct 05 '16 10:10

Sli


People also ask

Does OData require Entity Framework?

For this tutorial, we'll use Entity Framework (EF) Code First to create the back-end database. Web API OData does not require EF. Use any data-access layer that can translate database entities into models.

What is OData in .NET core?

OData stands for Open Data Protocol that helps to build and consuming of RESTFul APIs. It is an ISO/IEC approved and OASIS standard. OData will take care of various approaches about RESTful API like Status codes, URL conventions, request and response headers, media types, query options, payload formats, etc.

What is EnableQuery?

The [EnableQuery] attribute is an action filter that parses, validates, and applies the query. The filter converts the query options into a LINQ expression. When the controller returns an System. Linq. IQueryable type, the IQueryable LINQ provider converts the LINQ expression into a query.


1 Answers

I managed to make it work, but I didn't use the provided OData routing because I needed more granularity. With this solution, you can create your own web API, while still allowing the use of OData query parameters.

Notes:

  • I used Nuget package Microsoft.AspNetCore.OData.vNext, version 6.0.2-alpha-rtm, which requires .NET 4.6.1
  • As fas as I can tell, OData vNext only support OData v4 (so no v3)
  • OData vNext seems to have been rushed, and is packed with bugs. For example, the $orderby query parameter is broken

MyEntity.cs

namespace WebApplication1 {     public class MyEntity     {         // you'll need a key          public int EntityID { get; set; }         public string SomeText { get; set; }     } } 

Startup.cs

using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.OData.Abstracts; using Microsoft.AspNetCore.OData.Builder; using Microsoft.AspNetCore.OData.Extensions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System;  namespace WebApplication1 {     public class Startup     {         public Startup(IHostingEnvironment env)         {             var builder = new ConfigurationBuilder()                 .SetBasePath(env.ContentRootPath)                 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)                 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)                 .AddEnvironmentVariables();             Configuration = builder.Build();         }          public IConfigurationRoot Configuration { get; }          public void ConfigureServices(IServiceCollection services)         {             services.AddMvc();              /* ODATA part */             services.AddOData();             // the line below is used so that we the EdmModel is computed only once             // we're not using the ODataOptions.ModelManager because it doesn't seemed plugged in             services.AddSingleton<IODataModelManger, ODataModelManager>(DefineEdmModel);         }          private static ODataModelManager DefineEdmModel(IServiceProvider services)         {             var modelManager = new ODataModelManager();              // you can add all the entities you need             var builder = new ODataConventionModelBuilder();             builder.EntitySet<MyEntity>(nameof(MyEntity));             builder.EntityType<MyEntity>().HasKey(ai => ai.EntityID); // the call to HasKey is mandatory             modelManager.AddModel(nameof(WebApplication1), builder.GetEdmModel());              return modelManager;         }          public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)         {             loggerFactory.AddConsole(Configuration.GetSection("Logging"));             loggerFactory.AddDebug();              if (env.IsDevelopment())             {                 app.UseDeveloperExceptionPage();                 app.UseBrowserLink();             }             else             {                 app.UseExceptionHandler("/Home/Error");             }              app.UseStaticFiles();              app.UseMvc(routes =>             {                 routes.MapRoute(                     name: "default",                     template: "{controller=Home}/{action=Index}/{id?}");             });         }     } } 

Controller.cs

using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.OData; using Microsoft.AspNetCore.OData.Abstracts; using Microsoft.AspNetCore.OData.Query; using System.Linq;  namespace WebApplication1.Controllers {     [Produces("application/json")]     [Route("api/Entity")]     public class ApiController : Controller     {         // note how you can use whatever endpoint         [HttpGet("all")]         public IQueryable<MyEntity> Get()         {             // plug your entities source (database or whatever)             var entities = new[] {                 new MyEntity{ EntityID = 1, SomeText = "Test 1" },                 new MyEntity{ EntityID = 2, SomeText = "Test 2" },                 new MyEntity{ EntityID = 3, SomeText = "Another texts" },             }.AsQueryable();              var modelManager = (IODataModelManger)HttpContext.RequestServices.GetService(typeof(IODataModelManger));             var model = modelManager.GetModel(nameof(WebApplication1));             var queryContext = new ODataQueryContext(model, typeof(MyEntity), null);             var queryOptions = new ODataQueryOptions(queryContext, HttpContext.Request);              return queryOptions                 .ApplyTo(entities, new ODataQuerySettings                 {                     HandleNullPropagation = HandleNullPropagationOption.True                 })                 .Cast<MyEntity>();         }     } } 

How to test

You can use the following URI : /api/Entity/all?$filter=contains(SomeText,'Test'). If it works correctly, you should only see the first two entities.

like image 143
Métoule Avatar answered Oct 06 '22 06:10

Métoule