本文目录
通过实例学习Fireasy开发(上篇)
通过实例学习Fireasy开发(中篇)
通过实例学习Fireasy开发(下篇)
通过实例学习Fireasy开发(补充)
前面的功能已经开发完成了,但是这里专门用一章来进行补充。
一、EasyUI验证
不知道你有没有发现,我们虽然在EmployeeMetada里加了验证特性RequiredAttribute、StringLengthAttribute,但是页面并没有在data-options里生成validType。这是由于少了Fireasy.Portal组件造成了。我们只需要把ValidateBoxSettingBinder和NumberBoxSettingBinder这两个类拷贝过来就行了。
ValidateBoxSettingBinder主要负责将基本的验证绑定到ValidateBoxSettings对象的ValidType属性中。
/// <summary> /// <see cref="ValidateBoxSettings"/> 的绑定者。 /// </summary> public class ValidateBoxSettingBinder : ISettingsBinder { public bool CanBind(ISettingsBindable settings) { return typeof(ValidateBoxSettings).IsAssignableFrom(settings.GetType()); } public void Bind(Type modelType, string propertyName, ISettingsBindable settings) { //模型类型必须实现自 IEntity if (!typeof(IEntity).IsAssignableFrom(modelType)) { return; } //获取对应的依赖属性 var property = PropertyUnity.GetProperty(modelType, propertyName); if (property == null) { return; } var vsettings = settings as ValidateBoxSettings; //获取依赖属性所指定的验证特性 var validTypes = new List<string>(); foreach (var validation in ValidationUnity.GetValidations(property)) { ParseValidation(vsettings, validation, validTypes); } vsettings.ValidType = validTypes.ToArray(); } /// <summary> /// 解析 <see cref="ValidationAttribute"/> 对象。 /// </summary> /// <param name="settings"></param> /// <param name="validation">要解析的 <see cref="ValidationAttribute"/> 对象。</param> /// <param name="validTypes">如果 <paramref name="validation"/> 能与 EasyUI 的客户端验证所对应,则添加到 validType 属性中。</param> private void ParseValidation(ValidateBoxSettings settings, ValidationAttribute validation, List<string> validTypes) { //必填验证特性 var required = validation as RequiredAttribute; if (required != null) { settings.Required = true; return; } //长度验证特性 var stringLength = validation as StringLengthAttribute; if (stringLength != null) { validTypes.Add(string.Format("length[{0},{1}]", stringLength.MinimumLength, stringLength.MaximumLength)); return; } //电话验证特性 var telphone = validation as TelphoneAttribute; if (telphone != null) { validTypes.Add("phone"); return; } //手机验证特性 var mobile = validation as MobileAttribute; if (mobile != null) { validTypes.Add("mobile"); return; } //手机或电话验证特性 var telOrMobile = validation as TelphoneOrMobileAttribute; if (telOrMobile != null) { validTypes.Add("phoneOrMobile"); return; } //邮箱验证特性 var email = validation as EmailAttribute; if (email != null) { validTypes.Add("email"); return; } var ascii = validation as AsciiCodingAttribute; if (ascii != null) { validTypes.Add("ascii"); return; } var un = validation as UserNameAttribute; if (un != null) { validTypes.Add("username"); return; } var chinese = validation as ChineseCodingAttribute; if (chinese != null) { validTypes.Add("chinese"); } } }
NumberBoxSettingBinder主要负责把小数位、最大值、最小值绑定到NumberBoxSettings对象中。
/// <summary> /// <see cref="NumberBoxSettings"/> 的绑定者。 /// </summary> public class NumberBoxSettingBinder : ISettingsBinder { public bool CanBind(ISettingsBindable settings) { return typeof(NumberBoxSettings).IsAssignableFrom(settings.GetType()); } public void Bind(Type modelType, string propertyName, ISettingsBindable settings) { var nsettings = settings as NumberBoxSettings; if (!typeof(IEntity).IsAssignableFrom(modelType)) { return; } //获取对应的依赖属性 var property = PropertyUnity.GetProperty(modelType, propertyName); if (property == null) { return; } nsettings.Precision = property.Info.Scale; //找 RangeAttribute 特性 var range = ValidationUnity.GetValidations(property).Where(s => s is RangeAttribute).Cast<RangeAttribute>().FirstOrDefault(); if (range != null) { nsettings.Min = range.Minimum.To<decimal?>(); nsettings.Max = range.Maximum.To<decimal?>(); } } }
最后,在global.asax的Application_Start方法里,加入以下的代码:
protected void Application_Start(object sender, EventArgs e) { //资源打包配置 BundleHelper.Config(); //默认是使用MEF导出服务的,改成使用aspx对应的类文件作为服务 HttpConfiguration.Default.ServiceFactory = new AspPageServiceFactory(); //配置http服务的路由,依子目录的级数而定 HttpConfiguration.MapHttpRoute("{service}.ajx/{action}"); HttpConfiguration.MapHttpRoute("{p1}/{service}.ajx/{action}"); HttpConfiguration.MapHttpRoute("{p1}/{p2}/{service}.ajx/{action}"); HttpConfiguration.MapHttpRoute("{p1}/{p2}/{p3}/{service}.ajx/{action}"); SettingsBindManager.RegisterBinder("validatebox", new ValidateBoxSettingBinder()); SettingsBindManager.RegisterBinder("numberbox", new NumberBoxSettingBinder()); }
为了达到效果,我们修改一下WebApplication1.Data项目下的EmployeeMetadata类(在Employee_Ex.cs文件内),为Mobile加上手机验证,为Name、Post、Sex和Birthday加上必填验证。
public class EmployeeMetadata { /// <summary> /// 属性 Id 的验证特性。 /// </summary> [Required] [StringLength(36)] public object Id { get; set; } /// <summary> /// 属性 DeptId 的验证特性。 /// </summary> [Required] [StringLength(36)] public object DeptId { get; set; } /// <summary> /// 属性 No 的验证特性。 /// </summary> [StringLength(20)] public object No { get; set; } /// <summary> /// 属性 Sex 的验证特性。 /// </summary>
[Required] public object Sex { get; set; } /// <summary> /// 属性 Name 的验证特性。 /// </summary> [Required] [StringLength(20)] public object Name { get; set; } /// <summary> /// 属性 Birthday 的验证特性。 /// </summary> [Required] public object Birthday { get; set; } /// <summary> /// 属性 Post 的验证特性。 /// </summary> [Required] public object Post { get; set; } /// <summary> /// 属性 Mobile 的验证特性。 /// </summary> [Fireasy.Data.Validation.Mobile] [StringLength(20)] public object Mobile { get; set; } /// <summary> /// 属性 Address 的验证特性。 /// </summary> [StringLength(100)] public object Address { get; set; } /// <summary> /// 属性 Description 的验证特性。 /// </summary> [StringLength(500)] public object Description { get; set; } /// <summary> /// 属性 State 的验证特性。 /// </summary> public object State { get; set; } /// <summary> /// 属性 DelFlag 的验证特性。 /// </summary> public object DelFlag { get; set; } /// <summary> /// 属性 Photo 的验证特性。 /// </summary> [StringLength(200)] public object Photo { get; set; } }
好了,我们现在来看看EmployeeEdit.aspx有表单有没有加上数据验证:
二、数据列表的排序
datagrid的columns中,我们默认生成了sortable属性,但是后台代码中并没有使用排序。其实代码是生成了的,只需将注释行Order(sorting)取消就要以了。
/// <summary> /// 根据查询条件获取员工。 /// </summary> /// <param name="deptNo">部门编码。</param> /// <param name="startTime">开始日期。</param> /// <param name="endTime">结束时间。</param> /// <param name="post">岗位类别。</param> /// <param name="keyword">关键字</param> /// <returns></returns> [ExceptionBehavior(true)] public object GetEmployees(string deptNo, DateTime? startTime, DateTime? endTime, PostKinds? post, string keyword) { var pager = EasyUIHelper.GetDataPager(); var sorting = EasyUIHelper.GetSorting(); using (var context = new DbContext()) { var list = context.Employees .Segment(pager) .OrderBy(sorting) .AssertWhere(!string.IsNullOrEmpty(deptNo), s => s.Dept.No.StartsWith(deptNo)) .AssertWhere(startTime != null, s => s.Birthday >= startTime.Value.StartOfDay()) .AssertWhere(endTime != null, s => s.Birthday <= endTime.Value.EndOfDay()) .AssertWhere(post != null, s => s.Post == post) .AssertWhere(!string.IsNullOrEmpty(keyword), s => s.Name.Contains(keyword)) .Select(s => new { s.Id, s.No, s.Name, Sex = ((Sex)s.Sex).GetDescription(), Post = s.Post.GetDescription(), s.Birthday, s.Mobile, s.Address, s.Description }) .ToList(); return EasyUIHelper.Transfer(pager, list); } }
但有一种情况,即没有单击列头的情况下,默认使用某一属性排序,比如按编码降序排列,这时,Order方法可以这样写:
/// <summary> /// 根据查询条件获取员工。 /// </summary> /// <param name="deptNo">部门编码。</param> /// <param name="startTime">开始日期。</param> /// <param name="endTime">结束时间。</param> /// <param name="post">岗位类别。</param> /// <param name="keyword">关键字</param> /// <returns></returns> [ExceptionBehavior(true)] public object GetEmployees(string deptNo, DateTime? startTime, DateTime? endTime, PostKinds? post, string keyword) { var pager = EasyUIHelper.GetDataPager(); var sorting = EasyUIHelper.GetSorting(); using (var context = new DbContext()) { var list = context.Employees .Segment(pager) .OrderBy(sorting, u => u.OrderByDescending(t => t.No)) .AssertWhere(!string.IsNullOrEmpty(deptNo), s => s.Dept.No.StartsWith(deptNo)) .AssertWhere(startTime != null, s => s.Birthday >= startTime.Value.StartOfDay()) .AssertWhere(endTime != null, s => s.Birthday <= endTime.Value.EndOfDay()) .AssertWhere(post != null, s => s.Post == post) .AssertWhere(!string.IsNullOrEmpty(keyword), s => s.Name.Contains(keyword)) .Select(s => new { s.Id, s.No, s.Name, Sex = ((Sex)s.Sex).GetDescription(), Post = s.Post.GetDescription(), s.Birthday, s.Mobile, s.Address, s.Description }) .ToList(); return EasyUIHelper.Transfer(pager, list); } }
三、图片上传
员工表设计了一个照片的字段,但是之前我们并没有用到它,现在我们就把照片上传给加上。
首先,要引入上传所需的js,只需要在 BundleHelper.Render 后面再加上 upload 就可以了。
<%= BundleHelper.Render("base", "upload") %>
接下来修改一下表单的table,加一个图片和上传图片的链接:
<table class="form-body"> <tr> <td class="addon">部门</td> <td><%= Html.ComboBox(s => s.DeptId).MarkDelayedSet().MarkNoClear()%></td> <td rowspan="4" style="text-align:center"> <img id="img" src="../Images/nopic1.png" style="width: 161px; height: 110px" /><br /> <a target="#img" class="uploader">上传图片</a> </td> </tr> <tr> <td class="addon">编号</td> <td><%= Html.TextBox(s => s.No) %></td> </tr> <tr> <td class="addon">性别</td> <td><%= Html.ComboBox(s => s.Sex, typeof(WebApplication.Data.Model.Sex), new ComboBoxSettings { PanelHeight = 0 }).MarkNoClear() %></td> </tr> <tr> <td class="addon">姓名</td> <td><%= Html.TextBox(s => s.Name) %></td> </tr> <tr> <td class="addon">出生日期</td> <td><%= Html.DateBox(s => s.Birthday) %></td> </tr> <tr> <td class="addon">职务</td> <td><%= Html.ComboBox(s => s.Post, typeof(WebApplication.Data.Model.PostKinds), new ComboBoxSettings { PanelHeight = 0 }).MarkNoClear() %></td> </tr> <tr> <td class="addon">手机号码</td> <td><%= Html.TextBox(s => s.Mobile) %></td> </tr> <tr> <td class="addon">家庭住址</td> <td><%= Html.TextBox(s => s.Address) %></td> </tr> <tr> <td class="addon">个人说明</td> <td colspan="3"><%= Html.TextMultiBox(s => s.Description) %></td> </tr> <tr> <td class="addon">可用</td> <td><%= Html.CheckBox(s => s.State, true) %></td> </tr> </table>
增加一个initUploader函数,用于添加“上传图片”的事件初始化,并在页面加载完成后调用initUploader函数:
$(function () { loadInfo(); initUploader(); }); //初始化上传插件 function initUploader() { $('.uploader').upload({ extFilter: 'png;jpg;jpeg;gif', action: 'EmployeeEdit.ajx/UploadPhoto?id=' + id, useHttpModule: false, onFileExtError: function () { common.alert('只允许上传png、jpg、jpeg、gif类型的文件。') }, onComplete: function (result, index, ele) { $(ele.attr('target')).attr('src', '../' + result.fileName); $(ele.attr('target')).attr('file', result.fileName); if (id != '') { var data = { id: id, url: encodeURI(result.fileName) }; $.post('EmployeeEdit.ajx/UpdatePhoto', data); } } }); }
upload是一个jquery插件,可以为.uploader标签添加上传文件的事件。上传的处理请求是UploadPhoto方法,上传完后,更新员工的照片,即不需要点击保存按钮,也能生效。在EmployeeEdit.aspx.cs里增加UploadPhoto方法:
/// <summary> /// 上传照片。 /// </summary> /// <returns></returns> public object UploadPhoto() { ServiceContext.Current.ResultWriter = new FileUploadResultWriter(); var request = HttpContext.Current.Request; if (request.Files.Count == 0) { return null; } var file = request.Files[0]; var virtualPath = "upfiles\\photos"; var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, virtualPath); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } var fileId = Guid.NewGuid().ToString(); var fileExt = file.FileName.Substring(file.FileName.LastIndexOf(".")); var fileName = Path.Combine(path, fileId + fileExt); var virtualFile = Path.Combine(virtualPath, fileId + fileExt).Replace("\\", "/"); file.SaveAs(fileName); return Result.Success("上传成功。", new { fileId, fileName = virtualFile }); }
DefaultResultWriter用于改变content-type,因为默认是使用application/json的,但是此时它会提示下载json,因此把content-type改成text/json即可。
/// <summary> /// 针对文件上传的结果写入器。 /// </summary> public class FileUploadResultWriter : DefaultResultWriter { protected override void WriteJson(ServiceContext serviceContext, HttpResponseBase response, object value) { response.ContentType = "text/json"; response.Write(JsonSerialize(serviceContext, value)); } }
再增加一个UpdatePhoto方法,只用更新实体的Photo属性就可以了:
/// <summary> /// 更新照片路径。 /// </summary> /// <param name="id"></param> /// <param name="url"></param> public void UpdatePhoto(string id, string url) { using (var context = new DbContext()) { context.Employees.Update(new Employee { Photo = url }, s => s.Id == id); } }
最后,在EmployeeEdit.aspx中修改loadInfo函数的saveInfo函数如下:
//加载信息 function loadInfo() { if (id != '') { $.getJSON('EmployeeEdit.ajx/GetEmployee?id=' + id, function (data) { common.processResult(data, function () { $('#form1').form('load', data); deptId = data.DeptId; loadDepts(); if (data.Photo) { $('#img').attr('file', data.Photo); $('#img').attr('src', data.Photo); } }); }); $('#btnSaveAndNew').remove(); } else { loadDepts(); } } //保存信息 function saveInfo(isNew) { if (!$('#form1').form('validate')) { return; } var postData = new Object(); //将表单填充的内容序列化为json var data = $('#form1').form('save'); data.DeptId = $('#cboDeptId').combotree('getValue'); data.Photo = $('#img').attr('file'); postData["info"] = JSON.stringify(data); common.showProcess(); $.post('EmployeeEdit.ajx/SaveEmployee?id=' + id, postData, function (result) { common.processResult(result, function () { if (isNew) { $('#form1').form('clear'); } id = isNew ? '' : result.data; common.setReturnValue(true); }); }); }
照片上传至此结束。
非常感谢你的支持,在此附上示例的源代码:
★★★源代码下载★★★