• CRUD全栈式编程架构之控制器的设计


    页面

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

      

  • 相关阅读:
    Python-asyncio
    Python-异步编程
    软件工程个人作业01
    《构建之法》阅读笔记6
    《构建之法》阅读笔记5
    《构建之法》阅读笔记4
    《构建之法》阅读笔记3
    《构建之法》第二章阅读笔记
    《构建之法》第一章阅读笔记
    开发web信息管理系统用到的相关技术
  • 原文地址:https://www.cnblogs.com/Skyven/p/5658733.html
Copyright © 2020-2023  润新知