четверг, 22 июля 2010 г.

Настройка шаблона IoC в ASP.NET MVC с использованием Castle Windsor

На днях у меня возникла необходимость настроить шаблон инверсии управления(IoC) в ASP.NET MVC 2. В первой версии с этим никаких проблем не возникло, поскольку в сети есть куча информации на данную тему (хотя бы книга Стивена Сандерсона). Но во второй версии, которая и идет в VS 2010, изменился интерфейс IControllerFactory, вследствие чего реализация собственного класса фабрики контроллеров требует изменение старого кода. Покопавшись в сети, я удивился тому, как мало информации на сей счет. В результате пришлось около часа просматривать забугорные форумы (в русскоязычных источниках, конечно, ничего не было). Даже на официальных сайтах Castle Windsor и ASP.NET про это ни слова (хотя, может, я и google искать не умеем). В результате наткнулся на блог Ali Bastani и его статью "Setting Up IoC in ASP.NET MVC using Castle Windsor". Сама статья почти полностью основана на книге Стивена Сандерсона "Pro ASP.NET MVC framework" (ASP.NET MVC Framework для профессионалов). Но в добавок ко всему содержит так же и модификацию класса фабрики контроллеров. Частичный перевод этой статьи с добавлением нескольких своих замечаний я и хочу привести ниже.

Аннотация: эта статья содержит информация, необходимую для настройки шаблона инверсии управления (IoC) в приложениях ASP.NET MVC, используя Castle Windsor.

В этой статья я расскажу вам обо всех шагах необходимых для того, чтобы вы могли создать собственную фабрику контроллеров для реализации IoC в вашем приложении. Более подробную информацию об IoC и том, зачем использовать его, вы можете найти здесь http://www.mikesdotnetting.com/Article/117/Dependency-Injection-and-Inversion-of-Control-with-ASP.NET -MVC
Я узнал о том, как использовать Windsor для реализации IoC в выдающейся книге Стивена Сандерсона "Pro ASP.NET MVC framework" (ASP.NET MVC Framework для профессионалов).
...
Последнюю версию Castle Windsor можно взять с его официального сайта.

И так, сначала добавим ссылки в наш проект на след. сборки: "Castle.Core.dll", "Castle.MicroKernel.dll", "Castle.Windsor.dll"



Далее создадим класс фабрики контроллеров. В корневой папке проекта создадим файл WindsorControllerFactory.cs. В него запишем след. код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Castle.Windsor;
using Castle.Windsor.Configuration.Interpreters;
using Castle.Core.Resource;
using System.Reflection;
using Castle.Core;
using System.Web.Routing;

namespace Mvc2Application6
{
public class WindsorControllerFactory : DefaultControllerFactory
{
WindsorContainer container;
// The constructor:
// 1. Sets up a new IoC container
// 2. Registers all components specified in web.config
// 3. Registers all controller types as components
public WindsorControllerFactory()
{
// Instantiate a container, taking configuration from web.config
container = new WindsorContainer(
new XmlInterpreter(new ConfigResource("castle"))
);
// Also register all the controller types as transient
var controllerTypes =
from t in Assembly.GetExecutingAssembly().GetTypes()
where typeof(IController).IsAssignableFrom(t)
select t;
foreach (Type t in controllerTypes)
container.AddComponentWithLifestyle /* Либо используйте AddComponentLifeStyle для второй версии */
(t.FullName, t,LifestyleType.Transient);
}

// Constructs the controller instance needed to service each request this part is Updated to be compatible with MVC 2
protected override IController
GetControllerInstance
(RequestContext requestContext, Type controllerType)
{
return (IController)container.Resolve(controllerType);
}
/*
* For MVC 1 use this
protected override IController GetControllerInstance(Type controllerType)
{
return (IController)container.Resolve(controllerType);
}*/
}
}

Этот код реализует интерфейс IControllerFactory (это след. хотя бы из того, что он наследует класс DefaultControllerFactory). Далее нам осталось внести след. изменения в Web.config:






//содержимое


Лучше сделать это сразу перед system.web (здесь это показано)

Далее в configSections добавить след. строку (если узел configSections еще не создан, то его след. создать внутри configuration)



Теперь необходимо указать ASP.NET использовать новую фабрику контроллеров, делается это добавлением строки


ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory());

