7客户端视图层(V)
最后更新于:2022-04-01 16:24:21
1)视图层后台代码,很简洁,主要是处理视图模型的创建,当然,如果你利用一些技术,这里是可以不需要代码的。不过我的观点是任何事情都不要搞成洁癖,如果页面的所有控制都放在视图模型层,反而会使得视图模型层最后就变成了视图的后台代码,那么这个MVVM模式就失去了意义。
~~~
using MEntities;
using RIAServices.Web;
using System.Threading;
using System.Dynamic;
using System.Windows.Data;
using MAppStructure.ViewModel;
namespace MAppStructure
{
public partial class MainPage : UserControl
{
public DynamicDataViewModel ViewModel
{
get {
return this.DataContext as DynamicDataViewModel;
}
set{
this.DataContext = value;
}
}
public MainPage()
{
InitializeComponent();
//也可以通过其它方式进行构建。在简单应用中,这个文件就基本不用自己写代码了。
this.ViewModel = new DynamicDataViewModel();
}
~~~
2)页面绑定.
~~~
<sdk:DataGrid x:Name="CustomGrid" ItemsSource="{Binding Path=DataTable.Rows}" Grid.Row="1" AutoGenerateColumns="False" >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn CanUserReorder="True" Binding="{Binding Path=[EmployeeID].Value,Mode=OneWay}" CanUserResize="True" CanUserSort="True" Width="Auto" />
<sdk:DataGridTextColumn CanUserReorder="True" Binding="{Binding Path=[EmployeeName].Value,Mode=OneWay}" CanUserResize="True" CanUserSort="True" Width="Auto" />
<sdk:DataGridTextColumn CanUserReorder="True" Binding="{Binding Path=[EmployeeDesc].Value,Mode=OneWay}" CanUserResize="True" CanUserSort="True" Width="Auto" />
<sdk:DataGridTextColumn CanUserReorder="True" Binding="{Binding Path=[EmployeeAge].Value,Mode=OneWay}" CanUserResize="True" CanUserSort="True" Width="Auto" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
~~~
注意上面的绑定方式和路径语法。
~~~
<Button Content="查询" Command="{Binding Path=Commands[Button1Command]}" CommandParameter="{Binding ElementName=CustomGrid,Path=SelectedItem}" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="344,160,0,0" Name="Search" VerticalAlignment="Center" Width="75"/>
~~~
这中命令处理模式,稍微改进一下,就可以做成自动动态绑定,比一个个定义命令的方式要好很多,有利于维护的集中处理。
到此为止,这个系列就完成了,上面代码都是经过测试的,实际运行的。只是如果你的数据量比较大的话,需要修改服务端Web.Config的配置,增大可序列化对象的最大数。相关问题处理可上网搜,很多的。另外消息机制也可以采用,这个可以利用微软的轻量级框架去做,但切勿烂用。
PS:希望大家多提意见,如有更好的方法希望能不吝赐教。
6客户端视图模型层(VM)
最后更新于:2022-04-01 16:24:19
3)视图模型层DynamicDataViewModel .cs
~~~
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using MAppStructure.Datasource;
using System.ComponentModel;
using System.Collections.Generic;
using MEntities;
namespace MAppStructure.ViewModel
{
/// <summary>
/// 视图模型层,注意必须实现INotifyPropertyChanged接口,这样VM层才能绑定。
/// ViewModel可以做一个基类,这个基类主要是实现INotifyPropertyChanged接口
/// 和你需要的一些公共方法,这里为了简单些,就没有做基类了.
/// </summary>
public class DynamicDataViewModel : INotifyPropertyChanged
{
/// <summary>
/// 视图模型层主要采用的模型层服务实例,注意
/// 可以调用多个模型层得服务实例,这里是演示,所以只做了一个。
/// </summary>
private DynamicDataSource theS;
public DynamicDataViewModel()
{
theS = new DynamicDataSource();
//初始化并注册命令.
Commands = new Dictionary<string, MyCommand>();
Commands.Add("Button1Command", new MyCommand(OnButtonCommand));
}
private void OnButtonCommand(object parameter)
{
LoadData();
MessageBox.Show("ok");
}
/// <summary>
///命令字典集合,这样做的好处一是可以减少定义命令的硬代码,同时提供了一种动
///态命令的可能性,并有利于扩展.
/// </summary>
public Dictionary<string, MyCommand> Commands
{
get;
private set;
}
private List<DynamicDataRow> _DataSource;
public List<DynamicDataRow> DataSource
{
get
{
return _DataSource;
}
private set
{
_DataSource = value;
RaisePropertyChanged("DataSource");
}
}
private DynamicDataTable _DataTable;
/// <summary>
/// 获取来的动态数据表.
/// </summary>
public DynamicDataTable DataTable
{
get
{
return _DataTable;
}
private set
{
_DataTable = value;
RaisePropertyChanged("DataTable");
}
}
/// <summary>
/// 数据加载.
/// </summary>
private void LoadData()
{
theS.GetDynamicDataTable("select * from EmployeeInfo ", op =>
{
if (op.HasError == false)
{
DataSource = op.Value.Rows;
DataTable = op.Value;
}
else
{
MessageBox.Show(op.ErrorMsg);
}
}, null);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
/// <summary>
/// 自己的命令类,主要为了命令的绑定.这里是典型的命令模式,命令的接收者是本VM.不过这里的
/// 命令接收者并没有作为命令的成员,而是采用委托方式,在这种情况下更为便利。
/// </summary>
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
private Action<object> _action;
public MyCommand(Action<object> Action)
{
this._action = Action;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (_action != null)
{
_action(parameter);
}
}
}
}
~~~
这里大家注意的是,我的命令采用的不是一般的定义方式,而是采用字典集合的方式进行,好处上面讲了,请大家注意页面的绑定方式。
5客户端模型层(M)
最后更新于:2022-04-01 16:24:16
1)服务代理层
这里的代码都是系统自动产生的,主要负责代理服务端得方法执行,没什么特别可说的.
2)客户端模型层DynamicDataSource.cs
~~~
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using RIAServices.Web;
using MEntities;
using System.ServiceModel.DomainServices.Client;
namespace MAppStructure.Datasource
{
/// <summary>
/// 规范化自己的调用参数,目的是将InvokeOperation调用参数转换为
/// 自己易于处理的参数形式
/// </summary>
/// <typeparam name="T"></typeparam>
public class InvokeEventArgs<T>
{
public T Value { get; set; }
public string ErrorMsg { get; set; }
public object UserState { get; set; }
public bool HasError { get; set; }
}
/// <summary>
/// 负责与服务代理层通信,负责调用代理层方法,并将参数转换为客户
/// 端易于理解和处理的形式
/// </summary>
public class DynamicDataSource
{
public void GetDynamicDataTable(string strSQL, Action<InvokeEventArgs<DynamicDataTable>> CallBack, object UserState)
{
DynamicDataContext theContext = new DynamicDataContext();
theContext.GetDynamicTable(strSQL, op => {
CallBack(CreateEventArgs<DynamicDataTable>(op));
},UserState);
}
//这个函数的目的可以将服务调用的错误集中在此进行处理,或者将错误信息转换成客户端比较容易处理的形式。
private static InvokeEventArgs<T> CreateEventArgs<T>(InvokeOperation<T> InvokeOp)
{
InvokeEventArgs<T> theArgs = new InvokeEventArgs<T>();
if (InvokeOp.HasError == true)
{
//将错误标志为已处理.
InvokeOp.MarkErrorAsHandled();
theArgs.HasError = true;
//这里的提示方式可以根据自己的需要进行处理
theArgs.ErrorMsg = InvokeOp.Error.InnerException.Message;
}
else
{
theArgs.HasError = false;
theArgs.Value = InvokeOp.Value;
}
theArgs.UserState = InvokeOp.UserState;
return theArgs;
}
}
}
~~~
这一层的目的就是进行数据及服务的预处理,客户端得缓存也需放在这一层。因为VM层是针对V的,一般情况下很难共用,而M层中的很多服务都可以出现在不同的VM 中,这也是为什么要单独在服务代理层上再加一层的原因,而且这一层可以建立基类,便于控制和扩展。服务代理层是没办法做基类的,因为代码都是动态生成的。
4业务逻辑与服务层
最后更新于:2022-04-01 16:24:14
1)业务逻辑层:DynamicDataBusi.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using MEntities;
using System.Data.SqlClient;
namespace BBusiness
{
public class DynamicDataBusi
{
public DynamicDataTable GetDynamicDataTable(string strSQL, string ConnStr)
{
SqlConnection theConn = new SqlConnection(ConnStr);
DataTable theTable = new HDatabase.DynamicDataAccess().GetDataTable(strSQL, theConn);
DynamicDataTable theDynamicTable = new DynamicDataTable();
if (theTable != null)
{
foreach (DataColumn col in theTable.Columns)
{
DynamicDataColumn theCol = new DynamicDataColumn();
theCol.Caption = col.Caption;
theCol.DataType = col.DataType.Name;
theCol.FieldName = col.ColumnName;
theCol.Length = col.MaxLength;
theCol.FormatString = "";
theDynamicTable.Columns.Add(theCol);
}
foreach (DataRow row in theTable.Rows)
{
DynamicDataRow theRow = new DynamicDataRow();
for (int i = 0; i < theTable.Columns.Count; i++)
{
DynamicDataField theDataField = new DynamicDataField();
theDataField.FieldName = theTable.Columns[i].ColumnName;
theDataField.DataType = theTable.Columns[i].DataType.Name;
theDataField.Value = row[i];
theRow.DataFields.Add(theDataField);
}
theDynamicTable.Rows.Add(theRow);
}
}
return theDynamicTable;
}
}
}
~~~
所有要提供给客户端得实体的打包,以及服务端得实体缓存之类的都可以封装到这一层。业务逻辑层另外的最主要的功能就是业务逻辑的处理了,简单的新增,修改,删除和查询都可在这里封装,有的虽然只是简单的调用数据访问层,但也不要让服务层直接调用。因为在这一层可以增加很多功能,比如冲突检测,逻辑检查等。
2)RIA 服务层:DynamicDataService
~~~
namespace RIAServices.Web
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using MEntities;
using BBusiness;
// TODO: 创建包含应用程序逻辑的方法。
[EnableClientAccess()]
public class DynamicDataService : DomainService
{
static string conn = "Data Source=127.0.0.1;Initial Catalog=DEVTEST;Persist Security Info=True;User ID=sa;Password=tian777888";
[Invoke]
public DynamicDataTable GetDynamicTable(string strSQL)
{
//在这里检查调用是否合法
return new DynamicDataBusi().GetDynamicDataTable(strSQL, conn);
}
}
}
~~~
大家要注意,我的数据库连接出现在这一层,纯粹是巧合,数据库连接应该放到数据访问层或者配置文件里,如果是比较复杂的应用,比如SaaS,还并需用单独的类进行管理。
另外注意,这里我没有直接将服务层放在承载silverlight客户端得webapp上,而是建立的RIA服务类库。
到这里,服务端的实现就完成了,编译后,客户端就可以看到我们的实体,并可调用服务方法。
后面,我们继续建立客户端的应用。
友情提示:以上代码经过实测,绝对可以OK的。另外注意你们的WCF RIA Services 至少要到SP1,否则会有编译错误.
3数据库访问层和数据访问层
最后更新于:2022-04-01 16:24:12
这两层实际上就是大多数实体框架所处的层次,在这两个层次方面,大家可以参考动软的方式,当然,也可以自己构建,也可以利用现有的成熟的实体框架。但对于大型项目或者产品型项目,最好还是不要使用那些复杂的实体框架,因为更新,维护,升级都不太可控,而且很多时候都会有一些限制,不太利于构建高效动态的业务应用(再怎么强大,还是没有直接用SQL语句与数据库打交道强大,而且使用框架时,如果利用了缓存,那么存储过程使用,其它SQL语句的使用,数据的同步都是个大问题)
下面的代码是这两层的一个示例:
1)数据库访问层:
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
namespace DBHelper
{
public class DbHelper
{
/// <summary>
/// 执行查询语句,返回DataSet
/// </summary>
/// <param name="SQLString">查询语句</param>
/// <returns>DataSet</returns>
public DataSet QueryData(string SQLString, DbConnection conn, DbTransaction Trans)
{
SqlConnection connection = conn as SqlConnection;
connection.Open();
try
{
DataSet ds = new DataSet();
SqlDataAdapter command = new SqlDataAdapter(SQLString, connection);
command.Fill(ds, "ds");
command.Dispose();
return ds;
}
catch
{
return null;
}
}
}
}
~~~
如果需要支持多种数据库,只要在这里建立一个抽象层,然后根据用户的数据库类型动态的决定采用相应的数据库访问层实例即可。
2)数据访问层:
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using DBHelper;
using System.Data.Common;
namespace HDatabase
{
public class DynamicDataAccess
{
public DataTable GetDataTable(string strSQL, DbConnection Conn)
{
DataSet theDS = new DbHelper().QueryData(strSQL, Conn, null);
if (theDS != null && theDS.Tables.Count > 0)
{
return theDS.Tables[0];
}
return null;
}
}
}
~~~
这里可以增加简单的数据访问方法,比如新增,修改,删除等。如果要处理事务的话,在参数中增加一个数据库事务参数,有业务逻辑去决定事务,而不要在底层做。另外要注意的是如果业务逻辑层调用数据访问层方法没有提供数据库连接或者事务的时候,应采用默认的配置创建数据库连接,并不做事务处理。因为毕竟一个系统绝大部分的应用都不会需要时时变更数据库连接。记住:我们之所以在数据访问方法中提供连接与事务参数的目的是为业务逻辑层提供一种可以自行采用的数据连接和事务的手段。
2实体的组织(续)
最后更新于:2022-04-01 16:24:10
3)数据行DynamicDataRow.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
[Serializable]
public partial class DynamicDataRow
{
public List<DynamicDataField> DataFields { get; private set; }
public DynamicDataRow()
{
DataFields = new List<DynamicDataField>();
}
}
}
~~~
共享代码部分:DynamicDataRow.Shared.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
public partial class DynamicDataRow
{
public DynamicDataField this[string FieldName]
{
get
{
DynamicDataField theField = null;
foreach (var fld in DataFields)
{
if (fld.FieldName == FieldName)
{
theField = fld;
break;
}
}
return theField;
}
}
public DynamicDataField this[int Index]
{
get
{
return DataFields[Index];
}
}
}
}
~~~
4)数据表:
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
[Serializable]
public partial class DynamicDataTable
{
public List<DynamicDataRow> Rows { get; private set; }
public string TableName { get; set; }
public List<DynamicDataColumn> Columns { get; private set; }
public DynamicDataTable()
{
Rows = new List<DynamicDataRow>();
Columns = new List<DynamicDataColumn>();
}
}
}
~~~
数据表共享代码部分:DynamicDataTable.Shared.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
public partial class DynamicDataTable
{
public DynamicDataColumn this[string FieldName]
{
get
{
DynamicDataColumn theCol = null;
foreach (var col in Columns)
{
if (col.FieldName == FieldName)
{
theCol = col;
break;
}
}
return theCol;
}
}
public DynamicDataColumn this[int Index]
{
get
{
return Columns[Index];
}
}
}
}
~~~
实体的组织原则:
A)尽可能简单,外部程序集依赖应尽可能少,这样任何其它层都可以引用它,也便于穿越通信层,毕竟实体只是数据的载体;
B)索引器无法自动到达客户端,索引器构建主要是为了客户端绑定的时候提供一致的语法和语义;
C)如果实体有继承体系,那么索引器可能无法共享到客户端,这个时候可以直接把代码添加到客户端中即可,注意命名空间要保持与服务器一致。
2实体的组织
最后更新于:2022-04-01 16:24:07
在本篇中,我们不仅演示实体的结构,而且我们利用集合属性来打造万能实体(类似于DataTable)。 下面是代码:
1)首先我们定义Column,主要提供字段列信息:DynamicDataColumn.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
[Serializable]
public partial class DynamicDataField
{
public string FieldName { get; set; }
public string StrValue { get; set; }
public DateTime DTValue { get; set; }
public Byte[] ByteArrayValue { get; set; }
public string DataType { get; set; }
}
}
~~~
2)定义数据字段,用于字段数据的存储,如果数据类型都可以整成字符串的话,其实可以简化这块,而是参照我前面的文章中提到的,利用字典集合来做,但这里提供的方式会更专业一些:DynamicDataField.cs
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
[Serializable]
public partial class DynamicDataField
{
public string FieldName { get; set; }
public string StrValue { get; set; }
public DateTime DTValue { get; set; }
public Byte[] ByteArrayValue { get; set; }
public string DataType { get; set; }
}
}
~~~
DynamicDataField.Shared.cs:因为有些辅助方法无法自动生成,可通过代码共享来达到索引器穿越的目的
~~~
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MEntities
{
public partial class DynamicDataField
{
public object Value
{
get
{
if (this.DataType == "datatime")
{
return this.DTValue;
}
if (this.DataType == "byte[]")
{
return this.ByteArrayValue;
}
return this.StrValue;
}
set
{
if (this.DataType == "datatime")
{
DTValue = (DateTime) value;
}
if (this.DataType == "byte[]")
{
this.ByteArrayValue =(byte[])value;
}
this.StrValue = value.ToString();
}
}
}
}
~~~
因为数据类型穿越没什么实际意义,所以这里用字符串来表达数据类型,而没有用Type类型。因为数据类型可以是数据库的数据类型,用字符串表达更为自由。
待续.....
1整体架构
最后更新于:2022-04-01 16:24:05
在这个系列里我们将学习一般业务系统的整个过程,涉及到从数据库一直到silverlight页面的各个方面。示例中遵循我一贯的风格,不采用任何第三方框架。但为了简单起见,这里不考虑多种数据库支持(其实多种数据库支持在可以利用存储过程的情况下,非常简单,封装一个数据库访问层即可),同时为了减轻贴图的压力,我们假设各位对于基本的silverlight的程序创建没有任何问题。下面是整个程序的大致框架:
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-08-02_57a041c733b2f.gif)
1)数据库:示例中采用sqlserver.
2)应用服务层:分为4.5层,其中实体层是大部分层次都需共享的,因此力求简单,干净(除了必要的属性外,不要给实体任何方法),其它4层一次是:
A)数据库访问层:封这一层的目的一是为了简化数据访问层访问数据库的接口,二是为了支持多种不同类型的数据库;
B)数据访问层:这层主要是提供各种业务数据的访问包括查询,修改,删除,新增等。这一层的目的是为了使得业务逻辑层对数据的访问数据库无关(是不是数据库,何种数据库都可无关),并使得业务层在业务逻辑处理过程中可以采用搭积木的方式进行,有利于复杂业务逻辑的处理和复用。
C)业务逻辑层:负责处理业务逻辑,同时RIA服务层数据需要规整也在此进行封装,如果业务逻辑同时涉及到多个数据,也可以在这里通过创建不同的数据访问实例来实现。
D) WCF RIA 服务层:主要负责对Silverlight客户端提供服务,同时检查客户端与服务端调用的安全,注意这一层除了安全检查外,不进行任何业务逻辑处理,而是直接调用业务逻辑的相应方法。
以上的分层好处是:业务逻辑可以除了提供给RIA服务层外,其实对于Aspnet,webservice,Winform等都是适用的,可以最大限度的复用业务逻辑。
3)SilverLight客户端:分4层,依次是实体与服务代理层,模型层,视图模型层,视图层。
A)实体与服务代理层:这层包括自动生成的实体和服务代理,主要负责与服务端得远程服务调用。
B)模型层(M):负责处理数据缓存,负责处理调用服务代理层的服务。增加这一层的目的就是要使得对于VM层而言,数据是来自本地还是网络服务不再关心,毕竟silverlight属于富客服端技术体系;
C)视图模型层(VM):负责视图的模型数据组织,并提供一定的对视图的控制功能。
D)视图层(V):与客户交互,并将交互情况及时反馈给VM层。
前言
最后更新于:2022-04-01 16:24:03
> 原文出处:[Silverlight实战示例](http://blog.csdn.net/column/details/silverlight.html)
作者:[田世权](http://blog.csdn.net/hawksoft)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# Silverlight实战示例
> 在这个系列里我们将学习一般业务系统的整个过程,涉及到从数据库一直到silverlight页面的各个方面。示例中遵循我一贯的风格,不采用任何第三方框架。