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

问题描述 投票:1回答:1

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

所以,这是我的情况。我正在使用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);
    }
}

如何生成一个链接,以便它不是硬编码而且是模板化的?

c# asp.net asp.net-core hateoas hal
1个回答
1
投票

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

看来,LinkGenerator旨在构建一个完整有效的URL,因此所有参数都是必需的。我所寻找的实际上是一种路线模式。

在调试时,我发现IEndpointAddressScheme<RouteValuesAddress>服务注入了LinkGnerator。它用于实际找到路线patern。之后,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
        };
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.