300

We are currently rewriting/converting our ASP.NET WebForms application using ASP.NET Core. Trying to avoid re-engineering as much as possible.

There is a section where we use HttpContext in a class library to check the current state. How can I access HttpContext.Current in .NET Core 1.0?

 var current = HttpContext.Current;
     if (current == null)
      {
       // do something here
       // string connection = Configuration.GetConnectionString("MyDb");
      }

I need to access this in order to construct current application host.

$"{current.Request.Url.Scheme}://{current.Request.Url.Host}{(current.Request.Url.Port == 80 ? "" : ":" + current.Request.Url.Port)}";
abatishchev
  • 98,240
  • 88
  • 296
  • 433
HaBo
  • 13,999
  • 36
  • 114
  • 206
  • 3
    First ask yourself, why do you need to access it from a class library. 80% of the cases, there is no reason and just bad design to even try to. In your case above you don't. Change the design of your class library, pass in the parameters you need rather then access from it. Less coupling, better maintainable code. Look into `IOptions` – Tseng Jul 25 '16 at 15:41
  • @Tseng lets say I want to pass it. Then what am I supposed to pass? That is the whole question here. what is the namespace or object that is equivalent to HttpContext in ASP.NET Core – HaBo Jul 25 '16 at 15:43
  • read your question, and realize that this is not the question and it barely says anything. With such little information its hard to provide. Second, read my first comment for the right direction – Tseng Jul 25 '16 at 15:48
  • 9
    @Tseng: in your comment you didn't indicate why you thought it was a bad question. It was about why the person shouldn't be trying to do what he's trying to do. People use `HttpContext.Current` everywhere in ASP.NET whether they should or not. I don't see the great harm in asking what the equivalent is in ASP.NET Core. The answer may be that there is none or there's a different/better way. – Scott Hannen Jul 25 '16 at 16:12
  • What is it that you're actually trying to get from the HttpContext? – Scott Hannen Jul 25 '16 at 16:13
  • Get Current Host as this $"{current.Request.Url.Scheme}://{current.Request.Url.Host}{(current.Request.Url.Port == 80 ? "" : ":" + current.Request.Url.Port)}"; – HaBo Jul 25 '16 at 16:14

3 Answers3

401

As a general rule, converting a Web Forms or MVC5 application to ASP.NET Core will require a significant amount of refactoring.

HttpContext.Current was removed in ASP.NET Core. Accessing the current HTTP context from a separate class library is the type of messy architecture that ASP.NET Core tries to avoid. There are a few ways to re-architect this in ASP.NET Core.

HttpContext property

You can access the current HTTP context via the HttpContext property on any controller. The closest thing to your original code sample would be to pass HttpContext into the method you are calling:

public class HomeController : Controller
{
    public IActionResult Index()
    {
        MyMethod(HttpContext);

        // Other code
    }
}

public void MyMethod(Microsoft.AspNetCore.Http.HttpContext context)
{
    var host = $"{context.Request.Scheme}://{context.Request.Host}";

    // Other code
}

HttpContext parameter in middleware

If you're writing custom middleware for the ASP.NET Core pipeline, the current request's HttpContext is passed into your Invoke method automatically:

public Task Invoke(HttpContext context)
{
    // Do something with the current HTTP context...
}

HTTP context accessor

Finally, you can use the IHttpContextAccessor helper service to get the HTTP context in any class that is managed by the ASP.NET Core dependency injection system. This is useful when you have a common service that is used by your controllers.

Request this interface in your constructor:

public MyMiddleware(IHttpContextAccessor httpContextAccessor)
{
    _httpContextAccessor = httpContextAccessor;
}

You can then access the current HTTP context in a safe way:

var context = _httpContextAccessor.HttpContext;
// Do something with the current HTTP context...

IHttpContextAccessor isn't always added to the service container by default, so register it in ConfigureServices just to be safe:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();
    // if < .NET Core 2.2 use this
    //services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    // Other code...
}
Richard Garside
  • 87,839
  • 11
  • 80
  • 93
