写了博文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. python中super与成员属性

    super的使用直接看例子: class A(): def __init__(self, a): print('init A...') self.a = a class B(A): def __ini ...

  2. jenkins 相关默认信息

    (1) 默认安装目录    /usr/lib/jenkins/:jenkins安装目录,WAR包会放在这里.  ( 2 )  默认配置文件 /etc/sysconfig/jenkins:jenkins ...

  3. Linux命令练习.ziw

    2017年1月10日, 星期二 Linux命令练习 1.统计/usr/bin/目录下的文件个数: # ls /usr/bin | wc -l 判断 /home/goldin目录是否有文件 2.取出当前 ...

  4. spring-boot RestTemplate 连接池

    以前我们项目都是基于Apache HttpClient 连接池进行web 接口调用,后来用spring-boot, 发现 RestTemplate 挺好用. 简单介绍下: 什么是RestTemplat ...

  5. 小程序web-view wx.miniProgram.postMessage 坑记录

    web-view吧,其实微信官方应该是非常不支持在小程序上嵌套web的,它希望你直接用小程序上的代码,而放弃web的实现,当然,也是为了防止用小程序去嵌套别的广告页面.所以官方对web-view的操作 ...

  6. Mysql查看建表语句以及修改引擎

    更多内容推荐微信公众号,欢迎关注: 1 查看系统支持的存储引擎 show engines; 2 查看表使用的存储引擎 两种方法: a.show table status from db_name wh ...

  7. UNIX环境高级编程 第10章 信号

    SIGSTOP和SIGKILL区别是:前者是使进程暂时停止,即中止,也就是说使进程暂停,将进程挂起,比如你在终端里面执行一个脚本或者程序,执行到一半,你想暂停一下,你按下ctrl+z,就会导致终端发送 ...

  8. [转]ROS(Robot Operating System)常用环境变量介绍

    本文简单介绍ROS系统中常用的环境变量用途及设置方式.ROS系统环境中除了必须配置的环境变量以外,其他的也是十分有用,通过修改变量路径,可以设置ROS系统中log文件存放路径,单元测试结果存放路径等. ...

  9. vim加密文件

    一.加密文件内容 vim gt-1.sh 输入:X 注意是大写的X 输入密码 然后,保存 再次访问,需要输入密码 如果输入密码错误,内容显示为乱码 用cat或more查看文件内容,显示为乱码:用vi重 ...

  10. asp.net 获取音视频时长 的方法

    http://www.evernote.com/l/AHPMEDnEd65A7ot_DbEP4C47QsPDYLhYdYg/ 日志:   1.第一种方法:   调用:shell32.dll ,win7 ...