【问题标题】:Mapping dynamic odata routes with ASP.NET Core OData 8.0使用 ASP.NET Core OData 8.0 映射动态 OData 路由
【发布时间】:2022-01-12 16:17:50
【问题描述】:

我有一个应用程序,其中 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


    【解决方案1】:

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

    https://github.com/OData/AspNetCoreOData/blob/master/sample/ODataDynamicModel/ 请参阅 MyODataRoutingApplicationModelProviderMyODataRoutingMatcherPolicy,它们会将自定义 IEdmModel 传递给控制器​​。

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

    【讨论】:

    • 如果我之前看到过该解决方案。我更喜欢我的解决方案,因为它只使用 OData(反序列化/验证)所需的部分,而不是使用路由,所以我会坚持使用我的。但是我会将您的解决方案标记为答案,因为它或多或少是官方的,并且它使用与我的问题中提出的相同方法 - 所以感谢链接
    【解决方案2】:

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

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

    使用您喜欢的路由创建一个简单的 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()
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多