Dependency injection in asp.net core - dependency-injection

I have following code:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IWsApiProvider, WsApiProvider>();
services.AddScoped<IApplicationUserRepository, ApplicationUserRepository>();
...
}
WsApiProvider has following:
public Guid SessionId { get; set; }
public IWSocketProvider WsApi { get; set; }
In Invoke method I'm updating these properties:
public Task Invoke(HttpContext httpContext, IOptions<AppSettings> appSettings)
{
...
this._wsApiProvider.SessionId = sessionGuid;
this._wsApiProvider.WsApi = connection;
...
}
And then I'm going to Controller where I injected Repository:
public AccountController(IApplicationUserRepository applicationUserRepository)
{
this._applicationUserRepository = applicationUserRepository;
}
public ApplicationUserRepository(IWsApiProvider wsApi) : base(wsApi)
{
}
And here I have wsApi object with empty properties. Two questions:
Why in repository constructor I have this object with empty properties?
Is there any way to create one instance of IWsApiProvider for all dependencies per request (non-singleton solution)?
Thank you in advance
UPDATED. The whole middleware class:
public class WsApiMiddleware
{
private readonly RequestDelegate _next;
private readonly IWsApiProvider _wsApiProvider;
private const string QisSessionId = "QisSessionId";
public WsApiMiddleware(RequestDelegate next, IWsApiProvider wsApiProvider)
{
_next = next;
this._wsApiProvider = wsApiProvider;
}
public Task Invoke(HttpContext httpContext, IOptions<AppSettings> appSettings)
{
var sessionId = httpContext.Request.Cookies[QisSessionId];
var sessionGuid = Guid.Empty;
if (!string.IsNullOrEmpty(sessionId))
{
Guid.TryParse(sessionId, out sessionGuid);
}
var connection = ConnectionsPool.GetSocket(sessionGuid);
if (connection == null)
{
connection = new WSocketProvider(null);
var connectTask = Task.Run(async () =>
await connection.Connect(appSettings.Value.WsApiServerEndPointUri, CancellationToken.None)
);
Task.WaitAll(connectTask);
var sessionService = new SessionService(connection);
var sessionOpenTask = Task.Run(async () =>
{
SessionDataState sessionData = null;
//TODO [W-8/6/2017] - think about better solution for situation when sessionId doesn't exist on the server
try
{
sessionData = await sessionService.OpenSession(sessionGuid != Guid.Empty ? (Guid?)sessionGuid : null);
}
catch (Exception ex)
{
sessionData = await sessionService.OpenSession();
}
sessionGuid = sessionData.SessionId;
if (!sessionData.ClientType.HasValue)
{
await sessionService.LoginClient();
}
ConnectionsPool.TryAddConnection(sessionGuid, connection);
httpContext.Response.Cookies.Append(QisSessionId, sessionGuid.ToString());
});
Task.WaitAll(sessionOpenTask);
}
this._wsApiProvider.SessionId = sessionGuid;
this._wsApiProvider.WsApi = connection;
return this._next(httpContext);
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class WsApiMiddlewareExtensions
{
public static IApplicationBuilder UseWsApiMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<WsApiMiddleware>();
}
}

From the ASP.Net core middleware doc :
Middleware is constructed once per application lifetime. Because middleware is constructed at app startup, not per-request, scoped lifetime services used by middleware constructors are not shared with other dependency-injected types during each request.
And the most important part in you situation:
If you must share a scoped service between your middleware and other types, add these services to the Invoke method's signature. The Invoke method can accept additional parameters that are populated by dependency injection.
Since IWsApiProvider is a scoped service(i.e. per request), it should be passed as an argument to the Invoke method, as follow:
public class WsApiMiddleware
{
private readonly RequestDelegate _next;
// no longer passed in the constructor
public WsApiMiddleware(RequestDelegate next)
{
_next = next;
}
// passed as an argument to Invoke, via dependency injection
public Task Invoke(HttpContext httpContext, IWsApiProvider wsApiProvider, IOptions<AppSettings> appSettings)
{
wsApiProvider.SessionId = "SessionId";
wsApiProvider.WsApi = "WsApi";
return this._next(httpContext);
}
}

Related

How do you inject a repo into a service in ASP.Net Core MVC

I have a really odd problem.
I have defined a Repository for a DbSet Called PageHits.
This is the interface:
public interface IPageHitRepo
{
IQueryable<PageHit> PageHits { get; }
void AddPageHit(PageHit pageHit);
void SaveChanges();
}
This is the implementation:
public class PageHitRepo : IPageHitRepo
{
private CtasContext _context;
public PageHitRepo(CtasContext context)
{
_context = context;
}
public IQueryable<PageHit> PageHits => _context.PageHits;
public void AddPageHit(PageHit pageHit)
{
_context.Add(pageHit);
}
public void SaveChanges()
{
_context.SaveChanges();
}
}
I configure in startup.ConfigureServices like this:
services.AddScoped<IPageHitRepo, PageHitRepo>();
This is all just normal stuff. I use this to inject into controllers all the time using constructor injection.
But right now I want to inject this repo service into another service called PageHitService.
It has a lot going on but the injection part should be simple.
I have a IPageHitService interface defined like this:
public interface IPageHitService
{
// Default implimentation overridden in PageHitService.
Task Invoke(IServiceProvider sp) => Task.FromResult(true);
Task EmailSiteHitNotification(PageHit pageHit, IServiceProvider sp);
Task LogPageHitToDB(PageHit pageHit, IServiceProvider sp);
}
Then the top of the PageHitService looks like this:
public class PageHitService : IPageHitService
{
private IPageHitRepo _pageHitRepo;
public PageHitService(IPageHitRepo pageHitRepo)
{
_pageHitRepo = pageHitRepo;
}
public async Task Invoke(IServiceProvider sp)
{
HttpContext httpContext =
sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
IWebHostEnvironment env = sp.GetRequiredService<IWebHostEnvironment>();
IConfiguration config = sp.GetRequiredService<IConfiguration>();
// _context = sp.GetRequiredService<CtasContext>();
bool visitSet =
!string.IsNullOrEmpty(httpContext.Session.GetString("VisitSet"));
and here is the registration in StartUp.ConfigureServices:
services.AddScoped<IPageHitService, PageHitService>();
But when I try to compile I get this error:
There is no argument given that corresponds to the required formal parameter 'pageHitRepo' of 'PageHitService.PageHitService(IPageHitRepo)'
Why do we not get this error in constructors?
The way I understand DI in .Net Core is it looks at the constructor in the controller and if it sees a dependency it resolves it by the registration in startup and then if it sees a dependency in the service it is resolving it resolves that service's dependency and so on.
Why is it giving me this error.
I understand if I make a default paramterless constructor the error goes away but then ASP.Net Core DI does not know which constructor to use and both constructors get hit and my page ends up with a 500 error and cannot be shown.
What am I missing here?
Update 1:
Don't know if this can help.
But in answer to Steve's comment below I do have a BaseController class and a Base PageModel.
public class BaseController : Controller
{
public BaseController(IServiceProvider sp)
{
IPageHitService pageHitService = sp.GetRequiredService<IPageHitService>();
// pageHitService.Invoke(sp).Await();
pageHitService.Invoke(sp).Wait();
}
#region Callbacks if you use AsyncMethod.Await() extension method.
private void Completed()
{
Console.WriteLine("Completed pageHitService.Invoke from BaseController");
}
private void HandleError(Exception ex)
{
// TODO: Log Error to DB;
}
#endregion
}
public class BasePageModel : PageModel
{
public BasePageModel(IServiceProvider sp)
{
IPageHitService pageHitService = sp.GetRequiredService<IPageHitService>();
// pageHitService.Invoke(sp).Await();
pageHitService.Invoke(sp).Wait();
}
#region Callbacks if you use AsyncMethod.Await() extension method.
private void Completed()
{
Console.WriteLine("Completed pageHitService.Invoke from BaseController");
}
private void HandleError(Exception ex)
{
// TODO: Log Error to DB;
}
#endregion
}
But I can't see this affecting the constructor and causing an error there.
Update 2
I found the offending code in the PageHitService:
PageHitService pageHitService = new PageHitService();
Invoke was static because I was fooling around with going the factory route.
And I could not call the non-static methods so I had to instantiate an instance to call the 2 methods in the API.
Nothing is static now so I am going to comment out that line and just try to call the methods straight up.
Here is the full class:
public class PageHitService : IPageHitService
{
private IPageHitRepo _pageHitRepo;
public PageHitService(IPageHitRepo pageHitRepo)
{
_pageHitRepo = pageHitRepo;
}
public async Task Invoke(IServiceProvider sp)
{
HttpContext httpContext =
sp.GetRequiredService<IHttpContextAccessor>().HttpContext;
IWebHostEnvironment env = sp.GetRequiredService<IWebHostEnvironment>();
IConfiguration config = sp.GetRequiredService<IConfiguration>();
// _context = sp.GetRequiredService<CtasContext>();
bool visitSet =
!string.IsNullOrEmpty(httpContext.Session.GetString("VisitSet"));
PageHit PageHit = new PageHit
{
SessionID = httpContext.Session.Id,
UserIP = httpContext.Connection.RemoteIpAddress.ToString(),
EnvironmentName = env.EnvironmentName,
Url = httpContext.Request.PathBase + httpContext.Request.Path,
ReferrerUrl = httpContext.Request.Headers["Referer"].ToString(),
DateTime = DateTime.Now
};
PageHitService pageHitService = new PageHitService();
bool logPageHitsOn = bool.Parse(config["LogPageHitsOn"]);
bool emailSiteHitNotificationsOn = bool.Parse(config["EmailSiteHitNotificationsOn"]);
if (!visitSet)
{
// create session
httpContext.Session.SetString("VisitSet", "true");
// Emails (1st visit of session)
if (emailSiteHitNotificationsOn)
{
await pageHitService.EmailSiteHitNotification(PageHit, sp);
}
}
// Logs all PageHits to DB.
if (logPageHitsOn)
{
await pageHitService.LogPageHitToDB(PageHit, sp);
}
}
public async Task EmailSiteHitNotification(PageHit pageHit,
IServiceProvider sp)
{
IEmailService emailService = sp.GetRequiredService<IEmailService>();
StringBuilder body = new StringBuilder();
body.AppendFormat($"<b>SessionID:</b> {pageHit.SessionID}");
body.Append("<br /><br />");
body.AppendFormat($"<b>User Ip:</b> {pageHit.UserIP}");
body.Append("<br /><br />");
body.AppendFormat($"<b>Environment:</b> {pageHit.EnvironmentName}");
body.Append("<br /><br />");
body.AppendFormat($"<b>Url (Page Hit):</b> {pageHit.Url}");
body.Append("<br /><br />");
body.AppendFormat($"<b>ReferrerUrl:</b> {pageHit.ReferrerUrl}");
body.Append("<br /><br />");
body.AppendFormat($"<b>DateTime:</b> {pageHit.DateTime}");
body.Append("<br /><br />");
string subject = "CTAS Hit Notification";
await emailService.SendEmail(body.ToString(), subject);
}
public async Task LogPageHitToDB(PageHit pageHit,
IServiceProvider sp)
{
CtasContext context = sp.GetRequiredService<CtasContext>();
await context.PageHits.AddAsync(pageHit);
await context.SaveChangesAsync();
}
}

Background Thread that uses ApplicaitonDBContext

I am trying to wire up a background thread that will update the database once an hour from Active Directory. I am not sure how to pass the current
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
// Add framework services.
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("Connection")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1).AddSessionStateTempDataProvider();
services.AddSession();
services.AddHttpContextAccessor();
services.AddSingleton(Configuration);
services.AddScoped<IAppDbRepository, AppDbRepository>();
services.AddScoped<IActiveDirectoryUtility, ActiveDirectoryUtility>();
services.AddScoped<IActiveDirectoryManager, ActiveDirectoryManager>();
services.AddHostedService<LdapManager>();
services.AddScoped<ILdapManager, LdapManager>();
}
In the LdapManager class I would like to call the UpdateUsers method every hour:
public class LdapManager : ILdapManager, IHostedService
{
private IConfiguration _configuration = null;
private Logging _logger;
private List<string> ldapConnectorForDirectoryEntries = new List<string>();
public LdapManager(IConfiguration configuration)
{
_configuration = configuration;
UpdateUsers();
SyncActiveDirectoryUsers();
}
public void SyncActiveDirectoryUsers()
{
try
{
using (var waitHandle = new AutoResetEvent(false))
{
ThreadPool.RegisterWaitForSingleObject(waitHandle, (state, timeout) => { UpdateUsers(); }, null, TimeSpan.FromHours(1), false);
}
}
catch
{
throw;
}
}
}
The UpdateUsers() method should be able to call the applicationDBContext.SaveChanges() method.
How can I ensure that the LDAP manger class can use the Application DB context?
You probably want class LdapManager : BackgroundService, ILdapManager
BackgroundService is .NET Core 2.1, there is a code sample available for core 2.0
Inject IServiceScopeFactory and override Task ExecuteAsync( ), run a while loop there.
while(!stoppingToken.IsCancellationRequested)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
...; // do your stuff
}
await Task.Delay(myConfig.BackgroundDelay, stoppingToken);
}
And here is a good read about this on MSDN, including the code sample for 2.0
For accessing ApplicationDbContext from HostedService.
DbHostedService
public class DbHostedService : IHostedService
{
private readonly ILogger _logger;
public DbHostedService(IServiceProvider services,
ILogger<DbHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is starting.");
DoWork();
return Task.CompletedTask;
}
private void DoWork()
{
_logger.LogInformation("Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
var user = context.Users.LastOrDefault();
_logger.LogInformation(user?.UserName);
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Consume Scoped Service Hosted Service is stopping.");
return Task.CompletedTask;
}
}
Register DbHostedService
services.AddHostedService<DbHostedService>();

Calling service/repository methods in ASP.Net Core middleware

ASP.Net Core noob here...I am using an ASP.Net Core WebAPI core project using DNX451 with EF 6.
I have a requirement to implement API Key auth in our service. To do this I have created middleware that gets information from the request and proceeds with authentication. It is SUPPOSED to go to the database, get the key to match, and then return and do the validation.
Here is the middleware implemented to look at the context and get the APIKey
AuthenticationHandler
public class AuthorizationHandler
{
private readonly RequestDelegate _next;
private IAuthenticationService _authenticationService;
public AuthorizationHandler(RequestDelegate next, IAuthenticationService authService)
{
_authenticationService = authService;
_next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = _authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}
}
Here is the startup class with context and middleware registration
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
builder.AddEnvironmentVariables();
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped(k => new DbContext(Configuration["Data:Context:ConnectionString"]));
// Add framework services.
services.AddMvc();
}
// 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.UseIISPlatformHandler();
app.UseStaticFiles();
app.RegisterAuthorizationHeader();
app.RegisterAuthorization();
app.UseMvc();
}
// Entry point for the application.
public static void Main(string[] args) => WebApplication.Run<Startup>(args);
}
Here is Auth service
public interface IAuthenticationService
{
string GetApiKey(string location);
}
public class AuthenticationService: IAuthenticationService
{
private IApiKeyRepository _apiKeyRepository;
public AuthenticationService(IApiKeyRepository repo)
{
_apiKeyRepository= repo;
}
public string GetApiKey(string location)
{
return _apiKeyRepository.GetApiKeyByLocation(location);
}
}
The repo
public interface IApiRepository
{
string GetApiKeyByLocation(string location);
}
public class ApiRepository: IApiRepository
{
private DbContext _context;
public ApiRepository(DbContext context)
{
_context = context;
}
public string GetApiKeyByLocation(string location)
{
var apiRow = _context.ApiKeyStore.FirstOrDefault(a => a.Location == location);
return apiRow == null ? string.Empty : apiRow.APIKey;
}
}
When attempting this I get the following error:
The context cannot be used while the model is being created. This
exception may be thrown if the context is used inside the
OnModelCreating method or if the same context instance is accessed by
multiple threads concurrently. Note that instance members of DbContext
and related classes are not guaranteed to be thread safe.
Now, when I debug this every break point is hit twice. I believe I understand WHY this issue is occurring but have no idea how to fix it.
Can someone give me an idea, please? Any better solution ideas?
To use scoped dependencies in a middleware (which is necessarily a singleton by definition), the best approach is to flow it as a parameter of InvokeAsync instead of flowing it via the constructor:
public async Task Invoke(HttpContext context, IAuthenticationService authenticationService)
{
try
{
var apiKey = context.Request.Headers["Key"];
var location = context.Request.Headers["Host"];
var locationKey = authenticationService.GetApiKey(location);
if (apiKey == locationKey)
await _next(context);
context.Response.StatusCode = 403;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
catch (Exception ex)
{
context.Response.StatusCode = 500;
context.Response.Headers.Add("WWW-Authenticate",
new[] { "Basic" });
}
}

MVC Get/Impersonate Windows User In Repository

I have an intranet application that uses the Windows username and passes that to a procedure to return data.
I'm using dependency injection, but I don't believe I have the method to get the username separated properly.
I'm trying to keep this secure by not passing in the username as a parameter, but I also want to be able to impersonate (or bypass my GetWindowsUser() method) and send in another username so I can test results for other users.
One idea I had for this was to set a session variable in another page with another (impersonated) username, then check if that session variable exists first before grabbing the actual user name, but I couldn't figure out how to access the session variable in the repository.
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
return _dropDownDataRepository.MyList();
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}
REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
public HttpResponseMessage MyList()
{
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
//Grab the windowsUser from the method
var windowsUser = utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
}
INTERFACE
public interface IDropDownDataRepository : IDisposable
{
HttpResponseMessage MyList();
}
UTILITY CLASS
public class Utility
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UPDATE 1
In addition to what Nikolai and Brendt posted below, the following is also needed to allow Web Api controllers work with the session state.
Accessing Session Using ASP.NET Web API
Abstract the Utility class and inject it into the repository.
Then you can stub or mock for testing.
public interface IUtility
{
string GetWindowsUser();
}
public class TestUtility : IUtility
{
public string GetWindowsUser()
{
return "TestUser";
}
}
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private IUtility _utility;
public DropDownDataRepository(IUtility utility)
{
_utility = utility;
}
}
EDIT
Also the repository should not return an HTTPResponseMessage type it should just return a List<T> of the domain model you're accessing.
i.e.
public List<Model> MyList()
{
//Grab the windowsUser from the method
var windowsUser = _utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
return sourceQuery
}
Then move the JSON portion to the controller.
One idea I had for this was to set a session variable in another page
with another (impersonated) username, then check if that session
variable exists first before grabbing the actual user name, but I
couldn't figure out how to access the session variable in the
repository.
Potentially, if you add in a dependency to session, you need to isolate it, e.g.
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private ISession session;
public DropDownDataRepository(ISession session)
{
this.session = session;
}
public HttpResponseMessage MyList()
{
var myUserName = this.session.UserName;
// ... etc
With ISession being something like:
public interface ISession
{
string UserName { get; }
}
Implemented as:
public class MySession : ISession
{
public string UserName
{
get
{
// potentially do some validation and return a sensible default if not present in session
return HttpContext.Current.Session["UserName"].ToString();
}
}
}
Of course there is the potential to decouple this MySession class from HttpContext if desired.
With regards to this:
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
Yes, anytime you create a new object you are tightly coupling them together, which will give you issues, for example, if you try to unit test it in isolation.
In this instance you could extract an IUtility interface from Utility:
public class Utility : IUtility
{
string GetWindowsUser();
}
Then:
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private IUtility utility;
public DropDownDataRepository(IUtility utility)
{
this.utility = utility;
// .... etc
Then you have removed the depenedency between Utility and DropDownDataRepository, and can substitute in another type or mock with ease.
I got a lot of help from Nikolai and Brent and got most of the way there with their posted answers, but ended up figuring out the complete answer on my own. The problems I was having were related to not being able to access session variables in a WebAPI. So, I'm sure there are cleaner solutions to this, but I definitely improved what I had and came up with the following code, which works.
This answer was needed to allow access to the session variable in Web Api - Accessing Session Using ASP.NET Web API
GLOBAL.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
UnityConfig.RegisterComponents();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
//Added to allow use of session state in Web API
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
//Added to allow use of session state in Web API
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative);
}
protected void Session_Start(Object sender, EventArgs e)
{
//Default set the session variable to none
Session["_impersonatedUser"] = "none";
}
protected void Session_End(Object sender, EventArgs e)
{
//Reset the session variable to blank
Session["_impersonatedUser"] = "";
}
}
UNITY.config
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IDropDownDataRepository, DropDownDataRepository>();
container.RegisterType<IUtilityRepository, UtilityRepository>();
container.RegisterType<ISessionRepository, SessionRepository>();
//MVC5
//Unity.MVC5 NuGet Package
DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));
//WEB API
//Unity.WebApi NuGet Package
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
}
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
var sourceQuery = _dropDownDataRepository.MyList();
//JSON stuff moved to controller
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
protected override void Dispose(bool disposing)
{
_dropDownDataRepository.Dispose();
base.Dispose(disposing);
}
}
DROPDOWNDATA REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
private IUtilityRepository _utilityRepository;
private ISessionRepository _sessionRepository;
//Dependency Injection of Utility and Session
public DropDownDataRepository(IUtilityRepository utilityRepository, ISessionRepository sessionRepository)
{
_utilityRepository = utilityRepository;
_sessionRepository = sessionRepository;
}
//Changed to a list here
public List<MyProcedure> MyList()
{
string windowsUser;
//Check the session variable to see if a user is being impersonated
string impersonatedUser = _sessionRepository.ImpersonatedUser;
//Grab the windowsUser from the Utility Repository
windowsUser = _utilityRepository.GetWindowsUser();
if (impersonatedUser != "none")
{
windowsUser = impersonatedUser;
}
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.MyProcedure(windowsUser)
select p).ToList();
return sourceQuery;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
DROPDOWNDATA INTERFACE
public interface IDropDownDataRepository : IDisposable
{
//Changed to list here
List<MyProcedure> MyList();
}
UTILITY REPOSITORY
public class UtilityRepository : IUtilityRepository
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UTILITY INTERFACE
public interface IUtilityRepository
{
string GetWindowsUser();
}
SESSION REPOSITORY
public class SessionRepository : ISessionRepository
{
public string ImpersonatedUser
{
get
{
return HttpContext.Current.Session["_impersonatedUser"].ToString();
}
}
}
SESSION INTERFACE
public interface ISessionRepository
{
string ImpersonatedUser { get; }
}

Web API, Light Inject and Passing a Static Dictionary to the data layer

We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea

Resources