是否可以复制/克隆 Web 请求的 HttpContext

发布于 2024-08-05 19:16:54 字数 650 浏览 6 评论 0原文

克隆当前请求的 HttpContext 实例的最简单方法是什么?

我正在 Asp.net MVC v1 中开发一个应用程序。我升级了常规 PartialView 功能,使其实际上具有行为非常相似的子控制器,但具有自己的上下文。当您使用 PartialViews 时,您必须在主视图的控制器操作中填充部分视图的视图数据。我创建了自己的功能,可以从视图内调用控制器操作。这样我得到:

  • 我不必在主视图的控制器操作中提供子视图的数据
  • 子控制器方法可以更封装地操作数据,与其他视图/控制器没有任何关系

问题是每个子控制器请求都使用 HttpContext。因此,当我在子控制器中设置一些 HttpContext.Item 时,它实际上会填充实际请求的 HttpContext。

这就是为什么我想克隆 HttpContext。我已经在使用:

HttpContext subContext = new HttpContext(request, response);
// what happened to Session, User, Items etc. properties?

但这除了请求和响应之外没有设置任何其他内容。但我可能还需要其他属性和集合...比如会话、项目、用户...等。

What's the easiest way to clone current request's HttpContext instance?

I'm developing an app in Asp.net MVC v1. I upgraded the regular PartialView capabilities to actually have sub-controllers that act very similar, but have their own context. When you use PartialViews you have to fill view data for the partial view in your main view's controller action. I created my own functionality that makes it possible to call controller actions from within a view. This way I get:

  • I don't have to provide sub-view's data in my main view's controller action
  • sub controller methods can manipulate data more encapsulated without any relation to other views/controllers

The problem is that each sub-controller request uses HttpContext. So when I set some HttpContext.Item in a sub-controller it actually populates HttpContext of the actual request.

That's why I want to clone HttpContext. I'm already using:

HttpContext subContext = new HttpContext(request, response);
// what happened to Session, User, Items etc. properties?

but this doesn't set anything else than request and response. But I would probably also need other properties and collections... Like Session, Items, User... etc.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

感情洁癖 2024-08-12 19:16:54

虽然“不可能”的答案是正确的,但还有一种替代方案比将值写入当前上下文然后重写回其原始状态要干净得多。解决方案是完全基于您选择的 URL 创建一个新的 HttpContext 对象。

// A new request/response is constructed to using a new URL.
// The new response is using a StreamWriter with null stream as a backing stream 
// which doesn't consume resources

using (var nullWriter = new StreamWriter(Stream.Null))
{
    var newRequestUri = new Uri("http://www.somewhere.com/some-resource/");
    var newRequest = new HttpRequest("", newRequestUri.ToString(), newRequestUri.Query);

    var newResponse = new HttpResponse(nullWriter);
    var newContext = new HttpContextWrapper(new HttpContext(newRequest, newResponse));

    // Work with the new context here before it is disposed...
} 

参考: https://github.com/maartenba/MvcSiteMapProvider/issues/278#issuecomment -34905271

While the "Not Possible" answer is correct, there is an alternative that is much cleaner than writing values into the current context and then rewriting back to its original state. The solution is to make a new HttpContext object entirely that is based on the URL of your choosing.

// A new request/response is constructed to using a new URL.
// The new response is using a StreamWriter with null stream as a backing stream 
// which doesn't consume resources

using (var nullWriter = new StreamWriter(Stream.Null))
{
    var newRequestUri = new Uri("http://www.somewhere.com/some-resource/");
    var newRequest = new HttpRequest("", newRequestUri.ToString(), newRequestUri.Query);

    var newResponse = new HttpResponse(nullWriter);
    var newContext = new HttpContextWrapper(new HttpContext(newRequest, newResponse));

    // Work with the new context here before it is disposed...
} 

Reference: https://github.com/maartenba/MvcSiteMapProvider/issues/278#issuecomment-34905271

吃→可爱长大的 2024-08-12 19:16:54

对于 ASP.Net Core/.Net 5,以下内容将起作用(基于 SignalR 的 ASP.Net Core 源代码,如果您需要更多功能,只需添加它们)。

public static HttpContext Clone(this HttpContext httpContext, bool copyBody)
{
     var existingRequestFeature = httpContext.Features.Get<IHttpRequestFeature>();

     var requestHeaders = new Dictionary<string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase);
     foreach (var header in existingRequestFeature.Headers)
     {
         requestHeaders[header.Key] = header.Value;
     }

     var requestFeature = new HttpRequestFeature
     {
         Protocol = existingRequestFeature.Protocol,
         Method = existingRequestFeature.Method,
         Scheme = existingRequestFeature.Scheme,
         Path = existingRequestFeature.Path,
         PathBase = existingRequestFeature.PathBase,
         QueryString = existingRequestFeature.QueryString,
         RawTarget = existingRequestFeature.RawTarget,
         Headers = new HeaderDictionary(requestHeaders),
     };

     if(copyBody)
     {
          // We need to buffer first, otherwise the body won't be copied
          // Won't work if the body stream was accessed already without calling EnableBuffering() first or without leaveOpen
          httpContext.Request.EnableBuffering();
          httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
          requestFeature.Body = existingRequestFeature.Body;
     }

     var features = new FeatureCollection();
     features.Set<IHttpRequestFeature>(requestFeature);
        // Unless we need the response we can ignore it...
     features.Set<IHttpResponseFeature>(new HttpResponseFeature());
     features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
     
     var newContext = new DefaultHttpContext(features);

     if (copyBody)
     {
         // Rewind for any future use...
         httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
     }

        // Can happen if the body was not copied
     if(httpContext.Request.HasFormContentType && httpContext.Request.Form.Count != newContext.Request.Form.Count)
     {
         newContext.Request.Form = new Microsoft.AspNetCore.Http.FormCollection(httpContext.Request.Form.ToDictionary(f => f.Key, f => f.Value));
     }

     return newContext;            
}

