Fireasy

Fireasy与Asp.net MVC结合

Fireasy之前都是使用HttpService来为jquery ajax提供服务,这个HttpService实际上和MVC的原理机制是一样的,只是它支持两种方式,一种是使用统一的一个类来提供服务(基于MEF导入),另一种是使用aspx的类文件提供服务,具体使用哪一种,根据项目的性质来决定。

Asp.net MVC也就了解了一些皮毛,还不是很熟悉,正在深度学习中。不过基于以前的开发习惯,我觉得MVC要进行以下几点的改进:

(1)异常处理。MVC实现了一个异常滤过器HandleErrorAttribute,可以对执行action发生的异常进行处理,以便返回错误信息。但是它默认返回了一个View,我希望在发生异常时记录日志,并在前台进行友好的提示。

(2)Json序列化。翻出JsonResult的源代码来看,你会发现它的json序列化是由JavaScriptSerializer类完成的,对于这个类,我不想说什么。

(3)Action参数的反序列化。对于Fireasy的轻实体模型而言,它无法进行反序列化。

暂时先处理这三个问题吧,后来遇到问题再贴上来。

      一、异常处理

在之前的开发模式中,我们都是使用ajax取数据和保存数据,我觉得相对于mvc的model bind来说这样比较灵活,因为毕竟使用easyui,在取数的时候可能会有一些复杂的处理,我们都放在前台上编码吧。

在读取和保存数据的时候,避免不了会发生一些异常,如果按mvc的默认处理,我们将无法接收异常信息,这是一样相当头疼的事情。因此,我们对HandleErrorAttribute做一些改进:

对json的包装一会儿会说。如果是ClientNotificationException异常,直接显示错误信息,否则记录日志,返回提示信息。

    /// <summary>
    /// 控制器方法执行发生异常时,记录日志并返回友好的提示信息。
    /// </summary>
    public class HandleErrorAttribute : System.Web.Mvc.HandleErrorAttribute
    {
        /// <summary>
        /// 处理异常信息。
        /// </summary>
        /// <param name="filterContext"></param>
        public override void OnException(ExceptionContext filterContext)
        {
            if (IsJsonResult(filterContext))
            {
                HandleExceptionForJson(filterContext);
            }
            else
            {
                HandleException(filterContext);
            }

            LogException(filterContext);
        }

        /// <summary>
        /// 判断返回结果是否为 Json 类型。
        /// </summary>
        /// <param name="filterContext"></param>
        /// <returns></returns>
        protected virtual bool IsJsonResult(ExceptionContext filterContext)
        {
            if (ActionContext.Current != null)
            {
                var desc = ActionContext.Current.ActionDescriptor as ReflectedActionDescriptor;
                if (desc != null)
                {
                    return typeof(JsonResult).IsAssignableFrom(desc.MethodInfo.ReturnType);
                }
            }

            return false;
        }

        /// <summary>
        /// 处理返回结果为Json的异常信息。
        /// </summary>
        /// <param name="filterContext"></param>
        protected virtual void HandleExceptionForJson(ExceptionContext filterContext)
        {
            //如果是通知类的异常,直接输出提示
            var notifyExp = filterContext.Exception as Fireasy.Common.ClientNotificationException;
            if (notifyExp != null)
            {
                filterContext.Result = new JsonResultWrapper(new JsonResult { Data = Result.Fail(notifyExp.Message) });
                filterContext.ExceptionHandled = true;
                return;
            }
            else
            {
                filterContext.Result = GetHandledResult();
                filterContext.ExceptionHandled = true;
            }
        }

        /// <summary>
        /// 处理一般返回结果的异常信息。
        /// </summary>
        /// <param name="filterContext"></param>
        protected virtual void HandleException(ExceptionContext filterContext)
        {
            var errorPage = ConfigurationManager.AppSettings["error-page"];
            if (!string.IsNullOrEmpty(errorPage))
            {
                filterContext.Result = new RedirectResult(errorPage);
                filterContext.ExceptionHandled = true;
            }
        }

        /// <summary>
        /// 记录异常日志。
        /// </summary>
        /// <param name="filterContext"></param>
        protected virtual void LogException(ExceptionContext filterContext)
        {
            //记录日志
            var logger = LoggerFactory.CreateLogger();
            if (logger != null)
            {
                var controllerName = (string)filterContext.RouteData.Values["controller"];
                var actionName = (string)filterContext.RouteData.Values["action"];

                logger.Error(string.Format("执行控制器 {0} 的方法 {1} 时发生错误。",
                    controllerName, actionName), filterContext.Exception);
            }
        }

        /// <summary>
        /// 获取处理后的返回结果。
        /// </summary>
        /// <returns></returns>
        protected virtual ActionResult GetHandledResult()
        {
            if (ActionContext.Current != null)
            {
                //检查是否定义了 ExceptionBehaviorAttribute 特性
                var attr = ActionContext.Current.ActionDescriptor
                    .GetCustomAttributes(typeof(ExceptionBehaviorAttribute), false)
                    .Cast<ExceptionBehaviorAttribute>().FirstOrDefault();

                if (attr != null)
                {
                    //返回空数组,一般用在列表绑定上
                    if (attr.EmptyArray)
                    {
                        return new JsonResultWrapper(new JsonResult { Data = new string[0] });
                    }
                    //使用提示信息
                    else if (!string.IsNullOrEmpty(attr.Message))
                    {
                        return new JsonResultWrapper(new JsonResult { Data = Result.Fail(attr.Message) });
                    }
                }
            }

            return new JsonResultWrapper(new JsonResult { Data = Result.Fail("发生错误,请查阅相关日志或联系管理员。") });
        }
    }

