• [Translation]Silverlight 4MVVM with Commanding and WCF RIA Services Virus


    原文地址:Silverlight 4 - MVVM with Commanding and WCF RIA Services

      在我的前一篇文章 “WCF RIA Services and a guide to use DTO/”Presentation Model””,我提到要写一篇文章来讲述ViewModel。在这篇文章中,我将向你展示如何利用Silverlight 4 Commanding 和ViewModel来实现MVVM模式。在文章中我将使用Unity作为依赖注入框架将我的Repository注入DomainService,为了达到目的,我们需要创建自己的DomainServiceFactory,在我的“WCF RIA Services Unity DomainServiceFactory” 中你将会学到如何使用Unity和依赖注入,在本文章中就不再多说。

      架构和设计

      下图显示的是我经常在建立RIA应用的时候使用的架构和设计。

      

      本文的服务层使用WCF RIA Service的DomainService,Domain Model将会非常简单,只是一个Customer实体和一个CustomerRepository。下图是Customer的类图

      

      下面是CustomerRepository的接口代码

      

    public interface ICustomerRepository
      {
         Customer GetCustomerByID(
    int customerID);

         
    void Update(Customer customer);
      }
      

      注意:本文的重点不是如何实现ICustomerRepository接口,这里假设使用Entity Framework,Linq to SQL或者NHibernate、ADO.NET都可以。这些不是本文的重点。

      下面的代码是使用了WCF RIA Services的DomainService,ICustomerRepository应该被注入。

      

    代码
     [EnableClientAccess()]
     
    public class CustomerService : DomainService
     {
          
    private ICustomerRepository _customerRepository;

          
    public CustomerService(ICustomerRepository customerRepository)
          {
              _customerRepository 
    = customerRepository;
          }

          
    public CustomerDto GetCustomerByID(int customerID)
          {
              var customer 
    = _customerRepository.GetCustomerByID(customerID);

              
    return MapCustomerToCustomerDto(customer);
          }

          
    public void UpdateCustomer(CustomerDto customer)
          {
              
    if (customerDto == null)
                  
    throw new ArgumentNullException("customer");

              _customerRepository.Update(MapCustomerDtoToCustomer(customerDto));
          }
      }
      

      你可能已经发现了,在domain 实体Customer和CustomerDto(Data Transfer Object)之间有一个映射。如果我们使用过WCF,我们就会将对象实体映射为数据契约。在这里使用了类似AutoMapper 的框架来帮助我们代替手工映射。在内网应用中,网速良好,没有太复杂的业务逻辑的情况下,我们可以在不进行映射的情况下直接使用Customer实体。但是这些都依赖于我们构建的应用,在我们作出正确决定之前,已经有很多因素被包含进来。因此,本文也不是银弹。下面是CustomerDto的代码,在里面使用了一些验证规则,如果使用ViewModel,这些代码应该放入ViewModel中。

      

    代码
     public class CustomerDto
     {
         [Key]
         
    public int CustomerID { getset; }

         [Required]
         [StringLength(
    32, MinimumLength=2)]
         
    public string FirstName { getset; }

         [Required]
         [StringLength(
    32, MinimumLength = 2)]
         
    public string LastName { getset; }

         
    public int Age { getset; }
    }

       注意:我将会使用自己的DomainServiceFactory在创建CustomerService的时候注入ICustomerRepository,可以通过阅读here. 来指导更多相关知识。

      

      现在服务层已经完成,继续来实现client端,将会使用ViewModel和Silverlight4的Commanding。

      ViewModel

      一个ViewModel就是View的一个代理类。想像一下电影《矩阵》,我们只看见屏幕上面的一些字符,通过这些字符我们看见了世界。可以将ViewModel想象成字符,通过他们我们可以看见View。使用ViewModel的原因是分离关注,我们不想在View中看见逻辑代码。使用这种分离,即使UI还没有摆放好的时候,我们也可以通过自动化测试来测试View。

      下面是一段ViewModel代码,使用了Commanding

      

    代码
     public class CustomerViewModel : ViewModel
        {
            
    public CustomerViewModel()
            {
            }

            
    public string FirstName
            {
                
    get;
                
    set;
            }

            
    public string LastName
            {
                
    get;
                
    set;
            }

            
    public bool IsAdult
            {
                
    get;
            }       
         public bool IsLoading        
        {          
           get;           
            internal set;    
          }        
         public ICommand Save
            {
                
    get;
            }
        }
      

      ViewModel代表了一个应该显示两个输入框的View,一个是firstname,一个是lastname,和一个代表Customer是否audit的只读checkbox。View还应该由一个Save按钮。IsLoading用来显示或者隐藏进度条。这里View将会使用ViewModel

      

      下面是View的xaml代码,不需要后台代码,只需要将ViewModel作为资源加入,将数据绑定到ViewModel。

      

    代码
    <UserControl
    xmlns:my="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"    x:Class="SilverlightApplication1.MainPage"
        xmlns
    ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x
    ="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d
    ="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc
    ="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:vm
    ="clr-namespace:SilverlightApplication1.ViewModels"
        mc:Ignorable
    ="d"
        d:DesignHeight
    ="300" d:DesignWidth="400">

        
    <UserControl.Resources>
            
    <vm:CustomerViewModel x:Name="customerViewModel"/>
        
    </UserControl.Resources>

        
    <Grid x:Name="LayoutRoot" DataContext="{StaticResource customerViewModel}">

            
    <my:BusyIndicator IsBusy="{Binding IsLoading}"></my:BusyIndicator>

            
    <TextBox Text="{Binding FirstName, Mode=TwoWay, NotifyOnValidationError=True}"  ... />
            
    <TextBox Text="{Binding LastNaem, Mode=TwoWay, NotifyOnValidationError=True}" ... />

            
    <TextBlock Text="First Name:" .../>
            
    <TextBlock Text="Last Name:" .../>

            
    <CheckBox IsChecked="{Binding IsAdult}" Content="Is Adult" .../>
        
            
    <Button Command="{Binding Save}" Content="Save" ... />
        
        
    </Grid>
    </UserControl>
        

      ViewModel的实现代码

      你会发现CustomerViewModel继承自ViewModel,ViewModel实现乐INotifyPropertyChanged和INotifyDataErrorInfo接口。你可以在ViewModel on my other post about INotifyDataErrorInfo class. 中找到具体的实现。在CustomerViewModel的构造函数中调用WCF RIA Services来获取一个Customer,因为操作是异步的,我们不知道多长时间可以有结果,因此将IsLoading属性设置为true显示正在加载,下面是实现的代码。

      

    代码
    public class CustomerViewModel : ViewModel, ISaveableViewModel
      {
            CustomerContext _customerContext 
    = new CustomerContext();
            CustomerDto _customerDto 
    = new CustomerDto();

            
    bool _isLoading = false;

            
    public CustomerViewModel()
            {
                
    this.IsLoading = true;

                _customerContext.Load
    <CustomerDto>(                               _customerContext.GetCustomerByIDQuery(10),
                                   loadOperation 
    =>
                                   {
                                     _customerDto 
    = loadOperation.Entities.SingleOrDefault();
                                     LoadingCompleted();
                                   }, 
    null);
            }


            
    public string FirstName
            {
                
    get { return _customerDto.FirstName; }
                
    set
                {
                    
    if (_customerDto.FirstName != value)
                    {
                        _customerDto.FirstName 
    = value;
                        NotifyPropertyChanged(
    "FirstName");
                    }
                }
            }


            
    public string LastName
            {
                
    get { return _customerDto.LastName; }
                
    set
                {
                    
    if (_customerDto.LastName != value)
                    {
                        _customerDto.LastName 
    = value;
                        NotifyPropertyChanged(
    "LastName");
                    }
                }
            }


            
    public bool IsLoading
            {
                
    get { return _isLoading; }
                
    internal set            {               _isLoading = value;               NotifyPropertyChanged("IsLoading");            }
            }

            
            
    public bool IsAdult
            {
                
    get { return _customerDto.Age >= 18; }
            }


            
    public ICommand Save
            {
                
    get { return new SaveCommand(this); }
            }


            
    internal void SaveCustomer()
            {
                _customerContext.SubmitChanges();
            }           
    private void LoadingCompleted()        {            NotifyPropertyChanged("FirstName");            NotifyPropertyChanged("LastName");            NotifyPropertyChanged("IsAdult");            this.IsLoading = false;        }
     }
      

      看起来可能有点奇怪,一个空的CustomerDto被分配给_CustoemrDto字段。CustoemrViewModel的其他属性将会访问这个字段和他的属性,为了避免在get和set中添加一大丢检查是否null的代码,使用了一个默认的CustomerDto。因此,虽然有点丑陋,但是在每个属性中避免了一大丢代码。在构造函数中将会加载一个Customer。

      

    代码
     public CustomerViewModel()
    {
       
    this.IsLoading = true;

       _customerContext.Load
    <CustomerDto>(
                 _customerContext.GetCustomerByIDQuery(
    10),
                 loadOperation 
    =>
                 {
                      _customerDto 
    = loadOperation.Entities.SingleOrDefault();
                      LoadingCompleted();
                 }, 
    null);
    }

      IsLoading属性设置为true,可以保证加载数据的时候显示进度条。当Customer对象加载完毕,_CustomerDto会设置为加载的customer对象,将会调用LoadingCompleted方法。方法将会保证隐藏进度条,通知View变化,因此控件绑定将会调用绑定属性的get方法显示customer的内容。

      Commanding  

      你可以从“Silverlight 4 Commanding enables ViewModels”. 中阅读一些关于Commanding的知识。在ViewModel中有一个方法SaveRule,是CustomerViewModel实现了ISaveableViewModel 接口。CustomerViewModel中的Save属性可以返回一个SaveCommand。

      



      
    public ICommand Save
    {
        
    get { return new SaveCommand(this); }
    }

    internal void SaveRule()
    {
         _customerContext.SubmitChanges();
    }

      Save属性绑定了View的Save按钮的command属性,下面就是Save属性返回的SaveCommand实现代码

      

    代码
    public class SaveCommand : ICommand
    {
        
    private ISaveableViewModel _view;

        
    public SaveCommand(ISaveableViewModel view)
        {
            _view 
    = view;
        }

        
    public bool CanExecute(object parameter)
        {
           
    return true;
        }

        
    public event EventHandler CanExecuteChanged;

        
    public void Execute(object parameter)
        {
            _view.SaveRule();
         }
    }

      在SaveCommand中使用了一个ISaveableViewModel接口。保证加入ViewModel的Command会被执行,Command可以被其他View重用。

      总结:

      本文向你展示了如何使用WCF RIA Services,Model View View Model模式以及Commanding。代码可能不是最好的,没有加入更多的错误验证。

      

  • 相关阅读:
    Codeforces 451A Game With Sticks
    POJ 3624 Charm Bracelet
    POJ 2127 Greatest Common Increasing Subsequence
    POJ 1458 Common Subsequence
    HDU 1087 Super Jumping! Jumping! Jumping!
    HDU 1698
    HDU 1754
    POJ 1724
    POJ 1201
    CSUOJ 1256
  • 原文地址:https://www.cnblogs.com/virusswb/p/1708656.html
Copyright © 2020-2023  润新知