How to pass parameter to an Autofac Module from a resolve dependency - c#

Currently I have a autofac module which receives a string as parameter, and that string is coming from another dependency.
Im trying to pass it using below code, my question is: Is this the right approach or there is a better/improved way to do it?
builder.RegisterBuildCallback(c =>
{
var configService = c.Resolve<IConfigurationService>();
var module = new LoggingModule(configService.GetConfigurationValue("LoggerName"));
module.Configure(c.ComponentRegistry);
});

Generally try to avoid build callbacks and trying to configure the container based on configuring the container. To be honest, I'm surprised this even works since the container and registry are effectively immutable.
It would be better to use a lambda registration to resolve things. Since we don't know what your logging module is doing, let's say right now it's this:
public class LoggingModule
{
private readonly string _name;
public LoggingModule(string name)
{
this._name = name;
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterInstance(new MyLogger(this._name));
}
}
Even if it's not exactly this, it's pretty easy to adapt something similar - the parameter is coming into the module and being used by a registration.
You could move that into the registration itself and remove the module entirely.
builder.Register(ctx =>
{
var configService = ctx.Resolve<IConfigurationService>();
var name = configService.GetConfigurationValue("LoggerName");
return new MyLogger(name);
}).SingleInstance();
This avoids having to "know the parameter up front" and also avoids trying to reconfigure the container. You still get to register the config in DI and resolve it like you want.

Related

Minimal API Registering Dependencies

I am trying to register dependencies but one thing that is strange is when using specific handlers. For example, take a simple scenario like:
using Microsoft.AspNetCore.Mvc;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var handler = new CustomerHandler(); // Compiler error here
app.MapGet("/customers/{id}",
(
[FromQuery(Name = "msg")]string? name,
[FromRoute(Name = "id")]string id) => handler.Get(id, name));
app.Run();
The handler accepts a single item in its constructor..
public class CustomerHandler
{
private readonly IGetCustomerQuery _getCustomerQuery;
public CustomerHandler(IGetCustomerQuery getCustomerQuery)
{
_getCustomerQuery = getCustomerQuery;
}
public async Task<IResult> Get(string id, string name)
{
return Results.Ok(new Customer { Id = id, Name = name });
}
}
I guess what is the "correct" way to specify these dependencies? I would typically use BuildServiceProvider() and use Get<T>() to create the handler but this is not ideal from what I have read. So I guess would the ideal way be to create these instances? Should I forgo the handler approach?
Please note this is a very simple examaple but the implementation of
IGetCustomerQuery would take in configuration settings to a DB for
example. I guess using the traditional Web API approach this is mitigated in a way.
New minimal hosting model has new way of handling DI, via WebApplicationBuilder.Services:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<CustomerHandler>(); // register with correct lifetime
builder.Build() will build the service provider and then minimal API's binding mechanism can be used to resolve the handler:
app.MapGet("/customers/{id}",
(
[FromQuery(Name = "msg")]string? name,
[FromRoute(Name = "id")]string id,
[FromServices]CustomerHandler handler // possibly attribute can be skipped
) => handler.Get(id, name));
P.S.
I would typically use BuildServiceProvider() and use Get<T>() to create the handler
Please never do that.

WebApi - Autofac cannot resolve parameter HttpRequestMessage