对异常的处理分两种情况,如果是返回Json,则返回结果是Json格式,否则定向到出错页面

      二、Json序列化

Json的序列化是在JsonResult的ExecuteResult里完成,它用JavaScriptSerializer这个东东来序列化,有点想不通,webapi都使用Json.Net了,mvc5还是这样,估计MS不想用Json.Net。

实在不想在Action里使用其他的Result类来替代Json()方法,因为这样做会破坏程序的可维护性。那么最好的办法就是对JsonResult进行包装。

    /// <summary>
    /// 对 <see cref="JsonResult"/> 进行包装,重写序列化对象的方法。
    /// </summary>
    public class JsonResultWrapper : JsonResult
    {
        private JsonResult result;

        /// <summary>
        /// 初始化 <see cref="JsonResultWrapper"/> 类的新实例。
        /// </summary>
        /// <param name="result"></param>
        public JsonResultWrapper(JsonResult result)
        {
            result.JsonRequestBehavior = System.Web.Mvc.JsonRequestBehavior.AllowGet;
            this.result = result;
        }

        /// <summary>
        /// 将结果输出到 Response。
        /// </summary>
        /// <param name="context"></param>
        public override void ExecuteResult(ControllerContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            if (result == null)
            {
                throw new ArgumentNullException("result");
            }

            if ((result.JsonRequestBehavior == System.Web.Mvc.JsonRequestBehavior.DenyGet) &&
                string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
            {
                throw new InvalidOperationException("不能在Url中访问。");
            }

            var response = context.HttpContext.Response;
            if (!string.IsNullOrEmpty(result.ContentType))
            {
                response.ContentType = result.ContentType;
            }
            else
            {
                response.ContentType = "application/json";
            }

            if (result.ContentEncoding != null)
            {
                response.ContentEncoding = result.ContentEncoding;
            }

            if (result.Data != null)
            {
                response.Write(SerializeJson(context, result.Data));
            }
        }

        /// <summary>
        /// 将数据序列化为 Json 字符串。这里使用了 Fireasy 提供的 Json 序列化方法。
        /// </summary>
        /// <param name="context"></param>
        /// <param name="data">要序列化的数据。</param>
        /// <returns></returns>
        protected virtual string SerializeJson(ControllerContext context, object data)
        {
            var option = new JsonSerializeOption();
            if (ActionContext.Current != null)
            {
                //json转换器
                var converters = ActionContext.Current.Converters.Where(s => s is JsonConverter).Cast<JsonConverter>();
                option.Converters.AddRange(converters);
            }

            //jsonp的处理
            //var jsoncallback = context.HttpContext.Request.Params["callback"];

            var serializer = new JsonSerializer(option);
            var json = serializer.Serialize(data);

            //if (!string.IsNullOrEmpty(jsoncallback))
            //{
            //    return string.Format("{0}({1})", jsoncallback, json);
            //}

            return json;
        }
    }

注意了,上面代码中标红的ActionContext是一个线程内的上下文对象,它的作用是在方法执行期间可以由用户在Action里得到该对象并往里面加东西,比如JsonConverter。

    /// <summary>
    /// 控制器操作方法执行期间的上下文对象。
    /// </summary>
    public class ActionContext : Scope<ActionContext>
    {
        internal ActionContext(ControllerContext controllerContext)
        {
            ControllerContext = controllerContext;
            Converters = new List<ITextConverter>();
        }

        /// <summary>
        /// 获取控制器上下文对象。
        /// </summary>
        public ControllerContext ControllerContext { get; private set; }

        /// <summary>
        /// 获取动作相关的 <see cref="ActionDescriptor"/> 对象。
        /// </summary>
        public ActionDescriptor ActionDescriptor { get; internal set; }

        /// <summary>
        /// 获取文本转换器列表。
        /// </summary>
        public List<ITextConverter> Converters { get; private set; }

        /// <summary>
        /// 获取动作方法的参数字典。
        /// </summary>
        public IDictionary<string, object> Parameters { get; internal set; }
    }

那么它在什么时候被创建,什么时候被销毁呢,幸好有ControllerActionInvoker这个类可以进行继承。

    public class ControllerActionInvoker : System.Web.Mvc.ControllerActionInvoker
    {
        internal static ControllerActionInvoker Instance = new ControllerActionInvoker();

        /// <summary>
        /// 执行控制器的动作。
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="actionName"></param>
        /// <returns></returns>
        public override bool InvokeAction(ControllerContext controllerContext, string actionName)
        {
            using (var scope = new ActionContext(controllerContext))
            {
                return base.InvokeAction(controllerContext, actionName);
            }
        }

        /// <summary>
        /// 执行动作方法。
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="actionDescriptor"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        protected override ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
        {
            if (ActionContext.Current != null)
            {
                ActionContext.Current.ActionDescriptor = actionDescriptor;
                ActionContext.Current.Parameters = parameters;
            }

            var result = base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
            var jsonResult = result as JsonResult;
            if (jsonResult != null && !(result is JsonResultWrapper))
            {
                return WrapJsonResult(jsonResult);
            }

            return result;
        }

        /// <summary>
        /// 对 <see cref="JsonResult"/> 对象进行包装并转换输出结果。
        /// </summary>
        /// <param name="jsonResult"></param>
        /// <returns></returns>
        protected virtual ActionResult WrapJsonResult(JsonResult jsonResult)
        {
            return new JsonResultWrapper(jsonResult);
        }
    }

这样,在Action方法里,都可以通过ActionContext.Current获得此实例,可获得ActionDescriptor等等定义信息,以及序列化转换器。

另外,上面的代码中,重写InvokeActionMethod方法,对JsonResult进行包装。

如果你是使用Json.Net,那一样的道理,换成Json.Net的序列化就行了。

      三、Action参数反序列化

在上面的这个类ControllerActionInvoker中,重写GetParameterValue方法,对参数进行反序列化。

    public class ControllerActionInvoker : System.Web.Mvc.ControllerActionInvoker
    {
        internal static ControllerActionInvoker Instance = new ControllerActionInvoker();

        /// <summary>
        /// 获取参数的值。
        /// </summary>
        /// <param name="controllerContext"></param>
        /// <param name="parameterDescriptor"></param>
        /// <returns></returns>
        protected override object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor)
        {
            var value = base.GetParameterValue(controllerContext, parameterDescriptor);
            if (value == null)
            {
                //对json进行反序列化,由于使用了基于 Fireasy AOP 的实体模型,所以必须使用 Fireasy 的反序列化方法
                var json = controllerContext.HttpContext.Request.Params[parameterDescriptor.ParameterName];
                if (!string.IsNullOrEmpty(json))
                {
                    try
                    {
                        var option = new JsonSerializeOption();
                        if (ActionContext.Current != null)
                        {
                            //json转换器
                            var converters = ActionContext.Current.Converters.Where(s => s is JsonConverter).Cast<JsonConverter>();
                            option.Converters.AddRange(converters);
                        }

                        var serializer = new JsonSerializer(option);
                        value = serializer.Deserialize(json, parameterDescriptor.ParameterType);
                    }
                    catch (Exception exp)
                    {
                        var logger = LoggerFactory.CreateLogger();
                        if (logger != null)
                        {
                            var message = string.Format("无法解析控制器 {0} 的方法 {1} 的参数 {2} 的值。\n\n数据为: {3}",
                                parameterDescriptor.ActionDescriptor.ControllerDescriptor.ControllerName,
                                parameterDescriptor.ActionDescriptor.ActionName,
                                parameterDescriptor.ParameterName,
                                json);

                            logger.Error(message, exp);
                        }
                    }
                }
            }

            return value;
        }
    }

