web-dev-qa-db-fra.com

Comment intégrer correctement OData à ASP.net Core

J'essaie de créer un nouveau projet ASP.NET Core avec une "simple" API Web utilisant OData et EntityFramework. J'ai déjà utilisé OData avec les anciennes versions d'ASP.NET.

J'ai mis en place un contrôleur avec seulement une simple fonction get. J'ai réussi à le faire fonctionner avec des commandes OData de base comme filtre et top, mais je ne parviens pas à faire fonctionner la commande expand. Je pense que c'est parce que je n'arrive pas à comprendre comment le configurer dans Startup.cs. J'ai essayé beaucoup de choses, notamment en suivant des échantillons d'odata de Github:

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

Dans mon fichier de démarrage, j'essaie d'exclure certaines propriétés de la classe Service qui n'a aucun effet. Donc, le problème réside peut-être dans la manière dont j'utilise l'interface IDataService. (ApplicationContext l'implémente comme dans les exemples)

Pour être clair, je crée une api web ASP.NET Core avec l'intégralité du .NET Framework et pas seulement le framework .Core. Mon code actuel est un mélange du meilleur/du pire des deux exemples et fonctionne dans le sens où je peux filtrer WebAPI mais ne pas le faire développer ou masquer les propriétés.

Quelqu'un peut-il voir ce qui me manque? Avez-vous un exemple ASP.NET Odata en état de marche? Je suis nouveau dans toute la configuration de startup.cs? Je suppose que je cherche quelqu'un qui a fait ce travail.

Manette

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

Commencez

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

Dépendances 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"
}
25
Sli

J'ai aussi obtenu Microsoft.AspNetCore.OData.vNext, version 6.0.2-alpha-rtm au travail, mais j'ai utilisé le code suivant pour mapper le modèle Edm aux routes:

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

avec services.AddOData()

C'est étrange mais cela semble fonctionner avec .Net Core 1.1

6
Alex Buchatski

J'ai réussi à le faire fonctionner, mais je n'ai pas utilisé le routage OData fourni, car j'avais besoin de plus de précision. Avec cette solution, vous pouvez créer votre propre API Web, tout en permettant l'utilisation de paramètres de requête OData.

Remarques: 

  • J'ai utilisé le package Nuget Microsoft.AspNetCore.OData.vNext, version 6.0.2-alpha-rtm, qui nécessite .NET 4.6.1 
  • Autant que je sache, OData vNext ne supporte que OData v4 (donc pas de v3)
  • OData vNext semble avoir été pressé et regorge de bogues. Par exemple, le paramètre de requête $orderby est cassé

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

Comment tester

Vous pouvez utiliser l'URI suivant: /api/Entity/all?$filter=contains(SomeText,'Test'). Si cela fonctionne correctement, vous ne devriez voir que les deux premières entités.

6
Métoule

J'ai un référentiel github qui génère automatiquement des contrôleurs ASP.NET Core OData v4 à partir d'un premier modèle de code EF, en utilisant T4. Il utilise Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm. Pourrait être d'intérêt.

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

2
Aldous Zodiac

Du côté du serveur API Web: 

Le moyen le plus simple à utiliser est l'attribut direct [EnableQuery]. Maintenant, avec la récente version 7.x pacakge, cela fonctionne très bien.

Vous pouvez également facilement avoir impl générique, comme ci-dessous. L’idée est d’avoir une méthode commune et de ne pas être ambiguë en fonction du nom d’entité dont vous avez besoin. Avec Linq2RestANC pour la consommation côté client, vous pouvez également transmettre vos paramètres de requête personnalisés. Comme dans l'exemple ci-dessous, si vous avez 2 tables Movies1 et Movies2, les requêtes seront appliquées directement à votre base de données uniquement, lorsque vous réalisez une condition $ expand et un sous-filtre/sous-processus en leur sein.

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

Pour la consommation côté client- --> N'utilisez pas le proxy de service ODATA. Il est bogué et génère beaucoup d'erreurs .--> Simple.OData.Client est bon. Mais retarde la prise en charge des requêtes imbriquées dans expand ..__, par exemple. /Products?$expand=Suppliers($select=SupplierName;$top=1;)Pour une telle extension interne, il ne prend plus en charge le filtrage. Cela est suivi comme bug n ° 200

-> Linq2RestANC est un beau choix. De manière trop native, il ne prend pas en charge les développements imbriqués, mais il est implémenté en héritant de IQueryProvider natif. Il a donc fallu 3 à 4 heures pour modifier et tester les scénarios de développement développés au niveau imbriqué et profond. Vous aurez besoin de changer un peu à Expressionprocessor.cs "Développez" Et ParameterBuilder.cs GetFullUri () pour le faire fonctionner.

0
user3585952

On dirait que cela est actuellement en alpha avec l’équipe OData . selon ce numéro

0
bigtlb