Controller Constructor with primitive parameters - asp.net-core

In an attempt to refactor code, I have recently changed my Controller to be constructed by taking string instead of IConfiguration and that is coming from another controller.
StatusController.cs
public class StatusController : Microsoft.AspNetCore.Mvc.ControllerBase
{
protected StatusDAL status_dal;
public StatusController(string connectionString)
{
status_dal = new StatusDAL(connectionString);
}
}
This particular controller is being called by another controller
HVIRCalcController.cs
public class HVIRCalcController : BaseServicesController
{
private readonly string _connectionString;
public HVIRCalcController(IConfiguration config)
{
_connectionString = config.GetConnectionString("toolboxConnectionStrWPS");
}
[HttpPost]
public IActionResult PostTask(int service_id, [FromBody]HVIRServiceRequestParams hvir_params)
{
StatusAck sack = new HVIR_Calc_Engine(_connectionString).BatchCalc(hvir_params);
}
}
HVIRCalcEngine.cs
public class HVIR_Calc_Engine
{
private string _connectionString;
public HVIR_Calc_Engine(string connectionString)
{
//this.config = config;
_connectionString = connectionString;
status_dal = new StatusDAL(connectionString);
pg_dal = new PostgresDAL(connectionString);
}
public StatusAck BatchCalc(HVIRServiceRequestParams hvirparams)
{
StatusAck statusack = new StatusController(_connectionString).PostStatusRecord();
}
}
It gives me the error:
Unable to resolve service for type 'System.String' while attempting to activate 'WPS_WebAPI.Controllers.StatusController'
Is there a way to avoid passing IConfiguration from HVIRCalcController all the way to StatusController?

Related

How to access Controller class variable value in data access layer?

Code in appsettings.json:
{
"Logging":{
"LogLevel":{
"Default":"Warning"
}
},
"AllowedHosts":"*",
"ConnectionStrings":{
"DefaultConnection":"Server=localhost;Port=5432;Database=CrudDataBase;User Id=xyy;Password=xyz123###;"
}
}
Code in HomeController:
[Route("api/[controller]/[action]")]
[ApiController]
public class EcoSystemHomeController : ControllerBase
{
EcoBusinessHome ebl= new EcoBusinessHome();
public string ConnectionString;
private readonly IConfiguration configuration;
public EcoSystemHomeController(IConfiguration config)
{
this.configuration = config;
}
[HttpGet]
public object GetServiceStatus()
{
try
{
ConnectionString = configuration.GetConnectionString("DefaultConnection");
JObject StatusObj = new JObject();
StatusObj.Add(ConnectionString);
return StatusObj;
}
catch
{
return null;
}
}
}
My app follows the three-layer architecture. I have a variable named ConnectionString inside EcoSystemHomeController. It holds the connection string value. I want to use this ConnectionString variable in the data access layer. How can I achieve it?
Reading AppSettings from AppSettings.json using IConfiguration interface
In the below example, the IConfiguration is injected in the Controller and assigned to the private property Configuration.
Then inside the Controller, the AppSettings are read from the AppSettings.json file using the GetSection function.
Test Code:
private IConfiguration Configuration;
public TestController(IConfiguration _configuration)
{
Configuration = _configuration;
}
public IActionResult Index()
{
//ConnectionStrings
string appName = this.Configuration.GetSection("ConnectionStrings")["DefaultConnection"];
return View();
}
Result:

Create database context from cookie and base path in Entity Framework Core