I have an issue with RegisterHttpRequestMessage not working for me and cannot figure out what I'm doing wrong. This is specifically when I try to manually resolve a service that accepts the HttpMessageRequest as a parameter.
I'm using modules to register components in my builder, and currently my module in the main web project looks like this:
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
builder.RegisterType<SourceSystemViewModel>().AsImplementedInterfaces().InstancePerRequest();
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("ViewModelValidator"))
.AsImplementedInterfaces()
.PropertiesAutowired();
// Etc etc etc
SourceSystemViewModel is currently quite simple and looks like this:
public interface ISourceSystemViewModel
{
SourceSystem Value { get; }
}
public class SourceSystemViewModel : ISourceSystemViewModel
{
public SourceSystemViewModel(HttpRequestMessage request)
{
Value = request.Headers.GetSourceSystem();
}
public SourceSystem Value { get; }
}
GetSourceSystem is just an extension method that pulls out the header value. I have tried both registering SourceSystemViewModel with and without InstancePerRequest but it doesn't make a difference. The moment autofac tries to resolve ISourceSystemViewModal (and ultimately HttpRequestMessage) it throws this:
An exception of type
'Autofac.Core.Registration.ComponentNotRegisteredException' occurred
in Autofac.dll but was not handled in user code
Additional information: The requested service 'System.Net.Http.HttpRequestMessage' has not been registered. To avoid
this exception, either register a component to provide the service,
check for service registration using IsRegistered(), or use the
ResolveOptional() method to resolve an optional dependency.
Using autofac 3.5 (webapi dll is quoted as autofac.webapi2 3.4).
Any ideas much appreciated!
Notes
I'll add any findings as I come across them...
Update 1
I took a look at how RegisterHttpRequestMessage works and it does indeed add a message handler called CurrentRequestHandler to HttpConfiguration. When my request comes in I can see that this message handler still exists. So the method seems to do what it's supposed to, it's just not resolving the request message for me...
Update 2
I have noticed that while in the context of a controller and therefore have access to the HttpRequestMessage I can resolve both objects. Like this:
ILifetimeScope requestLifetimeScope = Request.GetDependencyScope().GetRequestLifetimeScope();
var h = requestLifetimeScope.Resolve<HttpRequestMessage>();
var sourceSystemViewModel = requestLifetimeScope.Resolve<ISourceSystemViewModel>();
Update 3
It's important to note that I am manually trying to resolve a service that is expecting the HttpMessageRequest as an injected parameter. For example, this fails for me:
using (var httpRequestScope = IocProxy.Container.Context.BeginLifetimeScope("AutofacWebRequest"))
{
var sourceSystemViewModel = httpRequestScope.Resolve<ISourceSystemViewModel>();
}
I had the same problem and based on SO I was able to get it working with
public class NLoggerTraceWriterModule : Module
{
private HttpConfiguration _config;
public NLoggerTraceWriterModule(HttpConfiguration config)
{
this._config = config;
}
protected override void Load(ContainerBuilder builder)
{
builder.RegisterInstance(this._config).As<HttpConfiguration>();
builder.Register(c =>
c.Resolve<HttpConfiguration>()
.Services
.GetService(typeof(ITraceWriter)) as ITraceWriter)
.As<ITraceWriter>();
}
}
registering this module as
// Call RegisterHttpRequestMessage to add the feature.
builder.RegisterHttpRequestMessage(config);
builder.RegisterModule(new Helper.Logging.NLoggerTraceWriterModule(config));
Even though this wasn't the issue for OP, here's another solution since this is currently the only Google result for "The requested service System.Net.Http.HttpRequestMessage' has not been registered."
If you've created an HttpConfiguration instance, make sure that's what you're passing to the registration function.
In my case, this was resolved by changing this:
builder.RegisterHttpRequestMessage(GlobalConfiguration.Configuration);
To this:
builder.RegisterHttpRequestMessage(Startup.HttpConfiguration);

Autofac shared objects require different registrations per controller but InstancePerApiControllerType won't work

