I created a fresh ASP.NET Core Web API project. Here is ConfigureServices in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var cache = serviceProvider.GetService<IMemoryCache>();
cache.Set("key1", "value1");
//_cahce.Count is 1
}
As you see I add an item to IMemoryCache. Here is my controller:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly IMemoryCache _cache;
public ValuesController(IMemoryCache cache)
{
_cache = cache;
}
[HttpGet("{key}")]
public ActionResult<string> Get(string key)
{
//_cahce.Count is 0
if(!_cache.TryGetValue(key, out var value))
{
return NotFound($"The value with the {key} is not found");
}
return value + "";
}
}
When I request https://localhost:5001/api/values/key1, the cache is empty and I receive a not found response.
In short, the cache instance you're setting the value in is not the same as the one that is later being retrieved. You cannot do stuff like while the web host is being built (i.e. in ConfigureServices/Configure. If you need to do something on startup, you need to do it after the web host is built, in Program.cs:
public class Program
{
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var cache = host.Services.GetRequiredService<IMemoryCache>();
cache.Set("key1", "value1");
host.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
}
As #selloape saids, if you manullay call BuildServicesProvider, you are creating a new provider, that will be not used in your controllers.
You can use a hosted service to intialize your cache
public class InitializeCacheService : IHostedService
{
private readonly IServiceProvider _serviceProvider;
public InitializeCacheService (IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
using (var scope = _serviceProvider.CreateScope())
{
var cache = _serviceProvider.GetService<IMemoryCache>();
cache.Set("key1", "value1");
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Add it in your ConfigureServices
services.AddHostedService<InitializeCacheService>();
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2
Related
I need to access current HttpContext in a static method or a utility service.
With classic ASP.NET MVC and System.Web, I would just use HttpContext.Current to access the context statically. But how do I do this in ASP.NET Core?
HttpContext.Current doesn't exist anymore in ASP.NET Core but there's a new IHttpContextAccessor that you can inject in your dependencies and use to retrieve the current HttpContext:
public class MyComponent : IMyComponent
{
private readonly IHttpContextAccessor _contextAccessor;
public MyComponent(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public string GetDataFromSession()
{
return _contextAccessor.HttpContext.Session.GetString(*KEY*);
}
}
Necromancing.
YES YOU CAN
Secret tip for those migrating large junks chunks (sigh, Freudian slip) 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>()
);
The most legit way I came up with was by injecting IHttpContextAccessor in your static implementation as follow:
public static class HttpHelper
{
private static IHttpContextAccessor _accessor;
public static void Configure(IHttpContextAccessor httpContextAccessor)
{
_accessor = httpContextAccessor;
}
public static HttpContext HttpContext => _accessor.HttpContext;
}
Then assigning the IHttpContextAccessor in the Startup Configure should do the job.
HttpHelper.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
I guess you should also need to register the service singleton:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Just to add to the other answers...
In ASP.NET Core 2.1, there's the AddHttpContextAccessor extension method, that will register the IHttpContextAccessor with the correct lifetime:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
// Other code...
}
}
According to this article: Accessing HttpContext outside of framework components in ASP.NET Core
namespace System.Web
{
public static class HttpContext
{
private static IHttpContextAccessor _contextAccessor;
public static Microsoft.AspNetCore.Http.HttpContext Current => _contextAccessor.HttpContext;
internal static void Configure(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
}
}
Then:
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>();
System.Web.HttpContext.Configure(httpContextAccessor);
return app;
}
}
Then:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app)
{
app.UseStaticHttpContext();
app.UseMvc();
}
}
You can use it like this:
using System.Web;
public class MyService
{
public void DoWork()
{
var context = HttpContext.Current;
// continue with context instance
}
}
In Startup
services.AddHttpContextAccessor();
In Controller
public class HomeController : Controller
{
private readonly IHttpContextAccessor _context;
public HomeController(IHttpContextAccessor context)
{
_context = context;
}
public IActionResult Index()
{
var context = _context.HttpContext.Request.Headers.ToList();
return View();
}
}
To access to the session object from a class without explicitly use dependency injection in class constructor follow the next steps:
Add a Singleton instance on Startup.cs (ConfigureServices):
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your target class declare an instance of HttpContextAccessor:
IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
Access to the session object :
string mySessionVar = _httpContextAccessor.HttpContext.Session.GetString("_MySessionVar");
EXAMPLE
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
YourClass.cs
public class YourClass {
public string yourProperty {
get{
IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
return _httpContextAccessor.HttpContext.Session.GetString("_YourSessionVar");
}
}
}
Enjoy :)
I have been trying to setup my component instance per each tenant using InstancePerTenant. However, the InstancePerTenant somehow behave like a SingleInstance. Is there something wrong with my setup?
I have this ContainerBuilder extension which configure the multitenant related dependencies.
public static ContainerBuilder AddMultitenancy(this ContainerBuilder builder)
{
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
builder.RegisterType<TenantStore>().As<ITenantStore>().SingleInstance();
builder.RegisterType<TenantResolverStrategy>().As<ITenantIdentificationStrategy>().SingleInstance();
return builder
}
The tenant is identified by hostname:
public class TenantResolverStrategy : ITenantIdentificationStrategy
{
private readonly IHttpContextAccessor httpContextAccessor;
public TenantResolverStrategy(
IHttpContextAccessor httpContextAccessor
)
{
this.httpContextAccessor = httpContextAccessor;
}
public bool TryIdentifyTenant(out object tenantId)
{
// hostname is the tenantId
tenantId = httpContextAccessor.HttpContext?.Request?.Host.Value;
return (tenantId != null || tenantId == (object)"");
}
}
TenantStore is just a class to resolve the tenant entity from database based on the tenantId (hostname)
public class TenantStore : ITenantStore
{
private readonly ITenantIdentificationStrategy tenantIdentificationStrategy;
private readonly MemoryCacheStore cacheStore;
private readonly ITenantService tenantService;
public TenantStore(
ITenantIdentificationStrategy tenantIdentificationStrategy,
MemoryCacheStore cacheStore,
ITenantService tenantService
)
{
this.tenantIdentificationStrategy = tenantIdentificationStrategy;
this.cacheStore = cacheStore;
this.tenantService = tenantService;
}
public async Task<TenantEntity> GetTenantAsync(object tenantId)
{
var hostName = (string)tenantId;
var tenant = cacheStore.Get<TenantEntity>(CacheType.Tenant, hostName);
if (tenant == null)
{
tenant = await tenantService.GetTenantByHostNameFromDatabaseAsync(hostName);
cacheStore.Set(tenant, CacheType.Tenant, hostName);
}
return tenant ?? new TenantEntity();
}
}
In Startup.cs, I am registering TenantSpecific with InstancePerTenant:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.AddMultitenancy();
builder.RegisterType<TenantSpecific>().As<ITenantSpecific>().InstancePerTenant();
}
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container
{
var strategy = container.Resolve<ITenantIdentificationStrategy>();
var multitenantContainer = new MultitenantContainer(strategy, container);
// Nothing important here
multitenantContainer.RegisterMultitenantSpecificStuff();
return multitenantContainer;
}
TenantSpecific.cs and TenantSpecificController.cs:
public class TenantSpecific
{
public Guid Id { get; set; }
public TenantSpecific()
{
this.Id = Guid.NewGuid();
}
}
public class TenantSpecificController : ApiController
{
private readonly ITenantSpecific tenantSpecific;
public TenantSpecificController(ITenantSpecific tenantSpecific)
{
this.tenantSpecific = tenantSpecific;
}
[HttpGet]
public IActionResult Get()
{
return Ok(tenantSpecific.Id);
}
}
In Program.cs
public static IHostBuilder CreateHostBuilder(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
.ConfigureWebHostDefaults(webHostBuilder =>
{
webHostBuilder
.UseConfiguration(ConfigurationModule.GetConfiguration())
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>();
});
return host;
}
When I invoke http://tenant1.localhost/tenant-specific/ and http://tenant2.localhost/tenant-specific, the constructor of TenantSpecific is only called once like Singleton. The tenantSpecific.Id returns the same value. So I assume InstancePerTenant is not working here.
Is there something wrong with my setup?
As written in the documentation ASP.net core multitenant support
You should add a call to AddAutofacMultitenantRequestServices() to add the required middleware to the root container which is required for multitenancy to work.
public void ConfigureServices(IServiceCollection services)
{
// This will all go in the ROOT CONTAINER and is NOT TENANT SPECIFIC.
services.AddMvc();
services.AddControllers();
// This adds the required middleware to the ROOT CONTAINER and
// is required for multitenancy to work.
services.AddAutofacMultitenantRequestServices();
}
I need to access current HttpContext in a static method or a utility service.
With classic ASP.NET MVC and System.Web, I would just use HttpContext.Current to access the context statically. But how do I do this in ASP.NET Core?
HttpContext.Current doesn't exist anymore in ASP.NET Core but there's a new IHttpContextAccessor that you can inject in your dependencies and use to retrieve the current HttpContext:
public class MyComponent : IMyComponent
{
private readonly IHttpContextAccessor _contextAccessor;
public MyComponent(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public string GetDataFromSession()
{
return _contextAccessor.HttpContext.Session.GetString(*KEY*);
}
}
Necromancing.
YES YOU CAN
Secret tip for those migrating large junks chunks (sigh, Freudian slip) 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>()
);
The most legit way I came up with was by injecting IHttpContextAccessor in your static implementation as follow:
public static class HttpHelper
{
private static IHttpContextAccessor _accessor;
public static void Configure(IHttpContextAccessor httpContextAccessor)
{
_accessor = httpContextAccessor;
}
public static HttpContext HttpContext => _accessor.HttpContext;
}
Then assigning the IHttpContextAccessor in the Startup Configure should do the job.
HttpHelper.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
I guess you should also need to register the service singleton:
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Just to add to the other answers...
In ASP.NET Core 2.1, there's the AddHttpContextAccessor extension method, that will register the IHttpContextAccessor with the correct lifetime:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
// Other code...
}
}
According to this article: Accessing HttpContext outside of framework components in ASP.NET Core
namespace System.Web
{
public static class HttpContext
{
private static IHttpContextAccessor _contextAccessor;
public static Microsoft.AspNetCore.Http.HttpContext Current => _contextAccessor.HttpContext;
internal static void Configure(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
}
}
Then:
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>();
System.Web.HttpContext.Configure(httpContextAccessor);
return app;
}
}
Then:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
}
public void Configure(IApplicationBuilder app)
{
app.UseStaticHttpContext();
app.UseMvc();
}
}
You can use it like this:
using System.Web;
public class MyService
{
public void DoWork()
{
var context = HttpContext.Current;
// continue with context instance
}
}
In Startup
services.AddHttpContextAccessor();
In Controller
public class HomeController : Controller
{
private readonly IHttpContextAccessor _context;
public HomeController(IHttpContextAccessor context)
{
_context = context;
}
public IActionResult Index()
{
var context = _context.HttpContext.Request.Headers.ToList();
return View();
}
}
To access to the session object from a class without explicitly use dependency injection in class constructor follow the next steps:
Add a Singleton instance on Startup.cs (ConfigureServices):
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
In your target class declare an instance of HttpContextAccessor:
IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
Access to the session object :
string mySessionVar = _httpContextAccessor.HttpContext.Session.GetString("_MySessionVar");
EXAMPLE
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
YourClass.cs
public class YourClass {
public string yourProperty {
get{
IHttpContextAccessor _httpContextAccessor = new HttpContextAccessor();
return _httpContextAccessor.HttpContext.Session.GetString("_YourSessionVar");
}
}
}
Enjoy :)
Let's assume that we have the following Program.cs:
public static class Program
{
public static async Task Main(string[] args)
{
await CreateWebHostBuilder(args).Build().RunAsync();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost
.CreateDefaultBuilder(args)
.ConfigureKestrel(options => { options.AllowSynchronousIO = false; })
.ConfigureFanciness()
.ConfigureLogging(ConfigureApplicationLogging)
.UseStartup<Startup>();
}
private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
var loggingConfiguration = context.Configuration.GetSection("Logging");
loggingBuilder.AddConfiguration(loggingConfiguration);
// var fancyService = SomehowGet<IFancyService>();
// if (fancyService.IsEnabled)
// loggingBuilder.AddEventLog(loggingConfiguration);
}
public static IWebHostBuilder ConfigureFanciness(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(delegate (WebHostBuilderContext context, IServiceCollection services)
{
var fancinessConfiguration = context.Configuration.GetSection("Fanciness");
services.Configure<FancinessSettings>(fancinessConfiguration);
services.AddSingleton<IFancyService, FancyService>();
// var fancyService = SomehowGet<IFancyService>();
// fancyService.Initialize();
});
}
}
And the following FancyService.cs:
public sealed class FancyService : IFancyService
{
private readonly ILogger<FancyService> logger;
private readonly IOptions<FancinessSettings> settings;
public FancyService(ILogger<FancyService> logger, IOptions<FancinessSettings> settings)
{
this.logger = logger;
this.settings = settings;
}
public bool IsEnabled { get; private set; }
public void Initialize()
{
// do the initialization work and set IsEnabled to some value
}
}
As demonstrated in the example files, loggingBuilder.AddEventLog(loggingConfiguration) depends on IFancyService.IsEnabled that is set when IFancyService.Initialize() is called.
To do that, I need access to an instance of IFancyService; is there a way to achieve this?
Once services.AddSingleton<IFancyService, FancyService>() has been called, you can get the instance of FancyService by building a ServiceProvider:
private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
var fancyService = loggingBuilder.Services.BuildServiceProvider().GetService<IFancyService>();
fancyService.Initialize();
var loggingConfiguration = context.Configuration.GetSection("Logging");
loggingBuilder.AddConfiguration(loggingConfiguration);
if (fancyService.IsEnabled)
loggingBuilder.AddEventLog(loggingConfiguration);
}
As discussed in comments, if your FancyService has no constructor dependency on ILogger<> :
public sealed class FancyService : IFancyService
{
public FancyService(IOptions<FancinessSettings> settings)
{ ... }
to initialize the IFancyService, simply use an implementation factory to create a IFancyService instance:
public static IWebHostBuilder ConfigureFanciness(this IWebHostBuilder hostBuilder)
{
return hostBuilder.ConfigureServices(delegate (WebHostBuilderContext context, IServiceCollection services)
{
var fancinessConfiguration = context.Configuration.GetSection("Fanciness");
services.Configure<FancinessSettings>(fancinessConfiguration);
services.AddSingleton<IFancyService, FancyService>(sp =>{
var fancy=ActivatorUtilities.CreateInstance(sp,typeof(FancyService)) as FancyService;
fancy.Initialize();
return fancy;
});
});
}
The same trick could also be used to register a logger provider. Since an ILogger<> service depends on IEnumerable<ILoggerProvider>, we could register an ILoggerProvider instance that provides an additional INullLogger in order to configure the logging behavior according to the current IFancyService :
private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
var loggingConfiguration = context.Configuration.GetSection("Logging");
var descriptor=ServiceDescriptor.Singleton<ILoggerProvider,NullLoggerProvider>(sp =>{
var provider = NullLoggerProvider.Instance;
var fancy = sp.GetRequiredService<IFancyService>();
if(fancy.IsEnabled)
{
loggingBuilder.AddDebug();
loggingBuilder.AddEventLog(loggingConfiguration);
loggingBuilder.AddEventSourceLogger();
// ... add more configuration as you like
}else{
loggingBuilder.AddConsole();
}
return provider;
});
loggingBuilder.Services.TryAddEnumerable(descriptor);
loggingBuilder.AddConfiguration(loggingConfiguration);
}
As a side note, be aware once the logger has been built, don't change the IFancyService.IsEnabled. That's because the ILogger<> service is a registered as a singleton and never changed once created.
I want to access JwtHelper from ExceptionHelper. But problem is ExceptionHelper must be static. And so, we can't create constructor and not access jwtHelper Method. How can I achieve access jwHelper from ExcewptionHelper.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddMvc();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDbContext<MyDbContext>();
services.AddTransient<IUnitOfWork, UnitOfWork>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
context.Response.AddApplicationError(error);
await context.Response.WriteAsync(error.Error.Message);
}));
app.UseHttpsRedirection();
app.UseMvc();
}
ExceptionHelper.cs
public static class ExceptionHelper
{
public static async Task AddApplicationError(this HttpResponse response)
{
Log log = new Log();
log.UserId = jwtHelper.GetValueFromToken(token, "UserId");??????
//in this line I can't access jwtHelper.
}
}
JwtHelper.cs
public class JwtHelper : IJwtHelper
{
private readonly IHttpContextAccessor httpContextAccessor;
public JwtHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetValueFromToken(string stream, string propertyName)
{
var jwt = httpContextAccessor.HttpContext.Request.Headers["Authorization"];
var handler = new JwtSecurityTokenHandler();
var tokens = handler.ReadToken(stream.Replace("Bearer ", "")) as JwtSecurityToken;
return tokens.Claims.FirstOrDefault(claim => claim.Type == propertyName).Value;
}
}
If I were you I would register JwtHelper with a Interface known as IJwtHelper.
It would look like this then
public class JwtHelper : IJwtHelper
{
private readonly IHttpContextAccessor httpContextAccessor;
public JwtHelper(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetValueFromToken(string propertyName)
{
var jwt= httpContextAccessor.HttpContext.Request.Headers["Authorization"];
// I can't access httpContextAccessor in this line.
var handler = new JwtSecurityTokenHandler();
var tokens = handler.ReadToken(jwt) as JwtSecurityToken;
return tokens.Claims.FirstOrDefault(claim => claim.Type == propertyName).Value;
}
}
public interface IJwtHelper
{
string GetValueFromToken(string propertyName);
}
In my startup.cs class I would then do
services.AddSingleton<IJwtHelper, JwtHelper>();
And then when you want to access your helper I would inject IJwtHelper
private IJwtHelper _jwtHelper;
public SomeConstructerOnClass(IJwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
public void SomeMethod(string property) {
var token = _jwtHelper.GetValueFromToken(property);
//Do something with token
}
where _jwtHelper is field of type IJwtHelper.
You will then be able to use GetValueFromToken quite fine anywhere you inject IJwtHelper
UPDATE
Your problem is that ExceptionHandler is Static , implement an interface and add it to container
public class ExceptionHelper : IExceptionHelper
{
private IJwtHelper _jwtHelper;
public ExceptionHelper(IJwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
public async Task AddApplicationError(this HttpResponse response)
{
Log log = new Log();
log.UserId = _jwtHelper.GetValueFromToken(token, "UserId");??????
}
}
public interface IExceptionHelper
{
Task AddApplicationError( HttpResponse response);
}
Then
services.AddSingleton<IExceptionHelper, ExceptionHelper>();
Now You will be able to inject it into your Configure method like so
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IExceptionHelper exceptionHelper)
{
app.UseExceptionHandler(builder => builder.Run(async context =>
{
var error = context.Features.Get<IExceptionHandlerFeature>();
//Resolved and available!
exceptionHelper.AddApplicationError(error);
await context.Response.WriteAsync(error.Error.Message);
}));
app.UseHttpsRedirection();
app.UseMvc();
}
If you follow me advice above from my initial response and my update everything should be fine and registered nicely in your container :)
You'll have to instantiate the JwtHelper class in order to access the instance variable (httpContextAccessor) from another class. Static methods, like GetValueFromToken, cannot access instance variables.