1 DTO、Form的划分
开讲之前先看如下图示:
其中,Dao层与EF框架、Service与Dao层之间的传输都是以Model、SQL(仅在分页操作)传递,Action与Service是以DTO和ISQLPaging接口传递的,而View(本项目是ExtJS)与Action之间传递有两种情况,一是Action传递给ExtJS,另一种是ExtJS请求数据给Action。
先说前一种情况,由于ExtJS一般是接受JSON或XML数据,所以在Action中就不能使用ViewData、ViewBag或TempData简单的传递了,因为ExtJS得不到,而要在Action中使用JSON方法,把一个class转换成JSON(或XML)对象返回给客户端。因为,Service传递给Action的是Model、IList<Model>对象,所以就需要把Model转换为JSON,我的办法是先把Model转移成DTO,然后用Json(DTO)方法返回JSON对象。
再来看后一种情况,这个比较好实现,为Action指定参数或实体类,MVC会自动绑定。在这里,我为Action指定Form类型参数,这样我可以在Form中编写验证方法,比如验证用户请求的数据是否合法。在领域模型设计中,DTO是不允许这么做的,DTO只允许包含属性,属于贫血。这也是我把Form从DTO中脱离出来的原因,如果把项目分的太细就显得太复杂了,所以,在企业级的开发中,请不要这么设计。另外,ASP.NET MVC中可以通过特性来验证Model,之前我在项目中也曾这么做过,但也遇到一些问题,比如:用户在注册和修改资料时,Model的所有属性并不是都输入的,当时的做法是创建两个Model,一个是UserRegister,一个是UserUpdateData,显示不合理。
看下最后的设计:
此时,就遇到了class之间的映射(转换)问题,在java开发中有BeanUtils.copyProperties和PropertyUtils.copyProperties方法实现对象之间的属性复制,但在.net类库中是没有相似的类的,但庆幸的是,已经有第三方公司实现了此功能 -- AutoMapper。
2 AutoMapper
网上已有不少文章说了AutoMapper的使用,这里我也提下几种常用的情况。
在使用前先到官方网站下载最新版(http://automapper.codeplex.com/releases),并引入到项目中(本项目中需要引入的是Web和Service两个)
2.1 简单映射
public class UploadDTO
{
public string Code { get; set; }
public string Name { get; set; }
public short IsRequired { get; set; }
public short FileType { get; set; }
public short IsSave { get; set; }
public string Extension { get; set; }
public string SavePath { get; set; }
public long MaxSize { get; set; }
public string ParentPattern { get; set; }
}
public partial class Upload
{
public string Code { get; set; }
public string Name { get; set; }
public short IsRequired { get; set; }
public short FileType { get; set; }
public short IsSave { get; set; }
public string Extension { get; set; }
public string SavePath { get; set; }
public long MaxSize { get; set; }
public string ParentPattern { get; set; }
}
Mapper.CreateMap<UploadDTO, Upload>();
var dto = new UploadDTO();
dto.Code = '10';
dto.Name = 'test';
var entity = Mapper.Map<UploadDTO, Upload>(dto);
这么看简单吧,就是把AddressDto实例所有属性复制到Address实体类中。
2.2 属性是class的映射
public class AdminDTO
{
public string Code { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public DateTime AddTime { get; set; }
public DateTime? LastTime { get; set; }
public string LastIP { get; set; }
public short ErrorCount { get; set; }
public short State { get; set; }
public string RoleCode { get; set; }
public string RoleName { get; set; }
}
public partial class Admin
{
public string Code { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public System.DateTime AddTime { get; set; }
public Nullable<System.DateTime> LastTime { get; set; }
public string LastIP { get; set; }
public short ErrorCount { get; set; }
public short State { get; set; }
public virtual Role Role { get; set; }
}
var map = Mapper.CreateMap<Admin, AdminDTO>();
map.ForMember(e => e.RoleCode, m => m.MapFrom(f => f.Role.Code));
map.ForMember(e => e.RoleName, m => m.MapFrom(f => f.Role.Name));
Admin entity = null;//假设这里不是null,实际是个Model实例
var dto = Mapper.Map<Admin, AdminDTO>(entity);
其他的情况请查询相关的网站,接下来看下DTO、Form中的定义。
3 DTO
为了方便,我把暂时用到的DTO放在一起:
public class UploadDTO
{
public string Code { get; set; }
public string Name { get; set; }
public short IsRequired { get; set; }
public short FileType { get; set; }
public short IsSave { get; set; }
public string Extension { get; set; }
public string SavePath { get; set; }
public long MaxSize { get; set; }
public string ParentPattern { get; set; }
}
public class RoleDTO
{
public string Code { get; set; }
public string Name { get; set; }
public short State { get; set; }
public short Sort { get; set; }
public IList<CategoryDTO> Categories { get; set; }
}
public class AdminDTO
{
public string Code { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public DateTime AddTime { get; set; }
public DateTime? LastTime { get; set; }
public string LastIP { get; set; }
public short ErrorCount { get; set; }
public short State { get; set; }
public string RoleCode { get; set; }
public string RoleName { get; set; }
}
public class AuthorizeDTO
{
public string RoleCode { get; set; }
public IList<string> CategoryCodes { get; set; }
}
public class CategoryDTO
{
public string Code { get; set; }
public string Name { get; set; }
public string English { get; set; }
public string ParentCode { get; set; }
public string ParentName { get; set; }
public string UploadCode { get; set; }
public string UploadName { get; set; }
public short IsPage { get; set; }
public short IsMenu { get; set; }
public short IsMap { get; set; }
public short EnableConsult { get; set; }
public string PageUrl { get; set; }
public string MenuUrl { get; set; }
public short Sort { get; set; }
public string iconCls { get; set; }
public bool leaf { get; set; }
public bool expanded { get; set; }
public IList<object> children { get; set; }
}
4 Form
public abstract class FormBase
{
public string id { get; set; }
public int page { get; set; }
public int limit { get; set; }
public int PageIndex
{
get
{
return (page - 1) < 0 ? 0 : (page - 1);
}
}
public int PageSize
{
get
{
return limit <= 0 ? 20 : limit;
}
}
public int RecordCount { get; set; }
public string Escape(string value)//SQL数据替换
{
if (String.IsNullOrEmpty(value))
return value;
return value.Replace("\'", "\'\'").Replace("%", "");
}
public bool CheckRegex(string str, string pattern, bool isRequired)//正则表达式校验
{
return CheckRegex(str, pattern, RegexOptions.IgnoreCase, isRequired);
}
public bool CheckRegex(string str, string pattern, RegexOptions options, bool isRequired)
{
Regex regex = new Regex(pattern, options);
return regex.IsMatch(str);
}
public bool CheckCount(string str, int length, bool isRequired)//字符个数校验
{
return CheckCount(str, length, length, isRequired);
}
public bool CheckCount(string str, int? minLength, int? maxLength, bool isRequired)
{
if (String.IsNullOrEmpty(str))
return !isRequired;
int length = str.Length;
if (minLength.HasValue && length < minLength.Value)
return false;
if (maxLength.HasValue && length > maxLength.Value)
return false;
return true;
}
public bool CheckLength(string str, int length, bool isRequired)//字符长度(双字节算2个)校验
{
return CheckLength(str, length, length, isRequired);
}
public bool CheckLength(string str, int? minLength, int? maxLength, bool isRequired)
{
if (String.IsNullOrEmpty(str))
return !isRequired;
int length = Encoding.Default.GetByteCount(str);
if (minLength.HasValue && length < minLength.Value)
return false;
if (maxLength.HasValue && length > maxLength.Value)
return false;
return true;
}
public bool CheckDateTime(DateTime? dt, DateTime? start, DateTime? stop, bool isRequired)//时间校验
{
if (!dt.HasValue)
return !isRequired;
if (start.HasValue && dt.Value < start.Value)
return false;
if (stop.HasValue && dt.Value > stop.Value)
return false;
return true;
}
public bool CheckInteger(int? value, int? minValue, int? maxValue, bool isRequired)//整型数据校验
{
if (!value.HasValue)
return !isRequired;
if (minValue.HasValue && value.Value < minValue.Value)
return false;
if (maxValue.HasValue && value.Value > maxValue.Value)
return false;
return true;
}
public bool CheckDouble(double? value, double? minValue, double? maxValue, bool isRequired)//双精度数据校验
{
if (!value.HasValue)
return !isRequired;
if (minValue.HasValue && value.Value < minValue.Value)
return false;
if (maxValue.HasValue && value.Value > maxValue.Value)
return false;
return true;
}
}
public class RoleForm : FormBase, ISQLPaging
{
public string Code { get; set; }
public string Name { get; set; }
public short State { get; set; }
public short Sort { get; set; }
public string QuerySQLWhere { get; set; }
public string QuerySQL
{
get
{
StringBuilder sb = new StringBuilder();
sb.Append("SELECT * FROM Role WHERE 1=1 ");
if (!String.IsNullOrEmpty(this.Code))
{
sb.Append("AND Code='" + base.Escape(this.Code) + "' ");
}
if (!String.IsNullOrEmpty(this.Name))
{
sb.Append("AND Name LIKE '%" + base.Escape(this.Name) + "%' ");
}
if (!String.IsNullOrEmpty(this.QuerySQLWhere))
{
sb.Append(this.QuerySQLWhere);
}
sb.Append(" ORDER BY Code ASC");
return sb.ToString();
}
}
public bool Validation()
{
return base.CheckLength(this.Code, 2, true)
&& base.CheckLength(this.Name, 2, 20, true);
}
public bool ValidationDelete()
{
return base.CheckLength(this.Code, 2, true);
}
}
public class AdminForm : FormBase, ISQLPaging
{
public string Code { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public DateTime AddTime { get; set; }
public DateTime? LastTime { get; set; }
public string LastIP { get; set; }
public short ErrorCount { get; set; }
public short State { get; set; }
public string RoleCode { get; set; }
public string QuerySQLWhere { get; set; }
public string QuerySQL
{
get
{
StringBuilder sb = new StringBuilder();
sb.Append("SELECT * FROM Admin WHERE 1=1 ");
if (!String.IsNullOrEmpty(this.Code))
{
sb.Append("AND Code='" + base.Escape(this.Code) + "' ");
}
if (!String.IsNullOrEmpty(this.Name))
{
sb.Append("AND Name LIKE '%" + base.Escape(this.Name) + "%' ");
}
if (!String.IsNullOrEmpty(this.QuerySQLWhere))
{
sb.Append(this.QuerySQLWhere);
}
sb.Append(" ORDER BY Code ASC");
return sb.ToString();
}
}
public bool ValidationAdd()
{
return base.CheckLength(this.Code, 5, 20, true)
&& base.CheckLength(this.Password, 5, 20, true)
&& base.CheckLength(this.Name, 2, 20, true)
&& base.CheckLength(this.RoleCode, 2, true);
}
public bool ValidationUpdate()
{
return base.CheckLength(this.Code, 5, 20, true)
&& base.CheckLength(this.Password, 5, 20, false)
&& base.CheckLength(this.Name, 2, 20, true)
&& base.CheckLength(this.RoleCode, 2, true);
}
public bool ValidationData()
{
return base.CheckLength(this.Code, 5, 20, true)
&& base.CheckLength(this.Password, 5, 20, false)
&& base.CheckLength(this.Name, 2, 20, true);
}
public bool ValidationDelete()
{
return base.CheckLength(this.Code, 5, 20, true);
}
}
public class UploadForm : FormBase
{
public string Code { get; set; }
public string Name { get; set; }
public short IsRequired { get; set; }
public short FileType { get; set; }
public short IsSave { get; set; }
public string Extension { get; set; }
public string SavePath { get; set; }
public long MaxSize { get; set; }
public string ParentPattern { get; set; }
}
public class AuthorizeForm : FormBase
{
public string RoleCode { get; set; }
public string[] CategoryCodes { get; set; }
public bool Validation()
{
if (!base.CheckLength(this.RoleCode, 2, true))
return false;
if (this.CategoryCodes != null && this.CategoryCodes.Length > 0)
{
foreach (var code in this.CategoryCodes)
{
if (!base.CheckLength(code, 2, 20, true))
return false;
}
}
return true;
}
}
public class CategoryForm : FormBase
{
public string Code { get; set; }
public string Name { get; set; }
public string English { get; set; }
public string ParentCode { get; set; }
public string UploadCode { get; set; }
public short IsPage { get; set; }
public short IsMenu { get; set; }
public short IsMap { get; set; }
public short EnableConsult { get; set; }
public string PageUrl { get; set; }
public string MenuUrl { get; set; }
public short Sort { get; set; }
public bool Validation()
{
return base.CheckLength(this.Code, 2, 20, true)
&& base.CheckLength(this.Name, 2, 20, true)
&& base.CheckLength(this.English, null, 20, false)
&& base.CheckLength(this.ParentCode, 2, 20, false)
&& base.CheckLength(this.UploadCode, 2, 20, false)
&& base.CheckLength(this.PageUrl, null, 250, false)
&& base.CheckLength(this.MenuUrl, null, 250, false)
&& base.CheckInteger(this.Sort, -1000, 1000, true)
&& (
String.IsNullOrEmpty(this.ParentCode)
? this.Code.Length == 2
: this.Code.Substring(0, this.ParentCode.Length) == this.ParentCode
);
}
public bool ValidationDelete()
{
return base.CheckLength(this.Code, 2, 20, true);
}
}
代码有些多,下一篇做完基本的ExtJS DEMO后,我将第一个版本的源码,请关注。