X-HTTP-метод-переопределение дает NotFound (404) в веб-API ASP.NET
Я пытаюсь переопределить метод HTTP, следуя описанным ниже шагам здесь. В основном, я создаю DelegatingHandler, аналогично следующему, и добавляю его как обработчик сообщения на Application_Start
.
public class MethodOverrideHandler : DelegatingHandler
{
readonly string[] _methods = { "DELETE", "HEAD", "PUT" };
const string _header = "X-HTTP-Method-Override";
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Check for HTTP POST with the X-HTTP-Method-Override header.
if (request.Method == HttpMethod.Post && request.Headers.Contains(_header))
{
// Check if the header value is in our methods list.
var method = request.Headers.GetValues(_header).FirstOrDefault();
if (_methods.Contains(method, StringComparer.InvariantCultureIgnoreCase))
{
// Change the request method.
request.Method = new HttpMethod(method);
}
}
return base.SendAsync(request, cancellationToken);
}
}
У меня есть следующие методы, определенные на моем контроллере:
-
persons/{id}
, GET
-
persons/{id}
, PUT
-
persons/{id}
, DELETE
Я могу называть их своими "родными" методами, и они работают так, как ожидалось. Однако, когда я пытаюсь вызвать их через POST, отправляя заголовок X-HTTP-Method-Override
с помощью "DELETE" или "PUT", он дает ошибку Not Found (404). Важно добавить, что когда она дает эту ошибку, она никогда не достигает MethodOverrideHandler
- я поставил точку останова, которая никогда не попадает; он попадает в точку останова, когда я вызываю обычные DELETE и PUT.
Я даже попытался добавить другой метод:
Когда я это сделаю, я получаю Не допускается метод (405).
Я думал, что обработчики сообщений были запущены до диспетчеров маршрутизации и диспетчера. Почему это дает мне 404?
Я не думаю, что это связано, но я не использую маршрутизацию веб-API по умолчанию. Вместо этого я сопоставляю с помощью настраиваемого атрибута, назначенного каждому методу, например:
routes.MapHttpRoute(
String.Format("{0}_{1}", operation.Name, service.ServiceId),
String.Format("{0}/{1}", service.RoutePrefix, routeTemplateAttribute.Template),
defaults,
new { httpMethod = GetHttpMethodConstraint(operation) });
[HttpDelete, RouteTemplate("persons/{id}")]
public HttpResponseMessage DeletePerson(string id)
{
// ...
}
EDIT: GetHttpMethodConstraint
ниже.
private static HttpMethodConstraint GetHttpMethodConstraint(MethodInfo methodInfo)
{
var methodResolver = HttpMethodResolver.FromMethodInfo(methodInfo);
return new HttpMethodConstraint(methodResolver.Resolve());
}
internal class HttpMethodResolver
{
private MethodInfo _methodInfo;
private HttpMethodResolver(MethodInfo methodInfo)
{
_methodInfo = methodInfo;
}
public static HttpMethodResolver FromMethodInfo(MethodInfo methodInfo)
{
return new HttpMethodResolver(methodInfo);
}
public string[] Resolve()
{
var verbs = new List<HttpMethod>();
if (MethodHasAttribute<HttpGetAttribute>())
{
verbs.Add(HttpMethod.Get);
}
else if (MethodHasAttribute<HttpPostAttribute>())
{
verbs.Add(HttpMethod.Post);
}
else if (MethodHasAttribute<HttpDeleteAttribute>())
{
verbs.Add(HttpMethod.Delete);
}
else if (MethodHasAttribute<HttpPutAttribute>())
{
verbs.Add(HttpMethod.Put);
}
else
{
throw new ServiceModelException("HTTP method attribute should be used");
}
return verbs.Select(v => v.Method).ToArray();
}
private bool MethodHasAttribute<T>() where T : Attribute
{
return GetMethodAttribute<T>() != null;
}
private T GetMethodAttribute<T>() where T : Attribute
{
return _methodInfo.GetCustomAttributes(typeof(T), true).FirstOrDefault() as T;
}
}
Ответы
Ответ 1
Я попытался воспроизвести вашу ошибку, но я не смог. Здесь вы можете загрузить мой простой проект с помощью обработчика сообщений: https://dl.dropbox.com/u/20568014/WebApplication6.zip
Я хотел бы указать, что обработчики сообщений выполняются до выполнения логики выбора действия. Таким образом, в вашем случае, вероятно, что-то еще вызывает проблему, и я думаю, что вы должны смотреть на других обработчиков сообщений, на ваш регистрационный код обработчика сообщений и т.д., Потому что проблема возникает из-за того, что ваш обработчик сообщений никогда не запускается.
Кроме того, я думаю, что ваша реализация IRouteConstraint
, GetHttpMethodConstraint
, выглядит подозрительной для меня.
Вот мой регистрационный код для обработчика сообщений:
protected void Application_Start(object sender, EventArgs e) {
var config = GlobalConfiguration.Configuration;
config.Routes.MapHttpRoute(
"DefaultHttpRoute",
"api/{controller}/{id}",
new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new MethodOverrideHandler());
}
Ответ 2
Я думаю, что у меня такая же проблема. Это похоже на то, что ограничения маршрута проверяются перед любыми обработчиками сообщений.
Итак, я создал настраиваемое ограничение, которое знает, чтобы проверить переопределенный метод HTTP:
class OverrideableHttpMethodConstraint : HttpMethodConstraint
{
public OverrideableHttpMethodConstraint(HttpMethod httpMethod) : base(httpMethod)
{
}
protected override bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
IEnumerable<string> headerValues;
if (request.Method.Method.Equals("POST", StringComparison.OrdinalIgnoreCase) &&
request.Headers.TryGetValues("X-HTTP-Method-Override", out headerValues))
{
var method = headerValues.FirstOrDefault();
if (method != null)
{
request.Method = new HttpMethod(method);
}
}
return base.Match(request, route, parameterName, values, routeDirection);
}
}