网站首页 > 技术文章 正文
页面
这里界面我采用jquery miniui来做的,当你完全了解了整个设计之后可以轻松切换到其他的js框架,个人认为类似muniui,easyui等等这类可以将web界面做得和winform类似的框架,特别适合做后台管理系统。要讨论controller的设计必须结合界面,这里我给出界面截图和控制器的代码,这一篇主要讲控制器的代码,下一篇再讲界面的设计。
上一篇忘记说了,IVeiwModel是一个dto或者说viewmode的接口,我的应用里面一般不严格区分viewmode和dto,这个接口之后一个long Id的属性,
代码如下:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; using System.Web.Mvc; using System.Web.Routing; using Coralcode.Framework.Extensions; using Coralcode.Framework.Log; using Coralcode.Framework.Models; using Coralcode.Framework.Mvc.Extensions; using Coralcode.Framework.Mvc.Models.MiniUI; using Coralcode.Framework.Mvc.Template; using Coralcode.Framework.Services; using Coralcode.Framework.Validator; using Newtonsoft.Json; using ViewType = Coralcode.Framework.Mvc.Models.MiniUI.ViewType; namespace Coralcode.Framework.Mvc.ControlContent { public abstract class CrudCoralController<TModel, TSearch, TOrder> : ContextCoralController where TModel : class, IViewModel, new where TSearch : SearchBase, new where TOrder : OrderBase, new { private readonly ICrudCoralService<TModel, TSearch, TOrder> _service; protected CrudCoralController(ICrudCoralService<TModel, TSearch, TOrder> service) { _service = service; } protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); var routeValues = Request.GetRouteValues; //页面的配置 ViewBag.Title = GetType.GetDescription; ViewBag.EditUrl = Url.Action("AddEdit", routeValues); ViewBag.DeleteUrl = Url.Action("BatchDelete", routeValues); ViewBag.ListUrl = Url.Action("List", routeValues); ViewBag.PageUrl = Url.Action("PageSearch", Request.GetRouteValues); ViewBag.ShowPager = true; } [HttpGet] public virtual ActionResult Index { var viewModelType = typeof(TModel); var properties = viewModelType.GetProperties(BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); var columns = new List<DataGridColumn>; foreach (var propertyInfo in properties) { var descAttr = propertyInfo.GetCustomAttribute<GridColumnAttribute>; if (descAttr == null) continue; var column = descAttr.DataGridColumn; if ((column.ViewType & ViewType.List) == 0) { continue; } if (string.IsNullOrWhiteSpace(column.Field)) { column.Field = propertyInfo.Name; } columns.Add(column); var dateType = propertyInfo.GetCustomAttribute<DataTypeAttribute>; if (dateType == null) continue; switch (dateType.DataType) { case DataType.Custom: break; case DataType.DateTime: column.Renderer = "onDateTimeRenderer"; break; case DataType.Date: column.Renderer = "onDateRenderer"; break; case DataType.Time: column.Renderer = "onTimeRenderer"; break; case DataType.Duration: break; case DataType.PhoneNumber: break; case DataType.Currency: break; case DataType.Text: break; case DataType.Html: break; case DataType.MultilineText: break; case DataType.EmailAddress: break; case DataType.Password: break; case DataType.Url: break; case DataType.ImageUrl: break; case DataType.CreditCard: break; case DataType.PostalCode: break; case DataType.Upload: break; default: throw new ArgumentOutOfRangeException; } } ViewBag.Header = columns; ListBindData; return View(new TSearch); } [HttpPost] public virtual JsonResult List(TSearch search) { return ToJson(_service.Search(search)); } [HttpPost] public virtual JsonResult PageSearch(TSearch search,PageInfo page,TOrder order) { return ToJson(_service.PageSearch(page,search, order)); } /// <summary> /// 这里用来做编辑 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpGet] public virtual ActionResult AddEdit(long? id) { TModel model = id.HasValue ? _service.Get(id.Value) : new TModel; AddEditBindData(model); return View(model); } [ValidateInput(false)] [HttpPost] public virtual JsonResult AddEdit(TModel model) { try { if (!ModelState.IsValid) return AjaxErrorResult(ModelState.GetErroreMessage); var ajaxMessage = ValidateAndPreProccess(model); if (ajaxMessage.State != ResultState.Success) { return AjaxErrorResult(ajaxMessage.Message); } if (model.Id < 1) _service.Create(model); else _service.Modify(model); } catch (Exception ex) { LoggerFactory.Instance.Error("{0}编辑产生错误;数据:{1}", ex, typeof(TModel), JsonConvert.SerializeObject(model ?? new TModel)); return AjaxExceptionResult(ex); } return AjaxOkResult; } /// <summary> /// 业务验证 /// 比如用户名唯一 /// </summary> /// <param name="model"></param> /// <returns></returns> protected virtual ResultMessage ValidateAndPreProccess(TModel model) { if (!EntityValidatorProvider.Validator.IsValid(model)) return new ResultMessage { State = ResultState.Fail, Message = string.Join(";<br />", EntityValidatorProvider.Validator.GetInvalidMessages(model)) }; return new ResultMessage { State = ResultState.Success }; } protected virtual void ListBindData { } /// <summary> /// 这里用来扩展绑定数据 /// </summary> protected virtual void AddEditBindData(TModel model) { //这里用来做数据绑定的操作 } [HttpPost] public virtual JsonResult Delete(long id) { return this.BatchDelete(id.ToString); } [HttpPost] public virtual JsonResult BatchDelete(string ids) { if (string.IsNullOrWhiteSpace(ids)) return AjaxErrorResult("参数不能为空"); try { var idList = ids.Split(',').ToList.ConvertAll(Convert.ToInt64); if (idList.Count < 1) return AjaxErrorResult("参数不能为空"); _service.Remove(idList); } catch (Exception ex) { LoggerFactory.Instance.Error("{0}编辑产生错误;数据:{1}", ex, typeof(TModel), JsonConvert.SerializeObject(ids)); return AjaxExceptionResult(ex); } return AjaxOkResult; } } public abstract class CrudCoralController<TModel, TSearch> : CrudCoralController<TModel, TSearch, OrderBase> where TModel : class, IViewModel, new where TSearch : SearchBase, new { protected CrudCoralController(ICrudCoralService<TModel, TSearch, OrderBase> service) : base(service) { } } public abstract class CrudCoralController<TModel> : CrudCoralController<TModel, SearchBase, OrderBase> where TModel : class, IViewModel, new { protected CrudCoralController(ICrudCoralService<TModel, SearchBase, OrderBase> service) : base(service) { } } }
操作
页面配置主要是在Controler.Initialize 方法中配置的。这里有个地方注意,在生成url的地方一定要带上routedata,这个可以充分利用mvc自带valueprovider的设计,结合菜单url可以为查询和模型的绑定提供值绑定。这部分会在完整demo放出后再详细说明。
新增/编辑
这里我把新增和编辑作为一个来对待,大部分情况都是这样,当id为0时候认为是新增,当id不为0的时候是编辑。当然如果你想分开只需添加一个配置和一个方法并结合界面js,扩展必须是允许的
删除
这里我只使用了批量删除的方法,界面上应该给出的是checkbox,选择之后点击删除即可.这里注意界面传输过来的数据是“,”分开的字符串,解析之后做删除操作。
查询
查询提供了未分页和分页两种。分页的话搭配showpager来配置。大部分后台列表都需要分页,但是如果结合三级菜单,结合url中routedata,可以将数据进行一定划分,数据量不大的情况下不用分页用list也一样方便。这里界面上我没有排序的功能,后面我整理demo的时候再给出排序吧。
列表
数据绑定
在index方法中我们首先取出模型的元数据,那些列需要绑定到界面,数据类型是什么,并且可以定义一些一些元。例如高度,宽度,当然最好是搭配界面自适应使用。另外给出listbindata这个没有实现的方法,主要是给查询时候绑定数据用的。例如你查询实体中有类型combox,可以获取到类型用ViewBag传递到界面使用。另外还可以给界面一些默认值,例如你查询实体里面有开始时间,结束时间,也可以在这里给出配置。当然如果你查询实体只是独享只提供一个表查询,那么最好是写在构造函数中。
分页
分页和查询只多了一个pageinfo(里面只包含当前页和行数)
ps:mvc的绑定机制会会导致无法赋值,或者复制错乱问题
>* pageinfo的属性名称不要和查询实体的属性名称冲突,除非是业务需要。
>* 另外一定不要再查询实体中定义类似page,search,order属性名。
>* 如area,controller,action的作为入参或者入参的属性也不要有
新增和编辑
区分和绑定
编辑通过id是否有值来区分,如果有值,那么会使用服务查询到数据绑定到界面,没有的话会new一个出来。AddEditBindData 方法是为页面绑定提供数据源,例如编辑界面有combox这类可以更方便绑定。有人会说可以用界面ajax无刷新绑定,我建议是尽量不要这么做,如果界面有几十个combox这里很容易导致大量的ajax请求,我称之为ajax灾难,所以我这里会强烈建议使用者使用后台提供数据绑定,避免上述问题。
验证和预处理
提交数据之后首先做的事mvc自带的验证,这里做了一个小封装,可以取出所有错误返回到界面,不过最佳方式是,界面mvc客户端js做绑定工作。具体可以查看artechmvc讲解中的方式。另外这里做了自定义的验证。这里把方法名叫ValidateAndPreProccess,因为有时候我们需要对传输过来的数据做一部分预处理,比如ids这种有可能你服务需要的是一个list,这时候就可以做一个转换了。另外验证和预处理逻辑很难区分出是那个先做哪个后做,所以这里将两个方法合并。预处理中调用了自定义验证,这里用的NlayerApp的验证方式,具体请自行搜索,其实这里并没有卵用。mvc的验证比这个做得好,但是后面做导入导出的时候会用到暂且就放这里吧,结合导入导出再说这部分。
public static class MvcExtensions { public static string GetErroreMessage(this ModelStateDictionary state) { return string.Join( Environment.NewLine,state.SelectMany(item => item.Value.Errors) .Select(item => item.ErrorMessage) .Where(item => !string.IsNullOrWhiteSpace(item))); } }
其他
AjaxXXXResult
分为成功,失败和部分成功,部分成功会在导入数据时候用到。在扩展控制器的顶层基类提供更多的方法也是做应用时候必须的,就算你啥都没有也建议你先占个位置.
using System; using System.Web.Mvc; using Coralcode.Framework.Models; using Coralcode.Framework.Mvc.ActionResults; namespace Coralcode.Framework.Mvc.ControlContent { public class CoralController : Controller { protected JsonResult AjaxOkResult(object data = null, string message = "success") { var result = new ResultMessage { State = ResultState.Success, Message = message, Data = data, }; return ToJson(result, JsonRequestBehavior.AllowGet); } protected JsonResult AjaxExceptionResult(Exception ex, object data = null) { var result = new ResultMessage { State = ResultState.Fail, Message = ex.ToString, Data = data, }; return ToJson(result, JsonRequestBehavior.AllowGet); } protected JsonResult AjaxErrorResult(object data = null, string message = "fail") { var result = new ResultMessage { State = ResultState.Fail, Message = message, Data = data, }; return ToJson(result, JsonRequestBehavior.AllowGet); } protected JsonResult AjaxPartSuccessResult(object data = null, string message = "partsuccess") { var result = new ResultMessage { State = ResultState.PartSuccess, Message = message, Data = data, }; return ToJson(result, JsonRequestBehavior.AllowGet); } protected JsonResult ToJson(object obj, JsonRequestBehavior behavior = JsonRequestBehavior.AllowGet) { return new CustomJsonResult { Data = obj, JsonRequestBehavior = behavior }; } } }
ResultMessage
这里泛型,主要是用在服务端调用api时候数据转换类型更方便,所有的我ajax请求都做了类似封装,这里会在和mvc结合时候重点说明。
namespace Coralcode.Framework.Models { public class BaseMessage { public BaseMessage { } public BaseMessage(ResultState state,string message="" ) { State = state; Message = message; } public ResultState State { get; set; } public string Message { get; set; } } public class ResultMessage : BaseMessage { public ResultMessage { } public ResultMessage(ResultState state, string message = "",object data=null) : base(state, message) { Data = data; } public object Data { get; set; } } public class ResultMessage<T> : BaseMessage { public ResultMessage { } public T Data { get; set; } public ResultMessage(ResultState state, string message = "",T data=default(T)) : base(state, message) { Data = data; } } public enum ResultState { Success, Fail, PartSuccess, } }
CustomJsonResult
这里对JsonResult 做了扩展,性能更快,并且解决long类型数据返回到界面数据最后两位丢失的问题。
using System; using System.Web; using System.Web.Mvc; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Coralcode.Framework.Mvc.ActionResults { public class CustomJsonResult : JsonResult { public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (JsonRequestBehavior == JsonRequestBehavior.DenyGet && String.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase)) { throw new InvalidOperationException; } HttpResponseBase response = context.HttpContext.Response; if (!String.IsNullOrEmpty(ContentType)) { response.ContentType = ContentType; } else { response.ContentType = "application/json"; } if (ContentEncoding != null) { response.ContentEncoding = ContentEncoding; } if (Data != null) { response.Write(JsonConvert.SerializeObject(Data, new IdToStringConverter)); } } } public class IdToStringConverter : JsonConverter { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JToken jt = JValue.ReadFrom(reader); return jt.Value<long>; } public override bool CanConvert(Type objectType) { return typeof(Int64) == objectType; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value.ToString); } } }
PS: 这个js精度丢失的问题是由于js本身设计时候一个bug。这里刚开始遇到也困扰很久,一直用类中一个字符串属性取代。但是后来狠下心,还是从mvc底层去解决问题。有问题不要绕,就是干不要怂-_-!
这里注意JsonRequestBehavior.AllowGet ,面试提问利器-_-!
PS:注意我所有的ps,都是踩过的坑,并且是不容易发现的坑。
猜你喜欢
- 2024-09-18 Python的GitHub核心资源库token意外曝光
- 2024-09-18 了解 JWT,JSON Web Token(JWT)是一个非常轻巧的规范
- 2024-09-18 电脑控制手机 易语言实现颜色识别功能
- 2024-09-18 Postman 的高效使用技巧,你知道吗?
- 2024-09-18 PlusToken转移2,631万个EOS,原账号仅剩余了350个EOS
- 2024-09-18 freeswitch修改mod_sofia模块并上报自定义头域
- 2024-09-18 鉴权必须了解的 5 个兄弟:cookie、session、token、jwt、单点登录
- 2024-09-18 SpringSecurity和JWT实现认证和授权
- 2024-09-18 电脑自动安装卸载手机软件-快来Get最简单便捷的方法
- 2024-09-18 使用 Express 和 Node.js 进行电话身份验证
- 1512℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 556℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 505℃MySQL service启动脚本浅析(r12笔记第59天)
- 483℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 482℃启用MySQL查询缓存(mysql8.0查询缓存)
- 462℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 442℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 439℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- c语言min函数头文件 (68)
- asynccallback (71)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)