All sources of this post can be found at my git.
Today, I will continue to sharing some knowledge that I know about
StructureMap on Multi-layers application. So I think everyone also know about
Multi-layers application, in past we often used the 3 layers (Presentation,
Business Logic and Data Access), but by the time maybe we had more than 3
layers as well. In past, when I used Spring.NET framework for IoC container, I
must make many configuration in XML file (who knew about XML hell???), it is
really terrible. Why? Because it is not a strong type, so very easy to got a
error when config an object and also hard to find cause of error in XML file.
But now we have DSL for config objects and also have a Fluent Interface for
working with config tasks. Some of containers also try to migrate to this way.
And I think it is really good to work with. So one question here, how do we
config on the multi-layers? Somebody will answer that we will config all
instances on the Presentation layer or Service layer in WCF service. Yes,
that's fine. And we shall config all instances in these layers.
But when I studied a Divide and Conquer algorithm at University. It is
really good for some situation in my life. And in config object for IoC
container, I also apply it as well, because I feel that we shall be easy to
read and manage object configurations if this config file is small and less
code. I did not like some application that config file is very long (maybe more
than 1000 line of codes), I called it is code-config hell. A long time ago, I
tried to find a best solution for this. And now some of IoCs have supported the
feature that permit you to config on many modules and after that it will merge
your config to its main container. You can reference to StructureMap, AutoFac,
MEF for this. In StructureMap, we have a Registry class; it looks like a part
of DSL config for your application. And you can call ObjectFactory to scan alll
registries in your application. I love this features. You can imagine that we
shall register all configs on modules and after that pass the namspace of these
modules to ObjectFactory for scanning. Every object is dynamic. And in AutoFac
also have Module class for this purpose. The important thing is code config
file will short and clear to read. It make you easy to maintain application in
the future (I used to open a code config files in Unity and tried to a instance
config inside it, it took me a lot of time). Almost people also know that
coding is only 10% and maintain is taking you 90% effort and money you got from
maintaining application.
It is enough to talk about the theory and the reasons. Now I will focus on
the way that we usually work with StructureMap. And try to make the
configuration is well when application boot. Do you know Bootstrapper? Yeah it
is great for this situation. I will use it for this post. And I also integrated
with Automapper. Wating and see how do it works.
The first thing we need to define the contract for BootStrapper. I have an
abstract class for BootStrapper as below:
public abstract class BootStrapperBase : RootObject
{
protected IConfigurator Configurator;
protected IList<string> AssemblyNames;
protected BootStrapperBase(IList<string> assemblyNames, IConfigurator configurator)
{
AssemblyNames = assemblyNames;
Configurator = configurator;
}
public virtual void BootAllConfig()
{
}
}
And I also have a interface for some tasks that will run inside BootStrapper
(one of it, I will implement for run new Profile for Automapper):
public interface IBootstrapperTask
{
void Execute();
}
And I must have a BootStrapper concrete class for run as:
public class MyBootStrapper : BootStrapperBase
{
private static volatile MyBootStrapper _instance;
private static readonly object SyncRoot = new Object();
/// <summary>
/// http://msdn.microsoft.com/en-us/library/ff650316.aspx
/// </summary>
public static MyBootStrapper GetInstance(IList<string> assemblies, IConfigurator configurator)
{
Contract.Assert(assemblies != null, "List of Registries is null");
Contract.Assert(assemblies.Count > 0, "List of Registries must be larger than zero");
Contract.Assert(configurator != null, "Configuratorr instance is null");
if (_instance == null)
{
lock (SyncRoot)
{
if (_instance == null)
_instance = new MyBootStrapper(assemblies, configurator);
}
}
return _instance;
}
private MyBootStrapper() : this(null, null) { }
private MyBootStrapper(IList<string> assemblyNames, IConfigurator configurator)
: base(assemblyNames, configurator)
{
}
public override void BootAllConfig()
{
RegisterRegistry();
RunBootstrapperTasks();
}
private void RegisterRegistry()
{
CatchExceptionHelper.TryCatchAction(() =>
{
ObjectFactory.Initialize(x =>
{
var coreRegistry =
(Registry)Activator.CreateInstance(typeof(CoreRegistry));
x.AddRegistry(coreRegistry);
x.Scan(scan =>
{
scan.LookForRegistries();
foreach (var an in
AssemblyNames.Where(an => string.Compare(an, typeof(CoreRegistry).Name) != 0))
{
scan.Assembly(an);
}
scan.AddAllTypesOf<IBootstrapperTask>();
scan.AddAllTypesOf(typeof(IConsumer<>));
scan.WithDefaultConventions();
});
});
ObjectFactory.AssertConfigurationIsValid();
}, Logger);
}
private static void RunBootstrapperTasks()
{
var tasks = MyObjectFactory.GetAllInstances<IBootstrapperTask>();
if (tasks == null) return;
foreach (var task in tasks)
{
task.Execute();
}
}
}
As you see it have a RegisterRegistry method for scan all Registry classes
based on list of assemblies that I inject from outside. I also have a
RunBootStrapperTasks method for run all the BootStrapper tasks that get from
StructureMap container.
Now I will show you the CoreRegistry class as below:
public class CoreRegistry : Registry
{
private readonly ILog _logger;
public CoreRegistry()
: this(new MyLogger())
{
}
public CoreRegistry(ILog logger)
{
_logger = logger;
RegisterAllComponents();
}
private void RegisterAllComponents()
{
CatchExceptionHelper.TryCatchAction(
() =>
{
For<ILog>().Use<MyLogger>();
For<IEventPublisher>().Use<EventPublisher>();
For<ISubscriptionService>().Use<EventSubscriptions>();
For<IMappingEngine>().Use(() => Mapper.Engine);
For<IConfigurator>().Use<Configurator>();
For<IAttributeScanEngine>().Use<AutoAttributeScanEngine>();
For<IDirectoryTask>().Use<DirectoryWrapper>();
}, _logger);
}
}
CoreRegistry class only contains all instances that need for core
application, such as ILog, IEventPublisher, ISubscriptionService,
IMappingEngine, IConfigurator...
Now I must implemented a class for wrapping StructureMap's ObjectFactory as
here:
public static class MyObjectFactory
{
private static readonly ILog Logger;
static MyObjectFactory()
{
Logger = new MyLogger();
}
public static object GetInstance(Type type)
{
return LogWriting(() => ObjectFactory.GetInstance(type));
}
public static T GetInstance<T>()
{
return LogWriting(ObjectFactory.GetInstance<T>);
}
public static object GetNamedInstance(Type type, string name)
{
return LogWriting(() => ObjectFactory.GetNamedInstance(type, name));
}
public static T GetNamedInstance<T>(string name)
{
return LogWriting(() => ObjectFactory.GetNamedInstance<T>(name));
}
public static IList<T> GetAllInstances<T>()
{
return LogWriting(ObjectFactory.GetAllInstances<T>);
}
static T LogWriting<T>(Func<T> func)
{
try
{
return func();
}
catch (Exception ex)
{
Logger.Error(ex.Message);
throw;
}
}
}
I only wrap some main functions of ObjectFactory.
Some unit testing for test the config file as below:
[TestClass]
public class BootstrapperTesting
{
protected IList<string> _assemblyNames;
protected IConfigurator _configurator;
[TestInitialize]
public void InitTestCase()
{
_assemblyNames = new List<string>
{
"ConfORMSample.Core"
};
_configurator = new Configurator();
MyBootStrapper.GetInstance(_assemblyNames, _configurator).BootAllConfig();
}
[TestMethod]
public void Can_Get_Logger_From_Container()
{
var logger = MyObjectFactory.GetInstance<ILog>();
Assert.IsNotNull(logger);
}
[TestMethod]
public void Can_Get_Mapping_Engine_From_Container()
{
var mappingEngine = MyObjectFactory.GetInstance<ILog>();
Assert.IsNotNull(mappingEngine);
}
[TestMethod]
public void Can_Get_Directory_Wrapper_From_Container()
{
var directoryWrapper = MyObjectFactory.GetInstance<IDirectoryTask>();
Assert.IsNotNull(directoryWrapper);
}
[TestCleanup]
public void CleanUpTestCase()
{
GC.SuppressFinalize(_configurator);
}
}
After that, you can write many Registry classes for you application. As here
I just write one Registry class for Repository:
public class RepositoryRegistry : Registry
{
public RepositoryRegistry()
{
For<ISpecification<News>>().Use<NewsAddedSpecification>();
For<INewsRepository>().Use<NewsRepository>();
For<ICategoryRepository>().Use<CategoryRepository>();
For<IFootballResultRepository>().Use<FootballResultRepository>();
}
}
And when you boot your application you only need config as here:
_assemblyNames = new List<string>
{
"ConfORMSample.Core",
"ConfORMSample.Repository"
};
_configurator = new Configurator();
MyBootStrapper.GetInstance(_assemblyNames, _configurator).BootAllConfig();
Wrap up: Really I don't talk this is a best way to do with StructureMap.
Some people shall not agree with this. So tell me you’re thinking by leave some
message below this post. I will be happy to know more about that. Happy coding!