As detailed in InstancePerApiControllerType not working, I am unable to use the InstancePerApiControllerType to configure my solution. The answer provided there works so long as I am directly injecting a ConnectionContext into the controller, or otherwise know that a class is only used by a specific controller. Unfortunately that is not the case in my situation:
ControllerA -> EngineA -> RepositoryA -> GenericEntityAccessor
ControllerB -> EngineB -> RepositoryB -> GenericEntityAccessor
The issue is when we come in through ControllerA, GenericEntityAccessor needs "string A" and from ControllerB it needs "string B".
Of course, the real situation is a little more complicated and there are some bad practices such as code that directly "news"-up a ConnectionContext (it's legacy code). I'm currently exploring providing another component that provides the connection string that is injected via Autofac and configured in the controller using Lazy, but the bad practices are causing problems there also (i.e. once I start to change things in the interface, all the dominoes start to fall over and I end up 15 classes later wondering how I got there).
Are there any patterns, techniques, etc. that address this type of thing? I can't imagine it's all that uncommon.
UPDATE:
To provide a few more specifics, since I'm having some trouble getting this to work, in general we have the following hierarchy, showing which scopes I've applied
Controller -> InstancePerApiRequest()
I*Repository -> ?
I*Manager -> ?
I*Builder -> ?
I*Adapter -> ?
ISqlServerConnectionContext -> ?
IConnectionContextCache -> InstancePerApiRequest()
I've got a number of components that directly take ISqlServerConntectionContext and I'm trying to provide it like so:
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerDependency();
Unfortunately at that point I'm getting a null for CurrectConnectionContext. My guess at this point is I've got some component that isn't rooted from the controller and I'm currently going through the dependencies manually attempting to find it (AFAIK the isn't a way for my to find out which object triggered Autofac to attempt to provide the ISqlServerConnectionContext when I'm debugging).
UPDATE 2:
It turns out I did have some issues where I was registering things improperly, and creating a dependency on ISqlServerConnectionContext for DocumentController, even though it did not have one (this was created through the delegate for something it did depend on).
Now I've got a circular reference that I'm pretty sure I've created myself in the registrations:
container.Register(x =>
{
if (x.IsRegistered<HttpRequestMessage>())
{
var httpRequestMethod = x.Resolve<HttpRequestMessage>();
var tokenHelper = x.Resolve<ITokenHelper>();
var token = tokenHelper.GetToken(httpRequestMethod);
return token ?? new NullMinimalSecurityToken();
}
return new NullMinimalSecurityToken();
}).As<IMinimalSecurityToken>().InstancePerApiRequest();
container.Register(c =>
{
var connectionContextCache = c.Resolve<IConnectionContextCache>();
var token = c.Resolve<IMinimalSecurityToken>();
var connection = (ISqlServerConnectionContext)connectionContextCache.CurrentConnectionContext;
connection.Token = token;
return connection;
}).As<ISqlServerConnectionContext>().InstancePerApiRequest();
The problem is ISqlServerConnectionContext has a property of type IMinimalSecurityToken which is optional, and definitely not used when the ISqlServerConnectionContext is being used to look up IMinimalSecurityToken, which depends on ISqlServerConnectionContext through ITokenHelper.
UPDATE 3:
For completeness, in order to solve my circular reference problem I needed to use named services, and use a SqlServerConnectionContext that did not have the IMinimalSecurityToken property set for the IOAuthTokenManager registration. Now I'm getting the dreaded
No scope with a Tag matching 'AutofacWebRequest' is visible
error, but I think that warrants a new question if I'm not able to solve it.
container.Register(c =>
{
var productId = WellKnownIdentifierFactory.Instance.GetWellKnownProductIdentifier(WellKnownProductIdentifiers.RESTSearchService);
var connectionString = ConfigurationManager.AppSettings[AppSettingsNames.DatabaseConnection];
var newConnectionContext = new SqlServerConnectionContext(connectionString) { ProductID = productId };
newConnectionContext.Open();
return newConnectionContext;
}).Named<ISqlServerConnectionContext>("OAuthTokenConnectionContext").InstancePerApiRequest();
container.Register(c => new SqlServerBuilderFactory(c.ResolveNamed<ISqlServerConnectionContext>("OAuthTokenConnectionContext"))).Named<IBuilderFactory>("OAuthTokenBuilderFactory").InstancePerApiRequest();
container.Register(c =>new OAuthTokenManager(c.ResolveNamed<IBuilderFactory>("OAuthTokenBuilderFactory"))).As<IOAuthTokenManager>().InstancePerApiRequest();
This can be solved using AutoFac's support for object graph lifetime scoping.
Cache the current SqlServerConnectionContext in an object scoped to the lifetime of your controller.
Within the SqlServerConnectionContext factory type, once the connection is created assign it to the backing field of the current lifetime-scoped cache
Any types scoped within the lifetimes scope of a controller can then access the connection associated with that controller through the cache
The only complexities I can think of are:
If the controller is not actually the root of a lifetime scope for all types with a dependency on a specific connection. I.e. if they fall outside the lifetime of the controller.
If any of the dependencies are registered as single instance. In which case they will not be able to resolve the Cache as it is currently implemented as it is PerApiRequest.
For example:
public interface ISqlServerConnectionContextCache
{
ISqlServerConnectionContext CurrentContext { get; set; }
}
public class SqlServerConnectionContextScopeCache : ISqlServerConnectionContextCache
{
public ISqlServerConnectionContext CurrentContext { get; set; }
}
public interface ISqlServerConnectionContextFactory
{
ISqlServerConnectionContext Create();
}
// The factory has the cache as a dependancy
// This will be the first use of the cache and hence
// AutoFac will create a new one at the scope of the controller
public class SqlServerConnectionContextFactory : ISqlServerConnectionContextFactory
{
private string _connectionString;
private ISqlServerConnectionContextCache _connectionCache;
public SqlServerConnectionContextFactory(ISqlServerConnectionContextCache connectionCache,
string connectionString)
{
_connectionCache = connectionCache;
_connectionString = connectionString;
}
public ISqlServerConnectionContext Create()
{
var connectionContext = new SqlServerConnectionContext(_connectionString);
connectionContext.Open();
_sqlServerConnectionContextProvider.CurrentContext = connectionContext;
return connectionContext;
}
}
public class MyController : ApiController
{
private ISqlServerConnectionContext _sqlServerConnectionContext;
public MyController(Func<string, ISqlServerConnectionContextFactory> connectionFactory)
{
_sqlServerConnectionContext = connectionFactory("MyConnectionString");
}
}
// As the cache is lifetime scoped it will receive the single instance
// of the cache associated with the current lifetime scope
// Assuming we are within the scope of the controller this will receive
// the cache that was initiated by the factory
public class MyTypeScopedByController
{
public MyTypeScopedByController(ISqlServerConnectionContextCache connectionCache)
{
var sqlServerConnectionContext = connectionCache.CurrentContext;
}
}
// AutoFac wiring
builder.RegisterType<SqlServerConnectionContextScopeCache>()
.As<ISqlServerConnectionContextCache>()
.InstancePerApiRequest();
builder.RegisterType<SqlServerConnectionContextFactory>()
.As<ISqlServerConnectionContextFactory>()
.InstancePerDependency();

Castle and NLog change connection string at runtime

i am using the NLog built in support for castle and trying to find a way to alter the connection string at run time.
this is my latest swing and miss, im sure it has to do the life cycle at this point as all of the configuration is null so i am guessing that castle has not yet wired up the guts on NLog.
private const string NLogConnectionString = "NLogConnection";
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.AddFacility<LoggingFacility>(l => l.UseNLog());
var config = new NLog.Config.LoggingConfiguration();
var dbtarget = config.FindTargetByName("database") as DatabaseTarget;
if (dbtarget != null)
{
dbtarget.ConnectionString = MethodThatGiveMeConnectionString(NLogConnectionString);
}
}
looking at this post it could be an option but based on the way things have been done here i dont want to change that and much prefer just providing the connection string directly to NLog.
looking here I know i can configure this at run time but i much prefer let most of the settings come from the config file and then just override the connection string.
So I found a solution that works but not sure it's the best approach.
Using this post as a reference. This comment was the most helpful:
Initially I tried to implement a custom ILoggerFactory and inject it into LoggingFacility via the customLoggerFactory. But that soon proved to be a dead end. Then I looked into the NLog integration and noticed that there's already a NLogFactory that has it's methods marked as virtual. So I was able to derive from this class
The problem the author is solving is different than my own but it got me to come up with this solution:
public class LoggerInstall : IWindsorInstaller
{
private const string NLogConnectionString = "NLogConnection";
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var config = new NLog.Config.LoggingConfiguration();
container.AddFacility<LoggingFacility>(l => l.LogUsing(new OwnNLogFactory(GetYourConnectionStringMethod(NLogConnectionString))));
}
}
public class OwnNLogFactory : NLogFactory
{
public OwnNLogFactory(string connectionString)
{
foreach (var dbTarget in LogManager.Configuration.AllTargets.OfType<DatabaseTarget>().Select(aTarget => aTarget))
{
dbTarget.ConnectionString = connectionString;
}
}
}
Still not sure this is the best solution but it works for now, would love to see other solutions if anyone has one

