I'm going around in circles with this. Is there any way to use the Castle Windsor dependency injection adapter (https://github.com/volosoft/castle-windsor-ms-adapter) with Azure Function apps?
The code example on the adaptor github page is for ASP.NET Core but I would have imagined that there'd be a way to specify the service provider used for a function app, too?
There have been no official documents on this but there is a feature request made by DavidJFowler
Please find the sample code from the above feature request.
The IJobActivatorEx implementation:
public class CastleWindsorJobActivator : IJobActivatorEx
{
private readonly WindsorContainer container;
public CastleWindsorJobActivator(WindsorContainer container) => this.container = container;
public T CreateInstance<T>(IFunctionInstanceEx functionInstance)
{
var disposer = functionInstance.InstanceServices.GetRequiredService<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
// Ensures a created Castle.Windsor scope is disposed at the end of the request
public sealed class ScopeDisposable : IDisposable
{
public IDisposable Scope { get; set; }
public void Dispose() => this.Scope?.Dispose();
}
public T CreateInstance<T>()
{
var disposer = container.Resolve<ScopeDisposable>();
disposer.Scope = container.BeginScope();
return container.Resolve<T>();
}
}
Startup class:
[assembly: FunctionsStartup(typeof(FunctionApp2.Startup))]
namespace FunctionApp2
{
public class Startup: FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var container = new WindsorContainer();
container.Register(Component.For<IScoped1>().ImplementedBy<Scoped1>().LifestyleScoped())
.Register(Component.For<IScoped2>().ImplementedBy<Scoped2>().LifestyleScoped())
.Register(Component.For<ISingleton1>().ImplementedBy<Singleton1>())
.Register(Component.For<ISingleton2>().ImplementedBy<Singleton2>());
// register function classes in container
var functions = Assembly.GetExecutingAssembly().GetTypes().Where(t =>
t.GetMethods().Any(m => m.GetCustomAttributes(typeof(FunctionNameAttribute), false).Any()));
foreach (var function in functions)
{
container.Register(Component.For(function).LifestyleScoped());
}
builder.Services.AddScoped<CastleWindsorJobActivator.ScopeDisposable>()
.AddSingleton<IJobActivator>(new CastleWindsorJobActivator(container));
container.AddServices(builder.Services);
}
}
}
Example function:
public class Function1
{
public Function1(IScoped1 scoped1, IScoped2 scoped2, ISingleton1 singleton1, ISingleton2 singleton2)
{
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
For now, this feature isn't officially released but you can follow other dependency injection patterns. You can raise a feature request from Microsoft QnA or from Azure Portal.
Related
I have a saga I'm trying to run with masstransit that requires a refit client, and i want to have the client dependency injected into the saga.
I'm using MassTransit 5.2.3 with MassTransit.Extensions.DependencyInjection 5.2.3 to setup as follows:
...
var serviceCollection = new ServiceCollection();
serviceCollection.AddRefitClient<IClient>(...).ConfigureHttpClient(...);
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider);
});
});
bus.Start();
...
the code for the saga is:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
private readonly IClient _client;
public Guid CorrelationId { get; set; }
public MySaga(IClient client)
{
_client = client;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
This causes a "Failed to create the saga connector for MyNamespace.MySaga" exception, with inner exception "ConfigurationException: The saga MyNamespace.MySaga must have either a default constructor and a writable CorrelationId property or a constructor with a single Guid argument to assign the CorrelationId"
MassTransit focus for sagas had been moved to state machine sagas, so overall Automatonymous sagas are preferred. For the "classic" sagas, the requirement is given to you in the error message. You can use the code from the Injection_Specs.cs
var refitClient = ...;
var serviceCollection = new ServiceCollection();
serviceCollection.AddMassTransit(c =>
{
c.AddSaga<MySaga>();
});
serviceCollection.AddScoped<MySaga>();
serviceCollection.AddSingleton<ISagaRepository<MySaga>, MessageSessionSagaRepository<MySaga>>(x => new MessageSessionSagaRepository<MySaga>());
var serviceProvider = serviceCollection.BuildServiceProvider();
var bus = Bus.Factory.CreateUsingAzureServiceBus(cfg =>
{
var host = ...;
...
cfg.ReceiveEndpoint(host, "MyQueue", e =>
{
...
e.Saga<MySaga>(serviceProvider,
x => x.UseExecute(ctx => ctx.Saga.Client = refitClient));
});
});
bus.Start();
Of course, you will need to have the Client public property in the saga class and you won't be using the constructor injection. You also need those two constructors that the error message is telling you to have:
class MySaga :
ISaga,
InitiatedBy<IStep1>,
Orchestrates<IStep2>
{
public IClient Client { get; set; }
public Guid CorrelationId { get; set; }
public MySaga()
{
}
public MySaga(Guid correlationId)
{
CorrelationId = correlationId;
}
public async Task Consume(ConsumeContext<IStep1> context) {...}
public async Task Consume(ConsumeContext<IStep2> context) {...}
}
Design consideration
One thing that I have to add is that the Saga in MassTransit is close to the Process Manager pattern. It means that sagas should not have any logic except the orchestration logic and should not do anything else except for handling messages. So, I won't recommend using things like REST API clients inside sagas. If the saga needs to get some data to make a decision about message routing, it should use messages to get that data.
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);
}
}
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; }
}
I am new to MEF and played around with MVC and it's working alright. However, when I tried with Web API, external api controllers weren't resolved. Here's my code for the DependencyResolver:
public class MefDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver, IDependencyResolver
{
private readonly CompositionContainer _container;
public MefDependencyResolver(CompositionContainer container)
{
this._container = container;
}
public object GetService(Type serviceType)
{
string name = AttributedModelServices.GetContractName(serviceType);
var service = this._container.GetExportedValueOrDefault<object>(name);
return service;
}
public IEnumerable<object> GetServices(Type serviceType)
{
string name = AttributedModelServices.GetContractName(serviceType);
try
{
var service = this._container.GetExportedValues<object>(name);
return service;
}
catch
{
return new object[] { };
}
}
public System.Web.Http.Dependencies.IDependencyScope BeginScope()
{
return this;
}
public void Dispose()
{}
}
and here's how it was used:
var _resolver = new MefDependencyResolver(_container);
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = _resolver;
and here's the external api controller:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
but when I visit my site:
http://localhost:65232/api/values
I got this error:
<Error>
<Message>
No HTTP resource was found that matches the request URI 'http://localhost:65232/api/values'.
</Message>
<MessageDetail>
No type was found that matches the controller named 'values'.
</MessageDetail>
</Error>
the same container works with my MVC Controller but can't seem to make it work with API controllers.
Am I missing anything?
There are two interfaces involved here, both with the same name IDependencyResolver hence the confusion. One is for MVC, and one is for Web API.
I solved it like this. I have:
public class WebApiDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver
{
...
}
and
public class MvcDependencyResolver : WebApiDependencyResolver, System.Web.Mvc.IDependencyResolver
{
public MvcDependencyResolver(CompositionContainer container)
: base(container)
{
}
}
The MvcDependencyResolver inherits from the WebApiDependencyResolver to avoid code duplication.
Then you need to register the resolver with MVC and Web API. So somewhere in your startup code you should have something like this:
var resolver = new MvcDependencyResolver(Container);
System.Web.Mvc.DependencyResolver.SetResolver(resolver); // install MEF dependency resolver for MVC
System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = resolver; // install MEF dependency resolver for Web API
Having said all this, I personally wouldn't go with MEF for controller resolution as it introduces a memory leak because MEF keeps references to IDisposable types and ApiController is IDisposable.
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