首页 > 解决方案 > WebAPI - 从请求中手动解析控制器和操作

问题描述

我想让我的 WebAPI 应用程序更改SessionStateBehavior基于操作属性的使用,如下所示:

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

但是,似乎我可以更改会话行为的唯一位置是在 my 内部(或在请求生命周期早期的类似位置),否则我会收到此错误HttpApplicationApplication_PostAuthorizeRequest

'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从请求中得到正确的。知道怎么得到吗?这不在控制器内部,所以我没有在那里准备好。或者,有没有更好的方法来做到这一点?我试图查看框架的反汇编代码以复制其中的一部分,但此时我很迷茫......


更新:

这是我最接近手动解决操作的方法,但它不起作用:

我已经注册了这两个服务:

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

我有另一种方法可以使它工作(从客户端发送标头值以修改会话行为),但是如果上面的版本可以工作,那就太好了。

更新:

最终,我根据客户端标头值设置会话行为,并在请求生命周期的后期根据操作属性验证发送该标头的有效性。如果有人可以解决我在上面遇到的动作解析代码,请随时在此处发布答案。

标签: c#asp.net-web-apisession-state

解决方案


我不知道这是否会对您有所帮助,但我只是在学习 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));

推荐阅读