Ответ 1
Во-первых, некоторые концептуальные детали. В приложении ASP.NET MVC типичной точкой входа для запроса страницы является контроллер. Мы хотим, чтобы контейнер Inversion of Control разрешал нам наши контроллеры, потому что тогда любые зависимости, которые имеют контроллеры, также могут быть автоматически разрешены просто путем перечисления зависимостей в качестве аргументов в конструкторах контроллеров.
Смущенный еще? Вот пример того, как вы будете использовать IoC, после того, как все будет настроено. Я думаю, что объяснение этого облегчает ситуацию!
public class HomeController : Controller
{
// lets say your home page controller depends upon two providers
private readonly IMembershipProvider membershipProvider;
private readonly IBlogProvider blogProvider;
// constructor, with the dependencies being passed in as arguments
public HomeController(
IMembershipProvider membershipProvider,
IBlogProvider blogProvider)
{
this.membershipProvider = membershipProvider;
this.blogProvider = blogProvider;
}
// so taking your Registration example...
[HttpPost]
public ActionResult Register( Web.Models.Authentication.Registration model)
{
if (ModelState.IsValid)
{
this.membershipProvider.CreateUser(model.Email, model.Password);
}
// If we got this far, something failed, redisplay form
return View(model);
}
}
Обратите внимание, что вам не приходилось самостоятельно решать, вы только что указали в контроллере, каковы зависимости. Вы также не указали на то, как выполняются зависимости - все это развязано. Это очень просто, здесь нет ничего сложного: -)
Надеюсь, в этот момент вы спросите: "Но как создается конструктор?" Здесь мы начинаем настраивать контейнер Castle, и мы делаем это целиком в проекте MVC Web (а не настойчивости или домена). Отредактируйте файл Global.asax, установив Castle Windsor в качестве контроллера factory:
protected void Application_Start()
{
//...
ControllerBuilder.Current
.SetControllerFactory(typeof(WindsorControllerFactory));
}
... и определите WindsorControllerFactory, чтобы ваши контроллеры были созданы Winsor:
/// Use Castle Windsor to create controllers and provide DI
public class WindsorControllerFactory : DefaultControllerFactory
{
private readonly IWindsorContainer container;
public WindsorControllerFactory()
{
container = ContainerFactory.Current();
}
protected override IController GetControllerInstance(
RequestContext requestContext,
Type controllerType)
{
return (IController)container.Resolve(controllerType);
}
}
Метод ContainerFactory.Current()
- это статический singleton, который возвращает сконфигурированный контейнер Castle Windsor. Конфигурация контейнера инструктирует Windsor о том, как разрешить ваши зависимости приложений. Так, например, у вас может быть контейнер, сконфигурированный для разрешения NHibernate SessionFactory, и ваш IMembershipProvider.
Мне нравится настраивать контейнер Castle с помощью нескольких "инсталляторов". Каждый установщик несет ответственность за другой тип зависимости, поэтому у меня был бы установщик Controller, установщик NHibernate, например, установщик поставщика.
Во-первых, у нас есть ContainerFactory:
public class ContainerFactory
{
private static IWindsorContainer container;
private static readonly object SyncObject = new object();
public static IWindsorContainer Current()
{
if (container == null)
{
lock (SyncObject)
{
if (container == null)
{
container = new WindsorContainer();
container.Install(new ControllerInstaller());
container.Install(new NHibernateInstaller());
container.Install(new ProviderInstaller());
}
}
}
return container;
}
}
... и тогда нам нужны все инсталляторы. Сначала ControllerInstaller
:
public class ControllerInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
AllTypes
.FromAssembly(Assembly.GetExecutingAssembly())
.BasedOn<IController>()
.Configure(c => c.Named(
c.Implementation.Name.ToLowerInvariant()).LifeStyle.PerWebRequest));
}
}
... и вот мой NHibernateInstaller
, хотя он отличается от вашего, вы можете использовать свою собственную конфигурацию. Обратите внимание, что я повторяю один и тот же экземпляр ISessionFactory
каждый раз, когда он разрешен:
public class NHibernateInstaller : IWindsorInstaller
{
private static ISessionFactory factory;
private static readonly object SyncObject = new object();
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var windsorContainer = container.Register(
Component.For<ISessionFactory>()
.UsingFactoryMethod(SessionFactoryFactory));
}
private static ISessionFactory SessionFactoryFactory()
{
if (factory == null)
{
lock (SyncObject)
{
if (factory == null)
{
var cfg = new Configuration();
factory = cfg.Configure().BuildSessionFactory();
}
}
}
return factory;
}
}
И, наконец, вы захотите определить свой ProvidersInstaller
:
public class ProvidersInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
var windsorContainer = container
.Register(
Component
.For<IMembershipProvider>()
.ImplementedBy<SubjectQueries>())
.Register(
Component
.For<IBlogProvider>()
.ImplementedBy<SubjectQueries>());
// ... and any more that your need to register
}
}
Этого должно быть достаточно кода для продолжения! Надеюсь, вы все еще со мной, так как красота контейнера замка становится очевидной очень скоро.
Когда вы определяете свою реализацию своего IMembershipProvider
в своем уровне персистентности, помните, что он имеет зависимость от NHibernate ISessionFactory
. Все, что вам нужно сделать, это следующее:
public class NHMembershipProvider : IMembershipProvider
{
private readonly ISessionFactory sessionFactory;
public NHMembershipProvider(ISessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
}
Обратите внимание, что поскольку Castle Windsor создает ваши контроллеры и провайдеры, переданные вашему контроллеру, поставщик автоматически передает реализацию ISessionFactory
, настроенную в вашем контейнере Windsor!
Вам больше не придется беспокоиться о создании экземпляров любых зависимостей. Ваш контейнер делает все это автоматически для вас.
Наконец, обратите внимание, что IMembershipProvider
должен быть определен как часть вашего домена, так как он определяет интерфейс для поведения вашего домена. Как уже отмечалось выше, на уровне сохранения добавляется реализация ваших доменных интерфейсов, которые работают с базами данных.