Postgres database has multiple schemes like company1, company2, ... companyN
Browser sends cookie containing scheme name . Data access operations should occur in this scheme. Web application user can select different scheme. In this case different cookie value is set.
Npgsql EF Core Data provider is used.
ASP NET MVC 5 Core application registers factory in StartUp.cs :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
services.AddScoped<IEevaContextFactory, EevaContextFactory>();
....
Home controller tries to use it:
public class HomeController : EevaController
{
public ActionResult Index()
{
var sm = new SchemeManager();
sm.PerformInsert();
....
This throws exception since factory member is null. How to fix this ?
public interface IEevaContextFactory
{
EevaContext Create();
}
public class EevaContextFactory : IEevaContextFactory
{
private IHttpContextAccessor httpContextAccessor;
private IConfiguration configuration;
public EevaContextFactory(IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
{
this.httpContextAccessor = httpContextAccessor;
this.configuration = configuration;
}
public EevaContext Create()
{
var builder = new DbContextOptionsBuilder<EevaContext>();
var pathbase = httpContextAccessor.HttpContext.Request.PathBase.Value;
var scheme = httpContextAccessor.HttpContext.Request.Cookies["Scheme"];
var csb = new NpgsqlConnectionStringBuilder()
{
Host = pathbase,
SearchPath = scheme
};
builder.UseNpgsql(csb.ConnectionString);
return new EevaContext(builder.Options);
}
}
Scheme data acess methods:
public class SchemeManager
{
readonly IEevaContextFactory factory;
public SchemeManager(IEevaContextFactory factory)
{
this.factory = factory;
}
public SchemeManager()
{
}
public void PerformInsert()
{
using (var context = factory.Create())
{
var commandText = "INSERT into maksetin(maksetin) VALUES (CategoryName)";
context.Database.ExecuteSqlRaw(commandText);
}
}
}
var sm = new SchemeManager()
... will call the no-parameter constructor on SchemeManager so the IEevaContextFactory is not injected. You should inject your factory into your controller and pass it into your SchemeManager.
Remove your no-parameter constructor. It's not needed.
public class HomeController : EevaController
{
private IEevaContextFactor eevaFactory;
public HomeController(IEevaContextFactory factory)
{
eevaFactory = factory;
}
public ActionResult Index()
{
var sm = new SchemeManager(eevaFactory);
sm.PerformInsert();
....
}
}
Your other option is to put the SchemeManager in the DI container and then the DI container will auto-resolve IEevaContextFactory on the constructor and then just inject SchemeManager into your controller.
Either way, remove that no-parameter constructor.

Manually trigger IOptionsMonitor<>.OnChange

In ASP.NET Core 2.1, I use IOptionsMonitor<> and have it set up so I can successfully get events for when I change the appSettings.json file. So this is working.
What I want to do now is to manually change some values in my options, through code, and have that trigger all my monitors. Is this possible?
For IOptionsMonitor<Locations>, it only changes the value in memory and did not save back to appsettings.json. For a workaround, you will need to implement your own method to save the changes back to appsettings.json.
define IWritableOptions which inherits from IOptions
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
implement your own WritableOptions
public class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly IHostingEnvironment _environment;
private readonly IOptionsMonitor<T> _options;
private readonly IConfigurationRoot _configuration;
private readonly string _section;
private readonly string _file;
public WritableOptions(
IHostingEnvironment environment,
IOptionsMonitor<T> options,
IConfigurationRoot configuration,
string section,
string file)
{
_environment = environment;
_options = options;
_configuration = configuration;
_section = section;
_file = file;
}
public T Value => _options.CurrentValue;
public T Get(string name) => _options.Get(name);
public void Update(Action<T> applyChanges)
{
var fileProvider = _environment.ContentRootFileProvider;
var fileInfo = fileProvider.GetFileInfo(_file);
var physicalPath = fileInfo.PhysicalPath;
var jObject = JsonConvert.DeserializeObject<JObject>(File.ReadAllText(physicalPath));
var sectionObject = jObject.TryGetValue(_section, out JToken section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) : (Value ?? new T());
applyChanges(sectionObject);
jObject[_section] = JObject.Parse(JsonConvert.SerializeObject(sectionObject));
File.WriteAllText(physicalPath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
_configuration.Reload();
}
}
Configure IWritableOptions<T>
public static class ServiceCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationSection section,
string file = "appsettings.json") where T : class, new()
{
services.Configure<T>(section);
services.AddTransient<IWritableOptions<T>>(provider =>
{
var configuration = (IConfigurationRoot)provider.GetService<IConfiguration>();
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
return new WritableOptions<T>(environment, options, configuration, section.Key, file);
});
}
}
Register in Startup
services.ConfigureWritable<Locations>(Configuration.GetSection("Locations"));
Use
public class OptionsController : Controller
{
private readonly IWritableOptions<Locations> _writableLocations;
public OptionsController(IWritableOptions<Locations> writableLocations)
{
_writableLocations = writableLocations;
}
public IActionResult Change(string value)
{
_writableLocations.Update(opt => {
opt.Name = value;
});
return Ok("OK");
}
}
It will fire the IOptionsMonitor<>.OnChange

Injecting Dependency into Web API Controller

I want to inject unity container into WebController.
I have UnityDependencyResolver:
public class UnityDependencyResolver : IDependencyResolver
{
readonly IUnityContainer _container;
public UnityDependencyResolver(IUnityContainer container)
{
this._container = container;
}
public object GetService(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return _container.ResolveAll(serviceType);
}
catch
{
return new List<object>();
}
}
public void Dispose()
{
_container.Dispose();
}
}
Then, in my Global.asax I add the following line:
var container = new UnityContainer();
container.RegisterType<IService, Service>
(new PerThreadLifetimeManager()).RegisterType<IDALContext, DALContext>();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
Then, If I use the following in a Web Controller:
private IService _service;
public HomeController(IService srv)
{
_service = srv;
}
It works fine.
But I want to inject it into WebAPI Controller, so if I do it the same way:
private IService _service;
public ValuesController(IService srv)
{
_service = srv;
}
It does not work, it says that constructor is not defined.
Ok, I create one more constructor:
public ValuesController(){}
And in this case it uses only this constructor and never the one where I should inject unity container.
Please advise.
Add this in your WebApiConfig:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Routes and other stuff here...
var container = IocContainer.Instance; // Or any other way to fetch your container.
config.DependencyResolver = new UnityDependencyResolver(container);
}
}
And if you want the same container you can keep it in a static variable, like so:
public static class IocContainer
{
private static readonly Lazy<IUnityContainer> Container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
return container;
});
public static IUnityContainer Instance
{
get { return Container.Value; }
}
}
More info can be found here:
http://www.asp.net/web-api/overview/advanced/dependency-injection
On a sidenote, I can also recommend the nuget-package Unity.Mvc. It adds a UnityWebActivator and support for PerRequestLifetimeManager.
https://www.nuget.org/packages/Unity.Mvc/

