Как правильно интегрировать OData с ASP.net Core
Я пытаюсь создать новый проект ASP.NET Core с помощью "простой" веб-api, используя OData и EntityFramework. Ранее я использовал OData со старыми версиями ASP.NET.
Я установил контроллер только с простой функцией get. Мне удалось заставить его работать с основными командами OData как с фильтром, так и сверху, но я не могу заставить команду expand работать. Я думаю, потому что я не могу понять, как настроить его в Startup.cs. Я пробовал много вещей, включая следующие образцы одатов из Github:
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 реализует его, как в примерах)
Чтобы быть ясным, я создаю ASP.NET Core web api с полной платформой .NET Framework и не только .Core framework. Мой текущий код представляет собой сочетание лучшего/худшего из обоих образцов и работает в том смысле, что я могу фильтровать WebAPI, но не могу заставить его расширять или скрывать свойства.
Может ли кто-нибудь увидеть, что у меня отсутствует, есть рабочий пример ASP.NET Odata. Я новичок в настройке в startup.cs? Думаю, я ищу того, кто сделал эту работу.
контроллер
[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;
}
}
Запуск
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": {
"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"
Ответы
Ответ 1
Мне удалось заставить его работать, но я не использовал предоставленную маршрутизацию OData, потому что мне нужна была более гранулированность. С помощью этого решения вы можете создать свой собственный веб-API, сохраняя при этом возможность использовать параметры запроса OData.
Примечания:
- Я использовал пакет Nuget
Microsoft.AspNetCore.OData.vNext
, версию 6.0.2-alpha-rtm
, для которого требуется .NET 4.6.1
- Как мне кажется, OData vNext поддерживает только OData v4 (так что нет v3)
- OData vNext, похоже, бросился и упакован с ошибками. Например, параметр запроса
$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<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>();
}
}
}
Как проверить
Вы можете использовать следующий URI: /api/Entity/all?$filter=contains(SomeText,'Test')
. Если он работает правильно, вы должны видеть только первые два объекта.
Ответ 2
Я также получил Microsoft.AspNetCore.OData.vNext
, version 6.0.2-alpha-rtm
, но я использовал следующий код для сопоставления модели Edm
с маршрутами:
services.AddOData();
// ...
app.UseMvc(routes =>
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");
IEdmModel model = modelBuilder.GetEdmModel();
routes.MapODataRoute(
prefix: "odata",
model: model
);
вместе с services.AddOData()
Это странно, но, похоже, работает с .Net Core 1.1
Ответ 3
У меня есть github repo, который автоматически генерирует контроллеры ASP.NET Core OData v4 из первой модели EF кода, используя T4. Он использует Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm. Может представлять интерес.
https://github.com/afgbeveridge/AutoODataEF.Core
Ответ 4
Похоже, что в настоящее время это альфа с командой OData.
в соответствии с этой проблемой
Ответ 5
С WEB API-сервера:
Самый простой способ использования - это прямой атрибут [EnableQuery]. Теперь с недавним 7.x pacakge, он отлично работает.
Вы также можете легко получить общий impl., Как показано ниже. идея имеет общий метод и устраняет неоднозначность на основе требуемого имени сущности. С Linq2RestANC для потребления на стороне клиента вы также можете легко передать свои пользовательские параметры запроса. Как показано в примере ниже, если у вас есть 2 таблицы Movies1 и Movies2, то запросы будут применяться непосредственно только к вашему db, когда вы выполняете условия $ expand и sub-filter/sub-process внутри них.
[EnableQuery]
public IActionResult Get([FromQuery] string name)
{
switch (name)
{
case "Movie2":
return Ok(new List<ViewModel>{new ViewModel(Movies2=_db.Movies2)});
}
return Ok(new List<ViewModel>{new ViewModel(Movies1=_db.Movies1)});
}
Для клиентской стороны consumption- → Не используйте ODATA Service proxy. Он глючит и бросает много ошибок. → Simple.OData.Client - это хорошо. Но отстает от поддержки вложенных запросов в пределах расширения. например./Продукты? $ Expand = Поставщики ($ select = SupplierName; $ top = 1;) Для такого внутреннего расширения он не поддерживает дальнейшую фильтрацию. Это отслеживается как ошибка № 200
→ Linq2RestANC - прекрасный выбор. Это тоже изначально не поддерживает вложенные расширения, но реализовано путем наследования собственного IQueryProvider, поэтому потребовалось 3-4 часа, чтобы изменить и протестировать завершенные сценарии расширения вложенного уровня. Вам нужно будет изменить в Expressionprocessor.cs "Развернуть" и ParameterBuilder.cs GetFullUri() немного, чтобы заставить его работать.
Ответ 6
Вам нужно наследовать контроллер из ODataController