I have a custom middleware from which I want to add a scoped dependency.
public class MyMiddleware {
private readonly RequestDelegate _next;
public MyMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext,
IOptionsSnapshot<ApiClientHttpSettings> settings,
IServiceCollection services)
{
services.AddScoped<ICustomer>(new Customer());
await _next(httpContext);
}
}
So that I can get it inside controllers:
public class CustomerController : ControllerBase
{
public ControllerBase(ICustomer customer)
{
}
}
But in the middleware IServiceCollection cannot be resolved.
I want to do this because there is a lot of logic to resolve the DI involved.
I can also try to do inside ConfigureServices but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Any pointer to right direction is really appreciated.
I can also try to do inside ConfigureServices, but then I wont get access to IOptionsSnapshot<SupplyApiClientHttpSettings> settings I need for every request.
Here is how you can get access to IOptionsSnapshot inside a custom service. The full source is here in GitHub.
Create your settings class.
public class SupplyApiClientHttpSettings
{
public string SomeValue { get; set; }
}
Add a value for it in configuration (e.g. in appsettings.json).
{
"someValue": "Value from appsettings"
}
Define your service and inject IOptionsSnapshot into it.
public class CustomerService
{
private readonly SupplyApiClientHttpSettings settings;
public CustomerService(IOptionsSnapshot<SupplyApiClientHttpSettings> options)
{
this.settings = options.Value;
}
public Customer GetCustomer()
{
return new Customer
{
SomeValue = settings.SomeValue
};
}
}
Wire together your configuration, options, and service in Startup.
public class Startup
{
IConfiguration Configuration;
public Startup()
{
Configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SupplyApiClientHttpSettings>(Configuration);
services.AddScoped<CustomerService>();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
Inject the service into your controller. Use the service to get the customer with the up to date options snapshot.
public class CustomerController : Controller
{
private readonly CustomerService customerService;
public CustomerController(CustomerService customerService)
{
this.customerService = customerService;
}
public IActionResult Index()
{
return Json(customerService.GetCustomer());
}
}
Here is the full source in GitHub.
The answer was quite simple and close. Following is just what I had to do :
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ICustomer>(provider => {
var settings = Configuration.GetSection("ApiClientHttpSettings").Get<ApiClientHttpSettings>();
return new Customer(settings.Name, settings.Age);
});
}
The above ticks all the boxes for me :
New instance for each request
Able to read updated config at the time of request
Create instance according to custom logic
Related
I have an azure function with below structure
Startup.cs
[assembly: FunctionsStartup(typeof(Edos.DatabaseInit.Startup))]
namespace Edos.DatabaseInit;
public class Startup
{
private readonly IConfiguration configuration;
public Startup(IConfiguration configuration)
{
this.configuration = configuration;
}
public void ConfigureServices(IServiceCollection collection)
{
collection.AddInfrastructure(this.configuration);
}
}
Function1.cs
public class CosmosInit
{
private readonly IMessagingService _messagingService;
public CosmosInit(IMessagingService messagingService)
{
_messagingService = messagingService;
}
[FunctionName("CosmosInit")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
await _messagingService.PushToTopic("topic", "message");
log.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult("");
}
}
And in infrastructure project
DependencyInjection.cs
public static class DependencyInjection
{
public static IServiceCollection AddInfrastructure(this IServiceCollection services, IConfiguration configuration)
{
var serviceBusConnectionString = Environment.GetEnvironmentVariable("ServiceBusConnectionString");
if (string.IsNullOrEmpty(serviceBusConnectionString))
{
throw new InvalidOperationException(
"Please specify a valid ServiceBusConnectionString in the Azure Functions Settings or your local.settings.json file.");
}
//using AMQP as transport
services.AddSingleton((s) => {
return new ServiceBusClient(serviceBusConnectionString, new ServiceBusClientOptions() { TransportType = ServiceBusTransportType.AmqpWebSockets });
});
services.AddScoped<IMessagingService, MessagingService>();
return services;
}
}
MessagingService.cs
public class MessagingService: IMessagingService
{
private readonly ServiceBusClient _serviceBusClient;
public MessagingService(ServiceBusClient serviceBusClient)
{
_serviceBusClient = serviceBusClient;
}
// the sender used to publish messages to the topic
public async Task<int> PushToTopic(string topic, string serviceMessage)
{
var sender = _serviceBusClient.CreateSender(topic);
var message = new ServiceBusMessage(serviceMessage);
await sender.SendMessageAsync(message);
return 1;
}
}
IMessagingService.cs
public interface IMessagingService
{
Task<int> PushToTopic(string topic, string message);
}
As you can see I have the service bus client is injected into the MessageService.
And in the Function startup as well I have added the infrastructure collection.
Thus I am getting the PushToTopic method within the function as well.
But whenever I am trying to call the function it triggers the below error
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'DataFoundation.Infrastructure.Services.IMessagingService' while attempting to activate 'DatabaseInit.CosmosInit'.
Please share your comments.. It took my whole day..
Your Startup class should extend FunctionsStartup class. And you should be overriding its Configure method instead.
Like so:
[assembly: FunctionsStartup(typeof(Edos.DatabaseInit.Startup))]
namespace Edos.DatabaseInit;
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var configuration = builder.GetContext().Configuration;
builder.Services.AddInfrastructure(configuration);
}
}
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 a multi-tenant ASP.NET Core web application. The current tenancy model is every tenant has a separate web app and SQL database. I'm trying to rearchitect it so that multiple tenants will be served by a single web app (but maintaining a separate database per tenant). I've been following this series of blog posts but I've hit a bit of a roadblock with configuration.
The app makes heavy use of the ASP.NET Core configuration system, and has a custom EF Core provider that fetches config values from the database. I'd like to preserve this if possible, it would be an awful lot of work to rip out and replace with something else (dozens of config settings used in hundreds of places).
The existing code is very standard:
public class MyAppSettings
{
public string FavouriteColour { get; set; }
public int LuckyNumber { get; set; }
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<MyAppSettings>(Configuration.GetSection("MyAppSettings"));
// etc....
}
}
// custom EF Core config provider wired up in Program.Main, but that doesn't actually seem relevant
I've already updated our custom provider so that it fetches all configuration values from all known tenant databases, and adds them all to the configuration system, prefixed with a tenant identifier, so the list of all config values fetched from the n different databases might look something like this:
Key Value
===============================================
TenantABC:MyAppSettings:FavouriteColour Green
TenantABC:MyAppSettings:LuckyNumber 42
TenantDEF:MyAppsettings:FavouriteColour Blue
TenantDEF:MyAppSettings:LuckyNumber 37
...
TenantXYZ:MyAppSettings:FavouriteColour Yellow
TenantXYZ:MyAppSettings:LuckyNumber 88
What I'd like to be able to do is somehow customise the way that the configuration is bound so that it resolves the tenant for the current request, and then uses the appropriate values, e.g. a request on abc.myapp.com would observe config values "Green" and "42", etc, without having to change all the dependent places that inject IOptionsMonitor<AppSettings> (or IOptionsSnapshot, etc). The linked blog series has a post about configuration that covers some gotchas that I expect I'll eventually run into around caching etc, but it doesn't seem to cater for this scenario of using completely different settings for different tenants. Conceptually it seems simple enough, but I haven't been able to find the correct place to hook in. Please help!
Here is an idea (not tested yet, however). You can save the default IConfiguration instance passed to the constructor of your Startup class and then register in DI your own implementation of IConfiguration that will use that default one and HttpContextAccessor (to get the current tenant).
So the code will look something like:
public class Startup
{
private IConfiguration _defaultConfig;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
_defaultConfig = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
. . . .
services.AddScoped<IConfiguration>(serviceProvider => {
var httpContextAccessor =
serviceProvider.GetService<IHttpContextAccessor>();
return new MyConfig(_defaultConfig, httpContextAccessor);
});
}
. . . .
}
public class MyConfig : IConfiguration
{
private readonly IConfiguration _defaultConfig;
private readonly IHttpContextAccessor _httpContextAccessor;
public MyConfig(IConfiguration defaultConfig, IHttpContextAccessor httpContextAccessor)
{
_defaultConfig = defaultConfig;
_httpContextAccessor = httpContextAccessor;
}
public string this[string key] {
get {
var tenantId = GetTenantId();
return _defaultConfig[tenantId + ":" + key];
}
set {
var tenantId = GetTenantId();
_defaultConfig[tenantId + ":" + key] = value;
}
}
protected virtual string GetTenantId()
{
//this is just an example that supposes that you have "TenantId" claim associated with each user
return _httpContextAccessor.HttpContext.User.FindFirst("TenantId").Value; ;
}
public IEnumerable<IConfigurationSection> GetChildren()
{
return _defaultConfig.GetChildren();
}
public IChangeToken GetReloadToken()
{
return _defaultConfig.GetReloadToken();
}
public IConfigurationSection GetSection(string key)
{
var tenantId = GetTenantId();
return _defaultConfig.GetSection(tenantId + ":" + key);
}
}
Here are 3 solutions that may be helpful. I don't recommend you the IOptionsMonitor<T> because the tenant value is extracted from HttpContext, makes no sense to use the IOptionsMonitor.
Shared code:
public static class Extensions
{
public static string GetTenantName(this HttpContext context)
{
switch (context.Request.Host.Host)
{
case "abc.localhost.com":
return "TenantABC";
case "def.localhost.com":
return "TenantDEF";
default:
throw new IndexOutOfRangeException("Invalid host requested");
}
}
public static MyAppSettings GetAppSettingsByTenant(this IConfiguration config, string tenant)
{
return new MyAppSettings
{
LuckyNumber = int.Parse(config[$"{tenant}:MyAppSettings:LuckyNumber"]),
FavouriteColour = config[$"{tenant}:MyAppSettings:FavouriteColour"]
};
}
}
Solution 1: Scoped MyAppSettings object.
Registration (Startup->ConfigureServices(IServiceCollection)`
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped(sp =>
{
var contextAccessor = sp.GetService<IHttpContextAccessor>();
var config = sp.GetService<IConfiguration>();
return config.GetAppSettingsByTenant(contextAccessor.HttpContext.GetTenantName());
});
...
Usage:
public class TestController : Controller
{
private readonly MyAppSettings _settings;
public TestController(MyAppSettings settings)
{
_settings = settings;
}
[HttpGet]
public IActionResult Index()
{
return Json(_settings);
}
}
Solution 2: IOptions<MyAppSettings
Registration (Startup->ConfigureServices(IServiceCollection)`
public class MyAppSettingsOptions : IOptions<MyAppSettings>
{
public MyAppSettingsOptions(IConfiguration configuration, IHttpContextAccessor contextAccessor)
{
var tenant = contextAccessor.HttpContext.GetTenantName();
Value = configuration.GetAppSettingsByTenant(tenant);
}
public MyAppSettings Value { get; }
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IOptions<MyAppSettings>, MyAppSettingsOptions>();
...
Usage
public class TestController : Controller
{
private readonly IOptions<MyAppSettings> _options;
public TestController(IOptions<MyAppSettings> options)
{
_options = options;
}
[HttpGet]
public IActionResult Index()
{
return Json(_options.Value);
}
}
Solution 3: IOptionsMonitor<MyAppSettings
Registration (Startup->ConfigureServices(IServiceCollection)`
public class MyAppSettingsOptionsMonitor : IOptionsMonitor<MyAppSettings>
{
public MyAppSettingsOptionsMonitor(IConfiguration configuration, IHttpContextAccessor contextAccessor)
{
var tenant = contextAccessor.HttpContext.GetTenantName();
CurrentValue = configuration.GetAppSettingsByTenant(tenant);
}
public MyAppSettings Get(string name)
{
throw new NotSupportedException();
}
public IDisposable OnChange(Action<MyAppSettings, string> listener)
{
return null;
}
public MyAppSettings CurrentValue { get; }
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IOptionsMonitor<MyAppSettings>, MyAppSettingsOptionsMonitor>();
...
Usage
public class TestController : Controller
{
private readonly IOptionsMonitor<MyAppSettings> _options;
public TestController(IOptionsMonitor<MyAppSettings> options)
{
_options = options;
}
[HttpGet]
public IActionResult Index()
{
return Json(_options.CurrentValue);
}
}
You can use DI services to config options
Sample code of your option class
public class MyAppSettings
{
public string FavouriteColor { get; set; }
public int LuckNumber { get; set; }
}
public interface IMySettingServices
{
string GetFavouriteColor();
int GetLuckNumber();
}
public class MySettingServices : IMySettingServices
{
private IHttpContextAccessor httpContextAccessor;
public MySettingServices(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public string GetFavouriteColor()
{
var headers = this.httpContextAccessor.HttpContext.Request.Headers;
//Write your logic with httpContextAccessor by extract tenant here, then return actual config by tenant name
if(this.httpContextAccessor.HttpContext.Request.Host.Host=="abc.test.com")
{
//Get color setting for abc.test.com
}
return "Green";
}
public int GetLuckNumber()
{
var headers = this.httpContextAccessor.HttpContext.Request.Headers;
//Write your logic with httpContextAccessor by extract tenant here, then return actual config by tenant name
if (this.httpContextAccessor.HttpContext.Request.Host.Host == "abc.test.com")
{
//Get luck number setting for abc.test.com
}
return 1;
}
}
Sample code of your ConfigureService
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddControllersWithViews();
services.AddSingleton<IMySettingServices, MySettingServices>();
services.AddOptions<MyAppSettings>().Configure<IMySettingServices>((setting, settingServices) => {
setting.FavouriteColor = settingServices.GetFavouriteColor();
setting.LuckNumber = settingServices.GetLuckNumber();
});//This is the key point of this answer, you are delegating your setting assignment to a services, so you can do whatever you want in your services, in your word, customise configuration binding
}
Sample code of use your configuration in controller
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly IOptions<MyAppSettings> settings;
public HomeController(ILogger<HomeController> logger, IOptions<MyAppSettings> settings)
{
_logger = logger;
this.settings = settings;
}
public IActionResult Index()
{
var favColor = settings.Value.FavouriteColor;
return View();
}
}
please be aware that when you want to access httpcontext, do not directly add services.AddScoped/AddSingleton/AddTransit<IHttpContextAccessor,HttpContextAccessor>(), this will result to DI system unable to resolve IHttpContextAccessor during ConfigureServices phases. Use services.AddHttpContextAccessor(); is the best way to do that
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 am new at using ASP.NET Core. Want to create an api using this new framework, but have some startup issues with dependency injection. It should be quite easy, but for somehow when using DI, I get an internal server error 500 when calling the controller from postman.
Controller:
[Route("api/[controller]")]
public class SomethingController : Controller
{
private readonly ISomethingService _somethingService;
public SomethingController(ISomethingService somethingService)
{
_somethingService = somethingService;
}
// GET: api/values
[HttpGet]
public int Get()
{
return _somethingService.status();
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
}
Service with interface
public interface ISomethingService
{
int status();
}
public class SomethingService : ISomethingService
{
SomethingService()
{
}
public int status()
{
var number = 3;
return number;
}
}
Startup class
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Add application services
services.AddTransient<ISomethingService, SomethingService>();
}
// 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)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
app.UseMvc();
}
}
As you see, I have already registered the service, so why does it not work as intended?
Also, Have tried to remove the injectiton from the controller, then the controller works fine.
Your SomethingService constructor is private. Make it public so DI can create one.