Mvc5 Hangfire Autofac No parameterless constructor defined for this object - autofac

I have an MVC5 webapplication which uses Autofac for DI registrations.
Now im using Hangfire for background jobs. When i schedule a job, i got an error.
What am i missing in here? I followed the descriptions given bij Hangfire and Hangfire.Autofac documentation.
Exception i got:
System.MissingMethodException
No parameterless constructor defined for this object.
System.MissingMethodException: No parameterless constructor defined for this object.
at System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck)
at System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)
at System.Activator.CreateInstance(Type type, Boolean nonPublic)
at System.Activator.CreateInstance(Type type)
at Hangfire.JobActivator.SimpleJobActivatorScope.Resolve(Type type)
at Hangfire.Server.CoreBackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.BackgroundJobPerformer.<>c__DisplayClass10_0.<PerformJobWithFilters>b__0()
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
at Hangfire.Server.BackgroundJobPerformer.InvokePerformFilter(IServerFilter filter, PerformingContext preContext, Func`1 continuation)
at Hangfire.Server.BackgroundJobPerformer.PerformJobWithFilters(PerformContext context, IEnumerable`1 filters)
at Hangfire.Server.BackgroundJobPerformer.Perform(PerformContext context)
at Hangfire.Server.Worker.PerformJob(BackgroundProcessContext context, IStorageConnection connection, String jobId, BackgroundJob backgroundJob, IReadOnlyDictionary`2& customData)
MyJob class:
public interface IMyJob
{
void DoSomething();
}
public class MyJob : IMyJob
{
private readonly ILogger _logger;
public MyJob(ILogger logger)
{
_logger= logger;
}
public void DoSomething()
{
Console.WriteLine("Recurring!");
}
}
I am using the following versions:
Hangfire 1.7.28
Hangfire.Autofac 2.3.1
Autofac 5.2.0
I have the following Hangfire configurations:
// Startup.cs
public void Configuration(IAppBuilder app)
{
// Configure DI dependancies
Bootstrapper.SetAutofacContainer(app);
// Configure authentication
ConfigureAuth(app);
// Configure Hangfire
`Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage("DefaultConnection");
var options = new DashboardOptions { Authorization = new[] { new HangfireAuthorizationFilter() } };
app.UseHangfireDashboard("/jobs", options);
app.UseHangfireServer();`
RecurringJob.AddOrUpdate<MyJob>("MYJOB", (x) => x.DoSomething(), Cron.MinuteInterval(5));
}
// My Autofac bootstrapper class
public class Bootstrapper
{
public static void SetAutofacContainer(IAppBuilder app)
{
var builder = new ContainerBuilder();
// Register Web controllers.
builder.RegisterControllers(typeof(MvcApplication).Assembly);
// Register model binders that require DI.
builder.RegisterModelBinders(typeof(MvcApplication).Assembly);
builder.RegisterModelBinderProvider();
// Register web abstractions like HttpContextBase.
builder.RegisterModule<AutofacWebTypesModule>();
// Enable property injection in view pages.
builder.RegisterSource(new ViewRegistrationSource());
// Enable property injection into action filters.
builder.RegisterFilterProvider();
// Identity
builder.RegisterType<ApplicationUserStore>().As<IUserStore<Medewerker, Guid>>().InstancePerLifetimeScope();
builder.RegisterType<ApplicationUserManager>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType<ApplicationSignInManager>().AsSelf().InstancePerLifetimeScope();
builder.Register<IAuthenticationManager>(c => HttpContext.Current.GetOwinContext().Authentication).InstancePerLifetimeScope();
builder.Register<IDataProtectionProvider>(c => app.GetDataProtectionProvider()).InstancePerLifetimeScope();
builder.RegisterType<Logger>().As<ILogger>().InstancePerLifetimeScope();
builder.RegisterType<MyJob>().AsSelf().InstancePerBackgroundJob();
// Build the container
var container = builder.Build();
Hangfire.GlobalConfiguration.Configuration.UseAutofacActivator(container);
JobActivator.Current = new AutofacJobActivator(container);
// Replace the MVC dependancy resolver with Autofac
DependencyResolver.SetResolver(new AutofacDependencyResolver(container));
// Set the dependency resolver for MVC.
var mvcResolver = new AutofacDependencyResolver(container);
DependencyResolver.SetResolver(mvcResolver);
// Register the Autofac middleware FIRST, then the Autofac MVC middleware.
app.UseAutofacMiddleware(container);
app.UseAutofacMvc();
}
}

Related

ASP.NET core EF ambiguous constructor

I use ASP.NET Core with .NET 5 and recently wanted to change from local development to Azure Web production mode.
Locally I use SQLite and everything works fine, on production I want to use Azure SQL.
However when I want to migrate my database, I get a rather long exception:
System.Exception: Could not resolve a service of type 'Server.Calendars.CalendarDataContext' for the parameter 'calendarDataContext' of method 'Configure' on type 'Server.Startup'.
---> System.InvalidOperationException: Unable to activate type 'Server.Calendars.CalendarDataContext'. The following constructors are ambiguous:
Void .ctor(Microsoft.Extensions.Configuration.IConfiguration)
Void .ctor(Microsoft.EntityFrameworkCore.DbContextOptions`1[Server.Calendars.CalendarDataContext])
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
at Microsoft.Extensions.DependencyInjection.AutoRegisterMiddleware.<>c__DisplayClass4_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter.<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
My class CalendarDataContext .cs for Azure SQL
public class CalendarDataContext : DbContext
{
public DbSet<CalendarEntry> CalendarEntries { get; set; }
protected readonly IConfiguration Configuration;
public CalendarDataContext(IConfiguration configuration)
{
Configuration = configuration;
}
public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
: base(options)
{ }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
options.UseSqlServer(Configuration.GetConnectionString("CalendarDatabase"));
}
}
}
and CalendarDataContextSqlite.cs for SQLite
public class CalendarDataContextSqlite : CalendarDataContext
{
public CalendarDataContextSqlite(IConfiguration configuration) : base(configuration) { }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
var databaseName = Configuration.GetConnectionString("CalendarDatabase");
var databasePath = PathHelper.DataPath(databaseName);
options.UseSqlite("Data Source=" + databasePath);
}
}
}
I think the issue is with the line CalendarDataContext(DbContextOptions<CalendarDataContext> options) that I need for creating a temporary InMemory-Database for my tests.
How can I make this ambiguous constructor less ambiguous?
Edit: Add startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Environment = environment;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
if (Environment.IsProduction())
{
services.AddDbContext<CalendarDataContext>();
}
else if (Environment.IsDevelopment())
{
services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
CalendarDataContext calendarDataContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
calendarDataContext.Database.Migrate();
}
}
Firstly, Add IConfiguration as a local member in your Startup.cs
IConfiguration Configuration;
public IWebHostEnvironment Environment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
then Register a configured CalendarDataContext or CalendarDataContextSqlite in Startup
public void ConfigureServices(IServiceCollection services)
{
if (Environment.IsProduction())
{
services.AddDbContext<CalendarDataContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("CalendarDatabase"));
}
else if (Environment.IsDevelopment())
{
services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>(options => {
var databaseName = Configuration.GetConnectionString("CalendarDatabase");
var databasePath = PathHelper.DataPath(databaseName);
options.UseSqlite("Data Source=" + databasePath);
});
}
}
Then, CalendarDataContext:
public class CalendarDataContext : DbContext
{
public DbSet<CalendarEntry> CalendarEntries { get; set; }
public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
: base(options) { }
protected CalendarDataContext(DbContextOptions options)
: base(options) { }
}
And, CalendarDataContextSqlite:
public class CalendarDataContextSqlite : CalendarDataContext
{
public CalendarDataContextSqlite(DbContextOptions<CalendarDataContextSqlite> options)
: base(options) { }
}
Now,
No need for the OnConfiguring in the context classes.
In production you'll have a configured CalendarDataContext to be injected wherever the constructor asks for a CalendarDataContext.
And for developerment you'll have a configured CalendarDataContextSqlite to be injected wherever the constructor asks for a CalendarDataContext.
That configured context will also be injected into Startup.Configure so you can migrate your database.

