You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于动态EDM创建不破坏属性路由的OData端点问题求助

问题:动态生成EDM模型后OData端点无法正常工作

我在C# Web API中尝试创建一组OData端点,不想通过硬编码C#类生成EDM模型,而是让管理员在API启动前通过appSettings.json定义「自定义视图」,运行时动态生成模型。appSettings.json里custom_views的键作为数据模型名,对应端点URL为{url}/Odata/模型名,fields定义模型属性,key指定键属性。

目前已实现动态生成EDM,访问/Odata/$metadata能得到正确的元数据,但配置服务后,控制器使用[Route("odata/{view_name}")]路由时,控制台出现以下警告:

The path template 'odata/{view_name}' on the action 'Get' in controller 'CustomView' is not a valid OData path template. Resource not found for the segment '{view_name}'.

同时访问{url}/OData/imdb_view时返回普通JSON,缺少@Odata.Context等OData格式标识,端点表现为普通REST端点而非OData端点。


相关代码

Startup.cs 配置服务代码

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(
        options =>
        {
            options.EnableEndpointRouting = false;
        })
        .AddOData(options =>
    {
        options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(null);
        options.AddRouteComponents("odata", GetEdmModel());
    });
}

GetEdmModel() 代码

private IEdmModel GetEdmModel()
{
    var customViews = Configuration.GetSection("custom_views").GetChildren()
        .Select(x => new DynamicEntityType
        {
            Name = x.Key,
            Key = x.GetValue<string>("key"),
            Fields = new Dictionary<string, string>()
        })
        .ToList();

    var builder = new ODataConventionModelBuilder();

    foreach (var customView in customViews)
    {
        Configuration.GetSection($"custom_views:{customView.Name}:fields").Bind(customView.Fields);
        var dynamicType = DynamicClassGenerator.CreateDynamicClass(customView.Name, customView.Fields);

        var entityType = builder.AddEntityType(dynamicType);

        PropertyInfo key = dynamicType.GetProperty(customView.Key);

        if (key is null)
        {
            throw new ArgumentException($"Key specified for {customView.Name} does not exist in the provided fields");
        }

        entityType.HasKey(key);

        builder.AddEntitySet(customView.Name, entityType);

    }

    return builder.GetEdmModel();
}

控制器代码

namespace InsightMaker.Middleware.PowerBIConnector.Controllers
{
    [Route("odata/{view_name}")]
    public class CustomViewController : ODataController
    {

        private readonly IConfiguration _configuration;
        private readonly ElasticContext _elasticContext;
        public CustomViewController(IConfiguration configuration, ElasticContext elasticContext)
        {
            _configuration = configuration;
            _elasticContext = elasticContext;
        }

        private DynamicEntityType GetViewInfo(string view_name)
        {
            var customViews = _configuration.GetSection("custom_views").GetChildren()
                .Select(x => new DynamicEntityType
                {
                    Name = x.Key,
                    Key = x.GetValue<string>("key"),
                    Sources = x.GetSection("sources").Get<string[]>(),
                    Fields = new Dictionary<string, string>()
                })
                .ToList();
            var activeView = customViews.FirstOrDefault(obj => obj.Name == view_name);
            if (activeView == null) { return null; }

            _configuration.GetSection($"custom_views:{activeView.Name}:fields").Bind(activeView.Fields);

            return activeView;
        }

        [EnableQuery]
        public ActionResult<IEnumerable<dynamic>> Get(string view_name)
        {
            var activeView = GetViewInfo(view_name);
            if (activeView == null) { return NotFound(); }

            var dynamicType = DynamicClassGenerator.CreateDynamicClass(activeView.Name, activeView.Fields);
            var userName = _configuration.GetValue<string>("loggedInUser");

            var resp = DataMappingService.MapResponseData(dynamicType, _elasticContext, activeView, userName);

            return Ok(resp);
        }
    }
}

解决方案

1. 移除控制器上的自定义Route属性

OData拥有独立的路由系统,会根据EDM模型自动生成匹配的路由规则。自定义的[Route("odata/{view_name}")]会干扰OData的路由识别逻辑,导致端点无法被判定为OData端点。直接删除该属性即可。

2. 修改Action获取实体集名称的方式

不需要手动通过路由参数传递view_name,可以从OData请求上下文中直接获取当前访问的实体集名称:

[EnableQuery]
public ActionResult<IEnumerable<dynamic>> Get()
{
    // 从OData请求上下文提取实体集名称
    var entitySetName = Request.ODataProperties().Path.EntitySet.Name;
    var activeView = GetViewInfo(entitySetName);
    
    if (activeView == null) 
    { 
        return NotFound(); 
    }

    var dynamicType = DynamicClassGenerator.CreateDynamicClass(activeView.Name, activeView.Fields);
    var userName = _configuration.GetValue<string>("loggedInUser");

    var resp = DataMappingService.MapResponseData(dynamicType, _elasticContext, activeView, userName);

    return Ok(resp);
}

3. 确保返回类型被OData正确序列化

由于返回的是dynamic类型,OData序列化器可能无法自动识别为EDM实体对象,导致响应缺少OData格式标识。可以将动态对象转换为IEdmEntityObject类型,确保序列化器能正确处理:

// 在Get方法中替换返回逻辑
var edmEntityType = GetEdmModel().FindEntitySet(entitySetName).EntityType() as EdmEntityType;
var edmEntityObjects = resp.Select(item => new EdmEntityObject(edmEntityType, item));

return Ok(edmEntityObjects);

需要引用以下命名空间:

using Microsoft.OData.Edm;
using Microsoft.OData.Edm.Library;
using Microsoft.AspNet.OData.Formatter;

4. 验证OData路由组件配置

确认AddRouteComponents("odata", GetEdmModel())中的路径"odata"与EDM模型中注册的实体集名称对应,保证OData路由能正确匹配到控制器的Action。


内容的提问来源于stack exchange,提问作者Omar Mauri

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 18:35:23