【.NET Core框架】HttpContext【转】

介绍

ttpContext是ASP.NET中的核心对象,每一个请求都会创建一个对应的HttpContext对象,我们的应用程序便是通过HttpContext对象来获取请求信息,最终生成响应,写回到HttpContext中,完成一次请求处理。

IHttpApplication

WebHost 在启动 IServer 时,会传入一个 IHttpApplication 类型的对象,Server 负责对请求的监听,在接收到请求时,会调用该对象的CreateContext方法创建HttpContext,调用ProcessRequestAsync方法将请求转交给我们的应用程序。IHttpApplication 的默认实现为 HostingApplication

public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
    private readonly RequestDelegate _application;
    private readonly IHttpContextFactory _httpContextFactory;

    public Context CreateContext(IFeatureCollection contextFeatures)
    {
        var context = new Context();
        var httpContext = _httpContextFactory.Create(contextFeatures);

        _diagnostics.BeginRequest(httpContext, ref context);

        context.HttpContext = httpContext;
        return context;
    }

    public Task ProcessRequestAsync(Context context)
    {
        return _application(context.HttpContext);
    }

    public void DisposeContext(Context context, Exception exception)
    {
        var httpContext = context.HttpContext;
        _diagnostics.RequestEnd(httpContext, exception, context);
        _httpContextFactory.Dispose(httpContext);
        _diagnostics.ContextDisposed(context);
    }
}

首先使用 IHttpContextFactory 来创建 HttpContext 实例,然后在 ProcessRequestAsync 方法中调用RequestDelegate,由此进入到我们的应用程序当中。
IHttpContextFactory 负责对 HttpContext 的创建和释放,分别对应着Create和Dispose方法,它的默认实现类为HttpContextFactory,定义如下:

public class HttpContextFactory : IHttpContextFactory
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly FormOptions _formOptions;

    public HttpContext Create(IFeatureCollection featureCollection)
    {
        var httpContext = new DefaultHttpContext(featureCollection);
        if (_httpContextAccessor != null)
        {
            _httpContextAccessor.HttpContext = httpContext;
        }

        var formFeature = new FormFeature(httpContext.Request, _formOptions);
        featureCollection.Set<IFormFeature>(formFeature);

        return httpContext;
    }

    public void Dispose(HttpContext httpContext)
    {
        if (_httpContextAccessor != null)
        {
            _httpContextAccessor.HttpContext = null;
        }
    }
}

如上,HttpContextFactory 只是简单的使用 new DefaultHttpContext(featureCollection) 来创建 HttpContext 的实例,而这里涉及到一个 IFeatureCollection 对象,它是由 Server 根据原始请求创建而来的,下面就先介绍一下该对象。

IFeatureCollection

它本质上也是一个 IDictionary<Type,object> 键值对,它是由Server构建,不同的Server对应不同的IFeatureCollection实现类

public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
{
    bool IsReadOnly { get; }
    int Revision { get; }
    object this[Type key] { get; set; }
    TFeature Get<TFeature>();
    void Set<TFeature>(TFeature instance);
}

IFeatureCollection内部包含一一系列的特性对象,以下是请求(Request)特性的定义:

public interface IHttpRequestFeature
{
    string Protocol { get; set; }
    string Scheme { get; set; }
    string Method { get; set; }
    string PathBase { get; set; }
    string Path { get; set; }
    string QueryString { get; set; }
    string RawTarget { get; set; }
    IHeaderDictionary Headers { get; set; }
    Stream Body { get; set; }
}

再看一下表单特性的定义:

public interface IFormFeature
{
    bool HasFormContentType { get; }
    IFormCollection Form { get; set; }
    IFormCollection ReadForm();
    Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);
}

HttpContext就是由这些特性对象构建,我们可以通过这些特性接口定义的属性来获取到原始上下文中描述的信息,并通过特性对象提供的方法来操作原始上下文,它就像Web Server与我们的应用程序之间的桥梁,完成抽象和具体之间的转换。
ASP.NET Core 提供了一系列丰富的特性对象,如 Session, Cookies, Query, Form, WebSocket, Request, Response 等等, 更详细的列表可以查看:Microsoft.AspNetCore.Http.Features

HttpContext

HttpContext 用来表示一个抽象的HTTP上下文,而HttpContext对象的核心又体现在用于描述请求的Request和描述响应的Response属性上。除此之外,它还包含一些与当前请求相关的其他上下文信息,如描述当前HTTP连接的ConnectionInfo对象,控制WebSocket的WebSocketManager,代表当前用户的ClaimsPrincipal对象的Session,等等:

public abstract class HttpContext
{
    public abstract IFeatureCollection Features { get; }
    public abstract HttpRequest Request { get; }
    public abstract HttpResponse Response { get; }
    public abstract ConnectionInfo Connection { get; }
    public abstract WebSocketManager WebSockets { get; }
    public abstract ClaimsPrincipal User { get; set; }
    public abstract IDictionary<object, object> Items { get; set; }
    public abstract IServiceProvider RequestServices { get; set; }
    public abstract CancellationToken RequestAborted { get; set; }
    public abstract string TraceIdentifier { get; set; }
    public abstract ISession Session { get; set; }
    public abstract void Abort();
}
  • Features:特性对象集合
  • RequestAborted:如果用户在请求的过程中关闭浏览器,可以通过此属性终止响应
  • User:代表当前认证用户
  • Items:当需要对整个管道共享一些与当前上下文相关的数据,可以将它保存在 Items 字典中

