Замок Виндзор, переопределяющий зарегистрированные компоненты
Я только начал использовать замок Виндзор (3.3.0
) в первый раз, и я застрял на регистрации на основе конвенции.
Я хотел бы зарегистрироваться как можно больше по названию соглашения (IDummyService
-> DummyService
):
var container = new WindsorContainer();
container.Register(Types.FromThisAssembly().Pick().WithServiceDefaultInterfaces());
container.Register(Component.For<IDummyService>().ImplementedBy<DummyService>().LifestyleSingleton()); // So long, I'm throwing here...
... но, конечно, чтобы можно было немного изменить некоторые из только что зарегистрированных компонентов (изменение времени жизни, параметров конструктора и т. д.)
Поскольку я хочу сделать регистрацию настолько простой, насколько это возможно, я бы хотел избежать сложных условий. Единственное решение, которое я нашел, простое - переместить пользовательский материал выше соглашения об имени:
var container = new WindsorContainer();
container.Register(Component.For<IDummyService>().ImplementedBy<DummyService>().LifestyleSingleton()); // Do custom stuff first...
container.Register(Types.FromThisAssembly().Pick().WithServiceDefaultInterfaces()); // And convention at the end...
Мой вопрос сейчас, это правильный путь, как решить мою регистрацию? Я вижу, что Виндзорский замок достаточно силен в этой области и скорее решит мою задачу должным образом, к сожалению, я не вижу много реальных примеров.
4 ответа
Замок Виндзор действительно очень могучая и зрелая платформа для внедрения зависимостей. Он имеет множество внутренних расширений, которые фактически позволяют делать то, к чему вы стремитесь. Смотрите здесь регистрацию сервисов сборки встроенного инструмента.
Я использую его уже почти 5 лет, и для меня лучше всего работает следующее:
// at the startup of the application
_container = (new WindsorContainer()
.AddHelperFacilities() // IWindsorContainer extension that comes from Framework.InversionOfControl
.AddWebApiAdapter() // IWindsorContainer extension that comes from Framework.InversionOfControl.WebApi
.InitializeDomainUsingConventions( // IWindsorContainer extension that comes from Framework.InversionOfControl
AppDomain.CurrentDomain, // domain for which container will be building registrations
"ApplicationName.*", // regext to speed up registration process by processing only services from application namespace
new WebApiControllersRegistrationConvention(), new DefaultRegistrationConvention())); // one or more conventions
// DefaultRegistrationConvention() comes from Framework.InversionOfControl
// WebApiControllersRegistrationConvention() comes from Framework.InversionOfControl.WebApi . A separate assembly to be referenced to avoid extra dependancies on Asp.NET WebApi assemblies
.Resolve<IApplication>.Start(); // resolves application specific entry point and launches the application
А затем для Framework.InversionOfControl:
namespace Framework.InversionOfControl
{
public static class WindowsContainerExtensions
{
public static IWindsorContainer InitializeDomainUsingConventions(
this IWindsorContainer container, AppDomain appDomain, string commonNamespaceDenominatorMask, params IRegistrationConvention[] registrationConventions)
{
var assembliesToInitialize = new List<Assembly>();
var runtimeAssemblies = new List<Assembly> { Assembly.GetCallingAssembly() };
var processedAssemblies = new List<Assembly>();
runtimeAssemblies.AddRange(appDomain.GetAssemblies());
foreach (var assembly in runtimeAssemblies)
{
ProcessAssembly(assembly, assembliesToInitialize, processedAssemblies, commonNamespaceDenominatorMask, commonNamespaceDenominatorMask == null);
}
var allRuntimeTypes = new List<Type>();
foreach (var assembly in assembliesToInitialize)
{
var assemblyTypes = assembly.GetTypes().ToList();
var installerTypes = assemblyTypes.Where(t => !t.IsInterface && !t.IsAbstract && t.GetInterfaces().Contains(typeof(IWindsorInstaller))).ToArray();
if (installerTypes.Any())
{
foreach (var installer in installerTypes.Select(installerType => (IWindsorInstaller)Activator.CreateInstance(installerType)))
{
container.Install(installer);
}
}
else
{
allRuntimeTypes.AddRange(assemblyTypes);
}
}
foreach (var registrationConvention in registrationConventions)
{
registrationConvention.RegisterTypesUsingConvention(container, allRuntimeTypes);
}
return container;
}
private static void ProcessAssembly(Assembly assembly, List<Assembly> assemblies, List<Assembly> processedAssemblies, string commonNamespaceDenominatorMask, bool fullScan)
{
if (processedAssemblies.Any(x => x.FullName == assembly.FullName)) return;
if (assembly == typeof(WindowsContainerExtensions).Assembly) return;
processedAssemblies.Add(assembly);
var initialize = (new Regex(commonNamespaceDenominatorMask, RegexOptions.IgnoreCase)).Match(assembly.FullName).Success;
if (initialize && assemblies.Any(x => x.FullName == assembly.FullName))
{
initialize = false;
}
if (initialize)
{
assemblies.Add(assembly);
}
foreach (var referencedAssembliyNames in assembly.GetReferencedAssemblies())
{
var referencedAssembliyNames1 = referencedAssembliyNames;
if (assemblies.Any(x => x.FullName == referencedAssembliyNames1.FullName)) continue;
if (fullScan == false && (new Regex(commonNamespaceDenominatorMask, RegexOptions.IgnoreCase)).Match(assembly.FullName).Success == false) continue;
Assembly referencedAssembliy;
try
{
referencedAssembliy = Assembly.Load(referencedAssembliyNames);
}
catch
{
continue;
}
ProcessAssembly(referencedAssembliy, assemblies, processedAssemblies, commonNamespaceDenominatorMask, fullScan);
}
}
public static IWindsorContainer AddHelperFacilities(this IWindsorContainer container)
{
container.AddFacility<TypedFactoryFacility>();
container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel));
container.Register(Component.For<IWindsorContainer>().ImplementedBy<WindsorContainer>());
container.Register(Component.For<IContainerAccessor>().ImplementedBy<ContainerAccessor>());
container.Resolve<IContainerAccessor>().Container = container;
return container;
}
}
public interface IRegistrationConvention
{
IWindsorContainer RegisterTypesUsingConvention(IWindsorContainer container, List<Type> assemblyTypes);
}
public class DefaultRegistrationConvention : IRegistrationConvention
{
/// <summary>
/// Register every service possible from the calling assembly with default singleton lifestyle
/// with the exception of ISomething Factory where the the ISomething GetSomething() where
/// Something that implements ISomething is registered with transient lifestyle
/// </summary>
public IWindsorContainer RegisterTypesUsingConvention(IWindsorContainer container, List<Type> assemblyTypes)
{
// Step 1: Factories installation.
// We register interfaces ending 'Factory' keyword like proxy (implementionless) factories.
var factoryServices = new List<Type>();
var factorySelector = new FullNameFactorySelector();
foreach (var factoryType in assemblyTypes.Where(t => t.Name.EndsWith("Factory") && t.IsInterface))
{
foreach (var method in factoryType.GetMethods())
{
if (method.Name.StartsWith("Get") == false) continue;
if (method.ReturnType.IsInterface == false) continue;
factoryServices.Add(method.ReturnType);
}
container.Register(Component.For(factoryType).AsFactory(factorySelector));
}
// Step 2: Rest of the services registrations
// transientServices list is populated with services that needs to has transient lifespan
// everything else needs to go as preconfigured lifestyle - lifeStyleType
var components = assemblyTypes.Where(t => !t.IsInterface && !t.IsAbstract);
foreach (var component in components)
{
// for every interface and implementation do registration
foreach (var service in component.GetInterfaces())
{
IRegistration registration;
Type service1 = service;
if (factoryServices.Any(x => x.FullName == service1.FullName))
{
if (component.IsGenericType)
{
// GetInterfaces() and GetMethod().ReturnType both returns Type.FullName = null
// Castle.Windsor fails to Resolve later generic types if registered type is with FullName = null,
// Workaround is to find the type with correct FullName from the 'assemblyTypes'
var serviceWithFullName = assemblyTypes.FirstOrDefault(x => x.Name == service1.Name);
if (serviceWithFullName == null) continue; // if null then the mapping is not supported by this convention
registration = Component.For(serviceWithFullName)
.ImplementedBy(component)
.LifestyleTransient()
.Named(serviceWithFullName.FullName + " / " + component.FullName);
}
else
{
registration = Component.For(service)
.ImplementedBy(component)
.LifestyleTransient()
.Named(service.FullName + " / " + component.FullName);
}
}
else
{
registration = Component.For(service)
.ImplementedBy(component)
.Named(service.FullName + " / " + component.FullName)
.LifeStyle.Is(LifestyleType.Singleton);
}
container.Register(registration);
}
}
return container;
}
}
}
Все вышеперечисленное делает то, что Castle Windsor делает уже модульным и расширяемым способом, не ограничивая возможности Castle Windsor. С помощью нескольких строк он регистрирует все приложение в соответствии с соглашениями и позволяет добавлять определенные соглашения, такие как: Mvc, WebApi, AutoMapper, Wcf, Quartz и другие.
Ваша последняя строка регистрации чрезвычайно широка, и хотя она будет работать в простом приложении, в большинстве реальных приложений она, вероятно, слишком упрощена.
Обычно сборка будет предоставлять один или несколько наборов сервисов. Используя различные методы выбора (например, InNamespace, BasedOn, Where), вы можете зарегистрировать каждый набор служб и настроить их жизненные циклы, зависимости, наименования и т. Д. Я склонен создавать отдельный метод для каждого набора служб. (например RegisterDataAccessComponents()
Очень ясное представление о наборах сервисов, предоставляемых сборкой, значительно упрощает повторное рассмотрение кода позже и выяснение того, что предоставляется, и отслеживание конфигурации, влияющей на поведение во время выполнения. Вы все еще регистрируетесь по соглашению, но делаете это немного более явно.
С этой целью я считаю, что создание реализаций IWindsorInstaller, которые берут на себя ответственность за регистрацию и подключение наборов сервисов, предлагаемых сборкой, также помогает отделить инициализацию контейнера от других задач инициализации приложения.
Одним из вариантов будет сделать пользовательскую конфигурацию путем реализации IContributeComponentModelConstruction
интерфейс - ProcessModel
Метод вызывается для каждого компонента:
public class ExtraConfiguration : IContributeComponentModelConstruction
{
public void ProcessModel(IKernel kernel, ComponentModel model)
{
if (model.Implementation == typeof(DummyService))
{
model.LifestyleType = LifestyleType.Singleton;
}
if ...
}
}
Затем вам нужно будет зарегистрировать это в контейнере до регистрации других компонентов:
container.Kernel.ComponentModelBuilder.AddContributor(new ExtraConfiguration());
Есть несколько ConfigureX
методы для этой цели, а именно ConfigureIf
или тип на основе ConfigureFor<IDummyService>
,
Вот ссылка на соответствующую документацию.