使用 ASP.NET Core OData 8.0 映射动态 OData 路由

问题描述 投票:0回答:3

我有一个应用程序,其中 EDM 数据类型是在应用程序运行时生成的(它们甚至可以在运行时更改)。松散地基于 OData DynamicEDMModelCreation Sample - 重构以使用新的端点路由。 EDM 模型在运行时动态生成,所有请求都转发到同一个控制器。

现在我想更新到最新的 ASP.NET Core OData 8.0 并且整个路由发生了变化,因此当前的解决方法不再起作用。

我已经阅读了更新的两篇博客文章Blog1Blog2,似乎我不能再使用“旧”解决方法了,因为端点内的函数MapODataRoute()现在已经消失了。似乎所有内置路由约定都不适合我的用例,因为所有内置路由约定都需要在调试时存在 EDM 模型。

也许我可以使用自定义的 IODataControllerActionConvention。我尝试通过将其添加到路由约定来激活该约定,但似乎我仍然缺少如何激活它的部分。

services.TryAddEnumerable(
    ServiceDescriptor.Transient<IODataControllerActionConvention, MyEntitySetRoutingConvention>());

这种方法有效吗?是否可以在新的 odata 预览中激活动态模型?或者有人知道如何为新的 odata 8.0 实现动态路由吗?

c# asp.net-core routes odata
3个回答
1
投票

这里有一个动态路由和动态模型的例子:

https://github.com/OData/AspNetCoreOData/blob/master/sample/ODataDynamicModel/ 请参阅

MyODataRoutingApplicationModelProvider
MyODataRoutingMatcherPolicy
,它们会将自定义 IEdmModel 传递给控制器。

HandleAllController
可以动态处理不同类型和EDM模型。


0
投票

经过 5 天的内部 OData 调试,我设法让它工作。以下是必要的步骤:

首先从控制器/配置服务中删除所有 OData 调用/属性,这些调用/属性可能会做一些时髦的事情(

ODataRoutingAttribute
AddOData()

创建一个简单的 asp.net 控制器,其中包含您喜欢的路由并将其映射到端点中

[ApiController]
[Route("odata/v{version}/{Path?}")]
public class HandleAllController : ControllerBase { ... }

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime applicationLifetime, ILoggerFactory loggerFactory)
{
  ...
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllers();
  }
}

创建并注册您的InputFormatWrapper和OutputFormatWrapper

public class ConfigureMvcOptionsFormatters : IConfigureOptions<MvcOptions> 
{
    private readonly IServiceProvider _services;
    public ConfigureMvcOptionsFormatters(IServiceProvider services)
    {
        _services = services;
    } 

    public void Configure(MvcOptions options)
    { 
        options.InputFormatters.Insert(0, new ODataInputFormatWrapper(_services));
        options.OutputFormatters.Insert(0, new OdataOutputFormatWrapper(_services));
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    services.ConfigureOptions<ConfigureMvcOptionsFormatters>();
    ...
}

public class ODataInputFormatWrapper : InputFormatter
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ODataInputFormatter _oDataInputFormatter;

    public ODataInputFormatWrapper(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
        //JSON and default is first - see factory comments 
        _oDataInputFormatter = ODataInputFormatterFactory.Create().First();
    }
    
    public override bool CanRead(InputFormatterContext context)
    {
        if (!ODataWrapperHelper.IsRequestValid(context.HttpContext, _serviceProvider))
            return false;

        return _oDataInputFormatter.CanRead(context);
    }

    public override Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        return _oDataInputFormatter!.ReadRequestBodyAsync(context);
    }
}

// The OutputFormatWrapper looks like the InputFormatWrapper

ODataWrapperHelper
中,您可以检查内容并获取/设置动态 edmModel。最后有必要设置这些
ODataFeature()
...这不太漂亮,但它完成了动态工作...

public static bool IsRequestValid(HttpContext context, IServiceProvider serviceProvider)
{
  //... Do stuff, get datasource
  var edmModel = dataSource!.GetModel();
  var oSegment = new EntitySetSegment(new EdmEntitySet(edmModel.EntityContainer, targetEntity, edmModel.SchemaElements.First(x => targetEntity == x.Name) as EdmEntityType));
   context.ODataFeature().Services = serviceProvider.CreateScope().ServiceProvider;
   context.ODataFeature().Model = edmModel;
   context.ODataFeature().Path = new ODataPath(oSegment);

   return true;
 }

现在是丑陋的事情:我们仍然需要在

ConfigureServices(IServiceCollection services)
中注册一些 ODataService。我在那里添加了一个名为
AddCustomODataService(services)
的函数,您可以自己注册大约 40 个服务,或者做一些时髦的反射...

所以也许如果 odata 团队的人读到这篇文章,请考虑打开

Microsoft.AspNetCore.OData.Abstracts.ContainerBuilderExtensions

我创建了一个

public class CustomODataServiceContainerBuilder : IContainerBuilder
这是内部
Microsoft.AspNetCore.OData.Abstracts.DefaultContainerBuilder
的副本,我在那里添加了功能:

public void AddServices(IServiceCollection services)
{
  foreach (var service in Services)
  {
    services.Add(service);
   }
}

还有丑陋的

AddCustomODataServices(IServiceCollection services)

private void AddCustomODataService(IServiceCollection services)
{
    var builder = new CustomODataServiceContainerBuilder();
    builder.AddDefaultODataServices();

    //AddDefaultWebApiServices in ContainerBuilderExtensions is internal...
    var addDefaultWebApiServices = typeof(ODataFeature).Assembly.GetTypes()
            .First(x => x.FullName == "Microsoft.AspNetCore.OData.Abstracts.ContainerBuilderExtensions")
            .GetMethods(BindingFlags.Static|BindingFlags.Public)
            .First(x => x.Name == "AddDefaultWebApiServices");

    addDefaultWebApiServices.Invoke(null, new object?[]{builder});
    builder.AddServices(services);
}

现在控制器应该再次工作(使用 odataQueryContext 和序列化) - 示例:

[HttpGet]
public Task<IActionResult> Get(CancellationToken cancellationToken)
{
   //... get model and entitytype
   var queryContext = new ODataQueryContext(model, entityType, null);
   var queryOptions = new ODataQueryOptions(queryContext, Request);

   return (Collection<IEdmEntityObject>)myCollection;
}

[HttpPost]
public Task<IActionResult> Post([FromBody] IEdmEntityObject entityDataObject, CancellationToken cancellationToken)
{
    //Do something with IEdmEntityObject
    return Ok()
}

0
投票

新的 MapODataRoute 已作为选项移至 .AddOData() 方法内。这是一个例子:

ODataModelBuilder odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<Example>("Example");
var odataModel = odataBuilder.GetEdmModel();

builder.Services.AddControllers().AddOData(config => {
    config.AddRouteComponents(odataModel);
});```
© www.soinside.com 2019 - 2024. All rights reserved.