改造完成,现在沿用以前的开发模式,但是底层使用了MVC的控制器,也算是一点小进步吧。昨天一讲课一边做了一个小实例,我也放上来分享给大家,以加深对Fireasy的了解。

示例下载

时间: 2024-08-06 03:46:17

Fireasy的相关文章

细说 Fireasy Entity Linq解析的几个独创之处

Fireasy Entity的linq内核解析是参考自iqtoolkit源码的,作者熟读源码并吸收其博大精深的思想后,结合项目中的一些需求,对它进行了几处改进.      一.逻辑删除标记 做管理系统的开发者可能会习惯于对数据做逻辑删除处理,即将数据打这一个标记,查询的时候将这些数据过滤掉.在Fireasy Entity的元数据定义PropertyMapInfo类中,有IsDeletedKey这样一个属性,表示使用此列作为逻辑删除标记.对应的,在查询数据的时候,需要把这个标记拼到LINQ里去,不

Fireasy版本发布 1.5.40.42030

开发指南 代码生成 1.5.40.42030  2015-4-1 ** Fireasy.Common1.完善To方法,可以对可枚举类型进行转换2.完善Json序列化对动态类型的支持 ** Fireasy.Data3.增加Update方法的另一个版本 ** Fireasy.Data.Entity4.增强Linq扩展方法Order和ThenBy5.实体增加All扩展方法,可以简便返回所有属性6.仓储增加Include.Associate和Batch方法,EntityContext增加Apply方法7

