如何正确地将OData与ASP.net Core集成

我正在尝试使用OData和EntityFramework创建一个带有“简单”web api的新ASP.NET Core项目。 我之前使用OData与旧版本的ASP.NET。

我已经设置了一个只有一个简单的get函数的控制器。 我已经设法使用基本的OData命令作为filter和顶部,但我无法使扩展命令工作。 我想这是因为我无法弄清楚如何在Startup.cs中设置它。 我尝试了很多东西,包括跟随Github的一些odata样本:

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

在我的启动文件中,我尝试从Service类中排除一些根本没有效果的属性。 所以问题可能在于我使用IDataService接口的方式。 (ApplicationContext像样本一样实现它)

要清楚我正在使用完整的.NET Framework创建一个ASP.NET Core web api,而不仅仅是.Core框架。 我当前的代码是两个样本中最好/最差的混合,并且我可以过滤WebAPI,但不能让它扩展或隐藏属性。

任何人都可以看到我缺少的东西og有一个工作的ASP.NET Odata样本。 我是startup.cs中整个设置的新手? 猜猜我正在找一个做过这项工作的人。

调节器

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

启动

 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.Create()); services.AddSingleton(); services.AddOData(builder => { //builder.EnableLowerCamelCase(); var service = builder.EntitySet("Services"); service.EntityType.RemoveProperty(x => x.CategoryId); service.EntityType.RemoveProperty(x => x.PreRequisiteses); }); services.AddSingleton<IGenericRepository, GenericRepository>(); services.AddSingleton(); } // 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()); //var serviceCtrl = nameof(ServicesController).Replace("Controller", string.Empty); //var service = builder.EntitySet(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": { "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" 

我设法让它工作,但我没有使用提供的OData路由,因为我需要更多的粒度。 使用此解决方案,您可以创建自己的Web API,同时仍允许使用OData查询参数。

笔记:

  • 我使用Nuget包Microsoft.AspNetCore.OData.vNext ,版本6.0.2-alpha-rtm ,它需要.NET 4.6.1
  • 正如我所知,OData vNext仅支持OData v4(所以没有v3)
  • OData vNext似乎已经匆忙,并且充满了bug。 例如, $orderby查询参数已中断

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(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(nameof(MyEntity)); builder.EntityType().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 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(); } } } 

怎么测试

您可以使用以下URI: /api/Entity/all?$filter=contains(SomeText,'Test') 。 如果它正常工作,您应该只看到前两个实体。

我也使用了Microsoft.AspNetCore.OData.vNextversion 6.0.2-alpha-rtm ,但我使用以下代码将Edm模型映射到路由:

 services.AddOData(); // ... app.UseMvc(routes => { ODataModelBuilder modelBuilder = new ODataConventionModelBuilder(); modelBuilder.EntitySet("Products"); IEdmModel model = modelBuilder.GetEdmModel(); routes.MapODataRoute( prefix: "odata", model: model ); 

以及services.AddOData()

这很奇怪,但它似乎与.Net Core 1.1一起使用

我有一个github repo,它使用T4从代码第一个EF模型自动生成ASP.NET Core OData v4控制器。 它使用Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm。 可能有兴趣。

https://github.com/afgbeveridge/AutoODataEF.Core

看起来这与OData团队目前处于alpha状态。 根据这个问题

从WEB API服务器端:

最简单的使用方法是直接[EnableQuery]属性。 现在有了最近的7.x pacakge,它运作良好。

您也可以轻松拥有通用impl。,如下所示。 想法有一个共同的方法,并根据您需要的实体名称消除歧义。 使用Linq2RestANC进行客户端消费,您也可以轻松传递自定义查询参数。 如下例所示,如果您有2个表Movies1和Movies2,那么当您在其中执行$ expand和子filter/子过程条件时,查询将仅直接应用于您的数据库。

 [EnableQuery] public IActionResult Get([FromQuery] string name) { switch (name) { case "Movie2": return Ok(new List{new ViewModel(Movies2=_db.Movies2)}); } return Ok(new List{new ViewModel(Movies1=_db.Movies1)}); } 

对于客户端消费 – >>不要使用ODATA服务代理。 这是错误的,并抛出了很多错误。 – > Simple.OData.Client很好。 但是在扩展中滞后支持嵌套查询。 例如。 / Products?$ expand =供应商($ select = SupplierName; $ top = 1;)对于这种内部扩展,它不支持进一步过滤。 这被追踪为错误#200

– > Linq2RestANC是一个很好的选择。 这本身也不支持嵌套扩展,但它是通过inheritance本机IQueryProvider来实现的,因此修改和测试已完成的嵌套深层扩展方案只花了3-4个小时。 您需要更改Expressionprocessor.cs“Expand”和ParameterBuilder.cs GetFullUri()以使其正常工作。

您需要从ODataControllerinheritance控制器