这篇文章主要是为了说明,咱们为何要使用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中。