使用AOP改进Fireasy实体模型

目前Fireasy中的实体模型虽然用CodeBuilder生成,但是还是比较庞大,手工维护起来比较吃力,因此一直想使用比较简单的一种模型,比如属性只有 get 和 set,最多加个ColumnAttribute之类的来做映射.但这样做的话,属性修改这一特性就无法保持了,即更新实体时不知道哪些属性有改动过. 我们先看一下目前生成的实体类代码: /// <summary> /// 部门 实体类. /// </summary> [Serializable] [EntityMapping(

通过实例学习Fireasy开发(补充)

      本文目录 通过实例学习Fireasy开发(上篇) 通过实例学习Fireasy开发(中篇) 通过实例学习Fireasy开发(下篇)       通过实例学习Fireasy开发(补充) 前面的功能已经开发完成了,但是这里专门用一章来进行补充.       一.EasyUI验证 不知道你有没有发现,我们虽然在EmployeeMetada里加了验证特性RequiredAttribute.StringLengthAttribute,但是页面并没有在data-options里生成validTyp

通过实例学习Fireasy开发(上篇)

Fireasy一直在发布新版本,但是怎么用,到底好不好用,估计大家都有这样的疑惑.所以,今天花点时间通过一个简单的示例一步一步来介绍fireasy的用法. 首先有必要介绍一下Fireasy的组件构成: Fireasy.Common 公共组件库,主要包含缓存管理.日志管理.序列化.动态编译.扩展方法等. Fireasy.Data 数据库组件库,提供数据库的操作,语法.批量插入.数据库构架等. Fireasy.Data.Entity 实体组件库,ORMapper.LINQ解析.数据上下文.树实体持久

通过实例学习Fireasy开发(下篇)

      本文目录 通过实例学习Fireasy开发(上篇) 通过实例学习Fireasy开发(中篇)       通过实例学习Fireasy开发(下篇) 通过实例学习Fireasy开发(补充)       一.员工列表页面 EmployeeList.aspx页面的需求是这样的,左边是部门树,右边是员工列表,点击部门节点后右边显示该部门下面的员工,同时提供岗位.生日期间查询. 对table:#dg处进行改造,加下一个treegrid列表: <div data-options="region:

通过实例学习Fireasy开发(中篇)

      本文目录 通过实例学习Fireasy开发(上篇) 通过实例学习Fireasy开发(下篇) 通过实例学习Fireasy开发(补充) 上篇我们介绍了进行Fireasy开发的前期准备,接下来,我们将通过两个小功能来演示怎么进行业务开发.       一.部门列表页面 部门是树型结构,Fireasy采用了00010001这样的编码来支持树结构实体的快速开发. 1. 修改Dept类(使用Dept_Ex分部类文件),在类名称上添加EntityTreeMappingAttribute特性,Inne

善用 NuGet 程序包管理器控制台

每种集成开发环境都会提供扩展包的下载与安装,VS (微软可视化集成开发环境) 下的叫程序包管理控制台,我们把他叫做<牛干 程序包管理控制台>. 在 工具=>NuGet 程序包管理器=> 程序包管理控制台,打开命令窗口. 这两条命令必学必会 PM> Install-Package 包 [-参数]PM> UnInstall-Package 包 [-参数] 参数可选(指定版本号或项目名) PM> Install-Package Microsoft.AspNet.Mvc

代码生成器(CodeBuilder) 2 正式发布

CodeBuilder是一个通过获取数据库表和字段定义,通过模板转换生成三层结构.实体模型等代码的工具. 以其他同类的代码生成器相比,CodeBuilder具有以下几个不凡的特性: 多数据源 支持 SqlServer.Oracle.MySQL.SQLite.PostgreSQL.Firebird,或 OleDb 提供的驱动,而是还可以使用 Power Designer 文档. 如果以上这些无法满足你,那么你可以自己动手,做到自己满意为止. 架构可扩展 表和字段的属性可以通过代码文件来扩展,模板中