Почему мой атрибут запускается во всех действиях, включая те, у которых нет атрибута?

У меня есть контроллер в моем веб-api. Позвольте называть его TimeController.

У меня есть действие GET и a 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 });

поэтому я могу получить доступ к нему спокойным образом.

Теперь я также хочу выполнить действие GET с использованием настраиваемого атрибута Route. Я использую код, очень похожий на то, о чем говорит Ричард Таскер в этом сообщении .

(разница заключается в том, что я использую регулярное выражение для получения версии из заголовка 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 запускается, хотя я не украсил его.

Если я добавлю атрибут в действие PUT с версией 1 или добавлю встроенный атрибут 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 для соответствия конфигурации маршрута

Ответ 1

Итак, мой вопрос: почему срабатывает атрибут, хотя я не украсил его?

Как видно из вашего вопроса, "когда я пытаюсь получить доступ к конечной точке PUT" и того факта, что он соответствует действию GET (а затем впоследствии запускает его ограничение), , вы не выпустили PUT запрос на сервер. Большинство браузеров не могут выдавать запрос PUT, для этого вам нужен фрагмент кода или script.

Пример

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]);
}

Ссылка: Как сделать запрос HTTP PUT?

Ответ 2

Я думаю, что это из-за вашей сигнатуры действия в сочетании с маршрутом по умолчанию

В вашем маршруте по умолчанию вы указываете атрибут идентификатора как необязательный, однако в вашем действии вы используете дни параметра, в этом случае среда не может его решить. вам либо нужно добавить его как параметр строки запроса, например:

?days={days}

Или измените подпись, чтобы принять id как входной.

Так как он не может изменить действие с днями в URL-адресе, он вернет 404

Лично я не использую маршруты по умолчанию и всегда использую маршрутизацию атрибутов, чтобы предотвратить это поведение

Ответ 3

Итак, мой вопрос: почему срабатывает атрибут, хотя я не украсил его?

Любые методы контроллера, не имеющие атрибута маршрута, используют маршрутизацию на основе условных обозначений. Таким образом, вы можете комбинировать оба типа маршрутизации в одном проекте.

Пожалуйста, просмотрите эту ссылку: attribute-routing-in-web-api-2

Также как метод не украшен атрибутом маршрута, когда среда веб-API получает HTTP-запрос, он пытается сопоставить URI с одним из шаблонов маршрутов в таблице маршрутизации. Если маршрут не совпадает, клиент получает ошибку 404. Вот почему вы получаете 404

См. также этот раздел: Маршрутизация в веб-интерфейсе ASP.NET