Как подружить ASP.NET Controls и DI-контейнер

Интро

В последнее время решил немного освежить свои знания в ASP.NET, в связи с чем углубился в процессы генерации кода контролов по разметке (*.ascx, *.aspx) и обнаружил что можно делать очень интересные решения, о которых  о хочу поведать. Итак сегодня мы узнаем, как подружить наш Dependency Injection контейнер с генерируемым контролами кодом.

Поехали

DependencyInjection_Solution[1]

В качестве DI-контейнера будет выступать Microsoft Unity, но это не принципиально, всё что будет касаться DI не зависит от используемого контейнера.

Проблема состоит в следующем – есть некоторый ASP.NET Control, в который мы хотим внедрит зависимости, а так же воспользоваться услугами Service Locator’а для управления интересующими нас зависимостями.

В Microsoft Unity есть некоторые средства для того, чтобы сделать это не прилагая особенных усилий: мы можем произвести инъекцию в свойство элемента управления, нас интересующее примерно следующим образом:

  1. Отметить атрибутом Dependency необходимое свойство
    public class MyControl : UserControl
    {
            [Dependency]
            public MyPresenter Presenter
            {
                get { return _presenter; }
                set
                {
                    _presenter = value;
                    _presenter.View = this;
                }
            }
    }
  2. Проинициализировать элемент управления можно следующим образом

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        _сontainer.BuildUp(GetType(), this);
    } 
  3. Позаботиться о местоположении контейнера в вашем приложении, я предлагаю использовать для этого HttpApplication, унаследовавшись от которого и произведя небольшие модификации файла global.asax мы получаем необходимое нам хранилище для контейнера, обращаться с ним необходимо примерно следующим образом

    ((Sapphire.Application)HttpContext.Current.ApplicationInstance).Container

Решение вполне пригодное, однако пуристические воззрения не дают оставить решение на данной стадии, и думаю, что просто необходимо заменить инъекцию свойства на инъекцию в конструктор, тем более подобный подход – это далеко не то, что мы можем выжать из Unity.

Т.е. наш интерес состоит в том, чтобы класс MyUserControl выглядел примерно так (думаю сборщику страницы это не совсем понравится)

public class MyControl : UserControl
{
    public MyControl(MyPresenter presenter)
    {
         _presenter = presenter;
         _presenter.View = this;
    }
}

Предлагаю этим и заняться. Начнём с того, что у элементов управления, описанных в разметке страницы, при генерации страницы указываются их конструкторы без параметров, интересно, как можно управлять данным процессом, первоначально, покопавшись в web.config я предполагал сделать это через:

<buildProviders>
    <add extension=«.aspx» type=«System.Web.Compilation.PageBuildProvider»/>
    <add extension=«.ascx» type=«System.Web.Compilation.UserControlBuildProvider»/>
    …
</buildProviders>

Однако реализация своего PageBuildProvider’а – довольно серьезное занятие, думаю отложить это для серьезной на то необходимости. Однако благодаря BuildProvider’ам можно генерить к примеру слой доступа к данным, для этого надо:

Написать и зарегестрировать обработчик для какого-нибудь своего расширения, к примеру *.dal и сделать что-нибудь наподобее http://www.codeproject.com/KB/aspnet/DALComp.aspx

кстати подобная логика реализована в SubSonic http://dotnetslackers.com/articles/aspnet/IntroductionToSubSonic.aspx

так же интересная реализация наследования страницы от generic типов http://stackoverflow.com/questions/1480373/generic-inhertied-viewpage-and-new-property

ещё можно, к примеру генерировать исключения, объекты передачи данных и многое другое, ограничением является лишь ваша фантазия.

Вообщем, данный вариант нам не подходит, необходимо сделать что-нибудь проще, и есть отличное решение, с помощью атрибута ControlBuilder мы можем указать свою логику сборки элемента управления из разметки, это будет выглядеть примерно так

[ControlBuilder(typeof(MyControlBuilder))]
public class UserControl : System.Web.UI.UserControl
{
}

