這篇文章主要是爲了說明,咱們爲何要使用Catel框架做爲開發WPF,Silverlight,和Windows phone7應用程序的開發框架。html
針對需對開發者,再使用架構的時候是但願有很大的自由度的,可是大部分框架則須要開發者徹底遵循他的規則,要麼都使用這個框架,要麼就不能使用。而catel不是,Catel包含不少的功能:日誌,診斷,反射,MVVM,用戶控件,窗體,這些全部的功能都是與其餘功能互補的,二針對Catel則是你能夠決定使用這些功能中的一個,或多個,或所有。而對於大部分應用程序都會有一個啓動項-bootstrapper,經過這個來決定你的架構,當這個決定後,好多東西都是定好的,好比說Views的名字必須這樣,控件的名字必須那樣,而Catel可讓你自由設置。還有一點最重要Catel能夠和其餘框架一塊兒使用,這樣開發者能夠很好的利用其餘框架來補充應用程序的每一個方面來完成一個最好的框架。git
有一件很重要的事情是大部分開發者來寫用了不少精力來實現對象的序列號。序列號是一種專業的技術,儘管少數的開發者可以掌握序列化方法(考慮程序集版本的改變,類的改變(增長或減小屬性)等等).而大部分開發者所認爲的,掌握的序列號技術,只是建立一個BinaryFormatter對象,以下面代碼所示:web
BinaryFormatter serializer = new BinaryFormatter(); var myObject = (MyObject)serializer.Deserialize(stream);
大部分開發者都不知道當出現以下狀況是反射會出現問題:數據庫
使用這個類是很是簡單的,你只須要定義一個新的類,繼承與DataObjectBase就能夠了。bootstrap
/// <summary> /// MyObject Data object class which fully supports serialization, /// property changed notifications, /// backwards compatibility and error checking. /// </summary> #if !SILVERLIGHT [Serializable] #endif public class MyObject : DataObjectBase<MyObject> { /// <summary> /// Initializes a new object from scratch. /// </summary> public MyObject() { } #if !SILVERLIGHT /// <summary> /// Initializes a new object based on <see cref="SerializationInfo"/>. /// </summary> /// <param name="info"><see cref="SerializationInfo"/> // that contains the information.</param> /// <param name="context"><see cref="StreamingContext"/>.</param> protected MyObject(SerializationInfo info, StreamingContext context) : base(info, context) { } #endif }
正如你在上面代碼中所示,MyObject類繼承於DataObjetBase,提供一個空的鉤子函數,但也有一個構造函數用於二進制序列號,上面的代碼看起來複雜,可是能夠由一個dataobject代碼段來構造,你只須要寫類的名字。架構
爲類定義屬性是很是簡單的,屬性與dependcy 屬性像是,使用這種方式來定義屬性的優勢以下:app
/// <summary> /// Gets or sets the name. /// </summary> public string Name { get { return GetValue<string>(NameProperty); } set { SetValue(NameProperty, value); } } /// <summary> /// Register the Name property so it is known in the class. /// </summary> public static readonly PropertyData NameProperty = RegisterProperty("Name", typeof(string), string.Empty);
我已經提到序列化好屢次了,讓我看來看看如何簡單序列化你的程序,而不考慮assembly版本,首先,將原來繼承於DataObjectBase的對象,改成繼承SavableDataObjectBase.依賴目標框架,許多屬性做爲序列化模式框架
下面的代碼顯示瞭如何存儲一個對象(若是能夠,也能夠存儲一個嵌套的對象)ide
var myObject = new MyObject(); myObject.Save(@"C:\myobject.dob");
看起來很是的簡單,可是這確實是你惟一須要作的事情,你也可以經過從新調用重載的方法,來指定序列化模式。函數
載入也是很簡單的,你能夠經過以下的代碼
var myObject = MyObject.Load(@"C:\myobject.dob");
DataObjectBase提供了一些功能,以下
IDataError
很是容易設置字段或業務的錯誤,經過SetFieldError和SetBussinessError方法,這些方法可以經過改寫ValidateFiled和ValidateBussinessRules的方法實現
IEditableObject
數據對象可以自動建立一個內部備份和存儲它,若是須要,使用IEditableobject接口
序列化
如說過好屢次的,使用SavableDataObjectBase對象,你可以存儲你的文件到流(如在disk中,stream在內存中等等)注意這個類並不適合數據庫通信,比較好的辦法是轉換他(ORM 匹配的方式,經過Entity Framework,NHibernate,LLBLGen Pro 等等)
這個API是位於Catel.Core程序集中,這個能夠不僅使用WPF(也能夠用於ASP.NET,Windows Forms)等等。
在近幾年,MVVM已經變成寫WPF,Sliverlight和Windows Phone應用程序的一種經常使用模式,模式自己是很是簡單的,但MVVM自己有許多的缺陷很問題:
1,如何在View-Model中顯示模型對話框或者是消息框。
2,如何在View-Model中執行進程
3,如何讓用戶在View-Model中選擇一個文件。
在我看來,有許多好的架構將其劃分出來,例如,人們直接在View-Model中調用MessageBox.show,若是你也是使用這種方式,問問你本身:誰會在一個單元測試中點擊按鈕。
在咱們真正開始用Catel進行開發前,咱們須要經過調查來肯定MVVM模式在Line of Business(LoB)是真正有用的。基於這個調整和研究,咱們建立了一個穩固的MVVM模型來解決MVVM模式中已知的這些問題 。
如大多數MVVM框架同樣,全部View-Models都使用ViewModelBase做爲基類,這個基類繼承與DataObjectBase類(在文章的前門提到過這個類),這個將會有以下的優勢:
/// <summary> /// Gets or sets the shop. /// </summary> [Model] public IShop Shop { get { return GetValue<IShop>(ShopProperty); } private set { SetValue(ShopProperty, value); } } /// <summary> /// Register the Shop property so it is known in the class. /// </summary> public static readonly PropertyData ShopProperty = RegisterProperty("Shop", typeof(IShop));
使用Model特性是很是強大的,基本上這是在View-Model中的擴展,若是模型支持IEditableObject,BaseEdit將自動在View-Model的Initialize中被調用。當前View-Model被Cancel,則CancelEdit也會被調用保證更改被回退掉。而當模型被定義時,也可使用ViewToModel特性,如你下面的代碼所看到的
/// <summary> /// Gets or sets the name of the shop. /// </summary> [ViewModelToModel("Shop")] public string Name { get { return GetValue<string>(NameProperty); } set { SetValue(NameProperty, value); } } /// <summary> /// Register the Name property so it is known in the class. /// </summary> public static readonly PropertyData NameProperty = RegisterProperty("Name", typeof(string));
在上面代碼中的ViewModel特性會自動匹配View-Model 中的Name屬性到Shop.Name屬性中,使用這種方式,你不須要手工去編寫這些代碼來從模型中獲取值,另外一個很好的效果是View-Model將自動驗證全部定義在Model特性中的對象,全部的字段和業務的錯誤將自動匹配到View-Model上
總的來講,Model和ViewModelToModel特性將肯定不須要重複驗證或者人工匹配。
Commanding 在Catel能被很好的支持,Catel支持Command類,這些在其餘框架中被稱之爲RelayCommand或者是DelegateCommand,在View-Model中定義一個Command是已經很是容易的時候,你能夠在下面的代碼中看到。
// TODO: Move code below to constructor Edit = new Command<object, object>(OnEditExecute, OnEditCanExecute); // TODO: Move code above to constructor /// <summary> /// Gets the Edit command. /// </summary> public Command<object, object> Edit { get; private set; } /// <summary> /// Method to check whether the Edit command can be executed. /// </summary> /// <param name="parameter">The parameter of the command.</param> private bool OnEditCanExecute(object parameter) { return true; } /// <summary> /// Method to invoke when the Edit command is executed. /// </summary> /// <param name="parameter">The parameter of the command.</param> private void OnEditExecute(object parameter) { // TODO: Handle command logic here }
有一些人並不喜歡ICommand實現,例如Caliburn(Micro)使用約定,而並不須要建立Command,這種作法有些缺陷。以下
服務室Catel解決須要將消息傳遞給用戶的問題,讓用戶開始一個進程,ViewModelBase有一個GetServcie方法來經過一個IOC容器得到得到實際實現的一個接口,對於WPF和Silverlight,Catel使用Unity,對於Windows Phone7,IOC是經過定製實現的。
在Catel中,服務最重要的效果是,默認狀況下,真正的實現是使用Unity容器註冊過的,經過Ioc容器,你能夠註冊測試實現,例如,MessageBox Click實際,在這種狀況下,你可以模仿用戶點擊OK,若是你想,你也能夠模仿Cancel,這樣你能夠經過單元測試用戶在View-Model中執行的全部可能的操做。
使用services時很是容易的,如前面所提到的,GetServcie方法獲取service的真正執行,例如,顯示一個消息給用戶,你可使用以下的代碼
var messageService = GetService<IMessageService>(); messageService.ShowError("An error occurred");
全部的服務都能被簡單的使用,Catel支持一些services,以下是services可以支持的目標框架
服務名稱和描述 | WPF | Sliverlight | WP7 |
ILogger -確保寫消息日誌 |
|||
ILocationService -返回地理位置 |
|||
IMessageService -顯示彈出框 |
|||
INavigationService - 導航服務 |
|||
IOpenFileService -讓用戶選擇文件並打開 |
|||
IPleaseWaitService – 在UI線程上啓動一個等待標記 |
|||
IProcessService - 執行進程 |
|||
ISaveFileService - 讓用戶選擇一個文件來保存 |
|||
IUIVisualizerService - 顯示模態窗口 |
固然你也能夠設計你本身的服務
2.3.5與其餘View-Models交互
大部分的框架須要設置複雜的消息系統或者其餘的技術來在View-Models直接進行通訊,這個方法的缺點在於一旦View-Model是指在ModuleX中實現,而且你關注View-Model,ModelX的開發者必須關注其餘的ViewModel,而如今你惟一須要作的事情是設置一個InterestedIn特性,以下代碼所示。
[InterestedIn(typeof(FamilyViewModel))] public class PersonViewModel : ViewModelBase
那麼,在PersionViewModel中(這個關注FamiliyViewModel的更改),你只則只須要修改OnViewModelPropertyChanged事件方法,便可。
/// <summary> /// Called when a property has changed for a view model type /// that the current view model is interested in. This can /// be accomplished by decorating the view model with the <see cref="InterestedInAttribute"/>. /// </summary> /// <param name="viewModel">The view model.</param> /// <param name="propertyName">Name of the property.</param> protected override void OnViewModelPropertyChanged(IViewModel viewModel, string propertyName) { // You can now do something with the changed property }
也有可能關注多個View-Models,從View-Model被傳遞到OnViewModelPropertyChanged中,很是容易檢查View-Model類型。
在MVVM中,有一個複雜架構問題,稱之爲」嵌套控件問題「,直到如今,咱們沒有發現其餘框架可以使用一個清晰的方式來解決這個問題。這個問題是嵌套的控件應該很容易獲取本身所在的View-Model,可是,如何去管理這個,咱們看到了許多的解決方案,好比:
1,在另外一個View-Model中定義嵌套的View-Models.
2,在最上層的View-Model上定義嵌套用戶控件,在用戶控件中顯示這些屬性
下面是這個問題的圖片顯示
一般的MVVM格式
Catel中的MVVM
如上面的圖片所示,Catel解決這個問題的方法是更加的專業,這個有以下理由說明:
1,分割關注點(每一個控件都只有一個View-Model來包含它本身的信息,而並不關係其子節點)
2,用戶控件能夠被重用
UserControl<TViewModel)可以基於一個實際的用戶控件的datacontext來構造一個ViewModel,例如,當你定義了嵌套的用戶控件,你惟一須要作的事情就是確認用戶控件的datacontext要有一個對象被注入到屬於用戶控件的View-Model中。
例如,咱們有一個persion控件,這個persion控件,只能使用一個有效的IPerson實例(View-Model僅有的構造函數),而後將以以下的方式定義在XAML中。
<Controls:PersonControl DataContext=」{Binding Person}」 />
用戶控件將關注datacontext的改變,而且嘗試去使用正確的datacontext來構建PersonViewModel,在咱們的例子中,IPerson接口的一個實例,將自動將IPersion對象轉換成PersionViewModel而且控件將有本身的View-Model.
我前面已經提到過不少次-Catel比其餘的MVVM模式,提供了更多的UI Elemenet,下面將是最受歡迎的幾個。
InfoBarMessageControl可以經過IDataWariningInfo和IDataErrorInfo接口顯示警告和錯誤給用戶,這個控件在以統一的方式顯示當前窗體或控件的當前狀態給用戶的功能上是很是有用的。
當使用WPF開發時,咱們一直須要以下的窗體:
1,肯定/取消 按鈕的數據窗體
2,肯定/取消/接受按鈕的應用程序設置/選項
3,在一個窗體上的關閉按鈕
建立這些窗體的步驟一直是相同的:
1,先在窗體的底部建立一個WrapPanel
2,一邊又一遍使用相同的RoutedUICommand對象增長按鈕。
DataWindow類使得很是容易的去建立一個基本窗體,簡單的給窗體指定一個模式,經過使用這個窗體,你可以將精力集中在實際業務的實現,而不須要擔憂按鈕自己的實現,這個能夠節省你的實際,在以下的例子中,是在XAML中編寫XMAL到實際的輸出控件上。
PleaseWaitWindow是一個在長時間操做中顯示等待的窗體,這個也有一個PleaseWaitHelper的類讓你更加容易的使用PleaseWaitWindow.
Catel也針對IO提供了不少擴展API,可能你會問爲何,下面就是顯示IO框架
1,支持文件盒文件夾名超過255個字符
2,Combine文件路徑或者URL的,好比
Path.Combine(「C:」, 「Windows」, 「Temp」);.
這些API貼近於System.IO的設計,以便於你很天然地使用它
這個API在Catel.Core程序集中,你可使用在除WPF之外的地方
在內部,Catel使用了不少反射技術來實現功能,這章將解釋這些反射實現的優勢,並非所有替換了.NET自己實現的反射類,Catel反射類是對默認狀況的一個擴展,這個程序及擴展更多的是針對Catel不一樣的擴展框架來作的一個相同的行爲,例如,在WPF中,咱們可以使用當前的AppDomain來載入程序集,然而,在Silverlight中,則須要咱們來查詢當前的Deployment對象來處理,在WP7中,則很難得到載入對象,經過一個獨立的類來實現,經過這種方式對全部的不一樣框架提供統一的接口
這個API在Catel.Core程序集中,你可使用在除WPF之外的地方
Catel使用log4net來做爲日誌處理的基礎,它也提供了額外的功能呢和擴展方法來確保很容易的去記錄異常和通用信息,由於Sliverlight和WP7並無日誌能力(客戶端),ILog實現一個虛擬的接口。
這個API在Catel.Core程序集中,你可使用在除WPF之外的地方
每個框架都被要求有比較好的性能,咱們須要比他們更好,所以,有450個單元測試時用來確保Catel的性能和同步的。這些單元測試在WPF和Sliverlight之間共享,來確保這些行爲時同樣的,經過這種方式,咱們嘗試確保在Silverlight在相同的框架下沒有性能損失。
如你如今所知道的,Catel支持Sliverlight實現,sliverlight是一個很是受歡迎的平臺,可是他缺乏已經在WPF中有效的特定,而後使用Catel的開發者,咱們嘗試在Catel讓他們都起到效果。
咱們也嘗試在Windows Phone 7上實現相同的方式,可是有些不一樣的東西針對WP7開發,這個是因爲不一樣的UI和狀態管理決定的。
In Silverlight, commands are not re-evaluated automatically because there is no CommandManager
that calls theCanExecute
on every routed event. If the user solves an error in the window by, for example, adding a value in an empty field, the CanExecute
state of the OK or Save command is not updated (and still is disabled). Other frameworks require the developer to manually refresh the commands.
Catel offers a clean way of providing the same behavior of WPF. Thanks to the ViewModelBase
propertyInvalidateCommandsOnPropertyChanged
(which is enabled by default for Silverlight and Windows Phone 7), all the ICommand
implementations on the View-Model are automatically re-evaluated for you. This way, the user will immediately see the OK or Save button become enabled after setting the value.
When developing software for Silverlight or Windows Phone 7, you have access to isolated storage.SavableDataObjectBase
supports saving and loading of complex graph objects to isolated storage out of the box. To save an object (including all the child objects), you can use the code below:
using (var isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()) { using (var isolatedStorageFileStream = isolatedStorageFile.OpenFile("UserData.dob", FileMode.Create, FileAccess.ReadWrite)) { myObject.Save(isolatedStorageFileStream); } }
using (var isolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()) { if (isolatedStorageFile.FileExists("UserData.dob")) { using (var isolatedStorageFileStream = isolatedStorageFile.OpenFile("UserData.dob", FileMode.Open, FileAccess.Read)) { return MyObject.Load(isolatedStorageFileStream); } } }
Navigation on Windows Phone 7 is very important. The navigation works the same like the web, thus with request variables. In Catel, it is very easy to navigate to another View (Model) using INavigationService
.
To navigate to another View directly, simply use this code inside your View-Model:
var navigationService = GetService<INavigationService>(); navigationService.Navigate("/UI/Pages/MyPage.xaml");
var parameters = new Dictionary<string, object>(); parameters.Add("MyObjectID", 1); var navigationService = GetService<INavigationService>(); navigationService.Navigate("/UI/Pages/MyPage.xaml", parameters);
The navigation service automatically converts this into the following URL:
/UI/Pages/MyPage.xaml?MyObjectID=1
The Windows Phone 7 device has a great feature: it can retrieve the current geographical location via GPS. However, how can we implement this correctly in MVVM? In Catel, a separate service is available to get the geographical location. The usage is very simple:
var locationService = GetService<ILocationService>(); locationService.LocationChanged += OnCurrentLocationChanged; locationService.Start();
It is very important to stop the service when you no longer need it (think about the battery of the user):
var locationService = GetService<ILocationService>(); locationService.LocationChanged -= OnCurrentLocationChanged; locationService.Stop();
In the OnCurrentLocationChanged
event, you can simply query the location from the EventArgs
:
private void OnCurrentLocationChanged(object sender, LocationChangedEventArgs e) { if (e.Location != null) { MapCenter = new GeoCoordinate(e.Location.Latitude, e.Location.Longitude, e.Location.Altitude); } }
咱們收到了許多用戶的關於MVVM的如何實現一個N層架構的問題,大部分的用戶認爲MVVM來替代業務層,但並非這種狀況,這個章節將解釋如何使用MVVM來寫一個LOB應用程序的可能,這個將是一個簡單或者是複雜的你所想要到東東。
依賴於終端用戶的複雜性的需求,你可能決定在你的應用程序中使用一個多層架構,我並不相信一個單一的標準,可是層的一個實際方案決定了每一個項目不能被超越建立??,可是沒有層將被遺忘。
這章中以圖片的形式顯示N層架構,MVVM如何被使用在架構中,好久以前,N層架構是被以一種關注點分離的方式來開發的,近來,數據訪問層(DAL)自己是一個複雜的東西,如今,有了許多的ORM技術來爲你生成DAL,一些ORM技術,如LLBLGen Pro,容許你在DAL中增長你本身的驗證和DAL與業務功能合起來。
寫兩層架構師很是簡單的,很是簡單的應用程序並不須要任何強類型或者是業務規則,下面是2層架構使用MVVM的展現圖。
正如你所看到的,使用3層架構的是DAL並無參與到MVVM中,業務邏輯層使用DTO對象做爲模型,而且將DTO對象轉換成DAL可以處理的Entties,咱們看到許多用戶從UI層開始引用DAL,可是這個是錯誤的,下面是3層架構中顯示的好的或壞的處理方法。
圖中的箭頭表示使用。
好的解決方案
在好的解決方案中,咱們看到UI層使用BL層,而後BL層使用DAL層,上一次能一直使用一個較低的」down-the-hill」,下面的層應該不要使用上面的層(在很是錯誤的解決方案中有顯示)
由於DAL並無被引用,因此須要建立DTO對象來在DAL和BL直接傳遞數據。DTO在UI層也是有效的。
錯誤的解決方案
錯誤的解決方案顯示,咱們看到開發者直接使用了UI層來訪問了DAL,這種方案中,他們不須要創造Custom DTO對象。
若是你想寫Custom DTO對象,惟一的選擇你須要寫個橫斷關注,能夠被全部的層使用,橫斷層中,你能夠針對全部的DAL的實體編寫接口,而且在UI和BL層中使用它。
如今,大部分的人們使用ORM映射方式,ORM映射可使用生成DAL代碼,你不須要花費時間本身寫,ORM模板是你的選擇,你能夠生成DTO對象。
很是錯誤的解決方案
很是錯誤的解決方案顯示用戶底層使用了上層的方法,這個事很是錯誤的,破壞了整個的關注點分離的原則,若是你認爲這正確地,那麼請你回答,你的BL和UI在WPF中,那麼你如何使用相同的BL到WP7或者是ASP.NET中。
4.3 MVVM如何與RIA服務和Silverlight結合
大部分的開發者發現很難去找到一個好的方式使用MVVM結合到Silverlight和RIA Service上,從過一個架構的觀點來看,RIA服務只是起到DAO的效果,而後Sliverlight中的RIA服務必須使用相同的辦法在DTO中。