Как подружить 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


Учимся проектировать на основе предметной области (DDD: Domain Driven Design)

1. Введение

В данной статье я хотел бы рассказать об этих трёх буквах, постоянно находящихся на слуху, но для многих являющихся тайной за семью печатями, а так же привести ряд ресурсов, с которыми неплохо было бы познакомиться при желании продолжить развитие в проектировании на основе предметной области (DDD: Domain Driven Design).

2. Так почему же DDD?

Есть несколько шаблонов реализации предметной области (Domain Logic) или бизнес-логики (Business Logic):

1) Table Module – представляет собой объект, в единственном экземпляре, обрабатывающий бизнес логику для всех записей в таблице базы данных, либо представления.

2) Transaction Script – организует взаимодействие с бизнес-логикой посредствам процедур, принимающих запросы с уровня представления.

3) Domain Model – непосредственно, объектная модель предметной области, включающая в себя как поведение, так и данные.

Эти шаблоны описаны более подробно Мартином Фаулером, в его книге “Архитектура корпоративных программных приложений. Шаблоны корпоративных приложений(Patterns of Enterprise Application Architecture (P of EAA)). В данной книге он показывает, что первые два шаблона более привлекательны в начале работы с предметной областью, однако так же обращает внимание, что при наращивании сложности логики предметной области стоит больше внимания уделять сопровождению инфраструктуры, используя первые два подхода, это время можно уменьшить, если обратиться в своём решении к третьему из вышеперечисленных шаблонов, так называемой “Модели предметной области”.

На основе этого сделаем небольшой вывод о том, что данный шаблон (“Модель предметной области”) лучше всего подойдёт, к примеру, для такой непростой области, как финансовый рынок. Большинство, создаваемого в наши дни программного обеспечения предназначено для различных нужд бизнеса, следовательно какие-то абстрактные, обобщенные решения находят своё место на рынке (с довольно таки высокой конкуренцией) всё реже и реже. К чему я пишу про всё это? Потому что DDD – это не только качественное проектирование, но так же и показательный пример того, как следует выделить предметную область в программном обеспечении, для того, чтобы проще преодолевать сложности, частые изменения, проблемы коммуникации и прочие недуги предметной области, вместо того чтобы разрабатывать уродливую, сложную для понимания систему, в которой любое изменение или исправление способно обрушить на вас лавину всё новых и новых дефектов.

DDD ни в коем случае не отрицает наследия практик разработки, таких как:

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

3. С чего можно начать?

Если мой “нудный PR”  проектирования на основе предметной области (DDD) вас до сих пор не утомил, то думаю нам стоит продолжить, если же иначе, то посмотрите хотя бы ссылки на материалы.

Первой книгой пролившей свет на DDD для широкой публики была так называемая “Большая синяя книга” (мем. BBB: Big Blue Book): Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans (на русский язык пока не переведена).

Книга довольна подробно рассказывает о том, что из себя представляет DDD, и все связанные аспекты, такие как: язык предметной области, шаблоны, практики проектирования, рефакторинг, моделирование, как сделать разработку гибкой и многое другое. Но даже если вы ознакомитесь со всеми вопросами, поднятыми в книге (что является не совсем простым занятием), вы обратите внимание, что вопросы рассматриваются только с теоретической точки зрения, оставляя весь простор для практики (книга не привязана к конкретной платформе разработки). Для большинства из нас чтение чистой теории, без подкрепления практическими примерами не нравится, в связи с этим можно обратить своё внимание на сокращенную (и свободную для доступа) версию этой книги, подготовленную порталом InfoQ: Domain Driven Design Quickly.

Есть так же несколько хороших презентаций Эрика Ивенса (Eric Evans), с которых можно начать:

1) DDD: putting the model to work

2) Eric Evans on DDD: Strategic Design

На портале InfoQ можно найти множество других презентаций, статей и интервью, посвященных DDD.

Итак, с теоретической частью мы разобрались, где же можно найти примеры практического применения DDD? Отличной книгой для этого является .NET Domain-Driven Design with C#, Problem – Design – Solution написанная Tim McCarthy. 

В этой книге вы наёдете практические примеры:

1) Как проходит процесс проектирования и разработки, от определения требований, до написания кода

2) Как организовывать архитектурные слои в своих решениях

3) Как применять шаблоны и практики DDD

4) Как построить небольшой каркас для DDD

5) Как изолировать домен предметной области от модели

6) Современные паттерны представления данных и взаимодействия с ними (Model-View-ViewModel) в такой среде как WPF (так же применимы к Silverlight) в практики.

Эта книга – отличный практикум по DDD, содержащий очень широкий пласт идей. Начинается книга с разработки требований, а заканчивается реализацией промышленного приложения, исходные коды которого доступны на Codeplex.