Passing connection string to Entity framework at runt time for each call

My Entity framework context is as following
public partial class MyContext : DbContext, IMyContext
{
static MyContext()
{
System.Data.Entity.Database.SetInitializer<MyContext>(null);
}
public MyContext()
: base("Name=MyContext")
{
}
I am resolving it through autofac in the following way
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
This db context gets called in repository layer
#region Fields
private readonly IMyContext _context;
#endregion
#region Constructors and Destructors
public EmployeeRepository(IMyContext context)
{
_context = context;
}
#endregion
public void Create(Employee emp)
{
this._context.Employee.Add(emp);
}
Now my issue is , I want to set the connection string dynamically per call. The connection string will be passed through a webapi which i want to pass on to this context. Can anyone help me how can i do that? I am confused about autofac here. Secondly how can i make sure each call sets connection string and does not cache it.
You can use a factory that will build the context and set the connectionstring for you.
public interface IContextFactory
{
IContext GetInstance();
}
public class MyContextFactory : IContextFactory
{
public IContext GetInstance()
{
String connectionString = this.GetConnectionString(HttpContext.Current);
return new MyContext(connectionString);
}
private String GetConnectionString(HttpContext context)
{
// do what you want
}
}
builder.RegisterType<MyContextFactory>()
.As<IContextFactory>()
.InstancePerRequest();
builder.Register(c => c.Resolve<IContextFactory>().GetInstance())
.As<IContext>()
.InstancePerRequest();
If you can't get connectionstring based on HttpContext, you can change contextFactory implementation to expect initialization by WebAPI before creating the instance. For example :
public interface IContextFactory
{
IContext GetInstance();
void Initialize(String connectionString);
}
public class MyContextFactory : IContextFactory
{
private String _connectionString;
public void Initialize(String connectionString)
{
this._connectionString = connectionString;
}
public IContext GetInstance()
{
if (this._connectionString == null)
{
throw new Exception("connectionString not initialized");
}
return new MyContext(this._connectionString);
}
}
At the beginning of your web API call (through attribute for example), you can call the Initialize method. Because the factory is InstancePerRequest you will have one instance for the duration of the request.
By the way, I'm not sure to understand this registration
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
It looks buggy because you will have 2 different registration of the same type and not for the same scope, is it intended ? Furthermore, it doesn't sound a good idea to register a DbContext, do you need this registration ?
The following registration looks better :
builder.RegisterType<MainContext>()
.As<IMainContext>()
.As<DbContext>()
.InstancePerRequest();

Autofac MVC5 and WebAPI2.1 not working

Here is my Bootstrap in global.asax
public class Global : HttpApplication {
void Application_Start(object sender, EventArgs e) {
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
RouteConfig.RegisterRoutes(RouteTable.Routes);
// initiate dependency injection resolver
DependencyInjectionResolver.RegisterDependencies(typeof(Global).Assembly);
}
}
Here is implementation
public class DependencyInjectionResolver {
private static IContainer _container;
public static TService Resolve<TService>() {
return DependencyResolver.Current.GetService<TService>();
}
public static void RegisterDependencies(Assembly assembly) {
var builder = new ContainerBuilder();
builder.RegisterFilterProvider();
builder.RegisterControllers(Assembly.GetExecutingAssembly());
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
builder.RegisterGeneric(typeof (Repository<>))
.As(typeof (IRepository<>))
.InstancePerHttpRequest()
.InstancePerLifetimeScope();
builder.RegisterType<LoggerService>()
.As<ILoggerService>()
.InstancePerApiRequest()
.InstancePerHttpRequest();
builder.Register(x =>
new MessageDataService(Resolve<ILoggerService>(), Resolve<IRepository<ApiRequestHistory>>()))
.As<IMessagesDataService>()
.InstancePerHttpRequest()
.InstancePerApiRequest();
#region setup
_container = builder.Build();
DependencyResolver.SetResolver(new AutofacDependencyResolver(_container));
GlobalConfiguration.Configuration.DependencyResolver
= new AutofacWebApiDependencyResolver(_container);
#endregion
}
}
Here is my exception:
An error has occurred.An error occurred when trying to create a controller of type 'MessageReportController'. Make sure that the controller has a parameterless public constructor.System.InvalidOperationException at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
at System.Web.Http.Controllers.HttpControllerDescriptor.CreateController(HttpRequestMessage request)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__0.MoveNext()An error has occurred.Type 'WayToLead.Web.UI.api.MessageReportController' does not have a default constructorSystem.ArgumentException at System.Linq.Expressions.Expression.New(Type type)
at System.Web.Http.Internal.TypeActivator.Create[TBase](Type instanceType)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.GetInstanceOrActivator(HttpRequestMessage request, Type controllerType, Func`1& activator)
at System.Web.Http.Dispatcher.DefaultHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
How to fix it to work with WebApi and Mvc controllers?
Check that you have registered all of your ApiControllers' dependencies and sub-dependencies. Somewhere deep inside the stack trace you might find exacly which dependency AutoFac wasn't able to resolve.
I'm guessing that since AutoFac couldn't satisfy all dependencies then the default dependency resolver takes over.

Stuck Working Through Steven Sanderson's "ASP.NET MVC 2..."

I received a brief introduction to ASP.NET MVC in school and I am trying to expand that knowledge base with Steven Sanderson's book Pro ASP.NET MVC 2 Framework. It's been a great help, but I have hit a wall in the example that implements Ninject. I believe the DI is setup correctly, but when I try to specify the bindings for a dependency in my Ninject controller class, I receive one of two brown screens of death:
No parameterless constructor defined for this object.
Stack Trace:
[MissingMethodException: No parameterless constructor defined for this object.]
System.RuntimeTypeHandle.CreateInstance(RuntimeType type, Boolean publicOnly, Boolean noCheck, Boolean& canBeCached, RuntimeMethodHandleInternal& ctor, Boolean& bNeedSecurityCheck) +0
System.RuntimeType.CreateInstanceSlow(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache) +98
System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipVisibilityChecks, Boolean skipCheckThis, Boolean fillCache) +241
System.Activator.CreateInstance(Type type, Boolean nonPublic) +69
System.Activator.CreateInstance(Type type) +6
System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +491
[InvalidOperationException: An error occurred when trying to create a controller of type 'SportsStore.WebUI.Controllers.ProductsController'. Make sure that the controller has a parameterless public constructor.]
System.Web.Mvc.DefaultControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +628
System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +204
System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +193
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +160
System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +80
System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +45
System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8898152
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184
Object reference not set to an instance of an object.
Stack Trace:
[NullReferenceException: Object reference not set to an instance of an object.]
SportsStore.WebUI.Infrastructure.SportsStoreServices.Load() in D:\Visual Studio 2010\Projects\CSharp2010AndDotNet4PlatformBook\SportsStore\SportsStore.WebUI\Infrastructure\NinjectControllerFactory.cs:33
Ninject.Modules.NinjectModule.OnLoad(IKernel kernel) in c:\Projects\Ninject\ninject\src\Ninject\Modules\NinjectModule.cs:60
Ninject.KernelBase.Load(IEnumerable`1 modules) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:222
Ninject.KernelBase..ctor(IComponentContainer components, INinjectSettings settings, INinjectModule[] modules) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:102
Ninject.KernelBase..ctor(INinjectModule[] modules) in c:\Projects\Ninject\ninject\src\Ninject\KernelBase.cs:57
Ninject.StandardKernel..ctor(INinjectModule[] modules) in c:\Projects\Ninject\ninject\src\Ninject\StandardKernel.cs:31
SportsStore.WebUI.Infrastructure.NinjectControllerFactory..ctor() in D:\Visual Studio 2010\Projects\CSharp2010AndDotNet4PlatformBook\SportsStore\SportsStore.WebUI\Infrastructure\NinjectControllerFactory.cs:18
SportsStore.WebUI.MvcApplication.Application_Start() in D:\Visual Studio 2010\Projects\CSharp2010AndDotNet4PlatformBook\SportsStore\SportsStore.WebUI\Global.asax.cs:32
Here is my code in the Ninject controller class:
public class NinjectControllerFactory : DefaultControllerFactory
{
// A Ninject "kernel" is the things that can supply object instances
private IKernel kernel = new StandardKernel(new SportsStoreServices());
// ASP.NET MVC calls this to get the controller for each request
protected override IController GetControllerInstance(RequestContext context, Type controllerType)
{
if (controllerType == null)
return null;
return (IController)kernel.Get(controllerType);
}
// Configures how abstract service types are mapped to concrete implementations
private class SportsStoreServices : NinjectModule
{
public override void Load()
{
Bind<IProductsRepository>()
.To<SqlProductsRepository>()
.WithConstructorArgument("connectionString",
ConfigurationManager.ConnectionStrings["AppDb"].ConnectionString
);
}
}
}
The book is pushing up against the limits of my understanding, but I am following along and have been able to debug everything up to this point. This has me stumped. Any idea where I would begin to debug this?
Both issues are related to Ninject not being setup or integrated correctly. The trunk version of Ninject allows you to bypass having to define custom controller factories. It's a cleaner setup and less confusing.
See:
http://codeclimber.net.nz/archive/2009/08/14/how-to-use-ninject-2-with-asp.net-mvc.aspx
Additionally you should check out the article listed here
Ninject and Custom Controller Factory
Did you update your application_start in global.asax to point to the ninject controller factory?
as such:
protected void Application_Start() {
..........
ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory ());
}

