写了博文ASP.NET MVC 3升级至MVC 5.1的遭遇:“已添加了具有相同键的项”之后,继续看着System.Web.Mvc.JsonValueProviderFactory的开源代码。

越看越不顺眼,越看心里越不爽!不爽的地方主要有两个:

1)依然在使用用性能低下且不开源的JavaScriptSerializer!打死也不用Json.NET!

2)作为一个工厂类,JsonValueProviderFactory实现复杂,而且工厂生产出的产品DictionaryValueProvider(IValueProvider的一个实现)也很复杂。

【先看第一个不爽】

JsonValueProviderFactory的工作之一是对json字符串进行反序列化,而Json.NET的反序列化性能远超JavaScriptSerializer,请看下图:

而微软MVC开发人员依然不思进取,用自家东西的痴心不改,继续用着JavaScriptSerializer。

private static object GetDeserializedObject(ControllerContext controllerContext)
{
//...
JavaScriptSerializer serializer = new JavaScriptSerializer();
object jsonData = serializer.DeserializeObject(bodyText);
return jsonData;
}

仅凭这一点,就让我产生了这样的冲动——基于Json.NET自己实现一个JsonValueProviderFactory。

【再看第二个不爽】

作为一个工厂类,JsonValueProviderFactory继承自ValueProviderFactory,重载了ValueProviderFactory的抽象方法GetValueProvider,返回接口IValueProvider的一个实现。(ControllerActionInvoker就是通过IValueProvider接口根据key得到Action各个参数的值)

IValueProvider的代码如下:

namespace System.Web.Mvc
{
public interface IValueProvider
{
bool ContainsPrefix(string prefix);
ValueProviderResult GetValue(string key);
}
}

接口很简单,先检查prefix是否存在,如果存在通过key取值。

JsonValueProviderFactory返回的DictionaryValueProvider就是干这个活的,但是为了生产DictionaryValueProvider,JsonValueProviderFactory进行了复杂的搬箱子操作,不仅用到了递归,而且还用了多个IDictionary<string, object>,代码让人看得头晕。

再看看DictionaryValueProvider的实现,也是复杂,而且还用到了PrefixContainer。

简单算个账:JsonValueProviderFactory的代码用了120行,DictionaryValueProvider的代码用了63行,PrefixContainer的代码用了219,一共用了402行代码(包含空行与命名空间的引用)。有些奢侈!

需要这么复杂吗?有更简单的解决方法吗?

【冲动不如行动】

解决问题的关键在于如何以更简单的方法实现IValueProvider的两个操作——ContainsPrefix与GetValue。

要实现这两个操作,先要摸清prefix与key的规律。于是先实现一个MockValueProvider,通过日志记录ControllerActionInvoker调用这个接口时使用的参数。

通过日志信息,找出了这样的规律:

1. 如果Action的参数是这样的:

public ActionResult PostList(AggSiteModel model)
{
}

ControllerActionInvoker会这样调用:

ContainsPrefix("model") -> 如果返回False -> 以AggSiteModel的属性名称依次ContainsPrefix,比如ContainsPrefix("PageTitle")【注:PageTitle是AggSiteModel的一个属性】 -> 如果返回True -> 会以prefix为key调用GetValue。

2. 如果Action的参数是数组:

public ActionResult PostList(AggSiteModel[] model)
{
}

ControllerActionInvoker会这样调用:

ContainsPrefix("[0]") -> 如果返回True -> ContainsPrefix("[0].PageTitle") -> 如果返回True -> GetValue("[0].PageTitle")

3. 依然是第1种的Action参数形式,只不过AggSiteModel有聚合。

public class AggSiteModel
{
public string PageTitle { get; set; }
public PagingBuilder Paging { get; set; }
}

ControllerActionInvoker会这样调用:

ContainsPrefix("model") -> 如果返回False -> ContainsPrefix("Paging.PageTitle")

看到这些key的特征,想到了Json.NET中的SelectTokens:

/// <summary>
/// Selects a collection of elements using a JPath expression.
/// </summary>
/// <param name="path">
/// A <see cref="String"/> that contains a JPath expression.
/// </param>
/// <returns>An <see cref="IEnumerable{JToken}"/> that contains the selected elements.</returns>
public IEnumerable<JToken> SelectTokens(string path)
{
return SelectTokens(path, false);
}

这是里key竟然与JPath惊人的相似!

看来Json.NET不仅可以搞定JsonValueProviderFactory,还可以搞定DictionaryValueProvider+PrefixContainer,实现代码应该不会超过100行。

【基于Json.NET实现CnblogsJsonValueProviderFactory】

public class CnblogsJsonValueProviderFactory : ValueProviderFactory
{
public override IValueProvider GetValueProvider(ControllerContext controllerContext)
{
if (controllerContext == null) throw new ArgumentNullException("controllerContext"); if (!controllerContext.HttpContext.Request.ContentType.
StartsWith("application/json", StringComparison.OrdinalIgnoreCase))
{
return null;
} var bodyText = string.Empty;
using (var reader = new StreamReader(controllerContext.HttpContext.Request.InputStream))
{
bodyText = reader.ReadToEnd();
}
if (string.IsNullOrEmpty(bodyText)) return null; return new JObjectValueProvider(bodyText.StartsWith("[") ?
JArray.Parse(bodyText) as JContainer :
JObject.Parse(bodyText) as JContainer);
}
} public class JObjectValueProvider : IValueProvider
{
private JContainer _jcontainer; public JObjectValueProvider(JContainer jcontainer)
{
_jcontainer = jcontainer;
} public bool ContainsPrefix(string prefix)
{
return _jcontainer.SelectToken(prefix) != null;
} public ValueProviderResult GetValue(string key)
{
var jtoken = _jcontainer.SelectToken(key);
if (jtoken == null || jtoken.Type == JTokenType.Object) return null;
return new ValueProviderResult(jtoken.ToObject<object>(), jtoken.ToString(), CultureInfo.CurrentCulture);
}
}