For ASP.Net Core/.Net 5 the following will work (based on the ASP.Net Core source code for SignalR, if you need more features just add them).

public static HttpContext Clone(this HttpContext httpContext, bool copyBody)
{
     var existingRequestFeature = httpContext.Features.Get<IHttpRequestFeature>();

     var requestHeaders = new Dictionary<string, StringValues>(existingRequestFeature.Headers.Count, StringComparer.OrdinalIgnoreCase);
     foreach (var header in existingRequestFeature.Headers)
     {
         requestHeaders[header.Key] = header.Value;
     }

     var requestFeature = new HttpRequestFeature
     {
         Protocol = existingRequestFeature.Protocol,
         Method = existingRequestFeature.Method,
         Scheme = existingRequestFeature.Scheme,
         Path = existingRequestFeature.Path,
         PathBase = existingRequestFeature.PathBase,
         QueryString = existingRequestFeature.QueryString,
         RawTarget = existingRequestFeature.RawTarget,
         Headers = new HeaderDictionary(requestHeaders),
     };

     if(copyBody)
     {
          // We need to buffer first, otherwise the body won't be copied
          // Won't work if the body stream was accessed already without calling EnableBuffering() first or without leaveOpen
          httpContext.Request.EnableBuffering();
          httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
          requestFeature.Body = existingRequestFeature.Body;
     }

     var features = new FeatureCollection();
     features.Set<IHttpRequestFeature>(requestFeature);
        // Unless we need the response we can ignore it...
     features.Set<IHttpResponseFeature>(new HttpResponseFeature());
     features.Set<IHttpResponseBodyFeature>(new StreamResponseBodyFeature(Stream.Null));
     
     var newContext = new DefaultHttpContext(features);

     if (copyBody)
     {
         // Rewind for any future use...
         httpContext.Request.Body.Seek(0, SeekOrigin.Begin);
     }

        // Can happen if the body was not copied
     if(httpContext.Request.HasFormContentType && httpContext.Request.Form.Count != newContext.Request.Form.Count)
     {
         newContext.Request.Form = new Microsoft.AspNetCore.Http.FormCollection(httpContext.Request.Form.ToDictionary(f => f.Key, f => f.Value));
     }

     return newContext;            
}
回忆躺在深渊里 2024-08-12 19:16:54

不可能

我想由于服务器会话状态,实际的深度克隆是不可能的。克隆还必须克隆此值,该值是 Web 服务器特定的内部资源,本质上是静态的且无法克隆。在这种情况下,Web 服务器将具有多个 Session 对象。

解决方法
反正。解决方法是在实例化子控制器处理之前设置额外的上下文值。处理完成后,我将值恢复为原始值。所以我实际上有像以前一样的背景。

Not possible

I guess an actual deep cloning is not possible because of server session state. Cloning would also have to clone this value, which is web server specific internal resource that is intrinsically static and can not be cloned. In this case a web server would have multiple Session objects for instance.

Workaround
Anyway. The workaround was to set additional context values before instantiating sub-controller processing. After processing is finished I reverted values back to original. So I actually had context as it was before.

风筝有风,海豚有海 2024-08-12 19:16:54

ASP.NET MVC 框架有意使抽象类的依赖关系与所有成员都是虚拟的。简而言之就是——可扩展性。

控制器依赖于 HttpContextBase,而不是 HttpContext。也许您可以让您的子控制器也依赖于 HttpContextBase,以便您可以包装它。
只是我的2分钱。

The ASP.NET MVC framework intentionally makes dependencies to abstract classes with all members virtual. That simply says - extensibility.

Controllers depend on HttpContextBase, not HttpContext. Perhaps you can make your sub-controllers depend on HttpContextBase too so you can wrap it.
Just my 2 cents.

弃爱 2024-08-12 19:16:54

我的使用

<% Html.RenderAction("Action", "Controller"); %>

效果非常好,使我能够创建完全隔离/封装的操作,而无需求助于复杂的代码。这似乎提供了相同的功能,但没有相同的复杂性。

渲染的视图是标准的部分视图,控制器操作就像任何其他视图一样。

I've used

<% Html.RenderAction("Action", "Controller"); %>

to great effect, allowing me to create completely isolated/escapsulated actions without resorting to complex code. This would seem to offer the same functionality without the same complexity.

The rendered views are standard partial views and the controller actions just like any other.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文