Change injected object at runtime

I want to have multiples implementation of the IUserRepository each implementation will work with a database type either MongoDB or any SQL database. To do this I have ITenant interface that have a connection string and other tenant configuration. The tenant is been injected into IUserRepository either MongoDB or any SQLDB implementation. What I need to know is how properly change the injected repository to choose the database base on the tenant.
Interfaces
public interface IUserRepository
{
string Login(string username, string password);
string Logoff(Guid id);
}
public class User
{
public Guid Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
public interface ITenant
{
string CompanyName { get; }
string ConnectionString { get; }
string DataBaseName { get; }
string EncriptionKey { get; }
}
Is important to know that the tenant id is been pass to an API via header request
StartUp.cs
// set inject httpcontet to the tenant implemantion
services.AddTransient<IHttpContextAccessor, HttpContextAccessor>();
// inject tenant
services.AddTransient<ITenant, Tenant>();
// inject mongo repository but I want this to be programmatically
services.AddTransient<IUserRepository, UserMongoRepository>();
Sample Mongo Implementation
public class UserMongoRepository : IUserRepository
{
protected ITenant Tenant
public UserMongoRepository(ITenant tenant) :
base(tenant)
{
this.Tenant = tenant;
}
public string Login(string username, string password)
{
var query = new QueryBuilder<User>().Where(x => x.Username == username);
var client = new MongoClient(this.Tenant.ConnectionString);var server = client.GetServer();
var database = client.GetServer().GetDatabase(this.Tenant.DataBaseName);
var user = database.GetCollection<User>.FindAs<User>(query).AsQueryable().FirstOrDefault();
if (user == null)
throw new Exception("invalid username or password");
if (user.Password != password)
throw new Exception("invalid username or password");
return "Sample Token";
}
public string Logoff(Guid id)
{
throw new NotImplementedException();
}
}
Tenant
public class Tenant : ITenant
{
protected IHttpContextAccessor Accesor;
protected IConfiguration Configuration;
public Tenant(IHttpContextAccessor accesor, IDBConfiguration config)
{
this.Accesor = accesor;
this.Configuration = new Configuration().AddEnvironmentVariables();
if (!config.IsConfigure)
config.ConfigureDataBase();
}
private string _CompanyName;
public string CompanyName
{
get
{
if (string.IsNullOrWhiteSpace(_CompanyName))
{
_CompanyName = this.Accesor.Value.Request.Headers["Company"];
if (string.IsNullOrWhiteSpace(_CompanyName))
throw new Exception("Invalid Company");
}
return _CompanyName;
}
}
private string _ConnectionString;
public string ConnectionString
{
get
{
if (string.IsNullOrWhiteSpace(_ConnectionString))
{
_ConnectionString = this.Configuration.Get(this.CompanyName + "_" + "ConnectionString");
if (string.IsNullOrWhiteSpace(_ConnectionString))
throw new Exception("Invalid ConnectionString Setup");
}
return _ConnectionString;
}
}
private string _EncriptionKey;
public string EncriptionKey
{
get
{
if (string.IsNullOrWhiteSpace(_EncriptionKey))
{
_EncriptionKey = this.Configuration.Get(this.CompanyName + "_" + "EncriptionKey");
if (string.IsNullOrWhiteSpace(_EncriptionKey))
throw new Exception("Invalid Company Setup");
}
return _EncriptionKey;
}
}
private string _DataBaseName;
public string DataBaseName
{
get
{
if (string.IsNullOrWhiteSpace(_DataBaseName))
{
_DataBaseName = this.Configuration.Get(this.CompanyName + "_" + "DataBaseName");
if (string.IsNullOrWhiteSpace(_DataBaseName))
throw new Exception("Invalid Company Setup");
}
return _DataBaseName;
}
}
}
Controller
public class UsersController : Controller
{
protected IUserRepository DataService;
public UsersController(IUserRepository dataService)
{
this.DataService = dataService;
}
// the controller implematation
}
You should define a proxy implementation for IUserRepository and hide the actual implementations behind this proxy and at runtime decide which repository to forward the call to. For instance:
public class UserRepositoryDispatcher : IUserRepository
{
private readonly Func<bool> selector;
private readonly IUserRepository trueRepository;
private readonly IUserRepository falseRepository;
public UserRepositoryDispatcher(Func<bool> selector,
IUserRepository trueRepository, IUserRepository falseRepository) {
this.selector = selector;
this.trueRepository = trueRepository;
this.falseRepository = falseRepository;
}
public string Login(string username, string password) {
return this.CurrentRepository.Login(username, password);
}
public string Logoff(Guid id) {
return this.CurrentRepository.Logoff(id);
}
private IRepository CurrentRepository {
get { return selector() ? this.trueRepository : this.falseRepository;
}
}
Using this proxy class you can easily create a runtime predicate that decides which repository to use. For instance:
services.AddTransient<IUserRepository>(c =>
new UserRepositoryDispatcher(
() => c.GetRequiredService<ITenant>().DataBaseName.Contains("Mongo"),
trueRepository: c.GetRequiredService<UserMongoRepository>()
falseRepository: c.GetRequiredService<UserSqlRepository>()));
You can try injecting a factory rather than the actual repository. The factory will be responsible for building the correct repository based on the current user identity.
It might require a little more boiler plate code but it can achieve what you want. A little bit of inheritance might even make the controller code simpler.