• 浅析ASP.Net Web API的Formatter


    本文的内容很多来自:http://www.asp.net/web-api/overview/formats-and-model-binding/json-and-xml-serialization

    我狗尾续貂地添加些额外的说明与见解,或许对英文不太好的朋友有些用。

    ASP.net的Web API和传统MVC网站有个很大的不同就是多了Formatter(格式化器),其实Formatter并不是什么新鲜东西,我觉得它只是另一种 Model绑定方法,简单地说,就是HTTP的数据到.Net对象的关系。MVC的Model Binding做过MVC网站的人应该都很熟悉了,就是尝试从HTTP请求中找到一些“key=value”的键值对,根据一些约定,匹配到Model的属性或Action参数上去,如果Model中有个值类型(例如int)的属性,而HTTP请求中又没有,那么会出现ArgumentException异常,并默认显示出大家非常熟悉的YSOD(Yellow Screen Of Death)。

    我想微软弄格式化器的原因是想让数据绑定具有更强的可伸缩性,想像一下:你可以用格式化器自定义日期的输出格式;通过格式化器,将一个字符串转变为一张小png图片输出;还有更清晰和明确地定义数据等等。

    一、有哪些Formatter?

    创建一个默认的MVC4 Web API工程,在WebApiConfig中加入:

        foreach (var fmt in config.Formatters)
        {
            System.Diagnostics.Debug.WriteLine(fmt.GetType());
        }

    默认情况下,能在Output窗口下看到:

    System.Net.Http.Formatting.JsonMediaTypeFormatter
    System.Net.Http.Formatting.XmlMediaTypeFormatter
    System.Net.Http.Formatting.FormUrlEncodedMediaTypeFormatter
    System.Web.Http.ModelBinding.JQueryMvcFormUrlEncodedFormatter

    能看到这些格式化器,一眼就看出来,JsonMediaTypeFormatter是用来负责JSON的序列化/反序列化的,XmlMediaTypeFormatter是用来负责XML的序列化/反序列化的,FormUrlEncodedMediaTypeFormatter用来处理URL带的请求参数,JQueryMvcFormUrlEncodedFormatter的处理内容应该跟表单数据相关。

    二、Web API是怎么处理XML的?

    一开始我以为Web API是用System.Xml.XmlSerializer来处理XML,但后来发现不是(默认不是)。很简单的证据就是:

    XmlSerializer默认不能序列化IEnumerable,它会报错说IEnumerable是接口,如果你要序列化,恐怕得自行实现IXmlSerializable接口;但默认情况下,Web API能轻松地将IEnumerable<Order>这样的类型序列化为XML并返回给客户端。那Web API默认用了什么XML序列化器呢?——DataContractSerializer。

    创建一个最简单的控制台程序,然后用下面的代码测试一下:

        IEnumerable<string> testobj = new string[] { "aaa", "bbb", "ccc" };
        DataContractSerializer ser = new DataContractSerializer(testobj.GetType());
        ser.WriteObject(Console.OpenStandardOutput(), testobj);
    
        //Error
        //XmlSerializer ser = new XmlSerializer(test.GetType());
        //ser.Serialize(Console.Out, test);

    我个人觉得使用DataContractSerializer更好,也就是Web API默认的设置,很明显,能序列化IEnumerable对我们来说太必要了。但有些习惯了使用XmlSerializer相关序列化特性(如[XmlAttribute],[XmlRoot],[XmlElement]之类)的人会比较喜欢XmlSerializer,要这样做也很简单,只需要在WebApiConfig.cs中加入:

    config.Formatters.XmlFormatter.UseXmlSerializer = true;

    DataContractSerializer的序列化特性其实也很丰富,WCF的相关文章会有很详尽的描述,这里就不展开了。

    三、用XML还是JSON?

    Web API会自动选择,选择的依据是请求的报文的HTTP Header:

    Accept: application/json
    Content-type: application/json

    Content-type表示请求的body中的数据类型,是JSON还是XML,还是图片或者别的;Accept表示这个请求所期待得到的数据类型。上面的请求表示请求报文body中的数据为JSON,所期待的返回的数据类型也是JSON。如果要XML,那么很简单,把application/json改为application/xml即可。

    我通过实验发现,Web API还有一条规则:如果请求的的数据类型由于某些原因无法正常得到,那么尝试以别的数据类型来返回。比如请求XML,而XML在序列化的时候出现了问题,那么Web API会尝试用JSON返回数据。

    四、序列化为XML失败的可能原因

    XML和JSON,我更喜欢JSON,因为简洁,XML包含了太多的标签、Schema以及namespace,很容易让人眼花缭乱,但有些客户端处理XML更为便利,所以我们还是要考虑一下XML的序列化问题。

    (1)缺乏不带参数的构造函数

    如果你的class缺乏不带参数的构造函数,那么序列化成XML的时候就会报错,我一开始也想不明白为什么会这样,要个不带参数构造函数干什么呢?直接把里面该序列化的东西序列化好不就OK了吗?大家花一分钟时间想想看啊!反正我想不出来为什么,你要是能想出来的话说明你比我聪明,呵呵……OK,言归正传了,原因其实很简单:反序列化!

    (2)类型没有被定义为public

    这个限制的原因可能是:序列化器认为,对非public的类型序列化会破坏数据的封装性。

    五、JSON的序列化器以及控制时间日期的格式

    MVC4的正式版跟之前的Beta版有不少差异,其中一个就是把JSON的序列化器默认为Newtonsoft.Json,看来微软现在想得很开,都开始往自己的开发环境里加入第三方的库了。但需要知道Newtonsoft.Json则个库也是在不断更新的,最好用NuGet来获取其最新版本。

    我个人认为Newtonsoft.Json是相当不错的,对IEnumerable,IDictionary等接口都支持得很好,下面是个简单的控制台例子,我们可以用它来观察JSON的序列化情况:

    Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(testObject)); 

    只有一行,是否非常简单?具体Newtonsoft.Json的使用可以参考它的官方站点:http://json.codeplex.com

    我这里特别要说明的一个问题是关于JSON的日期格式问题,众所周知,使用JSON的客户端大多是浏览器,浏览器的Javascript对日期格式的处理能力是要远远差于C#/Java之类的,默认情况下,日期会被序列化为“2012-10-12T13:18:20.1656358+08:00”这样的格式,直接把这个显示出来明显不够友好,假如我光是想显示“2012-10-12”,是不是就得用Javascript去操作这个字符串截取前10个字符?这样很不优雅并且不能确保一定可行,如果默认格式不是这样呢?对吧。后来我研究出一种方法,能很好解决这个问题(花了不少时间来搜索,咳咳……),我们来给Newtonsoft.Json添些料:

    namespace Newtonsoft.Json.Converters
    {
        public class SimpleDateConverter : DateTimeConverterBase
        {
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                DateTime date = new DateTime();
                DateTime.TryParse((string)reader.Value, out date);
                return date;
            }
    
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
            {
                writer.WriteValue(((DateTime)value).ToString("yyyy-MM-dd"));
            }
        }
    }

    然后像这样修饰DateTime类型的属性:

        [JsonConverter(typeof(SimpleDateConverter))]
        public DateTime Dt {get;set;}

    六、测试序列化和反序列化的一致性

    序列化和反序列化必须保持一致,否则程序就会乱套,如何确保?当然是测试一下,本文的开头提供的那个链接指向的那篇文章,在文章最后就提供了一套很不错的方法,使用起来很简单,我这里就借用下它上面的代码:

    string Serialize<T>(MediaTypeFormatter formatter, T value)
    {
        // Create a dummy HTTP Content.
        Stream stream = new MemoryStream();
        var content = new StreamContent(stream);
        /// Serialize the object.
        formatter.WriteToStreamAsync(typeof(T), value, stream, content.Headers, null).Wait();
        // Read the serialized string.
        stream.Position = 0;
        return content.ReadAsStringAsync().Result;
    }
    
    T Deserialize<T>(MediaTypeFormatter formatter, string str) where T : class
    {
        // Write the serialized string to a memory stream.
        Stream stream = new MemoryStream();
        StreamWriter writer = new StreamWriter(stream);
        writer.Write(str);
        writer.Flush();
        stream.Position = 0;
        // Deserialize to an object of type T
        return formatter.ReadFromStreamAsync(typeof(T), stream, null, null).Result as T;
    }
    
    // Example of use
    void TestSerialization()
    {
        var value = new Person() { Name = "Alice", Age = 23 };
        var xml = new XmlMediaTypeFormatter();
        string str = Serialize(xml, value);
        var json = new JsonMediaTypeFormatter();
        str = Serialize(json, value);
    
        // Round trip
        Person person2 = Deserialize<Person>(json, str);
    }

    这段代码写得很不错!

    七,只返回XML或只返回JSON

    如果你有特殊的需要,只允许返回XML或只允许返回JSON的话,(虽然很不建议这样)那么可以把对应的formatter拿掉即可。例如你可以这样拿掉XML Formatter(代码写在WebApiConfig.cs中):

        foreach (var fmt in config.Formatters)
        {
            
            System.Diagnostics.Debug.WriteLine(fmt.GetType());
            if (fmt is System.Net.Http.Formatting.XmlMediaTypeFormatter)
            {
                config.Formatters.Remove(fmt);
                break;
            }
        }
  • 相关阅读:
    mysql原生语句基础知识
    利用layui前端框架实现对不同文件夹的多文件上传
    简述layui前端ui框架的使用
    利用bootstrap-select.min.js实现bootstrap下拉列表的单选和多选
    使用pycharm进行远程开发部署调试设置 与 远程部署调试是否必须使用远程主机的解释器?
    博客园积分规则
    mysql 数据库的备份与恢复
    flask 利用flask_wtf扩展 创建web表单
    jquery ajax几种书写方式的总结
    LightSpeed 的Left Join Bug解决方案
  • 原文地址:https://www.cnblogs.com/guogangj/p/2721343.html
Copyright © 2020-2023  润新知