Orchard CMS, Autofac Relationship

I am trying to create a "A(UserManager) needs to create instances of B(UserClient)" relationship (http://code.google.com/p/autofac/wiki/RelationshipTypes) where B(UserClient) needs a HttpSessionStateBase..
UserClient
public class UserClient : IUserClient
{
public UserClient(HttpSessionStateBase session)
{
//...
}
//...
}
UserManager
public class UserManager : IUserManager
{
private readonly Func<IUserClient> userClientPerRequest;
private IUserClient UserClient
{
get
{
return userClientPerRequest();
}
}
public UserManager(Func<IUserClient> userClientPerRequest)
{
this.userClientPerRequest = userClientPerRequest;
}
public void DoStuff()
{
UserClient.DoStuff();
}
This is where is register autofac stuff
public class MyModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<UserManager>().As<IUserManager>().SingleInstance();
builder.RegisterType<UserClient>().As<IUserClient>().InstancePerHttpRequest();
builder.RegisterModule(new AutofacWebTypesModule());
//If i try this, i get Error 1 (printing errors after this code-block)
builder.Register<Func<IUserClient>>(c => c.Resolve<IUserClient>);
//If i try this, i get Error 2
builder.Register<Func<IUserClient>>(c => {
var ctx = c.Resolve<IComponentContext>();
return ctx.Resolve<IUserClient>;
});
//If i try this, well i always get null from GetService..
builder.Register<Func<IUserClient>>(c =>
DependencyResolver.Current.GetService<IUserClient>);
}
Looking at Autofac: Reference from a SingleInstance'd type to a HttpRequestScoped , they use some RequestContainer but i can find no such thing. :)
Error 1
This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored. Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.
Error 2
No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being reqested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
I have tried switching .InstancePerHttpRequest() to .InstancePerLifetimeScope() and a whole other different stuff.. Anyone have any ideas?
Thanks
When adding Autofac registrations manually in Orchard, use InstancePerMatchingLifetimeScope("shell"), if you need a singleton or InstancePerMatchingLifetimeScope("work"), if you need per-request instance.
I'm not sure if HttpSessionStateBase ctor argument can actually be resolved from the container. You could put IHttpContextAccessor there instead and use it to access the session state object inside IUserClient implementation.
And as Jim Bolla suggested - Func<IUserClient> (factory) is already available out of the box.
I don't think you need to do either of those registrations. Because of Relationship Types, Func<IUserClient> should already be available to you.

Categories