美文网首页ABP程序员
ABP入门系列(8)——Json格式化

ABP入门系列(8)——Json格式化

作者: 圣杰 | 来源:发表于2017-01-27 17:50 被阅读3539次

    ABP入门系列目录——学习Abp框架之实操演练
    源码路径:Github-LearningMpaAbp


    讲完了分页功能,这一节我们先不急着实现新的功能。来简要介绍下Abp中Json的用法。为什么要在这一节讲呢?当然是做铺垫啊,后面的系列文章会经常和Json这个东西打交道。

    一、Json是干什么的

    JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。 易于人阅读和编写。同时也易于机器解析和生成。JSON采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, JavaScript, Perl, Python等)。 这些特性使JSON成为理想的数据交换语言。

    Json一般用于表示:
    名称/值对
    {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"}
    数组

    {
        "people":[
            {"firstName":"Brett","lastName":"McLaughlin","email":"aaaa"},
            {"firstName":"Jason","lastName":"Hunter","email":"bbbb"},
            {"firstName":"Elliotte","lastName":"Harold","email":"cccc"}
        ]
    }
    ```
    
    #二、Asp.net Mvc中的JsonResult
    
    Asp.net mvc中默认提供了`JsonResult`来处理需要返回Json格式数据的情况。
    一般我们可以这样使用:
    ```
    public ActionResult Movies()
    {
        var movies = new List<object>();
    
        movies.Add(new { Title = "Ghostbusters", Genre = "Comedy", ReleaseDate = new DateTime(2017,1,1)  });
        movies.Add(new { Title = "Gone with Wind", Genre = "Drama", ReleaseDate = new DateTime(2017, 1, 3) });
        movies.Add(new { Title = "Star Wars", Genre = "Science Fiction", ReleaseDate = new DateTime(2017, 1, 23) });
    
        return Json(movies, JsonRequestBehavior.AllowGet);
    }
    
    ```
    其中`Json()`是Controller基类中提供的虚方法。
    返回的json结果格式化后为:
    ```
    [
      {
        "Title": "Ghostbusters",
        "Genre": "Comedy",
        "ReleaseDate": "\/Date(1483200000000)\/"
      },
      {
        "Title": "Gone with Wind",
        "Genre": "Drama",
        "ReleaseDate": "\/Date(1483372800000)\/"
      },
      {
        "Title": "Star Wars",
        "Genre": "Science Fiction",
        "ReleaseDate": "\/Date(1485100800000)\/"
      }
    ]
    ```
    仔细观察返回的json结果,有以下几点不足:
    * 返回的字段大小写与代码中一致。这就要求我们在前端中也要与代码中用一致的大小写进行取值(`item.Title`,`item.Genre`,`item.ReleaseDate`)。
    * 不包含成功失败信息:如果我们要判断请求是否成功,我们要手动通过获取json数据包的`length`获取。
    * 返回的日期未格式化,在前端还需自行格式化输出。
    
    #三、Abp中对Json的封装
    
    所以Abp封装了`AbpJsonResult`继承于`JsonResult`,其中主要添加了两个属性:
    * CamelCase:大小驼峰(默认为true,即小驼峰格式)
    * Indented :是否缩进(默认为false,即未格式化)
    
    并在`AbpController`中重载了`Controller`的`Json()`方法,强制所有返回的Json格式数据为`AbpJsonResult`类型,并提供了`AbpJson()`的虚方法。
    ```
    /// <summary>
    /// Json the specified data, contentType, contentEncoding and behavior.
    /// </summary>
    /// <param name="data">Data.</param>
    /// <param name="contentType">Content type.</param>
    /// <param name="contentEncoding">Content encoding.</param>
    /// <param name="behavior">Behavior.</param>
    protected override JsonResult Json(object data, string contentType, 
        Encoding contentEncoding, JsonRequestBehavior behavior)
    {
        if (_wrapResultAttribute != null && !_wrapResultAttribute.WrapOnSuccess)
        {
            return base.Json(data, contentType, contentEncoding, behavior);
        }
    
        return AbpJson(data, contentType, contentEncoding, behavior);
    }
    
    protected virtual AbpJsonResult AbpJson(
        object data,
        string contentType = null,
        Encoding contentEncoding = null,
        JsonRequestBehavior behavior = JsonRequestBehavior.DenyGet,
        bool wrapResult = true,
        bool camelCase = true,
        bool indented = false)
    {
        if (wrapResult)
        {
            if (data == null)
            {
                data = new AjaxResponse();
            }
            else if (!(data is AjaxResponseBase))
            {
                data = new AjaxResponse(data);
            }
        }
    
        return new AbpJsonResult
        {
            Data = data,
            ContentType = contentType,
            ContentEncoding = contentEncoding,
            JsonRequestBehavior = behavior,
            CamelCase = camelCase,
            Indented = indented
        };
    }
    ```
    
    在ABP中用Controler继承自AbpController,直接使用`return Json()`,将返回Json结果格式化后:
    ```
    {
      "result": [
        {
          "title": "Ghostbusters",
          "genre": "Comedy",
          "releaseDate": "2017-01-01T00:00:00"
        },
        {
          "title": "Gone with Wind",
          "genre": "Drama",
          "releaseDate": "2017-01-03T00:00:00"
        },
        {
          "title": "Star Wars",
          "genre": "Science Fiction",
          "releaseDate": "2017-01-23T00:00:00"
        }
      ],
      "targetUrl": null,
      "success": true,
      "error": null,
      "unAuthorizedRequest": false,
      "__abp": true
    }
    ```
    其中result为代码中指定返回的数据。其他几个键值对是ABP封装的,包含了是否认证、是否成功、错误信息,以及目标Url。这几个参数是不是很sweet。
    也可以通过调用`return AbpJson()`来指定参数进行json格式化输出。
    
    仔细观察会发现日期格式还是怪怪的。`2017-01-23T00:00:00`,多了一个T。查看`AbpJsonReult`源码发现调用的是**Newtonsoft.Json**序列化组件中的`JsonConvert.SerializeObject(obj, settings);`进行序列化。
    
    
    查看Newtonsoft.Json官网介绍,日期格式化输出,需要指定`IsoDateTimeConverter`的`DateTimeFormat`即可。
    ```
    IsoDateTimeConverter timeFormat = new IsoDateTimeConverter();
                timeFormat.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    
    JsonConvert.SerializeObject(dt, Formatting.Indented, timeFormat)
    ```
    
    那在我们Abp中我们怎么去指定这个`DateTimeFormat `呢?
    ABP中提供了`AbpDateTimeConverter`类继承自`IsoDateTimeConverter`。
    但查看ABP中集成的Json序列化扩展类:
    ```
    public static class JsonExtensions
      {
        /// <summary>Converts given object to JSON string.</summary>
        /// <returns></returns>
        public static string ToJsonString(this object obj, bool camelCase = false, bool indented = false)
        {
          JsonSerializerSettings settings = new JsonSerializerSettings();
          if (camelCase)
            settings.ContractResolver = (IContractResolver) new CamelCasePropertyNamesContractResolver();
          if (indented)
            settings.Formatting = Formatting.Indented;
          settings.Converters.Insert(0, (JsonConverter) new AbpDateTimeConverter());
          return JsonConvert.SerializeObject(obj, settings);
        }
      }
    ```
    明显没有指定DateTimeFormat,那我们就只能自己动手了,具体代码请参考[4种解决json日期格式问题的办法](http://www.cnblogs.com/best/p/3537030.html)的第四种办法。
    
    
    当有异常发生时,Abp返回的Json格式化输出以下结果:
    ```
    {
      "targetUrl": null,
      "result": null,
      "success": false,
      "error": {
        "message": "An internal error occured during your request!",
        "details": "..."
      },
      "unAuthorizedRequest": false
    }
    ```
    
    
    **当不需要abp对json进行封装包裹怎么办?**
    简单。只需要在方法上标记**`[DontWrapResult]`**特性即可。这个特性其实是一个快捷方式用来告诉ABP不要用AbpJsonResult包裹我,看源码就明白了:
    ```
    namespace Abp.Web.Models
    {
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
        public class DontWrapResultAttribute : WrapResultAttribute
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="DontWrapResultAttribute"/> class.
            /// </summary>
            public DontWrapResultAttribute()
                : base(false, false)
            {
    
            }
        }
        
        /// <summary>
        /// Used to determine how ABP should wrap response on the web layer.
        /// </summary>
        [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Method)]
        public class WrapResultAttribute : Attribute
        {
            /// <summary>
            /// Wrap result on success.
            /// </summary>
            public bool WrapOnSuccess { get; set; }
    
            /// <summary>
            /// Wrap result on error.
            /// </summary>
            public bool WrapOnError { get; set; }
    
            /// <summary>
            /// Log errors.
            /// Default: true.
            /// </summary>
            public bool LogError { get; set; }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="WrapResultAttribute"/> class.
            /// </summary>
            /// <param name="wrapOnSuccess">Wrap result on success.</param>
            /// <param name="wrapOnError">Wrap result on error.</param>
            public WrapResultAttribute(bool wrapOnSuccess = true, bool wrapOnError = true)
            {
                WrapOnSuccess = wrapOnSuccess;
                WrapOnError = wrapOnError;
    
                LogError = true;
            }
        }
    }
    ```
    在`AbpResultFilter`和`AbpExceptionFilter`过滤器中会根据`WrapResultAttribute`、`DontWrapResultAttribute `特性进行相应的过滤。
    
    #四、Json日期格式化
    第一种办法:前端JS转换:
    ```
        //格式化显示json日期格式
        function showDate(jsonDate) {
            var date = new Date(jsonDate);
            var formatDate = date.toDateString();
            return formatDate;
        }
    ```
    
    第二种办法:在Abp的WepApiModule(模块)中指定JsonFormatter的时间序列化时间格式。(已失效)
    ```
    GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.DateFormatString ="yyyy-MM-dd HH:mm:ss";
    ```
    
    PS:这种方法仅对WebApi有效。
    
    第三种办法:
    ```
    var converters = Configuration.Modules.AbpWebApi().HttpConfiguration.Formatters.JsonFormatter.SerializerSettings.Converters;
               foreach (var converter in converters)
               {
                  if (converter is AbpDateTimeConverter)
                  {
                      var tmpConverter = converter as AbpDateTimeConverter;
                       tmpConverter.DateTimeFormat = "yyyy-MM-dd HH:mm:ss";
                  }
    }
    ```
    
    ----
    
    #总结
    
    本节主要讲解了以下几个问题:
    1. Asp.net中JsonResult的实现。
    2. ABP对JsonResult的再封装,支持指定大小驼峰及是否缩进进行Json格式化。
    3. 如何对DateTime类型对象进行格式化输出。
      * Web层通过拓展AbpJsonResult,指定时间格式。
      * 前端,通过将Json日期转换为js的Date类型,再格式化输出。
      * WebApi,通过在Moduel中指定DateFormatString。

    相关文章

      网友评论

      • 543d66b67014:对于最新版本(当前v3.8)非core下使用,以上方法均失效了。解决方法:
        在WebApiModule中:
        public override void PostInitialize()
        {
        Configuration.Modules.AbpWebApi().HttpConfiguration.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new MyAbpCamelCasePropertyNamesContractResolver();
        }
        其中MyAbpCamelCasePropertyNamesContractResolver需自定义:
        public class MyAbpCamelCasePropertyNamesContractResolver : AbpCamelCasePropertyNamesContractResolver
        {
        protected override void ModifyProperty(MemberInfo member, JsonProperty property)
        {
        base.ModifyProperty(member, property);

        if(property.Converter is AbpDateTimeConverter converter)
        converter.DateTimeFormat = "yyyy-MM-dd";
        }
        }
      • p了个f:还有一种添加特性的方式来进行时间序列化。

        1、在Application层的根目录添加DateFormat类
        /// <inheritdoc />
        /// <summary>
        /// 日期格式化,格式化 yyyy-MM-dd
        /// </summary>
        public class DateFormat : IsoDateTimeConverter
        {
        public DateFormat()
        {
        base.DateTimeFormat = "yyyy-MM-dd HH:MM:ss";
        }
        }

        2、在需要序列化的日期字段添加特性: [JsonConverter(typeof(DateFormat))]
        [AutoMapFrom(typeof(Province))]
        public class ProvinceDto : EntityDto
        {
        /// <summary>
        /// 省份简称
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// 城市名称
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 创建时间
        /// </summary>
        [JsonConverter(typeof(DateFormat))]
        public DateTime CreateTime { get; set; }
        }

        3、调用还是按照以前那样。
        retun Json(对象)
        圣杰: @p了个f 👍
      • 夼中玥:第二种办法:在Abp的WepApiModule(模块)中指定JsonFormatter的时间序列化时间格式。没找到在哪里加
        圣杰:@夼中玥 WebApi项目中的Module里,可以自行查阅源码。

      本文标题:ABP入门系列(8)——Json格式化

      本文链接:https://www.haomeiwen.com/subject/ckgxittx.html