页面

这里界面我采用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> : ContextCoralControllerwhere 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,都是踩过的坑,并且是不容易发现的坑。

  

转载于:https://www.cnblogs.com/Skyven/p/5658733.html

CRUD全栈式编程架构之控制器的设计相关推荐

  1. 国际互联网计算与物联网大会ICOMP 2017高度赞誉DroiBaaS全栈式优化架构

    2017年5月,DroiBaaS坚持自研的全栈式优化云计算架构投稿国际互联网计算与物联网大会(The 18th International Conference on Internet Computi ...

  2. iOS全栈式开发工程师

    课程目录: --/iOS全栈式开发工程师/ ├──1.双师班课程介绍 | └──1.什么是双师教育模式.flv 13.19M ├──10.C语言之字符串 | └──1.字符串.flv 175.41M ...

  3. 推荐一款轻量级全栈式开源测试平台!

    1.RunnerGo介绍 今天给大家介绍一个好用的测试平台:RunnerGo(开源).RunnerGo是一款轻量级.全栈式的测试平台,支持接口管理.场景管理.性能测试.自动化测试等功能.与市面上的性能 ...

  4. 为PaaS云平台提供整合的全栈式监控

    作为一项日益受到欢迎的技术,平台即服务(Platform-as-a-Service,PaaS)可以在云端部署能够通过Web访问的应用.借助PaaS,用户不必关注详细的执行信息,例如操作系统.资源分配. ...

  5. 一个全栈式的应用集成平台,打破“信息孤岛”

    源宝导读:随着企业数字化进程的逐渐深入,企业存在大量的异构系统,各个系统之间信息传输.资源利用困难.本文将介绍明源云ERP为了打破这种"信息孤岛",而进行的思考与实践. 一.前言 ...

  6. 大数据全栈式开发语言 – Python

    前段时间,ThoughtWorks在深圳举办一次社区活动上,有一个演讲主题叫做"Fullstack JavaScript",是关于用JavaScript进行前端.服务器端,甚至数据 ...

  7. python全栈和java全栈_Python是全栈式开发语言吗?原因竟是这样!

    Python 的排名从去年开始就借助人工智能持续上升,现在它已经成为了第一名.但排在前四名的语言 Python.C.Java 和 C++都拥有广大的用户群体,并且他们的用户总量也十分相近.实际上,Di ...

  8. 无监控,不运维:解读企业全栈式监控运

    企业应用由单体应用系统向分布式系统的发展趋势已经不可逆转.十年前 "SOA" 大频率的出现在软件系统招标技术架构要求书中,相信用不了多久"微服务架构"也会被频繁 ...

  9. 如何学习(1):构建全栈式知识结构

    有次下班到家楼下等电梯,碰巧一位妈妈抱到两岁的小女孩在看旁边的宣传画.这时电梯还没到,这位妈妈就指着海报上的字读给小女孩,"这是太阳,那是月亮"--,想借这个机会教小孩认字. 这是 ...

最新文章

  1. Spring中获取Session的方法汇总
  2. 每日一九度之 题目1083:特殊乘法
  3. 2019-04(2)Python学习
  4. 推荐:一个VS插件——CopySourceAsHtml
  5. win7 64系统无法看见其他计算机,win7一直显示正在启动进不了系统怎么办
  6. Oracle 11g中文版高清视频教程
  7. nrf51822-广播模式
  8. OPPO Reno3系列旗舰官宣:骁龙765G+正反双曲面设计
  9. ETCD for java_etcd-java使用
  10. 公司只有1个测试,领导却让我同时操作1000个手机号
  11. 常见移动机器人轮直径校准实现(ROS)方法
  12. Centos6.5 源码安装MySql5.6.33
  13. stm32 jlink Unexceped core id found 0x00000000,excepted 0X3BA00477,MASK 0XFFFFFFFF
  14. 【NLP】非监督文本匹配算法——BM25
  15. 运行海康威视sdk实现拍照遇到的问题与解决
  16. Page Cache:为什么我的容器内存使用量总是在临界点?
  17. 数据集:人群行为识别数据库总结
  18. 11部程序员大电影你看过几个?
  19. Css打造一个简单的静态七巧板
  20. oninput 、onpropertychange 、addEvent思考

热门文章

  1. SHELL脚本之自动化安装通用二进制格式MariaDB
  2. 3月第四周全球域名解析商:万网DNSPod排名均上升1名
  3. Java classLoader【转】
  4. Soft Skill
  5. windowsCE镜像文件结构
  6. 1024 许个愿吧,万一实现了呢?
  7. ShardingSphere(五) 公共表配置,实现读写改操作
  8. jinfo-jvm参数信息工具
  9. Spring中的事务回滚 网上比较不错的文章
  10. upstream directive is not allowed here in