根据Controller类型和Acton名称建立路由模式

时间:2019-02-15 12:38:13

标签: c# asp.net asp.net-core hateoas hal

免责声明 :首先,我想提一下,我在互联网上寻找答案,阅读了所有文档,阅读了我可能遇到的所有问题在这里找到,但到目前为止还没有运气。

所以,这是我的情况。我正在使用ASP.NET Core 2.2构建API,并且正在使用HATEOAS(HAL规范和Halcyon库)。我应该提供链接以及资源本身。这首先促使我转向HATEOAS。一些链接是模板化的,因为它可能是一种PUT方法,并且id将由前端指定。

问题是,我的控制器可以具有非常不同的路由(使用基于属性的路由),并且硬编码链接是一件坏事,因为如果路由发生更改,我需要记住也要更改链接的使用位置。因此,我决定根据Controller类型和Action名称生成链接。我找到了LinkGenerator,但如果我没有为路由指定所有参数,似乎它返回null。这是一个代码示例:

[Route("api/metadata")]
[ApiController]
public class MetadataController : ControllerBase
{
    private readonly IMetadataProvider _metadataProvider;
    private readonly LinkGenerator _linkGenerator;

    public MetadataController(
        IMetadataProvider metadataProvider,
        LinkGenerator linkProvider)
    {
        _metadataProvider = metadataProvider;
        _linkGenerator = linkProvider;
    }

    [HttpGet]
    public IActionResult GetMetadata()
    {
        var metadata = _metadataProvider.GetMetadata();
        // here url will be 'null', because last parameter is null
        // and route requires parameter 'name' to be specified instead of 'null'
        // EXPECTED: "api/metadata/{name}"
        // ACTUAL: null
        string url = _linkGenerator.GetPathByAction(
            nameof(MetadataController.GetByName),
            nameof(MetadataController).Replace(nameof(Controller), string.Empty),
            null);

        var response = new HALResponse(metadata)
            .AddSelfLink(HttpContext.Request)
            .AddLinks(new Link(name, url));

        return Ok(response);
    }

    [HttpGet("{name}")]
    public IActionResult GetByName(string name)
    {
        var metadata = _metadataProvider.GetMetadataForEntity(name);
        return Ok(metadata);
    }
}

我如何生成链接,以便它不会被硬编码并被模板化?

1 个答案:

答案 0 :(得分:1)

经过几个小时的调试ASP.NET源代码,我想我找到了一种方法。

看来LinkGenerator旨在建立一个完整且有效的url,因此所有参数都是必需的。我要找的实际上是一个路由模式

在调试时,我发现将IEndpointAddressScheme<RouteValuesAddress>服务注入到LinkGnerator中。它实际上用于查找路线样式。之后, LinkGenerator 尝试填充所有参数。

以下代码已修复并可以正常工作:

[ApiController]
public class MetadataController : ControllerBase
{
    private readonly IMetadataProvider _metadataProvider;
    private readonly IEndpointAddressScheme<RouteValuesAddress> _endpointAddress;

    public MetadataController(
        IMetadataProvider metadataProvider,
        IEndpointAddressScheme<RouteValuesAddress> endpointAddress)
    {
        _metadataProvider = metadataProvider;
        _endpointAddress = endpointAddress;
    }

    [HttpGet]
    public IActionResult GetMetadata()
    {
        var metadata = _metadataProvider.GetMetadata();
        // EXPECTED: "api/metadata/{name}"
        // ACTUAL: "api/metadata/{name}"
        string actionName = nameof(MetadataController.GetById);
        string controllerName = nameof(MetadataController).Replace(nameof(Controller), string.Empty);
        var url = _endpointAddress.FindEndpoints(CreateAddress(actionName, controllerName))
            .OfType<RouteEndpoint>()
            .Select(x => x.RoutePattern)
            .FirstOrDefault();;

        var response = new HALResponse(metadata)
            .AddSelfLink(HttpContext.Request)
            .AddLinks(new Link(name, url));

        return Ok(response);
    }

    [HttpGet("{name}")]
    public IActionResult GetByName(string name)
    {
        var metadata = _metadataProvider.GetMetadataForEntity(name);
        return Ok(metadata);
    }

    private static RouteValuesAddress CreateAddress(string action, string controller)
    {
        var explicitValues = new RouteValueDictionary(null);
        var ambientValues = GetAmbientValues(httpContext);

        explicitValues ["action"] = action;
        explicitValues ["controller"] = controller;

        return new RouteValuesAddress()
        {
            AmbientValues = ambientValues,
            ExplicitValues = explicitValues
        };
    }
}