我有一个通用控制器(类似于.Net Core Override Controller Route for Generic Controller),它为所有动态类型注册了通用实现。
这很好。但是,在尝试使用其他过滤器值实现支持导航路由时,我遇到了一些问题。这个例子:
http://localhost/odata/EntityA(4711)/SubEntity?$ filter = category eq'ABC'
理论上可以正常工作,但是我需要提取ODataQueryOptions。
这就是我到目前为止所拥有的:
ExternalControllerFeatureProvider
public class ExternalControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
foreach (var candidate in _entityCompiler.GetTypes())
{
feature.Controllers.Add(
typeof(GenericController<>).MakeGenericType(candidate).GetTypeInfo()
);
}
}
}
GenericController
[Produces("application/json")]
[GenericControllerNameConvention]
[EnableQuery]
public class GenericController<T> : ODataController
{
public async Task<IQueryable<T>> Get([FromServices] ODataQueryOptions odataQueryOptions)
{
var parameters = ExtractQueryParameter(odataQueryOptions);
return await InternalGet(parameters);
}
public async Task<IQueryable<T>> Get([FromServices] ODataQueryOptions odataQueryOptions, [FromODataUri] object key)
{
var parameters = ExtractQueryParameter(odataQueryOptions);
AppendKeyAttributeFilter(parameters, key);
return await InternalGet(parameters);
}
public async Task<IActionResult> GetNavigation(Guid key, string propertyName)
{
var parameters = new Dictionary<string, object>();
AppendKeyAttributeFilter(parameters, key);
AppendExpandFilter(parameters, propertyName);
var rootObject = await InternalGet(parameters);
if (rootObject.Any())
{
var info = typeof(T).GetProperty(propertyName);
object value = info.GetValue(rootObject.FirstOrDefault());
return Ok(value);
}
return NotFound();
}
类似于此(http://odata.github.io/WebApi/03-04-custom-routing-convention/),我创建了一个 NavigationRoutingConvention ,它提取导航属性并从 GenericController调用 GetNavigation 方法和正确的 propertyName 。
问题在于此GenericController方法无法返回 IQueryable 或 IEnumerable ,而不能返回诸如 IActionResult 之类的一些非类型化类型。
为了在后端手动过滤我的数据源,我需要 ODataQueryOptions ,就像两个Get方法一样。问题在于,似乎底层框架需要知道正确的返回类型。
如果我在方法头添加 [FromServices] ODataQueryOptions ,则会出现以下异常:
System.InvalidOperationException:无法将EDM模型创建为 控制器“ EntityA”上的操作“ GetNavigation”具有返回类型 'System.Threading.Tasks.Task`1 [[Microsoft.AspNetCore.Mvc.IActionResult, Microsoft.AspNetCore.Mvc.Abstractions,版本= 2.1.1.0, 文化=中性,PublicKeyToken = adb9793829ddae60]]' 实现IEnumerable。在 Microsoft.AspNet.OData.ODataQueryParameterBindingAttribute.ODataQueryParameterBinding.GetEntityClrTypeFromActionReturnType(ActionDescriptor actionDescriptor) Microsoft.AspNet.OData.ODataQueryParameterBindingAttribute.ODataQueryParameterBinding.BindModelAsync(ModelBindingContext bindingContext)在 Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinder.BindModelAsync(ModelBindingContext bindingContext)在 Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext,IModelBinder modelBinder,IValueProvider valueProvider, ParameterDescriptor参数,ModelMetadata元数据,对象值)
在 Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider。<> c__DisplayClass0_0。d.MoveNext()
答案 0 :(得分:1)
所以我找到了解决方案。我放弃了自己的路由约定的想法,并添加了另一个Generic-Controller,尤其是用于子导航属性。在抽象的不起作用的代码下面,由一些私有部分清除...:-)
GenericSubNavigationController
[Produces("application/json")]
[GenericControllerNameConvention]
[EnableQuery]
public class GenericSubNavigationController<TBaseType, TSubType, TSubTypeDeclared> : GenericControllerBase<TBaseType>
{
public GenericSubNavigationController(ISubTypeEnricher subTypeEnricher) : base(subTypeEnricher)
{
}
public async Task<IQueryable<TSubTypeDeclared>> GetNavigation([FromServices] ODataQueryOptions odataQueryOptions, Guid key)
{
PropertyInfo propertyInfo = typeof(TBaseType).GetProperties().FirstOrDefault(x => x.PropertyType == typeof(TSubType));
string propertyName = propertyInfo.Name;
var parameters = new Dictionary<string, string>();
AppendKeyAttributeFilter(parameters, key);
AppendExpandFilter(parameters, propertyName);
var subParameters = new Tuple<string, Dictionary<string, string>>(propertyName, ExtractQueryParameter(odataQueryOptions));
var rootObject = await InternalGet<TBaseType>(parameters, subParameters);
if (rootObject.Any())
{
var info = typeof(TBaseType).GetProperty(propertyName);
object value = info.GetValue(rootObject.FirstOrDefault());
return new EnumerableQuery<TSubTypeDeclared>((IEnumerable<TSubTypeDeclared>) value);
}
return null;
}
}
为了正常工作,您必须在 ExternalControllerFeatureProvider 中实例化此控制器,该问题在我最初的问题中已经提到
ExternalControllerFeatureProvider
public class ExternalControllerFeatureProvider : IApplicationFeatureProvider<ControllerFeature>
{
private readonly IExternalCompiler _entityCompiler;
public ExternalControllerFeatureProvider(IExternalCompiler entityCompiler)
{
_entityCompiler = entityCompiler;
}
public void PopulateFeature(IEnumerable<ApplicationPart> parts, ControllerFeature feature)
{
var types = _entityCompiler.GetTypes().ToList();
foreach (var candidate in types)
{
feature.Controllers.Add(
typeof(GenericController<>).MakeGenericType(candidate).GetTypeInfo()
);
foreach (var propertyInfo in candidate.GetProperties())
{
Type targetType = propertyInfo.PropertyType.GenericTypeArguments.Any()
? propertyInfo.PropertyType.GenericTypeArguments.First()
: propertyInfo.PropertyType;
if (types.Contains(targetType))
{
var typeInfo = typeof(GenericSubNavigationController<,,>).MakeGenericType(candidate, propertyInfo.PropertyType, targetType).GetTypeInfo();
feature.Controllers.Add(typeInfo);
}
}
}
}
}
最后,我们必须更改使用的属性 GenericControllerNameConvention ,以更改方法的动作名称以反映默认的OData要求
GenericControllerNameConvention
public class GenericControllerNameConvention : Attribute, IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (!controller.ControllerType.IsGenericType || (controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericController<>) && controller.ControllerType.GetGenericTypeDefinition() !=
typeof(GenericSubNavigationController<,,>)))
{
// Not a GenericController, ignore.
return;
}
var entityType = controller.ControllerType.GenericTypeArguments[0];
controller.ControllerName = $"{entityType.Name}";
if (controller.ControllerType.GetGenericTypeDefinition() ==
typeof(GenericSubNavigationController<,,>))
{
foreach (var controllerAction in controller.Actions)
{
if (controllerAction.ActionName == "GetNavigation")
{
var subType = controller.ControllerType.GenericTypeArguments[1];
PropertyInfo propertyInfo = entityType.GetProperties().FirstOrDefault(x => x.PropertyType == subType);
controllerAction.ActionName = $"Get{propertyInfo.Name}";
}
}
}
}
}