从一个登录页面浅淡MVVM(一)
时间:2010-12-25 来源:Sunpire
在这里,我以一个登录页面为例子,和大家分享讨论一下 Silverlight 开发的一些点滴,包括 Validation、MVVM 等。
这个例子采用的是 Silverlight + WCF ,不是 Silverlight + WCF RIA Services,
如果您不熟悉 Silverlight + WCF ,请先学习这方面的内容。
这个例子分成三个阶段,分别是
二、MVVM模式(Model-View-ViewModel模式)
源代码下载 http://files.cnblogs.com/Sunpire/MVVMTutorial.zip
第一阶段:MV模式(Model-View模式)
其实,能使用MV模式,说明已经是入门了 Silverlight 了,有多少从ASP.NET、从Windows Form转过来的朋友,
不懂得 Binding、不懂得 Validation ,苦苦想要从 UI 中去访问数据,去校验数据。
这个例子的 WCF 端很简单,就是校验用户名、密码和验证码
WCF中的User类
namespace MVVMTutorial.Web.Models
{
[DataContract]
public class User
{
[DataMember]
public string UserName { get; set; }
[DataMember]
public string Password { get; set; }
}
}
LoginService.svc
namespace MVVMTutorial.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class LoginService
{
/// <summary>
/// 简单的记录校验码,为简单起见,认为校验码永久有效
/// </summary>
private static List<string> validationCodes = new List<string>();
/// <summary>
/// 生成简单的校验码
/// </summary>
/// <returns></returns>
[OperationContract]
public string GenerateValidationCode()
{
string ticks = DateTime.Now.Ticks.ToString();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int i = ticks.Length - 5; i >= ticks.Length - 8; i--)
{
sb.Append(ticks[i]);
}
if (!validationCodes.Contains(sb.ToString()))
{
validationCodes.Add(sb.ToString());
}
return sb.ToString();
}
/// <summary>
/// 简单登录判断
/// </summary>
/// <param name="usr"></param>
/// <param name="validationCode"></param>
/// <returns>1:用户名密码正确;0:用户名密码不正确;2:校验码无效</returns>
[OperationContract]
public short Login(Models.User usr , string validationCode)
{
if (!validationCodes.Contains(validationCode))
{
return 2;
}
if (usr != null && usr.UserName.ToLower() == "test" && usr.Password == "test")
{
return 1;
}
else
{
return 0;
}
}
}
}
接下来就是 Silverlight 端,生成 LoginService.svc 的代理类
在代理 LoginProxy 的 Reference.cs 中,会生成 User 类的代码,
我会将其 Copy 出来,单独放在一个低层次的类库项目中,如上图的 Models 项目的 User.cs 文件,
为什么要这样做? 这是因为 Validation 的需要。
Validation 需要使用到 System.ComponentModel.DataAnnotations 中的各种 Attribute,如
UserName 的写法是:
User.cs 中的 UserName
[Required]
[StringLength(6, MinimumLength = 3)]
[Display(Name = "用户名", Description = "不区分大小写")]
[RegularExpression("^[a-zA-Z0-9\\-_.]*$", ErrorMessage = "只能输入字母、数字-_.")]
[System.Runtime.Serialization.DataMemberAttribute()]
public string UserName
{
get
{
return this.UserNameField;
}
set
{
if ((object.ReferenceEquals(this.UserNameField, value) != true))
{
this.ValidateProperty("UserName", value);
this.UserNameField = value;
this.RaisePropertyChanged("UserName");
}
}
}
将 User 的 namespace 改为和 WCF 中的 User 的相同,生成 Models 项目,然后再更新 LoginProxy,
这样 Reference.cs 中不再含有对 User 的定义了。
Models 项目中存放的就是各种模型 Model,在实际项目中,绝对大数的 Model 来自于 WCF 中所使用到的 Entity,
在这个例子中,还针对 校验码 增加了一个 ValidationModel ,用于表示校验码模型,这个模型只有一种逻辑“校验码要和
从WCF中取得的校验码相同”。 实际上,在WCF端并没有这个 Model,这里加上这个 Model,也是为了在 UI 应用 Binding
和 Validation。
接下来看 View 部分:
LoginPage.xaml
<sdk:Label Height="28" Margin="8" Width="120" Target="{Binding ElementName=txtUserName}" />
<TextBox Height="23" Margin="8" Width="120" Grid.Column="1" Name="txtUserName"
Text="{Binding UserName,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" />
<sdk:Label Height="28" Margin="8" Width="120" Grid.Row="1" Target="{Binding ElementName=txtPassword}" />
<PasswordBox Height="23" Margin="8" Width="120" Grid.Row="1" Grid.Column="1" Name="txtPassword"
Password="{Binding Password,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" />
<sdk:Label Height="28" Margin="8" Width="120" Grid.Row="2"
Target="{Binding ElementName=txtValidationCode}" Name="lbValidationCode" />
<TextBox Height="23" Margin="8" Width="120" Grid.Row="2" Grid.Column="1" Name="txtValidationCode"
Text="{Binding Code,Mode=TwoWay,NotifyOnValidationError=True,ValidatesOnExceptions=True}" />
<TextBlock Grid.Column="2" Grid.Row="2" Height="23" Margin="8" Name="tbValidationCode"
Text="{Binding GivenCode,Mode=OneWay}" ToolTipService.ToolTip="请按此输入校验码" />
<Button Content="换一个" Grid.Column="3" Grid.Row="2" Height="23" Margin="8"
Name="btnChangeValidationCode" Width="75" Click="btnChangeValidationCode_Click" />
<Button Content="登录" Grid.Row="3" Grid.ColumnSpan="4" Margin="8" Name="btnLogin"
Click="btnLogin_Click" />
<sdk:ValidationSummary Grid.Row="3" Grid.ColumnSpan="4" Margin="8" Name="validationSummary1" />
这里使用了 Binding 和 ValidationSummary,个人认为这是使用 Silverlight 入门的标志:)
下图是运时的效果
接下来是 LoginPage.cs 的全部代码,代码较多,这是因为没有使用 ViewModel 这一层嘛,哈哈。
LoginPage.cs
public partial class LoginPage : Page
{
User usr;
ValidationModel validationModel;
LoginProxy.LoginServiceClient client;
public LoginPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(LoginPage_Loaded);
}
void LoginPage_Loaded(object sender, RoutedEventArgs e)
{
this.usr = new User();
this.validationModel = new ValidationModel();
this.DataContext = this.usr;
this.lbValidationCode.DataContext=this.tbValidationCode.DataContext
= this.txtValidationCode.DataContext = this.validationModel;
if (this.NeedInitializeClient())
{
this.InitializeClient();
}
this.client.GenerateValidationCodeAsync();
}
private void btnChangeValidationCode_Click(object sender, RoutedEventArgs e)
{
if (this.NeedInitializeClient())
{
this.InitializeClient();
}
this.client.GenerateValidationCodeAsync();
}
private void btnLogin_Click(object sender, RoutedEventArgs e)
{
if (this.validationSummary1.HasErrors)
{
this.validationSummary1.Focus();
return;
}
else
{
// 扩展方法,校验各个控件的数据绑定
this.LayoutRoot.Children.ValidateSource();
if (this.validationSummary1.HasErrors)
{
this.validationSummary1.Focus();
return;
}
}
if (this.NeedInitializeClient())
{
this.InitializeClient();
}
this.client.LoginAsync(this.usr , this.validationModel.Code);
}
/// <summary>
/// 检查是否要初始化代理
/// </summary>
/// <returns></returns>
bool NeedInitializeClient()
{
return this.client == null ||
this.client.State == System.ServiceModel.CommunicationState.Closed ||
this.client.State == System.ServiceModel.CommunicationState.Faulted;
}
/// <summary>
/// 初始化代理
/// </summary>
void InitializeClient()
{
// 使用默认的 Binding 和 EndpointAddress 初始化,在实际项目应使用
// new LoginProxy.LoginServiceClient(binding,remoteAddress)
this.client = new LoginProxy.LoginServiceClient();
this.client.GenerateValidationCodeCompleted += (sender, e) =>
{
if (e.Error == null)
{
this.validationModel.GivenCode = e.Result;
}
else
{
// 处理异常
MessageBox.Show(e.Error.Message);
}
};
this.client.LoginCompleted += (sender, e) =>
{
if (e.Error == null)
{
if (e.Result == 1)
{
// 登录成功
MessageBox.Show("登录成功");
}
else if (e.Result == 0)
{
// 用户名或密码错误
MessageBox.Show("用户名或密码错误");
}
else if (e.Result == 4)
{
// 校验码失效
MessageBox.Show("校验码失效");
}
}
else
{
// 处理异常
MessageBox.Show(e.Error.Message);
}
};
}
}
至此,这个例子的第一阶段完成了,这是一个完整的例子,
LogingPage.cs 中的代码也是相当清晰的了,
可是 LogingPage.cs 中必竟还是混杂了一些控制逻辑,这也导致了要进一步分离这些代码的需要,
这便是 ViewModel 层次。
二、MVVM模式(Model-View-ViewModel模式)
WCF中的User类










