5/21/2007 3:02:00 AM

Буду краток.

Майский релиз SCSF. Поддержка WPF, CAB, улучшенный гайдлайн и т.д.

Брать можно здесь: http://www.codeplex.com/smartclient

 

2/27/2007 7:45:00 AM

23-го февраля, видимо в качестве праздничного подарка, был выпущен релиз Composite Application Block, который поддерживает WPF. Ура!

Пока создатели SmartClient Software Factory грозятся выпустить в конце апреля новую версию их продукта, которая будет содержать инструменты для добавления View, использующих WPF (и еще нескольких новых фич), CAB с поддержкой WPF можно скачать здесь: http://www.codeplex.com/wpfcab

Для работы с ним нужно:

  1. Создать "пустой" WPF-проект.
  2. Удалить Window1.xaml и выкинуть из файла App.xaml упоминания о нем.
  3. Добавить в заголовок App.xml следующее указание неймспейса:

    xmlns:cab="clr-namespace:Microsoft.Practices.CompositeUI.Windows.Workspaces;assembly=Microsoft.Practices.CompositeUI.Windows"

  4. Создать в нем класс-аппликейшн, например ShellApplication, который унаследовать от ApplicationShellApplication<MainWorkItem, App>. Здесь MainWorkItem - это "главный" воркайтем приложения, а App - это класс, который уже есть в Вашем проекте и описывается файлами App.xaml и App.xaml.cs
  5. Создать класс Program (имя может быть любым), в котором определить статический метод Main так, как это делается в "обычном" WinForms-приложении. Запустить в нем созданный в п.2 класс-аппликейшн:
    new ShellApplication().Run();
  6. Установить класс Program как стартовый класс в свойствах приложения.

Все, проект WPF CAB готов. Дальше все как обычно, кроме того, что вместо WinForms можно использовать WPF для создания View.

Кстати, если сейчас запустить приложение, то у него даже не будет окна. Для того, чтобы исправить этот "недостаток", нужно создать новую WPF-форму с именем, скажем, ShellWinfow.xaml и переопределить известный метод в ShellApplication:

protected override void AfterShellCreated()
{
   
base.AfterShellCreated();
   
ShellWindow mainWindow = RootWorkItem.Items.AddNew<ShellWindow>();
   
Shell.MainWindow = mainWindow;
    mainWindow.Show();
}

Ну и еще одна "фича". В тех элементах, где планируется использование воркспейсов (DeckWorkspace, TabWorkspace, etc), в заголовок можно добавить следующий атрибут: xmlns:cab="http://schemas.microsoft.com/cab/wpf
Это позволит потом определять воркспейсы в XAML-файле следующим образом:

<cab:DeckWorkspace x:Name="headerWorkspace"/>

В остальном работа с CAB не изменилась.

Более подробно можно посмотреть в примере (BankShell), который теперь тоже "переведен" на CAB WPF.

Tags: ,

2/17/2007 10:30:00 PM

M-V-P (Model-View-Presenter) - это разновидность шаблона M-V-C (Model-View-Controller) с некоторыми существенными отличиями.
Я не буду описывать отличия, если кто-то интересуется, то суть этих отличий кратко описана у Дэррона Шэлла здесь и детально у Мартина Фаулера здесь.

Так же я не буду останавливаться на той части триады, которая называется Model. Полагаю, что с ней и так все понятно, так как эта часть в принципе не осведомлена о двух других, может существовать и без них. К тому же отличий от M-V-C в данном случае нет.

Я остановлюсь на реализации View и Presenter.

Справедливости ради нужно отметить, что Composite UI Application Block предлагает именно M-V-C, но при работе со Smart Client Software Factory вы будете общаться именно с M-V-P. Класс презентера (как и некоторые другие) не являются частью CAB, но будут созданы фабрикой в общем модуле вашего приложения.
В Composite Web Application Block презентер уже имеется изначально, поэтому я думаю, что соответствующая реализация будет включена и в следующий релиз CAB.

