写了博文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. T51071 Tony到死都想不出の数学题

    T51071 Tony到死都想不出の数学题 自己摘的题出了数据挂一下链接 \(a, b\) 均为整数 设 \(M(a)\) 为满足 \((a + b) | ab\) 的 \(b\) 的个数, 求 \( ...

  2. Interval GCD

    题目描述 给定一个长度为N的数列A,以及M条指令 (N≤5*10^5, M<=10^5),每条指令可能是以下两种之一:“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d ...

  3. HDU 1846 Brave Game 巴什博奕

    解题报告:Alice和Bob在做一个取石子游戏,有一堆n个石子,然后规定每个人每次最少要去1个石子,最多可以取m个石子,最后一次取完石子的人为胜. 巴什博奕,关键是找到必胜点和必败点,我们可以先列举出 ...

  4. Unity3D 程序打包报错(程序是连接数据库进行处理的)

    打包这个Unity3D的程序时出现错误(程序是由XML数据改成连接数据库): ArgumentException: The Assembly System.Configuration is refer ...

  5. 浏览器断点调试js

    说了一些 Chrome 开发者工具的技巧,其实并没有涉及到开发者工具最核心的功能之一:断点调试.断点可以让程序运行到某一行的时候,把程序的整个运行状态进行冻结.你可以清晰地看到到这一行的所有的作用域变 ...

  6. spfa+floyed+最长路+差分约束系统(F - XYZZY POJ - 1932)(题目起这么长感觉有点慌--)

    题目链接:https://cn.vjudge.net/contest/276233#problem/F 题目大意:给你n个房子能到达的地方,然后每进入一个房子,会消耗一定的生命值(有可能是负),问你一 ...

  7. 事件,使用.net自带委托EventHandler

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  8. SHELL 中的变量

    变量的分类 系统环境变量 系统本身所有,通常为大写字母 系统变量通过 set 或 declare 指令进行查看 UDV 变量(user defined variable ) 用户创建和维护,建议大写 ...

  9. PV操作2011

  10. 虚拟机 windows xp sp3 原版

    原版的镜像:http://www.7xdown.com/Download.asp?ID=3319&URL=http://d5.7xdown.com/soft2/&file=Window ...