基于Json.NET自己实现MVC中的JsonValueProviderFactory
写了博文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的更多相关文章
- MVC中处理Json和JS中处理Json对象
MVC中处理Json和JS中处理Json对象 ASP.NET MVC 很好的封装了Json,本文介绍MVC中处理Json和JS中处理Json对象,并提供详细的示例代码供参考. MVC中已经很好的封装了 ...
- Spring MVC 中的基于注解的 Controller【转】
原文地址:http://my.oschina.net/abian/blog/128028 终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 H ...
- Spring MVC中基于注解的 Controller
终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法以响 ...
- Spring MVC 中的基于注解的 Controller(转载)
终于来到了基于注解的 Spring MVC 了.之前我们所讲到的 handler,需要根据 url 并通过 HandlerMapping 来映射出相应的 handler 并调用相应的方法 ...
- MVC中使用Entity Framework 基于方法的查询学习笔记 (一)
EF中基于方法的查询方式不同于LINQ和以往的ADO.NET,正因为如此,有必要深入学习一下啦.闲话不多说,现在开始一个MVC项目,在项目中临床学习. 创建MVC项目 1.“文件”--“新建项目”-- ...
- 【转】MVC中处理Json和JS中处理Json对象
事实上,MVC中已经很好的封装了Json,让我们很方便的进行操作,而不像JS中那么复杂了. MVC中: public JsonResult Test() { JsonResult json = new ...
- .net mvc中json的时间格式
.net mvc中,通过return Json(DateTime.Now); 返回到视图时,日期格式变成这样,"/Date(1245398693390)/",如果要显示指定的日期时 ...
- 超高性能的json序列化之MVC中使用Json.Net
先不废话,直接上代码 Asp.net MVC自带Json序列化 /// <summary> /// 加载组件列表 /// </summary> /// <param na ...
- 解决MVC中JSON字符长度超出限制的异常
解决MVC中JSON字符长度超出限制的异常 解决方法如下: <configuration> <system.web.extensions> <scripting> ...
随机推荐
- 四大开源协议比较:BSD、Apache、GPL、LGPL
sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&a ...
- 搞ACM的你伤不起[转载] 原作者:RoBa
劳资六年前开始搞ACM啊!!!!!!!!!! 从此踏上了尼玛不归路啊!!!!!!!!!!!! 谁特么跟劳资讲算法是程序设计的核心啊!!!!!! 尼玛除了面试题就没见过用算法的地方啊!!!!!! 谁再跟 ...
- bzoj千题计划141:bzoj3532: [Sdoi2014]Lis
http://www.lydsy.com/JudgeOnline/problem.php?id=3532 如果没有字典序的限制,那么DP拆点最小割即可 加上字典序的限制: 按c从小到大枚举最小割边集中 ...
- 悲催的IE6 七宗罪大吐槽(带解决方法)第二部分
三.position:fixed无效 今天在IE6上遇到一个bug,本来想做一个消息提示框,让他在页面右上角停留一段时间后消失,这段时间内提示框随着页面的下拉一直出现在浏览器可见区的顶部,于是我用到了 ...
- 并发编程(三) IO模型
五 IO模型 常用的IO模型有4种: 阻塞IO 非阻塞IO IO多路复用 异步IO 不常用的有: 驱动信号 5.1 阻塞IO.非阻塞IO 阻塞IO:进程不能做其他的事情 非阻塞IO:等待数据无阻塞 阻 ...
- 微服务深入浅出(5)-- 声明式调用Feign
Feign的使用 Feign采用了声明式的API接口的风格,将Java Http客户端绑定到它的内部,从而调用过程变的简单. 配置文件: spring: application: name: eure ...
- js操作控制iframe页面的dom元素
1.代码1 index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8" ...
- Red Hat Enterprise Linux 7.2下使用RPM包安装SQL Server vNext
1.下载安装包 mssql-server:https://packages.microsoft.com/rhel/7/mssql-server/ mssql-tools:https://package ...
- 企业日志大数据分析系统ELK+KAFKA实现【转】
背景: 最近线上上了ELK,但是只用了一台Redis在中间作为消息队列,以减轻前端es集群的压力,Redis的集群解决方案暂时没有接触过,并且Redis作为消息队列并不是它的强项:所以最近将Redis ...
- Codeforces Round #504 D. Array Restoration
Codeforces Round #504 D. Array Restoration 题目描述:有一个长度为\(n\)的序列\(a\),有\(q\)次操作,第\(i\)次选择一个区间,将区间里的数全部 ...