Using structuremap with log4net wrapper

I have the following interface:
public interface ILogger
{
void Debug(string message, params object[] values);
void Info(string message, params object[] values);
void Warn(string message, params object[] values);
void Error(string message, params object[] values);
void Fatal(string message, params object[] values);
}
and the following implementation:
public class Log4netLogger : ILogger
{
private ILog _log;
public Log4netLogger(Type type)
{
_log = LogManager.GetLogger(type);
}
public void Debug(string message, params object[] values)
{
_log.DebugFormat(message, values);
}
// other logging methods here...
}
My idea was to use structuremap to instantiate the Log4netLogger class with using the Type of the class that did the logging. However, I can't for the life of me figure out how to pass the type of the calling class to structuremap so that it can be passed to the constructor of the logging implementation. Any advice on how to do that (or a better way) would be most appreciated.
We use a similar ILogger wrapper around log4net and typically use constructor injection. We use an interceptor as a factory method responsible for creating the Logger. Here is our typical registry for logging setup.
public class CommonsRegistry : Registry
{
public CommonsRegistry()
{
For<ILogger>()
.AlwaysUnique()
.TheDefault.Is.ConstructedBy(s =>
{
if (s.ParentType == null)
return new Log4NetLogger(s.BuildStack.Current.ConcreteType);
return new Log4NetLogger(s.ParentType);
});
var applicationPath = Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location);
var configFile = new FileInfo(Path.Combine(applicationPath, "log4net.config"));
XmlConfigurator.ConfigureAndWatch(configFile);
}
}
The parent type null check is necessary when there are dependencies on concrete types.
The rest is optional log4net setup stuff.
One thing I do like about this setup is the ability to use a null loggers for unit testing.
If the type parameter is context-specific, I don't think this is going to work as shown. If you need to pass something context specific in the constructor, you are likely going to have to create a factory interface and implementation that returns an instance of the ILogger:
public interface ILoggerFactory
{
ILogger Create(Type type);
}
public class LoggerFactory : ILoggerFactory
{
public ILogger Create(Type type)
{
return new Log4netLogger(type);
}
}
It might be possible to bootstrap StructureMap to supply the instance you want based on the type, but that assumes a limited number of types that you know in advance.
I really need to get out of the habit of answering my own question, but for those who run across it, here's the answer.
return ObjectFactory.With(type).GetInstance<T>();
I actually have a wrapper to structuremap (to avoid exposing the structuremap dependency to my app) that looks like the following:
public static class ServiceManager
{
public static T Get<T>()
{
return ObjectFactory.GetInstance<T>();
}
public static T Get<T>(Type type)
{
return ObjectFactory.With(type).GetInstance<T>();
}
}
Any time in the code I need a logger, I call the following:
ServiceManager.Get<ILogger>(GetType()).Info("Logging page view...");