包含空行与命名空间的引用,一共只有61行代码,远远少于MVC中的402行代码。

在项目中使用这个CnblogsJsonValueProviderFactory:

protected void Application_Start(Object sender, EventArgs e)
{
ValueProviderFactories.Factories.Remove(ValueProviderFactories.Factories.
OfType<JsonValueProviderFactory>().FirstOrDefault());
ValueProviderFactories.Factories.Add(new CnblogsJsonValueProviderFactory());
}

【美中不足】

Json.NET中的SelectTokens的path参数区分大小写,使用CnblogsJsonValueProviderFactory,在js中写json时,大小写必须要匹配。

看了一下Json.NET的开源代码,发现是与下面的代码有关:

internal class JPropertyKeyedCollection : Collection<JToken>
{
private static readonly IEqualityComparer<string> Comparer = StringComparer.Ordinal;
}

如果把StringComparer.Ordinal改为StringComparer.OrdinalIgnoreCase就能解决问题,但是不知道会不会给Json.NET的性能带来影响。

严格区分大小写也能接受,可以让代码更规范一些。

基于Json.NET自己实现MVC中的JsonValueProviderFactory的更多相关文章

  1. MVC中处理Json和JS中处理Json对象

    MVC中处理Json和JS中处理Json对象 ASP.NET MVC 很好的封装了Json,本文介绍MVC中处理Json和JS中处理Json对象,并提供详细的示例代码供参考. MVC中已经很好的封装了 ...

  2. Spring MVC 中的基于注解的 Controller【转】

    原文地址:http://my.oschina.net/abian/blog/128028 终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 H ...

  3. Spring MVC中基于注解的 Controller

         终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法以响 ...

  4. Spring MVC 中的基于注解的 Controller(转载)

           终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法 ...

  5. MVC中使用Entity Framework 基于方法的查询学习笔记 (一)

    EF中基于方法的查询方式不同于LINQ和以往的ADO.NET,正因为如此,有必要深入学习一下啦.闲话不多说,现在开始一个MVC项目,在项目中临床学习. 创建MVC项目 1.“文件”--“新建项目”-- ...

  6. 【转】MVC中处理Json和JS中处理Json对象

    事实上,MVC中已经很好的封装了Json,让我们很方便的进行操作,而不像JS中那么复杂了. MVC中: public JsonResult Test() { JsonResult json = new ...

  7. .net mvc中json的时间格式

    .net mvc中,通过return Json(DateTime.Now); 返回到视图时,日期格式变成这样,"/Date(1245398693390)/",如果要显示指定的日期时 ...

  8. 超高性能的json序列化之MVC中使用Json.Net

    先不废话,直接上代码 Asp.net MVC自带Json序列化 /// <summary> /// 加载组件列表 /// </summary> /// <param na ...

  9. 解决MVC中JSON字符长度超出限制的异常

    解决MVC中JSON字符长度超出限制的异常 解决方法如下: <configuration> <system.web.extensions> <scripting> ...

随机推荐

  1. [LeetCode] 398. Random Pick Index ☆☆☆

    Given an array of integers with possible duplicates, randomly output the index of a given target num ...

  2. poj 1419 Graph Coloring

    http://poj.org/problem?id=1419 题意: 一张图黑白染色,相邻点不能都染黑色,最多能染几个黑色点 最大点独立集 但是图不能同构为二分图,不能用二分图匹配来做 那就爆搜吧 还 ...

  3. openGL笔记-画基本图形

    #include "iostream" #include <GL/glut.h> #include<cmath> #include<vector> ...

  4. Coffeescript的安装与编译

    安装 npm install -g coffee-script 在cmd中输入coffee可以进入coffeescript的命令行模式(REPL),然而到我写完这篇博文为止,我觉得这并没有什么卵用 C ...

  5. ubuntu环境下eclipse的安装以及hadoop插件的配置

    ubuntu环境下eclipse的安装以及hadoop插件的配置 一.eclipse的安装 在ubuntu桌面模式下,点击任务栏中的ubuntu软件中心,在搜索栏搜索eclipse 注意:安装过程需要 ...

  6. 多页面应用_vue

    vue框架 vue:解决前端大型应用的开发,将之前几十个.几百个.更多的HTML页面,集成为1个HTML页面(当页面应用) jQuery:前端方法库. bootstrap:UI组件库. angular ...

  7. java.lang.IllegalArgumentException: class com.beisheng.maerte.mode.MyCouponVO declares multiple JSON fields named count

    原因是:子类和父类有相同的字段属性.解决办法:(1)将父类中的该字段去掉(不要),或者在需要打印的字段上加上注解@Expose (2):由于我报错的类都是在jar包里面,所以第一种方法不好使.只好采用 ...

  8. 延迟注入工具(python)

    延迟注入工具(python) #!/usr/bin/env python # -*- coding: utf-8 -*- # 延迟注入工具 import urllib2 import time imp ...

  9. 【TortoiseSVN】windows中连接SVN服务器的工具

    1.下载安装包: 可以到我的服务器地址进行下载,有32和64位的安装包: http://qiaoliqiang.cn/fileDown/TortoiseSVN-1.8.8.25755-win32-sv ...

  10. Shell-输入密码转换为*

    Code: read -p "请输入使用者都名称:" USER echo -e "请输入使用者密码: \c" while : ;do char=` #这里是反引 ...