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
Related
I wish to check various session values from within my custom class. One thing I want to test is if the LoggedIn variable is set when a user hits a specific page. I want to do this in a class so I don't have to repeat code.
Here is how I've registered the service / IHttpContextAccessor:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<clsSessionHelper>();
services.AddRazorPages();
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/App/Mpl/MplHome/MplHome", "");
});
services.AddAntiforgery(o => o.HeaderName = "XSRF-TOKEN");
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromSeconds(1200);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
// End Session Support
services.AddMemoryCache();
}
Here is how I've build the custom class:
public class clsSessionHelper
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ISession _session;
public clsSessionHelper(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_session = _httpContextAccessor.HttpContext.Session;
}
public string getValue(string SessionKey)
{
string strResult = _session.GetString(SessionKey);
return strResult;
}
When I try to use the my helper class in a xx.cshtml.cs file like be below
clsSessionHelper objSessionHelper = new clsSessionHelper();
Test = objSessionHelper.getValue("LoggedIn");
I get an intellsense error "there is no argument given that corresponds to the required formal parameter 'httpContextAccessor' of 'clsSessionHelper.clsSessionHelper(HttpContextAccessor)'.
No doubt, I'm messing up the dependency injection. Any help would be greatly appreaciated.
The issue is that at the time the custom class is initialized and the IHttpContextAccessor injected, it is too early in the request process to have access to the HttpContext and by extension the ISession
Move any code that depend on the HttpContext out of the construct and into a member that is invoked during the context of a request.
public class clsSessionHelper : ISessionHelper {
private readonly IHttpContextAccessor httpContextAccessor;
public clsSessionHelper(IHttpContextAccessor httpContextAccessor) {
this.httpContextAccessor = httpContextAccessor;
}
public string getValue(string SessionKey) {
ISession session = httpContextAccessor.HttpContext.Session;
string strResult = session.GetString(SessionKey);
return strResult;
}
}
Also abstract your custom class
public interface ISessionHelper {
string getValue(string SessionKey);
}
so it too can be injected where needed.
//...
services.AddScoped<ISessionHelper, clsSessionHelper>();
//...
Avoid trying to initialize it manually.
private readonly ISessionHelper objSessionHelper;
public MyPageModel(ISessionHelper objSessionHelper) {
this.objSessionHelper = objSessionHelper;
}
public async Task<IActionResult> OnGet() {
var Test = objSessionHelper.getValue("LoggedIn");
//...
}
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 am writing code for following: Access the current HttpContext in ASP.NET Core
I am receiving error. How would I resolve this?
Also, whats the code for Interface IMyComponent? Just want to be sure its correct.
Errors:
Type or namespace IMyComponent Cannot be found
The Name 'KEY' does not exist in current context.
public class MyComponent : IMyComponent
{
private readonly IHttpContextAccessor _contextAccessor;
public MyComponent(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public string GetDataFromSession()
{
return _contextAccessor.HttpContext.Session.GetString(*KEY*);
}
}
Some points you need to pay attention to:
1.You class inherit from an interface and implement a GetDataFromSession method.You need to define an interface IMyComponent first and register IMyComponent in staryup if you would like use by DI
public interface IMyComponent
{
string GetDataFromSession();
}
startup.cs
services.AddSingleton<IMyComponent, MyComponent>();
2.It seems that you would like to get data from session. The "Key" represents any session name (string).You need to enable session for asp.net core and set a session value first.
_contextAccessor.HttpContext.Session.SetString("Key", "value");
3.Register IHttpContextAccessor in your startup
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
4.Full demo:
MyComponent.cs
public class MyComponent : IMyComponent
{
private readonly IHttpContextAccessor _contextAccessor;
public MyComponent(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
public string GetDataFromSession()
{
_contextAccessor.HttpContext.Session.SetString("Key", "value");
return _contextAccessor.HttpContext.Session.GetString("Key");
}
}
public interface IMyComponent
{
string GetDataFromSession();
}
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(10);
options.Cookie.HttpOnly = true;
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddScoped<IMyComponent, MyComponent>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//other middlewares
app.UseSession();
app.UseMvc();
}
}
API Controller:
public class ForumsController : ControllerBase
{
private readonly IMyComponent _myComponent;
public ForumsController(IMyComponent myComponent)
{
_myComponent = myComponent;
}
// GET api/forums
[HttpGet]
public ActionResult<string> Get()
{
var data = _myComponent.GetDataFromSession();//call method and return "value"
return data;
}
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
I need to get HttpContext.Current and HostingEnvironment in a class EmployeeDataAccessLayer in AddEmployee function. I have written below code now I am facing a problem that how can I call/use the function AddEmployee in my controller.
Now, as I have created 2 new constructors with parameters IHttpContextAccessor and IHostingEnvironment respectively causing me problem, I am not getting a proper way to use it.
public class EmployeeDataAccessLayer
{
private readonly IHttpContextAccessor _httpContextAccessor;
private IHostingEnvironment _hostingEnvironment;
public EmployeeDataAccessLayer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public EmployeeDataAccessLayer(IHostingEnvironment environment)
{
_hostingEnvironment = environment;
}
public void AddEmployee(TblEmployee employee)
{
try
{
string folderName = "UploadFile/";
string sPath = "";
sPath = Path.Combine(_hostingEnvironment.WebRootPath, "~/" + folderName);
var hfc = _httpContextAccessor.HttpContext.Request.Form.Files;
}
I am following this article.
Most likely you haven't configured your controller to require an instance of EmployeeDataAccessLayer.
Be sure to register the EmployeeDataAccessLayer as a dependency like in the article you linked. Then your controller should take EmployeeDataAccessLayer as a constructor argument, you will store that as a readonly field and use it in your controller action. Then you should see that EmployeeDataAccessLayer has an instance of IHttpContextAccessor provided.
See a more complete example as the one you link is not complete(e.g. this one from microsoft).
As a side note, in your EmployeeDataAccessLayer you probably should not require the IHttpContext dependency if possible as others have mentioned in comments.
Try using interface as follows :
//controller
public class HomeController
{
private readonly IDataAccess _dataAccess;
public HomeController(IDataAccess dataAccess)
{
_dataAccess = dataAccess;
}
[HttpPost]
public ActionResult Index(TblEmployee employee)
{
_dataAccess.AddEmployee(employee);
return View();
}
}
// Startup
public void ConfigureServices(IServiceCollection services)
{
// add dependency
services.AddScoped<IDataAccess, EmployeeDataAccessLayer>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}
// Data Access Impl
public class EmployeeDataAccessLayer : IDataAccess
{
private readonly IHttpContextAccessor _httpContextAccessor;
private IHostingEnvironment _hostingEnvironment;
public EmployeeDataAccessLayer(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public EmployeeDataAccessLayer(IHostingEnvironment environment)
{
_hostingEnvironment = environment;
}
public void AddEmployee(TblEmployee employee)
{
try
{
string folderName = "UploadFile/";
string sPath = "";
sPath = Path.Combine(_hostingEnvironment.WebRootPath, "~/" + folderName);
var hfc = _httpContextAccessor.HttpContext.Request.Form.Files;
catch{}
}
}
// interface
public interface IDataAccess
{
void AddEmployee(TblEmployee employee);
}
Another ugly approach (Using service locator):
if you don't want DI and constructor, you can use service locator as follows:
public static class MyServiceLocator
{
public static IServiceProvider Instance { get; set; }
}
public void Configure(IApplicationBuilder app)
{
MyServiceLocator.Instance = app.ApplicationServices;
}
// Data Access
public class EmployeeDataAccessLayer
{
public void AddEmployee(TblEmployee employee)
{
try
{
IHttpContextAccessor httpContextAccessor =MyServiceLocator.Instance.GetService<IHttpContextAccessor>();
IHostingEnvironment hostingEnvironment=MyServiceLocator.Instance.GetService<IHostingEnvironment>();;
string folderName = "UploadFile/";
string sPath = "";
sPath = Path.Combine(_hostingEnvironment.WebRootPath, "~/" + folderName);
var hfc = _httpContextAccessor.HttpContext.Request.Form.Files;
}
catch{}
}
}