免责声明:首先,我想提一下,我在互联网上寻找答案,阅读所有文档,阅读我在这里可能找到的所有问题,但到目前为止还没有运气。
所以,这是我的情况。我正在使用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);
}
}
如何生成一个链接,以便它不是硬编码而且是模板化的?
经过几个小时的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
};
}
}