HttpContext 的默认实现使用的是 DefaultHttpContext 类型 ,而 DefaultHttpContext 便是对上面介绍的 IFeatureCollection 对象的封装:

public class DefaultHttpContext : HttpContext
{
    private FeatureReferences<FeatureInterfaces> _features;

    private HttpRequest _request;
    private HttpResponse _response;

    public DefaultHttpContext(IFeatureCollection features)
    {
        Initialize(features);
    }

    public virtual void Initialize(IFeatureCollection features)
    {
        _features = new FeatureReferences<FeatureInterfaces>(features);
        _request = InitializeHttpRequest();
        _response = InitializeHttpResponse();
    }

    protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this);
}

如上,DefaultHttpContext通过 Initialize 来完成从 IFeatureCollection 到 HttpContext 的转换,而各个属性的转换又交给了它们自己。

HttpRequest

HttpRequest 可以用来获取到描述当前请求的各种相关信息,比如请求的协议(HTTP或者HTTPS)、HTTP方法、地址,以及该请求的请求头,请求体等:

public abstract class HttpRequest
{
    public abstract HttpContext HttpContext { get; }
    public abstract string Method { get; set; }
    public abstract string Scheme { get; set; }
    public abstract bool IsHttps { get; set; }
    public abstract HostString Host { get; set; }
    public abstract PathString PathBase { get; set; }
    public abstract PathString Path { get; set; }
    public abstract QueryString QueryString { get; set; }
    public abstract IQueryCollection Query { get; set; }
    public abstract string Protocol { get; set; }
    public abstract IHeaderDictionary Headers { get; }
    public abstract IRequestCookieCollection Cookies { get; set; }
    public abstract long? ContentLength { get; set; }
    public abstract string ContentType { get; set; }
    public abstract Stream Body { get; set; }
    public abstract bool HasFormContentType { get; }
    public abstract IFormCollection Form { get; set; }
    public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken());
}

HttpRequest是一个抽象类,它的默认实现是DefaultHttpRequest:

public class DefaultHttpRequest : HttpRequest
{
    private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
    private FeatureReferences<FeatureInterfaces> _features;
    
    public DefaultHttpRequest(HttpContext context)
    {
        Initialize(context);
    }

    public virtual void Initialize(HttpContext context)
    {
        _context = context;
        _features = new FeatureReferences<FeatureInterfaces>(context.Features);
    }

    private IHttpRequestFeature HttpRequestFeature => _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);

    public override string Method
    {
        get { return HttpRequestFeature.Method; }
        set { HttpRequestFeature.Method = value; }
    }

    public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
    {
        return FormFeature.ReadFormAsync(cancellationToken);
    }
}

在 DefaultHttpRequest 中,并没有额外的功能,它只是简单的与 IHttpRequestFeature 中的同名属性和方法做了一个映射

HttpResponse

在了解了表示请求的抽象类 HttpRequest 之后,我们再来认识一下与它对应的,用来描述响应的 HttpResponse 类型:

public abstract class HttpResponse
{
    private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
    private static readonly Func<object, Task> _disposeDelegate = disposable =>
    {
        ((IDisposable)disposable).Dispose();
        return Task.CompletedTask;
    };

    public abstract HttpContext HttpContext { get; }
    public abstract int StatusCode { get; set; }
    public abstract IHeaderDictionary Headers { get; }
    public abstract Stream Body { get; set; }
    public abstract long? ContentLength { get; set; }
    public abstract string ContentType { get; set; }
    public abstract IResponseCookies Cookies { get; }
    public abstract bool HasStarted { get; }
    public abstract void OnStarting(Func<object, Task> callback, object state);
    public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback);
    public abstract void OnCompleted(Func<object, Task> callback, object state);
    public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);
    public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback);
    public virtual void Redirect(string location) => Redirect(location, permanent: false);
    public abstract void Redirect(string location, bool permanent);
}

HttpResponse也是一个抽象类,我们使用它来输出对请求的响应,如设置HTTP状态码,Cookies,HTTP响应报文头,响应主体等,以及提供了一些将响应发送到客户端时的相关事件。
其 HasStarted 属性用来表示响应是否已开始发往客户端,在我们第一次调用 response.Body.WriteAsync 方法时,该属性便会被设置为 True。需要注意的是,一旦 HasStarted 设置为 true 后,便不能再修改响应头,否则将会抛出 InvalidOperationException 异常,也建议我们在HasStarted设置为true后,不要再对 Response 进行写入,因为此时 content-length 的值已经确定,继续写入可能会造成协议冲突。

HttpResponse 的默认实现为 DefaultHttpResponse ,它与 DefaultHttpRequest 类似,只是对 IHttpResponseFeature 的封装,不过 ASP.NET Core 也为我们提供了一些扩展方法,如:我们在写入响应时,通常使用的是 Response 的扩展方法 WriteAsync :