В метод Application_Start(), находящегося в файле Global.asax.

Допустим, вы используете интерфейс IUserRepository


namespace Models.Abstract

{

public interface IUserRepository

{

IQueryable Users { get; }

}

}

И существует реализация этого интерфейса:

namespace Model.Concrete
{
public class SqlUserRepository : IUserRepository
{
private Table current;
public SqlUserRepository(String connectionString)
{
current = (new DataContext(connectionString)).GetTable();
}
public IQueryable Users
{
get { return current; }
}
}
}

Теперь, можно использовать данный интерфейс так:

public class UsersController : Controller
{
public IUserRepository myUser;
public UsersController(IUserRepository userRepository)
{

this.myUser = userRepository;
}

public ViewResult List()
{
return View(userRepository.Users.ToList());
}

}

Необходимо зарегистрировать интерфейс и его реализацию в CastleWindsor



service="Model.Abstract.IUserRepository, Model"
type="Model.Concrete.SqlUserRepository, Model"
lifestyle="PerWebRequest">

Server=.\SQLEXPRESS;DataBase=SportStore;Trusted_Connection=yes;





Теперь осталось зарегистрировать стиль поведения объекта PerRequestLifestyle в httpModules (если в web.config еще нет узла httpModules, то его следует объявить внутри system.web)



Ну, вот и все. Теперь CastleWindsor установлен и готов к использованию.

9 комментариев:

  1. спасибо, чуть-чуть запутался в книге
    нашел нужный совет

    ОтветитьУдалить
  2. Сейчас читаю даную книгу и у меня такая проблемка:
    Castle.MicroKernel.dll в версии 2.5.2 нету. Всё сделал так как в книге и у меня ругается: 1 Web.config (чтото вроде не может найти библиотеку в Castle.windsor. ...... in Castle.windsor)
    2 AddComponentLifeStyle для второй версии */
    (t.FullName, t,LifestyleType.Transient);

    ОтветитьУдалить
  3. К сожалению, у меня сейчас нет возможности скачать последнюю версии CastleWindsor, но, насколько я вас понял, проблема заключается в том, что в последней версии нет библиотеки Castle.MicroKernel.dll. Как только появится возможность, изучу изменения, пришедшие с этим обновлением. А пока могу лишь посоветовать использовать более раннюю версию. В конце концов, библиотека, скачанная мной незадолго до написания этой статьи, до сих пор прекрасно работает в одном из проектов.

    ОтветитьУдалить
  4. Евгений, пожалуйста отредактируйте настройки web.config. Они не отображаются!

    ОтветитьУдалить
  5. Здравствуйте, Евгений!

    По поводу Castle.MicroKernel.dll - в версии 2.5.3 эта библиотека встроена в CastleCore (попробуйте добавить сборку в проект и просмотреть содержимое - там она есть)

    У меня другая проблема - всё сделал так как Вы и написали, но каждый раз одна и та же ошибка

    Could not convert from 'DomainModel.Concrete.SqlProductsRepository, DomainModel' to System.Type - Maybe type could not be found

    Как вы думаете, в чём может быть проблема - вроде все нужные изменения в web.config сделаны

    ОтветитьУдалить
  6. Здравствуйте Евгений!
    Разобрал ваш пример однако при компиляции в методе GetControllerInstance выдает ошибку ArgumentNullException не подскажете как решить эту проблему?

    ОтветитьУдалить
  7. Здравствуйте, по вашему описанию трудно сделать вывод в чем именно дело, однако, на сколько я помню, данная функция - одна из немногих, что изменилась при переходе от MVC 1 к MVC 2. Возможно дело в этом. А вообще, причина может быть в чем угодно, например, некорректно заполненный Web.config.

    ОтветитьУдалить
  8. Здравствуйте Евгений еще раз.

    в конфиге ошибки нет. Я так же как и в книге Сандерсона отображаю список из экземпляра класса, и он отображаеться, однако, после отображения
    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)

    Вызывается еще раз только Type controllerType=null и происходит ошибка.

    ОтветитьУдалить
  9. По поводу отсутствия Castle.MicroKernel.dll смотрите след. ссылку:
    http://www.ssidelnikov.ru/2011/01/castle-microkernel/

    ОтветитьУдалить