M-V-P в данном случае предполагает наличие View - пользовательского интерфейса и наличие Presenter'а, который содержит логику работы пользователя над моделью.
При этом View "знает" о наличии Presenter'а "напрямую" в то время, как Presenter "знает" о наличии View опосредованно - через интерфейс.

То есть, мы, к примеру, имеем:

  1. Интерфейс ICustomerView, в котором определяются свойства и методы, отвечающие за поведение View;
  2. Класс CustomerView, который является реализацией ICustomerView и занимается непосредственно представлением данных. Кроме этого экземпляр CustomerView содержит ссылку на соответствующий Presenter, с которым и общается в рамках своей работы;
  3. Класс CustomerViewPresenter, который ничего не знает о CustomerView. Вместо этого он знает об интерфейсе ICustomerView, ссылку на который содержит.

В коде это выглядит так:

 

//Interface
public interface ICustomerView
{
    void ShowCustomerInfo(Customer customer);
}
 
//Presenter
public class CustomerViewPresenter : Presenter<ICustomerView>
{
    public override void OnViewReady()
    {
        base.OnViewReady();
        //Get the customer somewhere
        .....
        View.ShowCustomerInfo(customer);
    }
}
 
//View
public partial class CustomerView : UserControl, ICustomerView
{
    public CustomerView()
    {
        InitializeComponent();
    }
 
    [CreateNew]
    public CustomerViewPresenter Presenter
    {
        set
        {
            _presenter = value;
            _presenter.View = this;
        }
    }
 
    protected override void OnLoad(EventArgs e)
    {
        _presenter.OnViewReady();
    }
 
    public void ShowCustomerInfo(Customer customer)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new MethodInvoker(delegate() { ShowCustomerInfo(customer); }));
            return;
        }
 
        .....
    }
}

 

Отсюда видно, что при создании экземпляря CustomerView ObjectBuilder создает экземплярCustomerViewPresenter, ссылка на который теперь доступна внутри CustomerView. Сам же презентер получает ссылку на экземпляр, реализующий известный ему интерфейс, поэтому ему все равно, какой тип пользовательского интерфейса скрывается за ICustomerView.

Важно отметить то, что в случае M-V-P слой View сам обрабатывает пользовательский ввод и "уведомляет" презентер о необходимости получить/обработать данные модели, а так же о своем состоянии. В указанном примере View уведомляет Presenter о том, что он загружен и готов к работе (событие OnLoad в CustomerView).

Презентер же, выполнив какие-то действия (например, получив от ICustomerService экземпляр класса Customer), сообщает View о необходимости отобразить информацию об этом кастомере.

Здесь очень важным является тот момент, что View никакой информацией о логике Presenter'а  не располагает, поэтому реализация метода ShowCustomerInfo делается потокобезопасной. На самом деле, Presenter может начать обрабатывать данные асинхронно для того, чтобы пользовательский интерфейс не блокировался на время работы. Пока ведется обработка данных, Presenter может "просить" View информировать пользователя о прогрессе и лишь после окончания обработки вызвать метод ShowCustomerInfo. Поэтому View должен быть готов к тому, что практически любой вызов от презентера придет из контекста другого потока, реализуя методы интерфейса ICustomerView потокобезопасными.

На самом деле, это очень простая схема и именно так и нужно делать всегда: View лишь уведомляет Presenter о том, что пользователь захотел сделать что-то (например, вызывая _presenter.ApproveCustomer). View не в праве расчитывать на то, что Presenter ответит немедленно, вернув какой-то результат. Строго говоря, результаты вообще не в компетенции View, View не в праве решать, что делать с результатом. Вместо этого обработкой данных и получением результата занимается Presenter и после того, как операция выполнена, просто заставляет View сделать что-то другое.