Nate Barbettini
  • 51,256
  • 26
  • 134
  • 147
  • 8
    You forgot about `IHttpContextAccessor`, but since RC2 it's not registered by default since it brings some per request overhead. See the announcement https://github.com/aspnet/Announcements/issues/190 – Tseng Jul 25 '16 at 18:57
  • @Tseng Agreed, it's also possible with `IHttpContextAccessor`. I'll update my answer. – Nate Barbettini Jul 25 '16 at 19:00
  • 10
    Is it ok that it's Singleton? Should that be AddScoped instead? – syclee Mar 06 '17 at 03:41
  • 3
    @syclee I was wondering about that too, but according to the link above (and also the [basic MVC sample app](https://github.com/aspnet/Mvc/blob/master/test/WebSites/BasicWebSite/Startup.cs#L25)), the syntax in my answer is correct. – Nate Barbettini Mar 06 '17 at 23:54
  • 3
    I think accesser can be singleton... It is just something what allow us to get context... So result of access (context) is usual per request value. – Maxim May 13 '17 at 09:32
  • https://pfelix.wordpress.com/2016/11/01/accessing-the-http-context-on-asp-net-core/ as what @Maxim said, the accessor will be singleton but the context will be specific to each of the request – muhihsan Aug 03 '17 at 04:56
  • `httpContextAccessor.HttpContext = null` :( ? – Piotr Kula Dec 29 '17 at 16:15
  • 1
    @ppumkin Are you registering the `IHttpContextAccessor` in your Startup class? – Nate Barbettini Dec 29 '17 at 19:58
  • 2
    Yes.. but the problem is I was doing this in the Middleware... and that particular class is Injected.. but the context is not set yet.. But that is not a big problem.. because the middleware passes the context that is being built in.. so I rejigged my code to work with a HtttpContext so that I can pass it from anywhere and not depends soley on `IHttpContextAccessor` - Just one of those edge cases.. – Piotr Kula Jan 02 '18 at 13:58
  • 4
    Is there no simple way like in .NET Framework HttpContext.Current and why are they removing great features like that? Now I need to pas it everywhere I need it and I need it in a lot of classes. – Hrvoje Batrnek Oct 06 '18 at 23:09
  • 1
    @Hove Because removing it forces everyone to adopt better coding practices. Explicitly passing dependencies around is more verbose, but also far more testable and promotes a better separation of concerns. – Nate Barbettini Nov 29 '18 at 05:26
  • 5
    @Nate Barbettini Anywhere in the code you are in this current HttpContext and there is no benefit of passing it around, I am for simplicity and functionality, I don't see a downside of it, passing it just make me work more giving me no benefits. – Hrvoje Batrnek Nov 29 '18 at 09:10
  • 1
    See https://stackoverflow.com/a/38186047/3787891 – Assaf S. Apr 08 '19 at 11:02
78

Necromancing.
YES YOU CAN, and this is how.
A secret tip for those migrating large junks chunks of code:
The following method is an evil carbuncle of a hack which is actively engaged in carrying out the express work of satan (in the eyes of .NET Core framework developers), but it works:

In public class Startup

add a property

public IConfigurationRoot Configuration { get; }

And then add a singleton IHttpContextAccessor to DI in ConfigureServices.

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();

Then in Configure

    public void Configure(
              IApplicationBuilder app
             ,IHostingEnvironment env
             ,ILoggerFactory loggerFactory
    )
    {

add the DI Parameter IServiceProvider svp, so the method looks like:

    public void Configure(
           IApplicationBuilder app
          ,IHostingEnvironment env
          ,ILoggerFactory loggerFactory
          ,IServiceProvider svp)
    {

Next, create a replacement class for System.Web:

namespace System.Web
{

    namespace Hosting
    {
        public static class HostingEnvironment 
        {
            public static bool m_IsHosted;

            static HostingEnvironment()
            {
                m_IsHosted = false;
            }

            public static bool IsHosted
            {
                get
                {
                    return m_IsHosted;
                }
            }
        }
    }


    public static class HttpContext
    {
        public static IServiceProvider ServiceProvider;

        static HttpContext()
        { }


        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                // var factory2 = ServiceProvider.GetService<Microsoft.AspNetCore.Http.IHttpContextAccessor>();
                object factory = ServiceProvider.GetService(typeof(Microsoft.AspNetCore.Http.IHttpContextAccessor));

                // Microsoft.AspNetCore.Http.HttpContextAccessor fac =(Microsoft.AspNetCore.Http.HttpContextAccessor)factory;
                Microsoft.AspNetCore.Http.HttpContext context = ((Microsoft.AspNetCore.Http.HttpContextAccessor)factory).HttpContext;
                // context.Response.WriteAsync("Test");

                return context;
            }
        }


    } // End Class HttpContext 


}

Now in Configure, where you added the IServiceProvider svp, save this service provider into the static variable "ServiceProvider" in the just created dummy class System.Web.HttpContext (System.Web.HttpContext.ServiceProvider)

and set HostingEnvironment.IsHosted to true

System.Web.Hosting.HostingEnvironment.m_IsHosted = true;

this is essentially what System.Web did, just that you never saw it (I guess the variable was declared as internal instead of public).

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

    ServiceProvider = svp;
    System.Web.HttpContext.ServiceProvider = svp;
    System.Web.Hosting.HostingEnvironment.m_IsHosted = true;


    app.UseCookieAuthentication(new CookieAuthenticationOptions()
    {
        AuthenticationScheme = "MyCookieMiddlewareInstance",
        LoginPath = new Microsoft.AspNetCore.Http.PathString("/Account/Unauthorized/"),
        AccessDeniedPath = new Microsoft.AspNetCore.Http.PathString("/Account/Forbidden/"),
        AutomaticAuthenticate = true,
        AutomaticChallenge = true,
        CookieSecure = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest

       , CookieHttpOnly=false

    });

Like in ASP.NET Web-Forms, you'll get a NullReference when you're trying to access a HttpContext when there is none, such as it used to be in Application_Start in global.asax.

I stress again, this only works if you actually added

services.AddSingleton<Microsoft.AspNetCore.Http.IHttpContextAccessor, Microsoft.AspNetCore.Http.HttpContextAccessor>();

like I wrote you should.
Welcome to the ServiceLocator pattern within the DI pattern ;)
For risks and side effects, ask your resident doctor or pharmacist - or study the sources of .NET Core at github.com/aspnet, and do some testing.


Perhaps a more maintainable method would be adding this helper class

namespace System.Web
{

    public static class HttpContext
    {
        private static Microsoft.AspNetCore.Http.IHttpContextAccessor m_httpContextAccessor;


        public static void Configure(Microsoft.AspNetCore.Http.IHttpContextAccessor httpContextAccessor)
        {
            m_httpContextAccessor = httpContextAccessor;
        }


        public static Microsoft.AspNetCore.Http.HttpContext Current
        {
            get
            {
                return m_httpContextAccessor.HttpContext;
            }
        }


    }


}

And then calling HttpContext.Configure in Startup->Configure

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IServiceProvider svp)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();


    System.Web.HttpContext.Configure(app.ApplicationServices.
        GetRequiredService<Microsoft.AspNetCore.Http.IHttpContextAccessor>()
    );
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
15

There is a solution to this if you really need a static access to the current context. In Startup.Configure(….)

app.Use(async (httpContext, next) =>
{
    CallContext.LogicalSetData("CurrentContextKey", httpContext);
    try
    {
        await next();
    }
    finally
    {
        CallContext.FreeNamedDataSlot("CurrentContextKey");
    }
});

And when you need it you can get it with :

HttpContext context = CallContext.LogicalGetData("CurrentContextKey") as HttpContext;

I hope that helps. Keep in mind this workaround is when you don’t have a choice. The best practice is to use de dependency injection.

Hicham
  • 766
  • 7
  • 20
  • This doesn't work for me, should I be able to use this line in a refernced class library? HttpContext context = CallContext.LogicalGetData("CurrentContextKey") as HttpContext; context is just set to null when I do – Paul May 05 '17 at 15:22
  • You can use it in a library. but i think you just used the System.Web.HttpContextclass, it is not the same class as aspnet core http context (Microsoft.AspNetCore.Http.HttpContext) – Hicham May 06 '17 at 19:04