I have edited the original question since the same error is occurring the difference being the implementation, I have now added Ninject to the mix.
I have created a class for the validation rules
public class AlbumValidator : AbstractValidator<Album> {
public AlbumValidator() {
RuleFor(a => a.Title).NotEmpty();
}
}
I have created a ValidatorModule for Ninject
internal class FluentValidatorModule : NinjectModule {
public override void Load() {
AssemblyScanner.FindValidatorsInAssemblyContaining<AlbumValidator>()
.ForEach(result => Bind(result.InterfaceType).To(result.ValidatorType).InSingletonScope());
}
}
Here is my ValidatorFactory
public class NinjectValidatorFactory : ValidatorFactoryBase {
public override IValidator CreateInstance(Type validatorType) {
if (validatorType.GetGenericArguments()[0].Namespace.Contains("DynamicProxies")) {
validatorType = Type.GetType(string.Format("{0}.{1}[[{2}]], {3}",
validatorType.Namespace,
validatorType.Name,
validatorType.GetGenericArguments()[0].BaseType.AssemblyQualifiedName,
validatorType.Assembly.FullName));
}
return Container.Get(validatorType) as IValidator;
}
IKernel Container { get; set; }
public NinjectValidatorFactory(IKernel container) {
Container = container;
}
}
and the relevant parts from my Global
protected override void OnApplicationStarted() {
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
var factory = new NinjectValidatorFactory(Container);
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(factory));
DataAnnotationsModelValidatorProvider
.AddImplicitRequiredAttributeForValueTypes = false;
}
protected override IKernel CreateKernel() {
return Container;
}
IKernel Container {
get { return new StandardKernel(new FluentValidatorModule()); }
}
I load the sample site click on the create new album link and then click the create button leaving the title empty I am then greeted with the error protected override void OnApplicationStarted() {
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
var factory = new NinjectValidatorFactory(Container);
ModelValidatorProviders.Providers.Add(
new FluentValidationModelValidatorProvider(factory));
DataAnnotationsModelValidatorProvider
.AddImplicitRequiredAttributeForValueTypes = false;
}
protected override IKernel CreateKernel() {
return Container;
}
IKernel Container {
get { return new StandardKernel(
new Bootstrapper(),
new FluentValidatorModule()); }
}
I load up the create form and click create leaving the title empty low and behold an error
This property cannot be set to a null value.
The line it references is within the Entity Framework auto generated class, I traced the
Namespace.Contains("DynamicProxies")
and it was returning false, is this because I told EF to use a custom namespace SampleMusicStore.Web?
Or am I missing something else?
Cheers.
The problem is that Entity Framework is generating dynamic proxies on your classes, and then your system is trying to validate against the proxy classes instead of the classes you defined.
The way to resolve this is the same as this answer.
Related
I've read a lot of articles regarding database migration on startup and no matter what approach I use my efforts aren't going anywhere. My main problem that i'm getting is no parameterless constructor defined for type startup
I have my DataContext class
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DataContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (options.IsConfigured)
{
//means that context has been added during dependency injection and no further action required.
}
else
{
//means context is being accessed during Add-Migration therefore we need to set the options. The whole DI/Configuration process won't have run yet, so need some other way to get connection string.
//probably below is a bit too fancy, just hardcoding would be fine. But anyway it seems to work and transfers to different developers machines
//you must have {Values: { SqlConnectionString : xyz}} in local.settings.json in Unite.FunctionApp project dir
var localSettingsJson =
Path.Combine(local.settings.json");
var config = new ConfigurationBuilder()
.AddJsonFile(localSettingsJson, false)
.Build();
options.UseSqlServer(config["Values:SqlConnectionString"]);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{... }
My Startup Class
// register assembly
[assembly: FunctionsStartup(typeof(Startup))]
{
// inherit FunctionsStartup
public class Startup : FunctionsStartup
{
private DataContext _context;
public Startup(DataContext context)
{
_context = context;
}
public override void Configure(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var config = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), false)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IConfiguration>(config);
var sqlConnection = config["SqlConnectionString"] ??
throw new Exception("SQL Connection String Not Defined");
builder.Services.AddDbContext<DataContext>(options => options.UseSqlServer(sqlConnection));
_context.Database.MigrateAsync();
}
}
}
If I have my paramaterless DataContext method in my class why am i still getting this issue that it isn't defined?
Add your parameterless constructor before the other constructor in your DataContext class.
I am facing issue of mapping. My solution is as below.
I have two projects
1. Azure Function (v1) project with Framework: .Net Framework 4.7.2
2. Class Library with Framework: .Net Framework 4.7.2
In 1st project i have an azure function which is triggering using Queue and call my function BeginBackup(CancellationToken token, DateTime.Now()) which exist in my concreate class.
I am calling my function as below from my Azure function.
DependencyContext.Instance.Locator.GetInstance<IBackupTableStorageService>().BeginBackup(cancellationToken, SystemTime.UtcNow());
My DependencyContext.cs class is below.
using ClassLibrary1.BackupTableStorage;
using CommonServiceLocator;
using StructureMap;
using System;
using System.Collections.Generic;
namespace AzureFunctionsProject.Functions
{
public interface IDependencyContext
{
IServiceLocator Locator { get; }
}
public class DependencyContext : IDependencyContext
{
private static volatile IDependencyContext _instance;
private static readonly object SyncRoot = new Object();
public static IDependencyContext Instance
{
get
{
if (_instance == null)
{
lock (SyncRoot)
{
if (_instance == null)
{
_instance = new DependencyContext();
}
}
}
return _instance;
}
set => _instance = value;
}
public IServiceLocator Locator { get; private set; }
public DependencyContext()
{
Build();
}
private void Build()
{
var registry = new Registry();//Registry is the class belongs to StructureMap.
var container = new Container();
var serviceLocatorProvider = new StructureMapServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => serviceLocatorProvider);
Locator = serviceLocatorProvider;
// register the container itself
registry.For<IServiceLocator>().Use(ServiceLocator.Current);
// Apply the registry to the container
container.Configure(x =>
{
x.AddRegistry<Domain.Registry>();
x.AddRegistry(registry);
});
}
}
public class StructureMapServiceLocator : ServiceLocatorImplBase
{
private IContainer Container { get; set; }
public StructureMapServiceLocator(IContainer container)
{
Container = container;
}
protected override object DoGetInstance(Type serviceType, string key)
{
if (string.IsNullOrEmpty(key))
{
return Container.GetInstance(serviceType);
}
return Container.GetInstance(serviceType, key);
}
protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
{
foreach (object obj in Container.GetAllInstances(serviceType))
{
yield return obj;
}
}
}
}
I have another Registery class which i have created which have mapping of my interface with my concrete class.
Registery.cs file is below.
namespace ClassLibrary1.Domain
{
public class Registry: StructureMap.Registry
{
public Registry()
{
// The Localization Service needs to be passed a function to record exceptions
For<ILocalizationService>().Use<LocalizationService>()
.Ctor<LocalizationService.LogException>().Is(context => CreateExceptionLogger(context));
For<ICloudStorageWrapper>().Use<CloudStorageWrapper>();
For<IBackupTableStorageService>().Use<BackupTableStorageService>();
}
private LocalizationService.LogException CreateExceptionLogger(IContext context)
{
return (ex, c, m) =>
{
var logger = context.GetInstance<ILogicalOperationsLogger>();
logger.ErrorException(m, ex);
};
}
}
}
Like wise i have my Interface and respected to concreate class which has the definition of my method BeginBackup(CancellationToken token, DateTime.Now());
I am getting exception in DoGetInstance method in DependencyContext.cs class.
I am new in IoC and dependency injection, please let me know what i am doing wrong, if anything required to make question more clear please let me know.
Try to move the registration for IServiceLocator into the configure action. You can also remove the Registry-object you created:
private void Build()
{
var container = new Container();
var serviceLocatorProvider = new StructureMapServiceLocator(container);
ServiceLocator.SetLocatorProvider(() => serviceLocatorProvider);
Locator = serviceLocatorProvider;
// Apply the registry to the container
container.Configure(x =>
{
// register the container itself
x.For<IServiceLocator>().Use(serviceLocatorProvider);
x.AddRegistry<Domain.Registry>();
});
}
I've been at it for days, but I can't get Unity to inject anything with RegisterType<> into my Controller. I'm using Web Api 2, in Visual Studio 2015, with Unity 4. Whenever I try to inject IUnitOfWork or IRFContext, I get "message": "An error occurred when trying to create a controller of type 'ClPlayersController'. Make sure that the controller has a parameterless public constructor.".
I'm using the Unity.AspNet.WebApi to bootstrapp into WebApi. Below is my UnityWebApiActivator
[assembly: WebActivatorEx.PreApplicationStartMethod(typeof(mycompany.project.api.UnityWebApiActivator), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(mycompany.project.api.UnityWebApiActivator), "Shutdown")]
namespace mycompany.project.api
{
public static class UnityWebApiActivator
{
public static void Start()
{
var resolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
GlobalConfiguration.Configuration.DependencyResolver = resolver;
}
public static void Shutdown()
{
var container = UnityConfig.GetConfiguredContainer();
container.Dispose();
}
}
}
I'm using a Start.cs due to Owin.
[assembly: OwinStartup(typeof(mycompany.project.api.Startup))]
namespace mycompany.project.api
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
ConfigureOAuth(app);
config.DependencyResolver = new UnityDependencyResolver(UnityConfig.GetConfiguredContainer());
WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
public void ConfigureOAuth(IAppBuilder app)
{
OAuthAuthorizationServerOptions OAuthServerOptions = new OAuthAuthorizationServerOptions()
{
AllowInsecureHttp = true,
TokenEndpointPath = new PathString("/token"),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
Provider = new SimpleAuthorizationServerProvider(),
RefreshTokenProvider = new SimpleRefreshTokenProvider()
};
// Token Generation
app.UseOAuthAuthorizationServer(OAuthServerOptions);
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());
}
}
}
My WebApiConfig.cs is below:
namespace mycompany.project.api
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
log4net.Config.XmlConfigurator.Configure();
config.MapHttpAttributeRoutes();
config.EnableSystemDiagnosticsTracing();
config.Services.Add(typeof(IExceptionLogger),
new SimpleExceptionLogger(new LogManagerAdapter()));
config.Services.Replace(typeof(IExceptionHandler), new GlobalExceptionHandler());
}
}
}
My UnityConfig.cs is below
namespace mycompany.project.api
{
public class UnityConfig
{
#region Unity Container
private static Lazy<IUnityContainer> container = new Lazy<IUnityContainer>(() =>
{
var container = new UnityContainer();
RegisterTypes(container);
return container;
});
public static IUnityContainer GetConfiguredContainer()
{
return container.Value;
}
#endregion
public static void RegisterTypes(IUnityContainer container)
{
var config = new MapperConfiguration(cfg =>
{
//AutoMapper bindings
});
container.RegisterInstance<IMapper>(config.CreateMapper());
container.RegisterType<IRFContext, RFContext>(new PerThreadLifetimeManager());
container.RegisterType<IUnitOfWork, UnitOfWork>();
XmlConfigurator.Configure();
var logManager = new LogManagerAdapter();
container.RegisterInstance<ILogManager>(logManager);
}
}
}
All that I have in my Global.asax is below:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Error()
{
var exception = Server.GetLastError();
if (exception != null)
{
var log = new LogManagerAdapter().GetLog(typeof(WebApiApplication));
log.Error("Unhandled exception.", exception);
}
}
}
If my Controller is like this, it works fine:
public class ClPlayersController : ApiController
{
private readonly IMapper mapper;
public ClPlayersController(IMapper _mapper, IUnityContainer container)
{
mapper = _mapper;
}
But placing IUnitOfWork, like below, or the IRFContext, I get the error:
private readonly IMapper mapper;
private readonly IUnitOfWork unitOfWork;
public ClPlayersController(IMapper _mapper, IUnityContainer container, IUnitOfWork _unitOfWork)
{
mapper = _mapper;
unitOfWork = _unitOfWork;
}
I can't find, for the life of me, what I'm doing wrong. If I loop through the container.Registrations on the constructor, I find the mappings, but they refuse to get injected. Any hints?
EDIT
Below is the code for UnitOfWork and RFContext
namespace mycompany.project.data.configuracao
{
public class UnitOfWork : IUnitOfWork
{
private readonly IRFContext _rfContext;
private bool _disposed = false;
public UnitOfWork(IRFContext rfContext)
{
_rfContext = rfContext;
}
public void Commit()
{
if (_disposed)
{
throw new ObjectDisposedException(this.GetType().FullName);
}
_rfContext.SaveChanges();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing && _rfContext != null)
{
_rfContext.Dispose();
}
_disposed = true;
}
}
}
and
namespace mycompany.project.data.configuracao
{
public interface IUnitOfWork : IDisposable
{
void Commit();
}
}
and RFContext is a basic POCO generated DBContext
namespace mycompany.project.data.configuracao
{
using System.Linq;
public class RFContext : System.Data.Entity.DbContext, IRFContext
{
public System.Data.Entity.DbSet<ClGrupoEconomico> ClGrupoEconomicoes { get; set; }
//all my DbSets
public System.Data.Entity.DbSet<SpTipoLog> SpTipoLogs { get; set; }
static RFContext()
{
System.Data.Entity.Database.SetInitializer<RFContext>(null);
}
public RFContext()
: base("Name=RFContext")
{
}
public RFContext(string connectionString)
: base(connectionString)
{
}
public RFContext(string connectionString, System.Data.Entity.Infrastructure.DbCompiledModel model)
: base(connectionString, model)
{
}
public RFContext(System.Data.Common.DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{
}
public RFContext(System.Data.Common.DbConnection existingConnection, System.Data.Entity.Infrastructure.DbCompiledModel model, bool contextOwnsConnection)
: base(existingConnection, model, contextOwnsConnection)
{
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new ClGrupoEconomicoConfiguration());
//all my Configuration classes
modelBuilder.Configurations.Add(new SpTipoLogConfiguration());
}
public static System.Data.Entity.DbModelBuilder CreateModel(System.Data.Entity.DbModelBuilder modelBuilder, string schema)
{
modelBuilder.Configurations.Add(new ClGrupoEconomicoConfiguration(schema));
//all my configuration classes
modelBuilder.Configurations.Add(new SpTipoLogConfiguration(schema));
return modelBuilder;
}
}
}
Unfortunately the exception you are seeing can occur for several reasons. One of them is when Unity cannot resolve one or more of your injections.
An error occurred when trying to create a controller of type
'FooController'. Make sure that the controller has a parameterless
public constructor.
So, based on the information in your question your setup is apparently correct, since IMapper can be injected. Therefore I guess that UnitOfWork and RFContext have dependencies that Unity cannot resolve. Maybe a repository?
UPDATE:
The problem here is that your RFContext has several constructors.
https://msdn.microsoft.com/en-us/library/cc440940.aspx#cnstrctinj_multiple
When a target class contains more than one constructor with the same
number of parameters, you must apply the InjectionConstructor
attribute to the constructor that the Unity container will use to
indicate which constructor the container should use. As with automatic
constructor injection, you can specify the constructor parameters as a
concrete type, or you can specify an interface or base class for which
the Unity container contains a registered mapping.
In this case Unity doesn't know how to resolve your RFContext, and will try to use the constructor with the most parameters. You can solve it by using
container.RegisterType<IRFContext, RFContext>(new InjectionConstructor());
My database have different schema depending on user selections on runtime.
My code is below:
public partial class FashionContext : DbContext
{
private string _schema;
public FashionContext(string schema) : base()
{
_schema = schema;
}
public virtual DbSet<Style> Styles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=.\sqlexpress;Database=inforfashionplm;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Style>()
.ToTable("Style", schema: _schema);
}
}
Upon testing. I created a context instance with 'schema1'.
So far so good.
But when I create another context instance with different schema 'schema2', the resulting data in which the schema is still on 'schema1'.
Here is the implementation:
using (var db = new FashionContext("schema1"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Console.Clear();
using (var db = new FashionContext("schema2"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Later I noticed that the OnModelCreating is called only one time, so it is never called again when you create a new context instance of the same connection string.
Is it possible to have dynamic schema on runtime? Note: this is possible in EF6
One of possible way was mentioned above, but briefly, so I will try to explain with examples.
You ought to override default ModelCacheKeyFactory and ModelCacheKey.
ModelCachekeyFactory.cs
internal sealed class CustomModelCacheKeyFactory<TContext> : ModelCacheKeyFactory
where TContext : TenantDbContext<TContext>
{
public override object Create(DbContext context)
{
return new CustomModelCacheKey<TContext>(context);
}
public CustomModelCacheKeyFactory([NotNull] ModelCacheKeyFactoryDependencies dependencies) : base(dependencies)
{
}
}
ModelCacheKey.cs, please review Equals and GetHashCode overridden methods, they are not best one and should be improved.
internal sealed class ModelCacheKey<TContext> : ModelCacheKey where TContext : TenantDbContext<TContext>
{
private readonly string _schema;
public ModelCacheKey(DbContext context) : base(context)
{
_schema = (context as TContext)?.Schema;
}
protected override bool Equals(ModelCacheKey other)
{
return base.Equals(other) && (other as ModelCacheKey<TContext>)?._schema == _schema;
}
public override int GetHashCode()
{
var hashCode = base.GetHashCode();
if (_schema != null)
{
hashCode ^= _schema.GetHashCode();
}
return hashCode;
}
}
Register in DI.
builder.UseSqlServer(dbConfiguration.Connection)
.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory<CustomContext>>();
Context sample.
public sealed class CustomContext : TenantDbContext<CustomContext>
{
public CustomContext(DbContextOptions<CustomContext> options, string schema) : base(options, schema)
{
}
}
You can build the model externally and pass it into the DbContext using DbContextOptionsBuilder.UseModel()
Another (more advanced) alternative is to replace the IModelCacheKeyFactory to take schema into account.
I found a way to recreate the compiled model on each context creation.
public partial class MyModel : DbContext {
private static DbConnection _connection
{
get
{
//return a new db connection
}
}
private static DbCompiledModel _model
{
get
{
return CreateModel("schema name");
}
}
public MyModel()
: base(_connection, _model, false)
{
}
private static DbCompiledModel CreateModel(string schema)
{
var modelBuilder = new DbModelBuilder();
modelBuilder.HasDefaultSchema(schema);
modelBuilder.Entity<entity1>().ToTable(schema + ".entity1");
var builtModel = modelBuilder.Build(_connection);
return builtModel.Compile();
}
}
Because the order of modules being resolved is not guaranteed I'm having some problem achieving this:
I have a module which registers a ScheduleService this ScheduleService is responsible for trigger events at set intervals etc.
I'm able to load in different IScheduable items which i do so using the XML Configuration. The problem that i have, is the IScheduable items require the IScheduleService to be ready so it can register it's self.
So in my <autofac><modules> I have
<module type="Namespace.ScheduleServiceModule, Namespace" />
Then the idea was I could load in as many different ISchedulable items
<module type="SomeNamespace.ScheudleItem1, SomeNamespace />
<module type="SomeNamespace.ScheudleItem2, SomeNamespace />
<module type="SomeNamespace.ScheudleItem3, SomeNamespace />
<module type="SomeNamespace.ScheudleItem4, SomeNamespace />
This is currently how I do it in those scheduleitem modules:
protected override void Load(ContainerBuilder builder)
{
builder.RegisterCallback(registry =>
{
var scheduleService = new TypedService(typeof(IScheduleService));
var registrations = registry.RegistrationsFor(scheduleService);
if (registrations != null && registrations.Any())
{
IComponentRegistration componentRegistration = registrations.First();
componentRegistration.Activated += (sender, args) =>
{
IScheduleService scheduleService = args.Instance as IScheduleService;
if (scheduleService != null)
{
OnScheduleServiceAvailable(args.Context, scheduleService);
}
};
}
});
base.Load(builder);
}
This is the override in each of ScheduleItems
protected override void OnScheduleServiceAvailable(IComponentContext context,
IScheduleService scheduleService)
{
scheduleService.Add(
new SqlSyncSchedulable(Enabled, IntervalMS, ConnectionString, SqlSelect,
context.Resolve<ILoggerService>(),
context.Resolve<IPersonService>(),
context.Resolve<ILoggingEventService>(),
context.Resolve<ITemplateService>(),
context.Resolve<ITemplateLoggingEventService>(),
context.Resolve<IRuntimeSettingsService>()));
}
Which is quite intermittent. The ISchedule item should register itself but the problem is the Schedule service might be registered after those items.
There must be a way to achieve this?
I think your problem is not in the load order of the module, but is instead about dependency design.
You should design your modules and your dependencies in a way that they are not temporally coupled.
One of the many possible designs involves having the schedule service require a list of possible dependencies.
In this design, the responsibilitt of an ISchedule is in defining the parameters of a schedulable operation, you use Autofac Adapter pattern to wrap each schedule into a ISyncSchedulable operation, and the ScheduleService requires a List<ISyncSchedulable> in order to add them at initialization.
As an example (following your example, but not verbatim: I'm trying more to make a point than giving a complete solution):
using System;
using System.Collections.Generic;
using System.Linq;
using Autofac;
using NUnit.Framework;
namespace Example
{
public interface ISchedule
{
bool Enabled { get; }
long IntervalMs { get; }
string ConnectionString { get; }
string SqlSelect { get; }
}
public class Schedule : ISchedule
{
public bool Enabled
{
get { return true; }
}
public long IntervalMs
{
get { return 100000; }
}
public string ConnectionString
{
get { return "localhost;blabla"; }
}
public string SqlSelect
{
get { return "select 1 as A"; }
}
}
// let's assume SqlSyncSchedulable inherits from a common
// ISyncSchedulable interface
public interface ISyncSchedulable
{
void RunSchedule(ScheduleService scheduleService);
}
public class SqlSyncSchedulable : ISyncSchedulable
{
public ISchedule Schedule { get; private set; }
public OtherService OtherService { get; private set; }
public SqlSyncSchedulable(ISchedule schedule,
OtherService otherService
/*,ILoggerService loggerService
IPersonService personService, */
)
{
Schedule = schedule;
OtherService = otherService;
// read interval and other data from schedule,
// store service references as usual.
}
public void RunSchedule(ScheduleService scheduleService)
{
throw new NotImplementedException();
}
}
public class OtherService
{
}
public class ScheduleService
{
public ScheduleService(IList<ISyncSchedulable> schedulables, OtherService otherService /*, ... other dependencies */)
{
// there is no ADD! Autofac gives you a list of all possible
// ISyncSchedulable components
SyncSchedulables = schedulables;
// ... other dependencies
}
public IList<ISyncSchedulable> SyncSchedulables { get; set; }
// this code is not a proper implementation, nor a scheduler,
// it's just a placeholder
public void RunSchedules()
{
foreach (var schedule in SyncSchedulables)
{
// do your operations, involving ...
schedule.RunSchedule(this);
}
}
}
public class TestModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<ScheduleService>().AsSelf();
builder.RegisterType<OtherService>().AsSelf();
// don't worry about which type should be registered,
// and register each type inheriting from ISchedule
// coming from the current assembly
// You can even use a single registration for all the
// possible implementations of ISchedule, using techniques
// explained in http://docs.autofac.org/en/latest/register/scanning.html
builder.RegisterAssemblyTypes(GetType().Assembly)
.Where(t => t.GetInterfaces().Contains(typeof(ISchedule)))
.AsImplementedInterfaces()
.InstancePerDependency();
// This registration is a partial, because
// SqlSyncChedulable requires a single parameter
// of type ISchedule
builder.RegisterType<SqlSyncSchedulable>()
.AsImplementedInterfaces();
// for each ISchedule class, we register automatically
// a corresponding ISyncSchedulable, which
builder.RegisterAdapter<ISchedule, ISyncSchedulable>(RegisterISyncSchedulableForEachISchedule)
.InstancePerDependency();
}
private ISyncSchedulable RegisterISyncSchedulableForEachISchedule(IComponentContext context, ISchedule schedule)
{
// the parameter of type ISchedule is the corresponding schedule
var scheduleParam = new TypedParameter(typeof(ISchedule), schedule);
// all the other params are resolved automatically by Autofac.
return context.Resolve<ISyncSchedulable>(scheduleParam);
}
}
[TestFixture]
public class AutofacTest
{
[Test]
public void TestServiceResolution()
{
var builder = new ContainerBuilder();
builder.RegisterModule(new TestModule());
var container = builder.Build();
var service = container.Resolve<ScheduleService>();
Assert.That(service.SyncSchedulables[0].GetType(), Is.EqualTo(typeof(SqlSyncSchedulable)));
}
}
}
Please note that the module resolution order is now completely decoupled with the runtime resolution.