Такой подход позволяет нам получить следующие плюсы:

  1. Безболезненно менять View по нашему усмотрению (или даже заменить его полностью, например, на консоль или веб-страницу, лишь бы интерфейс был реализован);
  2. Изменять логику обработки пользовательского ввода так, как будет угодно.
  3. Получить механизм, работающий в рамках "правильной" архитектуры, то есть, когда части приложения просто нотифицируют друг друга о необходимости какой-либо реакции.
    View говорит презентеру: "Пользователь велел сделать то-то". Презентер делает что-то и через "полчаса" говорит View: "ну, я сделал, сообщи там как-нибудь".
    "Пользователь хочет новых данных" - "Нарисуй новые данные".
    "Пользователь сказал, что эти данные не годятся" - "Нарисуй вот эти данные"
    "Пользователь велен их стереть" - "Нафиг, скажи, что у него нет прав".
    И так далее. Никакая часть не ждет другую. Никакая часть не блокируется в ожидании (ну, потому, что не ждет). Никаких идиотских циклов ожидания с DoEvents().

Ко всему этому писать (и поддерживать!) код легко и приятно (а то иной раз просто хочется запустить мышкой в монитор, разбирая очередные 350 спагетти-строк в _button1_Click).

Пользуйтесь шаблонами проектирования, в них мудрость :) Возьмите "полуфабрикаты" в виде Composite Application Blocks, возьмите Software Factories - и они помогут вам придерживаться "правильного" пути.
И будет вам счастье.

Tags: , ,

2/17/2007 5:27:00 AM

Сегодня немного расскажу об архитектуре Composite Application Blocks (Web и UI) и о том, как ее использовать "во благо". Речь в основном пойдет о двух фундаментальных вещах: сервисах и шаблоне проектирования M-V-P (Model-View-Presenter).

В процессе работы над (любым) программным продуктом всегда хочется сделать так, чтобы отдельные части этого продукта, работая вместе "как часы", при этом как можно меньше зависели друг от друга.  Для того, чтобы работа над одной из частей никак не затрагивала другие части или чтобы легко можно было вообще заменить одну из частей так, чтобы остальные части об этом ничего не узнали. Эта проблема называется "связанностью" и решение ее тем лучше, чем меньше различные части системы знают о том, как реализованы другие.

Слабой связанности в Composite Application Blocks можно добиваться посредством создания сервисов.
Кто такой сервис? Сервисом может быть любая независимая часть вашего приложения.
Сервис дает возможность приложению задавать вопросы типа "что" не заботясь об ответах на вопрос "как". Наше приложение просит сервис дать список пользователей - и получает его не заботясь о том, был ли список получен из БД или с Веб-сервиса. Приложение просит сервис информировать пользователя о прогрессе, не заботясь о том, будет ли пользователю показан ProgressBar или отобразится окошечко "Please Wait".
Приложение знает что надо делать. Сервис знает как надо делать. Приложение знает, что где-то есть, кто-то, кого можно пнуть, чтобы он сделал свою работу.

О том, как это реализуется.
Сервис обычно состоит из "двух частей" - интерфейса сервиса и его реализации. Интерфейс сервиса обычно размещается где-нибудь в доступном для всех (интересующихся) частей приложения месте (в Shared-библиотеке), а реализация - где придется там, где удобнее реализовывать этот интерфейс (например в каком-то модуле).

Например, мы определим интерфейсы ISourceControlService и IProgressService в общей библиотеке, чтобы все части приложения имели к ним доступ. После этого мы можем создать модуль SourceSaveVersionController, в котором создать класс SourceSaveService : ISourceControlService, которая будет работать работать с Microsoft SourceSave.
При загрузке и инициализации этого модуля происходит регистрация сервиса в системе:

Services.AddNew<SourceSaveService, ISourceControlService>();

С этого момента из любой части приложения мы можем обратиться к сервису, отвечающему за контроль версий, получить экземпляр, реализующий ISourceControlService и даже не задумываться о том, с какой именно системой контроля версий мы имеем дело:

