WebAPI - Разрешить контроллер и действие вручную из запроса - PullRequest
0 голосов
/ 31 октября 2018

Я хотел, чтобы приложение WebAPI изменило используемые SessionStateBehavior на основе таких атрибутов действий:

    [HttpPost]
    [Route("api/test")]
    [SetSessionStateBehavior(SessionStateBehavior.Required)]    // <--- This modifies the behavior
    public async Task<int> Test(){}

Кажется, однако, что единственное место, где я могу изменить поведение сеанса, находится внутри Application_PostAuthorizeRequest моего HttpApplication (или в аналогичных местах, в начале срока действия запроса), в противном случае я получаю эту ошибку:

'HttpContext.SetSessionStateBehavior' can only be invoked before 'HttpApplication.AcquireRequestState' event is raised.

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

Я начал с этих строк кода, чтобы сначала разрешить контроллер:

 var httpCtx = HttpContext.Current;
 var ctrlSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
 var actionSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
 HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(httpCtx.Request);

Но в последней строке я не могу получить правильный HttpRequestMessage из запроса. Есть идеи, как это получить? Это не внутри контроллера, поэтому я не готов там. Или есть лучший способ сделать это? Я пытаюсь увидеть дизассемблированный код фреймворка, чтобы скопировать его части, но в этот момент я совершенно растерялся ...


UPDATE:

Это самое близкое к разрешению действие вручную, но оно не работает:

Я зарегистрировал эти две службы:

            container.RegisterType<IHttpControllerSelector, DefaultHttpControllerSelector>();
            container.RegisterType<IHttpActionSelector, ApiControllerActionSelector>();

... и попытайтесь получить требуемое поведение сессии следующим образом:

    private  SessionStateBehavior GetDesiredSessionBehavior(HttpContext httpCtx)
    {
        var config = GlobalConfiguration.Configuration;
        var diResolver = config.Services;
        var ctrlSel = diResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
         var actionSel = diResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;

        if (ctrlSel is null || actionSel is null)
        {
            return DefaultSessionBehavior;
        }


        var method = new HttpMethod(httpCtx.Request.HttpMethod);
        var requestMsg = new HttpRequestMessage(method, httpCtx.Request.Url);
        requestMsg.Properties.Add(HttpPropertyKeys.RequestContextKey, httpCtx.Request.RequestContext);
        requestMsg.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
        httpCtx.Request.Headers.Cast<string>().ForEach(x => requestMsg.Headers.Add(x, httpCtx.Request.Headers[x]));


        var httpRouteData = httpCtx.Request.RequestContext.RouteData;
        var routeData = config.Routes.GetRouteData(requestMsg);
        requestMsg.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
        requestMsg.SetRequestContext(new HttpRequestContext(){RouteData = routeData });
        requestMsg.SetConfiguration(config);
        var route = config.Routes["DefaultApi"];
        requestMsg.SetRouteData(routeData ?? route.GetRouteData(config.VirtualPathRoot, requestMsg));

        var routeHandler = httpRouteData.RouteHandler ?? new WebApiConfig.SessionStateRouteHandler();
        var httpHandler = routeHandler.GetHttpHandler(httpCtx.Request.RequestContext);
        if (httpHandler is IHttpAsyncHandler httpAsyncHandler)
        {
            httpAsyncHandler.BeginProcessRequest(httpCtx, ar => httpAsyncHandler.EndProcessRequest(ar), null);
        }
        else
        {
            httpHandler.ProcessRequest(httpCtx);
        }

        var values = requestMsg.GetRouteData().Values; // Hm this is empty and makes the next call fail...
        HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(requestMsg);

        IHttpController controller = controllerDescriptor?.CreateController(requestMsg);
        if (controller == null)
        {
            return DefaultSessionBehavior;
        }

        var ctrlContext = CreateControllerContext(requestMsg, controllerDescriptor, controller);
        var actionCtx = actionSel.SelectAction(ctrlContext);
        var attr = actionCtx.GetCustomAttributes<ActionSessionStateAttribute>().FirstOrDefault();
        return attr?.Behavior ?? DefaultSessionBehavior;
    }

У меня есть альтернативный хак, чтобы заставить его работать (отправка значений заголовка от клиента для изменения поведения сеанса), но было бы неплохо, если бы вышеприведенная версия работала.

UPDATE:

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

1 Ответ

0 голосов
/ 31 октября 2018

Я не знаю, будет ли это полезно для вас, но я просто проходил курс Pluralsight (https://app.pluralsight.com/player?course=implementing-restful-aspdotnet-web-api)), и в главе «Управление версиями» автор показывает, как реализовать селектор контроллера, где он имеет доступ к запросу.

Селектор контроллера выглядит так:

public class CountingKsControllerSelector : DefaultHttpControllerSelector
{
  private HttpConfiguration _config;
  public CountingKsControllerSelector(HttpConfiguration config)
    : base(config)
  {
    _config = config;
  }

  public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
  {
    var controllers = GetControllerMapping();

    var routeData = request.GetRouteData();

    var controllerName = (string)routeData.Values["controller"];

    HttpControllerDescriptor descriptor;

    if (controllers.TryGetValue(controllerName, out descriptor))
    {
      [...]

      return descriptor;
    }

    return null;
  }
}

И он зарегистрирован в WebApiConfig с:

config.Services.Replace(typeof(IHttpControllerSelector),
  new CountingKsControllerSelector(config));
...