public static class HttpResponseWritingExtensions
{
    public static Task WriteAsync(this HttpResponse response, string text, CancellationToken cancellationToken = default(CancellationToken))
    {
        return response.WriteAsync(text, Encoding.UTF8, cancellationToken);
    }

    public static Task WriteAsync(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
    {
        byte[] data = encoding.GetBytes(text);
        return response.Body.WriteAsync(data, 0, data.Length, cancellationToken);
    }
}

ASP.NET Core 还为 Response 提供了用来一个清空响应头和响应体的扩展方法:

public static class ResponseExtensions
{
    public static void Clear(this HttpResponse response)
    {
        if (response.HasStarted)
        {
            throw new InvalidOperationException("The response cannot be cleared, it has already started sending.");
        }
        response.StatusCode = 200;
        response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = null;
        response.Headers.Clear();
        if (response.Body.CanSeek)
        {
            response.Body.SetLength(0);
        }
    }
}

还有比较常用的发送文件的扩展方法:SendFileAsync ,获取响应头的扩展方法:GetTypedHeaders 等等,就不再细说。

IHttpContextAccessor

在 ASP.NET 4.x 我们经常会通过 HttpContext.Current 来获取当前请求的 HttpContext 对象,而在 ASP.NET Core 中,HttpContext 不再有 Current 属性,并且在 ASP.NET Core 中一切皆注入,更加推荐使用注入的方式来获取实例,而非使用静态变量。因此,ASP.NET Core 提供了一个 IHttpContextAccessor 接口,用来统一获取当前请求的 HttpContext 实例的方式:

public interface IHttpContextAccessor
{
    HttpContext HttpContext { get; set; }
}
public class HttpContextAccessor : IHttpContextAccessor
{
    private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();

    public HttpContext HttpContext
    {
        get
        {
            return _httpContextCurrent.Value;
        }
        set
        {
            _httpContextCurrent.Value = value;
        }
    }
}

这里使用了一个 AsyncLocal 类型来保存 HttpContext 对象,可能很多人对 AsyncLocal 不太了解,这里就来介绍一下:

在.NET 4.5 中引用了 async await 等关键字,使我们可以像编写同步方法一样方便的来执行异步操作,因此我们的大部分代码都会使用异步。以往我们所使用的 ThreadLocal 在同步方法中没有问题,但是在 await 后有可能会创建新实的例(await 之后可能还交给之前的线程执行,也有可能是一个新的线程来执行),而不再适合用来保存线程内的唯一实例,因此在 .NET 4.6 中引用了 AsyncLocal 类型,它类似于 ThreadLocal,但是在 await 之后就算切换线程也仍然可以保持同一实例。我们知道在 ASP.NET 4.x 中,HttpContext的 Current 实例是通过 CallContext 对象来保存的,但是 ASP.NET Core 中不再支持CallContext,故使用 AsyncLocal 来保证线程内的唯一实例。

不过,ASP.NET Core 默认并没有注入 IHttpContextAccessor 对象,如果我们想在应用程序中使用它,则需要手动来注册:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

在上面介绍的HttpContextFactory类的构造函数中会注入IHttpContextAccessor实例,并为其HttpContext属性赋值,并在Dispose方法中将其设置为null。

如何在ASP.NET Core中使用HttpContext.Current?

大家在ASP.NET 中大量用 HttpContext.Current获取HttpContext ,现在ASP.NET Core只能通过注入IHttpContextAccessor使用HttpContext
不过如果你还是想用静态 HttpContext.Current ,降低迁移旧程序的成本,还是可以实现的。
新建一个静态 HttpContext 类

    public static class HttpContext
    {
        private static IHttpContextAccessor _accessor;

        public static Microsoft.AspNetCore.Http.HttpContext Current => _accessor.HttpContext;

        internal static void Configure(IHttpContextAccessor accessor)
        {
            _accessor = accessor;
        }
    }

然后接着再添加一个扩展类。

    public static class StaticHttpContextExtensions
    {
        public static void AddHttpContextAccessor(this IServiceCollection services)
        {
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

        public static IApplicationBuilder UseStaticHttpContext(this IApplicationBuilder app)
        {
            var httpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
            HttpContext.Configure(httpContextAccessor);
            return app;
        }
    }

接着就可以在Startup 类中进行调用。
默认情况下如果在MVC项目中直接调用 UseStaticHttpContext() 即可。

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    app.UseStaticHttpContext();
}

在没有注入 HttpContextAccessor的项目中,还需在ConfigureServices 方法中调用

services.AddHttpContextAccessor();

然后就可以在其他地方使用HttpContext.Current。

public IActionResult Index()
{
    var statichttpcontext = HttpContextDemo.HttpContext.Current;
    return View();
}

这里只演示如何用,其实Controller 中可以直接使用base.HttpContext,ControllerBase类中有一个HttpContext 属性。

参考:
https://www.cnblogs.com/RainingNight/p/httpcontext-in-asp-net-core.html

posted @ 2020-08-25 09:32  .Neterr  阅读(535)  评论(0编辑  收藏  举报