ISourceControlService scControl = Services.Get<ISourceControlService>();

И теперь если мы заменим SourceSaveVersionController на, скажем, ClearCaseVersionController с соответствующей реализацией ISourceControlService, то для всего остального приложения эта смена пройдет более чем прозрачно. Потому, что остальное приложение знать не знает ни про какие модули и реализации, оно общается только с известным ему интерфейсом.

Для IProgressService можно поступить еще хитрее: реализовать этот интерфейс в главной форме приложения и зарегистрировать ее саму как сервис типа IProgressService. А можно сделать как-то иначе, придумать что-то другое, ведь уже стало понятно, что остальному коду совершенно безразлично то, кто предстанет перед ними под личиной IProgressService и как он будет выполнять свою работу :)

Сама работа с сервисами в Composite Application Blocks тоже очень удобна.
Например, для регистрации сервиса в CAB можно использовать такую конструкцию:

[Microsoft.Practices.CompositeUI.Service(typeof(IComponentsProvider))]
public class AccessComponentsProvider : IComponentsProvider
{.....}

В этом случае при загрузке модуля экземпляр сервиса будет автоматически создан и зарегистрирован.

Там, где нам необходимо общаться с сервисами, и в CAB и в Composite Web AB можно сделать так:

[ServiceDependency]
public IAuthorizationService AuthService
{
   
get { return _authService; }
   
set { _authService = value; }
}

ObjectBuilder сам найдет для вас подходящий сервис IAuthorizationService (если, конечно, он был зарегистрирован) и вызовет присвоит ссылку на него свойству AuthService. В случае, если нужный сервис зарегистрирован не был, вы получите null. Однако, в конструктор аттрибута ServiceDependency можно передать булевый параметр для того, чтобы ObjectBuilder возбуждал исключение если сервис найти не удается.

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

Используйте сервисы - и да будет вам счастье :)

Tags:

1/21/2007 5:10:00 AM

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

Например, уже существуют такие "фабрики", как Smart Client Software Factory, позволяющая легко и безо всякой рутины создавать приложения на основе CAB (Composite UI Application Block). С помощью этой "фабрики" мы оперируем сущностями CAB'а, такими, как "контроллер", "вью", "воркайтем" и т.д.
Существует (на мой взгляд менее удачная, но позволяющая значительно сократить время разработки) "фабрика" для создания слоя веб-сервисов, существуют и другие.

Теперь вот существует и Web Client Software Factory.

