ASP.NET WebApi2 OData 处理带有斜杠的查询 /

ASP.NET WebApi2 OData handling of queries with slash /

我用约定模型路由制作了一个 "standard" Web Api 2 OData 项目。以下 OData 查询正在运行:

/odata/Users

/odata/Users(123)

/odata/$metadata

/odata/Users?$select=Username

所以在我尝试这个之前一切似乎都很好,我认为这也是一个合法的 OData 查询:

/odata/Users(123)/Username

查询中的斜线 / 破坏了一切,它根本没有命中控制器 class 和 OData 身份验证流程。 Microsoft ASP.NET OData 实施是否应完全支持这一点?还是仅当我为每个 属性 之类的用户名定义具有正确路由的显式方法时才支持?有什么建议可以解决这个问题吗?我已经尝试过明确的 {*rest} 路线等

AFAIK,内置路由约定不包括用于 属性 访问的约定。您需要为每次 属性 访问添加许多操作。

但是,基于此资源 here,添加自定义路由约定来处理 属性 访问路径模板并不难:~/entityset/key/property

这是根据我上面分享的 link 改编的自定义路由约定

使用的程序集:Microsoft.AspNet.OData 7.4.1 - 该方法对于您可能正在使用的任何其他 OData Web API 库都是相同的

Class用于说明

public class Product
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

为 属性 访问添加路由约定

// Usings
using Microsoft.AspNet.OData.Routing;
using Microsoft.AspNet.OData.Routing.Conventions;
using System;
using System.Linq;
using System.Web.Http.Controllers;
// ...

public class CustomPropertyRoutingConvention : NavigationSourceRoutingConvention
{
    private const string ActionName = "GetProperty";

    public override string SelectAction(ODataPath odataPath, HttpControllerContext controllerContext, ILookup<string, HttpActionDescriptor> actionMap)
    {
        if (odataPath == null || controllerContext == null || actionMap == null)
        {
            return null;
        }

        if (odataPath.PathTemplate == "~/entityset/key/property" ||
            odataPath.PathTemplate == "~/entityset/key/cast/property" ||
            odataPath.PathTemplate == "~/singleton/property" ||
            odataPath.PathTemplate == "~/singleton/cast/property")
        {
            var segment = odataPath.Segments.OfType<Microsoft.OData.UriParser.PropertySegment>().LastOrDefault();

            if (segment != null)
            {
                string actionName = FindMatchingAction(actionMap, ActionName);

                if (actionName != null)
                {
                    if (odataPath.PathTemplate.StartsWith("~/entityset/key", StringComparison.Ordinal))
                    {
                        var keySegment = odataPath.Segments.OfType<Microsoft.OData.UriParser.KeySegment>().FirstOrDefault();
                        if (keySegment == null || !keySegment.Keys.Any())
                            throw new InvalidOperationException("This link does not contain a key.");

                        controllerContext.RouteData.Values[ODataRouteConstants.Key] = keySegment.Keys.First().Value;
                    }

                    controllerContext.RouteData.Values["propertyName"] = segment.Property.Name;

                    return actionName;
                }
            }
        }

        return null;
    }

    public static string FindMatchingAction(ILookup<string, HttpActionDescriptor> actionMap, params string[] targetActionNames)
    {
        foreach (string targetActionName in targetActionNames)
        {
            if (actionMap.Contains(targetActionName))
            {
                return targetActionName;
            }
        }

        return null;
    }
}

在您的控制器中添加单一方法来处理任何 属性

的请求
public class ProductsController : ODataController
{
    // ...
    [HttpGet]
    public IHttpActionResult GetProperty(int key, string propertyName)
    {
        var product = _db.Products.FirstOrDefault(d => d.Id.Equals(key));
        if (product == null)
        {
            return NotFound();
        }

        PropertyInfo info = typeof(Product).GetProperty(propertyName);

        object value = info.GetValue(product);

        return Ok(value, value.GetType());
    }

    private IHttpActionResult Ok(object content, Type type)
    {
        var resultType = typeof(OkNegotiatedContentResult<>).MakeGenericType(type);
        return Activator.CreateInstance(resultType, content, this) as IHttpActionResult;
    }
    // ...
}

在您的WebApiConfig.cs(或您配置服务的等效位置)

var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Product>("Products");

var routingConventions = ODataRoutingConventions.CreateDefaultWithAttributeRouting("odata", configuration);
routingConventions.Insert(0, new CustomPropertyRoutingConvention());

configuration.MapODataServiceRoute("odata", "odata", modelBuilder.GetEdmModel(), new DefaultODataPathHandler(), routingConventions);
configuration.Count().Filter().OrderBy().Expand().Select().MaxTop(null);
configuration.EnsureInitialized();

请求名称 属性:/Products(1)/Name

请求 ID 属性:/Products(1)/Id