我的网络API中有一个控制器。我们称之为TimeController
。
我有GET
行动和PUT
行动。它们看起来像这样:
public class TimeController : ApiController
{
[HttpGet]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.OK, DateTime.UtcNow);
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
我还有一个路线配置如下:
routes.MapHttpRoute("DefaultApi", "{controller}/{id}", new { id = RouteParameter.Optional });
所以我可以安静地访问它。
现在,我还想使用自定义Route属性对GET
操作进行版本控制。我使用的代码与Richard Tasker在此blog post中谈到的代码非常相似。
(不同之处在于我使用正则表达式从accept头获取版本。其他所有内容都差不多)
所以我的控制器现在看起来像这样:
public class TimeController : ApiController
{
private IService _service;
public TimeController(IService service)
{
_service = service;
}
[HttpGet, RouteVersion("Time", 1)]
public HttpResponseMessage Get()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow);
}
[HttpGet, RouteVersion("Time", 2)]
public HttpResponseMessage GetV2()
{
return Request.CreateResponse(HttpStatusCode.Ok, DateTime.UtcNow.AddDays(1));
}
[HttpPut]
public HttpResponseMessage Put(int id)
{
_service.Update(id);
return Request.CreateResponse(HttpStatusCode.OK);
}
}
但是,现在当我尝试访问PUT端点时,我从服务器获得了404响应。如果我在调试模式中单步执行代码,我可以看到RouteVersion
属性被触发,即使我还没有用它来修饰动作。
如果我将属性添加到版本为1的PUT操作,或者我添加内置的Route属性,如下所示:Route("Time")
那么它可以正常工作。
所以我的问题是:即使我没有用它来装饰动作,为什么属性会被触发?
编辑:以下是属性的代码:
public class RouteVersion : RouteFactoryAttribute
{
private readonly int _allowedVersion;
public RouteVersion(string template, int allowedVersion) : base(template)
{
_allowedVersion = allowedVersion;
}
public override IDictionary<string, object> Constraints
{
get
{
return new HttpRouteValueDictionary
{
{"version", new VersionConstraint(_allowedVersion)}
};
}
}
}
public class VersionConstraint : IHttpRouteConstraint
{
private const int DefaultVersion = 1;
private readonly int _allowedVersion;
public VersionConstraint(int allowedVersion)
{
_allowedVersion = allowedVersion;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection != HttpRouteDirection.UriResolution)
{
return true;
}
int version = GetVersionFromHeader(request) ?? DefaultVersion;
return (version == _allowedVersion);
}
private int? GetVersionFromHeader(HttpRequestMessage request)
{
System.Net.Http.Headers.HttpHeaderValueCollection<System.Net.Http.Headers.MediaTypeWithQualityHeaderValue> acceptHeader = request.Headers.Accept;
var regularExpression = new Regex(@"application\/vnd\.\.v([0-9]+)",
RegexOptions.IgnoreCase);
foreach (var mime in acceptHeader)
{
Match match = regularExpression.Match(mime.MediaType);
if (match.Success)
{
return Convert.ToInt32(match.Groups[1].Value);
}
}
return null;
}
}
Edit2 :我认为有一些混乱,所以我更新了Put动作以匹配路线配置
答案 0 :(得分:1)
所以我的问题是:即使我没有用它装饰动作,为什么属性会被触发?
从您尝试访问PUT端点时的问题表达方式以及它与GET操作匹配(然后随后运行其约束)的事实很清楚 你有没有向服务器发出PUT请求 。大多数浏览器都无法发出PUT请求,您需要一段代码或脚本来执行此操作。
using (var client = new System.Net.WebClient())
{
// The byte array is the data you are posting to the server
client.UploadData(@"http://example.com/time/123", "PUT", new byte[0]);
}
答案 1 :(得分:0)
我认为这是因为你的行动签名与默认路线相结合
在默认路由中,您将Id属性指定为可选,但在您的操作中,您使用参数days,在这种情况下,框架无法解析它。您必须将其添加为查询字符串参数,例如:
?days={days}
或者更改签名以接受id作为输入。
由于它无法在网址中使用天数恢复操作,因此会返回404
我个人不使用默认路由,并始终使用属性路由来防止这种行为
答案 2 :(得分:0)
所以我的问题是:即使我没有用它装饰动作,为什么属性会被触发?
任何没有路由属性的控制器方法都使用基于约定的路由。这样,您可以在同一个项目中组合两种类型的路由。
请看这个链接: attribute-routing-in-web-api-2
此外,由于方法未使用route属性进行修饰,因此当Web API框架收到HTTP请求时,它会尝试将URI与路由表中的某个路由模板进行匹配。如果没有路由匹配,则客户端收到404错误。这就是你得到404
的原因请同时查看此内容:Routing in ASP.NET Web API