Теперь разберемся с реализацией  MyControlBuilder, этот тип должен наследовать от ControlBuilder и с помощью перегрузки ProcessGeneratedCode мы с вами сможем указать сборщику на необходимость использования нашего кода вместо вызова конструктора без атрибутов элемента управления:

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit,
                                              CodeTypeDeclaration baseType,
                                              CodeTypeDeclaration derivedType,
                                              CodeMemberMethod buildMethod,
                                              CodeMemberMethod dataBindingMethod)
    {
      codeCompileUnit.Namespaces[0].Imports.Add(new CodeNamespaceImport(«Sapphire.Web.UI»));
      ReplaceConstructorWithContainerResolveMethod(buildMethod);
      base.ProcessGeneratedCode(codeCompileUnit, baseType, derivedType, buildMethod, dataBindingMethod);
    }

самое интересно скрывает метод ReplaceConstructorWithContainerResolveMethod

    private void ReplaceConstructorWithContainerResolveMethod(CodeMemberMethod buildMethod)
    {
      foreach (CodeStatement statement in buildMethod.Statements)
      {
        var assign = statement as CodeAssignStatement;

        if (null != assign)
        {
          var constructor = assign.Right as CodeObjectCreateExpression;

          if (null != constructor)
          {
            assign.Right =
              new CodeSnippetExpression(
                string.Format(«SapphireControlBuilder.Build<{0}>()»,
                              ControlType.FullName));
            break;
          }
        }
      }
    }

следуя по коду можно обратить внимание, что он заменяет вызов конструктора на вызов генерик-метода Build, в котором мы и обратимся к нашему контейнеру с просьбой вызвать наш элемент управления и проинициализировать его конструктор необходимыми зависимостями.

Однако это ещё не решении задания, т.к. есть метод динамической загрузки элемента управления Page.LoadControl(), для него придётся написать свой вариант

  public static class PageExtensions
  {
    public static UserControl LoadAndBuildUpControl(this Page page, string virtualPath)
    {
      var control = page.LoadControl(virtualPath);
      return SapphireControlBuilder.Build<UserControl>(control.GetType());
    }
  }

Вот мы и справились с поставленной задачей, однако это ещё не всё. А почему теперь не воспользоваться всеми преимуществами Unity, и не внедрить в наш элемент управления AOP времени исполнения с помощью Unity Interception.

К примеру мы можем сделать следующее

public class MyControl : UserControl
{
    [HandleException]
    public override void DataBind()
    {
      base.DataBind();
    }
}

Это будет означать, что обработка исключений должна добавляться на лету, к тому ж предоставляя нам возможность её изменения во время исполнения, для начала пусть её реализация будет примерно следующая

  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
  public class HandleExceptionAttribute : HandlerAttribute
  {
    public override ICallHandler CreateHandler(IUnityContainer container)
    {
      return new ExceptionHandler();
    }
  }

  public class ExceptionHandler : ICallHandler
  {
    /// <exception cref=»SapphireUserFriendlyException»><c>SapphireUserFriendlyException</c>.</exception>
    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
      var result = getNext()(input, getNext);
      if (result.Exception == null)
        return result;
      throw new SapphireUserFriendlyException();
    }

    public int Order { getset}
  }

Ну и конечно же надо сконфигурировать контейнер для создания наших прокси-обработчиков

    public static T Build<T>()
    {
      return (T)((Application)HttpContext.Current.ApplicationInstance)
        .Container
          . AddNewExtension<Interception>()
          .Configure<Interception>()
            .SetInterceptorFor<T>(new VirtualMethodInterceptor())
        .Container
          .Resolve<T>();
    }

Ресурсы

Sapphire.Application – для чего всё это реализовывалось http://github.com/butaji/Sapphire/tree/master/trunk/Sapphire.Application/

Дэвид предлагает реализации связывания с данными следующего поколения “Databinding 3.0” на основе аналогичного подхода http://weblogs.asp.net/davidfowler/archive/2009/11/13/databinding-3-0.aspx


Кратко о Patterns & Practices: SharePoint Guidance

Интро

clip_image001

