tag route parameter net data asp all c# asp.net-core odata asp.net-web-api-odata asp.net-core-webapi

c# - route - odata net core 2



Cómo integrar correctamente OData con ASP.net Core (6)

Estoy tratando de crear un nuevo proyecto ASP.NET Core con una api web "simple" usando OData y EntityFramework. Anteriormente he usado OData con versiones anteriores de ASP.NET.

He configurado un controlador con solo una simple función de obtención. Me las arreglé para hacerlo funcionar con comandos OData básicos como filtro y superior, pero no puedo hacer que el comando expandir funcione. Creo que es porque no puedo entender cómo configurarlo en Startup.cs. He intentado muchas cosas, incluyendo las siguientes muestras de odata de Github:

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

En mi archivo de inicio, trato de excluir algunas propiedades de la clase de servicio que no tiene ningún efecto. Entonces el problema puede estar en la forma en que estoy usando la interfaz IDataService. (El ApplicationContext lo implementa como en las muestras)

Para que quede claro, estoy creando una API web de ASP.NET Core con el completo .NET Framework y no solo .Core Framework. Mi código actual es una mezcla de lo mejor / lo peor de ambas muestras y funciona en el sentido de que puedo filtrar la WebAPI pero no puedo expandir u ocultar las propiedades.

¿Alguien puede ver lo que me falta og tener una muestra de Odata de ASP.NET que funcione? Soy nuevo en toda la configuración en startup.cs? Supongo que estoy buscando a alguien que haya hecho este trabajo.

Controlador

[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; } }

puesta en marcha

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 dependencias

"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"


Desde el lado del servidor WEB API:

La forma más sencilla de usar es el atributo directo [EnableQuery]. Ahora con el reciente 7.x pacakge, está funcionando muy bien.

También puede tener fácilmente implementos genéricos, como a continuación. La idea es tener un método común y eliminar la ambigüedad en función del nombre de entidad que requiera. Con Linq2RestANC para el consumo del lado del cliente, también puede pasar fácilmente sus parámetros de consulta personalizados. Como en el ejemplo a continuación, si tiene 2 tablas Movies1 y Movies2, entonces las consultas se aplicarán directamente solo en su base de datos, cuando realice una expansión de $ y condiciones de sub-filtro / sub-proceso dentro de ellas.

[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)}); }

Para el consumo del lado del cliente - - No usar proxy de servicio ODATA. Es un buggy y arroja muchos errores. -> Simple.OData.Cliente es bueno. Pero se retrasa el soporte para consultas anidadas dentro de expand. por ej. / ¿Productos? $ Expand = Proveedores ($ select = SupplierName; $ top = 1;) Para dicha expansión interna no es compatible con el filtrado adicional. Eso es rastreado como error # 200

-> Linq2RestANC es una hermosa opción. Eso también de forma nativa no es compatible con las expansiones anidadas, pero se implementa al heredar el IQueryProvider nativo, por lo que solo tomó 3-4 horas modificar y probar los escenarios de expansión de niveles anidados profundos. Tendrá que cambiar un poco en Expressionprocessor.cs "Expandir" y ParameterBuilder.cs GetFullUri () un poco para que funcione.


Es necesario heredar el controlador desde el controlador OData


Logré que funcionara, pero no usé el enrutamiento OData provisto porque necesitaba más granularidad. Con esta solución, puede crear su propia API web, a la vez que permite el uso de parámetros de consulta OData.

Notas:

  • Usé el paquete Nuget Microsoft.AspNetCore.OData.vNext , versión 6.0.2-alpha-rtm , que requiere .NET 4.6.1
  • Como puedo decir, OData vNext solo soporta OData v4 (entonces no v3)
  • OData vNext parece haber sido apresurado, y está lleno de errores. Por ejemplo, el parámetro $orderby query está roto

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?}"); }); } } }

Controlador.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>(); } } }

Como probar

Puede usar el siguiente URI: /api/Entity/all?$filter=contains(SomeText,''Test'') . Si funciona correctamente, solo debería ver las dos primeras entidades.



También obtuve Microsoft.AspNetCore.OData.vNext , version 6.0.2-alpha-rtm para funcionar, pero utilicé el siguiente código para asignar el modelo de Edm a las rutas:

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

junto con services.AddOData()

Es extraño pero parece funcionar con .Net Core 1.1


Tengo un repositorio de github que genera automáticamente los controladores ASP.NET Core OData v4 a partir de un código de primer modelo EF, utilizando T4. Utiliza Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm. Podría ser de interés.

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