Вся концепция книги построена на 3 книгах-столпах DDD:

  1. PoEAA Мартина Фаулера
  2. DDD Эрика Ивенса
  3. Applying Domain — Driven Design and Patterns by Jimmy Nilsson’s (“Применение шаблонов проектирования: проблемно-ориентированное проектирование приложений с примерами на C# и .NET” Джимми Нильссона )

В этой книге поверхностно рассмотрены все вопросы, техники и паттерны, применяемые в DDD, все примеры сопровождаются кодом, что упрощает понимание. Книга превосходная, однако русский перевод подкачал, поэтому, рекомендую прочитать оригинал.

 

 

Однако DDD – это не просто практические решения или шаблоны, это мышление и подход, и есть великое множество нюансов, которые необходимо учитывать, если вы решили следовать DDD, таких как: фокусирование на высокий приоритет отдается модели, выработка языка предметной области, контекст модели, процесс моделирования, разделение знаний, рефакторинг, стратегический дизайн и т.д…это является  основной причиной ознакомиться с книгой Эрика Ивенса, так как она даст вам более объемное и глубокое понимание философии DDD.

DDD не привязанны к конкретной технологии, однако соблюдать DDD будет не так просто, без наличия хороших средств и практик в вашем арсенале, таких как: TDD-фреймворк, ORM, возможность реализации независимости сохраняемости (Persistence Ignorance), IoC-контейнер (Inversion of Control), и возможностей AOP (Аспектно-Ориентированного Программирования), конечно не значит, что все эти инструменты нам понадобятся, однако они приблизят нас к реализации DDD на практике. Практичная ценность этих средств в том, что они позволять изолировать модель предметной области, что является ключевой целью DDD. Книга Джимми Нильссона может познакомить вас с возможностями и видами данных инструментов. Джимми так же показывает как использовать шаблоны реализации корпоративных приложений, и строить, благодаря им, цельное решение, основанное на современных инструментах и практиках.

Некоторые реализации шаблонов DDD на Ruby On Rails:

Some DDD (Domain Driven Design) Concepts implemented in Rails

4. Актуальные вопросы DDD

C DDD так же тесно связана такая тема, как DDDD: Distributed Domain Driven Design (Распределенный DDD). DDDD – это DDD в распределенных сценариях. В настоящее время существует не так много ресурсов, посвященных DDDD, в нескольких словах о DDDD: покрывает проблему реализации сообщений и DDD, разделение команд и запросов (Command Query Separation (CQS)) помогает реализовать данный подход. Грег Янг (Greg Young) сообщил, что готовит книгу, посвященную DDDD.

SOA и DDD – это ещё одна объемная тема, часто обсуждаемая Udi Dahan

5. DDD шаблоны, концепции и понятия

В промышленных приложениях DDD использует ряд шаблонов, часть которых описана в книге Эрика Ивенса, но, это не отменяет применение объектно-ориентированного подхода, включающего GoF-шаблонышаблоны Мартина Фаулера, описанные в его PoEAA, Шаблоны интеграции корпоративных приложений и т.д.…

Вот некоторые из них:

6. Примеры приложений

Найти хорошие примеры реализации DDD очень сложно, не потому что их не существует, а потому что реальная сила DDD реализуется в приложениях, используемых в довольно сложных областях, и, как правило, являющихся коммерческими проектами. Однако можно найти несколько неплохих проектов, в которых можно проследить некоторые идеи реализации шаблонов DDD.

Вот они:

1) Приложение Тима Маккарти его проект, описанный в деталях в его книге. Он описывает не только применение шаблонов, но так же акцентирует внимание в разработке модели предметной области с точки зрения DDD.

Проект так же интересен тем, что построен на .NET 3.5 и демонстрирует всю силу современного подхода связывания данных с моделью предметной области (data binding, реализация шаблона MVVM). Так же его стиль примечателен умением выделять абстракции и повторно используемый код.      

2) Следующий проект, на который следует обратить внимание – это приложение разработанное Yves Goeleven, создание данного приложения описано в его блоге (так же посвященному основным концептам DDD). Другим его приложением является DDD-каркас. Следует обратить внимание на его реализацию взаимодействия шаблонов Repository и Specification

3) Billy McCafferty разрабатывает потрясающий open source фреймворк, сфокусированный на DDD, под названием S#arp Architecture. У него есть очень хорошее описание, включающее в себя описание шаблонов и подходов, заключенных в фреймворке. Фреймворк нацелен на разработку ASP.NET MVC приложений с применением NHibernate.

4) C# Domain-Driven Design sample application ( ndddsample ), это приложение, разрабатываемое Джимми Нильссоном, демонстрирует разбиение приложения на ключевые слои с точки зрения DDD. Так же демонстрируется практическое применение шаблонов building block в предметной области перевозки грузов, описанной в его книге.

Этот проект основан на совместной работе компании Эрика Ивенса “Domain Language” и шведской консалтинговой компании “Citerus”.

Цель этого проекта:

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

здесь более подробная информация.

7. Ресурсы по Domain Driven Design

Официальный сайт — http://domaindrivendesign.org/

Группа обсуждений — http://tech.groups.yahoo.com/group/domaindrivendesign/ это взрослая группа, очень хороший источник идей, место для обсуждений всех видов проблем в области DDD. В ней на ваши вопросы могут ответить опытные в DDD люди, даже Эрик Ивенс :-).

Блог Jimmy Bogard’а — http://www.lostechies.com/blogs/jimmy_bogard/default.aspx

Блог Colin Jack’а — http://colinjack.blogspot.com/

Блог Greg Young’а — http://codebetter.com/blogs/gregyoung/default.aspx

Блог Casey Charlton’а — http://devlicio.us/blogs/casey/

Блог Udi Dahan’а — http://www.udidahan.com/

Введение в Domain-Driven Design — http://msdn.microsoft.com/ru-ru/magazine/dd419654.aspx

8. Заключение

Если вы заинтересованы в расширении ваших “объектно-ориентированных горизонтов” в сложных корпоративных системах и изучении новых способов разработки и проектирования, то DDD – именно то что нужно.

http://weblogs.asp.net/arturtrosin/archive/2009/02/09/domain-driven-design-learning.aspx


Пример TDD с использованием IoC-контейнера и Mock-фреймврка

Jarod Ferguson выступал с этим докладом на 2009 Boise Code Camp и выложил слайды и примеры кода. Советую ознакомиться.

Чуть подробнее читать здесь:

http://elegantcode.com/2009/03/29/boise-code-camp-test-driven-development-with-ioc-and-mocks/