В данной статье я хочу произвести обзор руководства под названием “SharePoint Guidance” от подразделения Microsoft patterns & practices. Данное руководство предназначено разработчикам/архитекторам SharePoint, в нём описаны основные принципы построения систем на данной платформе. Над руководством трудились выдающиеся представители разработки под SharePoint, а так же он упорно держится в списке самых активных проектов на CodePlex. Далее чуть подробнее.

Описание руководства

Руководство фактически состоит из нескольких аспектов:

  1. SharePoint Guidance Library – библиотека наиболее используемых и полезных функций, таких как управление конфигурацией, абстрагирование слоя данных, логирование событий и сервисная инфраструктура
  2. Документация, в которой подробно описаны все принципы построения приведенных примеров, а так же руководства по основным вопросам, возникающим в разработке на SharePoint.
  3. Contoso Partner Portal Reference Implementation – показательное приложение на MOSS некоей компании Contoso, являющее собой экстранет-портал, в приложении используются практики наиболее приближенные к промышленным решениям.
  4. Contoso Training Management Reference Implementation – простое приложение HR-отдела, демонстрирующее базовые принципы построения решений на WSS.
  5. QuickStarts – два небольших примера самых простых приложений на SharePoint, а так же доступа к данным

Помимо исходных кодов и юнит-тестов к ним, примерами так же являются установочные скрипты, билд-скрипты, что фактически является артифактами верно налаженного процесса производства ПО, что очень показательно и полезно с практической точки зрения.

image

Обзор руководства

1ая версия вышла в прошлом году (Dec2008), в настоящее время доступна 2ая версия данного руководства (Aug2009), о ней и будет идти речь.

  • Introduction – небольшое введение, а так же рекомендации к инструментам разработки: VSeWSS, U2U CAML Query Builder, CAML.NET, Typemock Isolator
  • Developing SharePoint Applications – основные сценарии разработки под платформу, а так же её ключевые возможности
  • Design and Development Guidelines – отображены ключевые моменты архитектуры SharePoint, а так же описаны подходы решения тех или иных задач разработки под платформу; описаны рекомендации к управлению жизненным циклом проектов
  • Application and Design Patterns – одна из наиболее интересных глав, в ней описаны как классические паттерны построения корпоративных приложений ложатся на решения под SharePoint
  • The SharePoint Guidance Library – описание библиотеки со всеми ключевыми возможностями и компонентами для разработки
  • Integrating Line-of-Business Systems – описание интеграции с существующими корпоративными системами
  • Considerations for Content-Driven Applications – здесь можно узнать о том, как правильно строить приложения, для хранения контента
  • Considerations for Enterprise-Scale Applications – рекомендации по созданию приложений масштаба предприятия
  • Considerations for Extranet Development – рекомендации по разработке и планированию экстранет-порталов
  • Partner Portal Reference Implementation – описание построения приложения-примера для MOSS
  • Training Management Reference Implementation– описание построения приложения-примера для WSS
  • QuickStarts – описание приложения для легкого старта работы с SharePoint

Критика

logo Данное руководство содержит в себе большое количество практичной полезной информации, однако в некоторых технических решениях моя точка зрения расходится с его создателями, в связи с чем мною был начат проект Sapphire: SharePoint Application Framework, который будет так же являться каркасом для построения решений на SharePoint, а так же включать примеры решений и необходимые в повседневной разработке компоненты.

Ресурсы по SharePoint Guidance

Скачать полную версию за август 2009 года можно здесь:

SharePoint Guidance так же представлен на CodePlex: http://www.codeplex.com/spg/ здесь можно задать интересующие вопросы и скачать все последние изменения.

Так же некоторые главы из SharePoint Guidance представлены в видеоряде на channel 9:

Ресурсы по SharePoint

TechNet располагает огромным количеством статей и книг по SharePoint, конечно в них не так много внимания уделено именно разработке, однако для формирования вижна платформы весьма полезно: http://technet.microsoft.com/en-us/library/cc262788.aspx

SharePoint Developer Center здесь можно найти практически всё, что нужно для разработки на SharePoint