Как правильно интегрировать OData с ASP.net Ядро

#c# #asp.net-core #odata #asp.net-web-api-odata #asp.net-core-webapi

#c# #asp.net-core #odata #asp.net-web-api-odata #asp.net-core-webapi

Вопрос:

Я пытаюсь создать новый ASP.NET Основной проект с «простым» веб-API, использующим OData и EntityFramework. Ранее я использовал OData со старыми версиями ASP.NET .

Я настроил контроллер только с простой функцией get. Мне удалось заставить его работать с базовыми командами OData, такими как filter и top, но я не могу заставить команду expand работать. Я думаю, это потому, что я не совсем понимаю, как настроить его в Startup.cs. Я много чего перепробовал, в том числе после некоторых примеров odata из 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 Основной веб — api с полным .NET 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. @l—«»»———«»»»»», Попробуйте использовать github.com/voronov-maxim/OdataToEntity ведьма имеет контейнер в качестве клиента, развернуть, выбрать и другие

Ответ №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') . Если он работает правильно, вы должны видеть только первые два объекта.

Комментарии:

1. подтверждено, что это работает с core 2.0, а также что происходит, когда у нас есть несколько объектов с отношениями между ними? сможем ли мы выполнить расширение и все эти операции?

2. Это решение позволяет вам лучше контролировать то, как вы предоставляете свои объекты через OData, но недостатком является то, что оно не генерирует маршруты автоматически. Так что нет, вы не сможете расширять дочерние объекты.

3. к сожалению, это не работает с SELECT / EXPAND

4. итак, вы бы выбрали вариант выше вместо этого?

5. Получена следующая ошибка при использовании core 2.2 и OData 7.1.0: не удается найти контейнер служб для маршрута, отличного от OData. Это может произойти при использовании компонентов OData на маршруте, отличном от OData, и обычно является проблемой конфигурации. Вызовите EnableDependencyInjection(), чтобы включить компоненты OData на маршрутах, отличных от OData. Это также может произойти, когда запрос был ошибочно обработан ASP.NET Базовый уровень маршрутизации вместо уровня маршрутизации OData, например, URL-адрес не включает префикс маршрута OData, настроенный с помощью вызова MapODataServiceRoute().

Ответ №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

Комментарии:

1. Это выглядит как интересный подход, который может помочь, если вы просто хотите применить OData в конкретных запросах.

2. Поддерживает 461, так что да.

Ответ №3:

У меня есть репозиторий github, который автоматически генерирует ASP.NET Основные контроллеры OData v4 из модели code first EF, использующие T4. Он использует Microsoft.AspNetCore.OData.vNext 6.0.2-alpha-rtm. Может представлять интерес.

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

Комментарии:

1. Я много часов пытался заставить это работать. Но по какой-то причине я не мог преобразовать его, и я получал какую-то очень странную ошибку, которую я не смог найти в Google. У вас случайно не найдется нескольких минут, чтобы помочь?

Ответ №4:

Похоже, что в настоящее время это находится в альфа-версии с командой OData. в соответствии с этой проблемой

Ответ №5:

Со стороны сервера WEB API:

Самый простой способ использования — это прямой атрибут [EnableQuery] . Теперь, с недавним 7.x pacakge, он отлично работает.

Вы также можете легко использовать общий язык, как показано ниже. идея заключается в том, чтобы иметь общий метод и устранять неоднозначность на основе требуемого имени объекта. С Linq2RestANC для использования на стороне клиента вы также можете легко передавать свои пользовательские параметры запроса. Как и в примере ниже, если у вас есть 2 таблицы Movies1 и Movies2, то запросы будут применяться непосредственно только к вашей БД, когда вы выполняете $expand и условия подфильтрации / подпроцесса внутри них.

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

Для использования на стороне клиента-
—> Не используйте прокси-сервер службы ODATA. Он глючит и выдает много ошибок.
—> Просто.OData.Клиент хороший. Но отстает поддержка вложенных запросов в expand.
например. /Products?$expand=Поставщики($select=SupplierName;$top=1;)
Для такого внутреннего расширения он не поддерживает дальнейшую фильтрацию. Это отслеживается как ошибка # 200

—> Linq2RestANC — прекрасный выбор. Это тоже изначально не поддерживает вложенные расширения, но оно реализовано путем наследования встроенного IQueryProvider, поэтому для изменения и тестирования завершенных сценариев расширения вложенного уровня потребовалось всего 3-4 часа. Вам нужно будет немного изменить в Expressionprocessor.cs «Развернуть» и ParameterBuilder.cs GetFullUri(), чтобы заставить его работать.

Ответ №6:

Вам нужно наследовать контроллер от ODataController