Хороших новостей несколько.
Во-первых, она опирается на Composite Web Application Block. Это, на мой взгляд, очень замечательная штука. Это, скажем так, CAB для ASP.NET (да, уважаемый Void, с ObjectBuilder'ом и т.д. и т.п. ;) ). Нет только EventBroker'а, да он там и не нужен.
Во-вторых, она опирается так же на Page Flow Application Block. Это не менее замечательная штука, позволяющая действительно (!) легко включать в веб-приложение Workflow Foundation. Это трудно переоценить, на мой взгляд.
В третьих, это все же "фабрика", так что, создавать приложение в рамках архитектуры этих двух блоков - что мышкой кликать, ниже я покажу.
В четвертых, все это дело (включая злополучный ObjectBuilder) замечательно документировано. Лично я документацию прочитал от первой строчки и до последней - написано четко, по делу, без воды, очень доходчиво: что, как, где, почему.

Словом, мне эта штука очень понравилась.
Несколько примеров в общем виде (быть может, если в голову взбредет, позже я напишу подробнее):

В контекстное меню solution'а "фабрика" добавит несколько новых пунктов. В частности, появится возможность добавлять модули в свое приложение (а где вы видели CAB без модулей?).

Можно добавить три типа модуля: Business Module, Foundation Module и Page Flow.

Business Module - это какая-то часть функциональности приложения. То есть, модуль в "чистом" его виде - есть модуль - есть функционал, нет модуля - нет функционала. Ну, например, можно сделать модуль с блогами, модуль с форумами, модуль с доской объявлений для сайта и т.д.

Foundation Module - это модули бизнес-логики. Там предполагается размещать различные сервисы (а где вы видели CAB без сервисов?), провайдеры и т.д. Например, можно сделать модуль, который содержит логику работы с данными. Поменялась база - стер старый модуль, добавил новый. Приложение продолжает работать.

Page Flow - ну, тут из названия все понятно. Здесь предлагается определять workflows наших походов по сайту. Ну, например, процесс выполнения заказа юзером. Сначала он попадает в свою корзину товаров, потом жмет "заказать" и попадает на форму ввода адреса и условий доставки, потом - страничка оплаты и в конце - поздравления. Или ошибки на каждом из шагов. Или что-то еще. Словом, все, что нам потребуется - это нарисовать state-диаграмму того, как в принципе могут осуществляться переходы и при каких условиях. Об остальном позаботится Workflow Foundation со всеми его плюсами.

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

Впрочем, в контекстном меню веб-сайта тоже прибыло. Там появился пунктик "Web Client Factory -> Add view (with presenter)". Клик по нему "родит" окошко в виде:
Add view with presenter "Фабрика" учтиво спросит, какое именно "View" нужно создать, к какому модулю оно относится и где будет размещаться.

На картинке видно, что создан будет и сам "View", и его интерфейс (IView1 в моем случае), и его Presenter (View1Presenter).

Связь между View и Presenter'ом тоже будет установлена автоматически. Если не полениться и зайти в code behind самой вьюшки, то это будет видно.

Собственно, вот таким вот нехитрым способом я уже создал вполне себе работающую болванку приложения. Сколько у меня ушло времени? Несколько минут?
Если эту болванку сейчас запустить - то там даже кое-какая навигация будет (о том почему она будет и откуда берется - может быть позже, но забегая вперед скажу: Composite Web Application Block + Factory - это вам не только мышкой тыкать да страницы создавать. Это еще и обильная функциональность, сдобренная набором сервисов).

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

1/10/2007 7:28:00 AM

Работаю с Enterprise Library, CAB и, в частности, с EventBroker уже почти год.
Всегда считал, что при работе с событиями через этот самый EventBroker происходит вызов подписчиков по порядку в цикле, причем если какой-то из подписчиков генерирует исключение, то EventBroker его перехватывает и вместо него кидает свое. Дескать, обработка события такого-то не прошла, получите и распишитесь.
Свойство InnerException оставалось нетронутым, то есть, равным null, что меня всегда раздражало: ну, получил, расписался, а в чем, собственно дело? Смысла в информации о том, что где-то сорвался какой-то из подписчиков не много, если не знаешь где и какой.

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

Оказывается, перебор подписчиков EventBroker действительно осуществляет в цикле и действительно перехватывает все исключения от всех подписчиков.
Но при этом он не прерывает цикл, а бережно складывает все полученные исключения в списочек и продолжает вызывать подписчиков.
После того, как подписчики кончились, он проверяет, а не пуст ли списочек исключений? Если он не пуст, то EventBroker действительно генерирует свое, новое (EventTopicException) исключение. Он действительно не заполняет свойство InnerException, так, как это просто не имеет смысла: если в списке находится более одного исключения, то какое выбрать в качестве InnerException?
Вместо этого класс EventTopicException имеет свойство Exceptions, которое содержит список всех исключений, произошедших во время обработки сообщения.

Вот так все просто. Примерно то, что хотел приделать к EventBroker'у я :)

Чувствую себя идиотом :)

P.S. Надо было писать этот пост с умным видом в тоне наставления, типа "для того, чтобы получить то-то надо посмотреть туда-то" :-) Опять стормозил ;)

 

Technorati tags: , , ,

Tags:

Powered by BlogEngine.NET 1.6.0.0

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter


Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2010

Sign in