基于动态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

