我遇到一个问题,即我创建的ASP.NET MVC html帮助器方法在每次调用时都不会“重新生成”。
helper方法的目的是创建要在angularjs框架中使用的Javascript对象。例如,这是一个使用helper方法的代码片段(从html页面的script标签内调用):
var app = angular.module( "appName", ["ui.bootstrap"] ); app.controller( 'appCtrl', function( $scope ) { $scope.model = @Html.ToJavascript( Model, new string[] { "FirstName", "LastName", "ID", "Role" } ); } );
Model是具有各种属性的类的实例,但我只希望将FirstName,LastName,ID和Role序列化为javascript对象。
Statis类中定义了ToJavascript()帮助器方法,如下所示:
public static HtmlString ToJavascript( this HtmlHelper helper, object toConvert, string[] includedFields = null, Formatting formatting = Formatting.Indented, ReferenceLoopHandling loopHandling = ReferenceLoopHandling.Ignore ) { using( var stringWriter = new StringWriter() ) using( var jsonWriter = new JsonTextWriter( stringWriter ) ) { var serializer = new JsonSerializer() { // Let's use camelCasing as is common practice in JavaScript ContractResolver = new SpecificFieldsResolver( includedFields ), Formatting = formatting, ReferenceLoopHandling = loopHandling, }; // We don't want quotes around object names jsonWriter.QuoteName = false; serializer.Serialize( jsonWriter, toConvert ); return new HtmlString( stringWriter.ToString() ); } }
这利用Json.NET进行实际的序列化。
Json.NET的许多很酷的功能之一是它允许您即时定义要序列化的字段。那就是SpecificFieldsResolver所做的。我将其定义如下:
public class SpecificFieldsResolver : CamelCasePropertyNamesContractResolver { private string[] _included; public SpecificFieldsResolver( string[] included ) { _included = included; } protected override JsonProperty CreateProperty( MemberInfo member, MemberSerialization memberSerialization ) { JsonProperty prop = base.CreateProperty( member, memberSerialization ); bool inclField = ( _included == null ) || _included.Contains( member.Name, StringComparer.CurrentCultureIgnoreCase ); prop.ShouldSerialize = obj => inclField; return prop; } }
让我感到困惑的是CreateProperty()的调用方式。具体来说,对于要序列化的每种类型的对象,似乎只调用一次。
这是一个问题,因为在另一个cshtml文件中,我再次调用ToJavascript(),它试图序列化相同类型的对象,但是要从序列化中输出不同的字段:
var app = angular.module( "app2Name", ["ui.bootstrap"] ); app.controller( 'app2Ctrl', function( $scope ) { $scope.model = @Html.ToJavascript( Model, new string[] { "FirstName", "LastName", "ID", "Role", "Category", "VoterID" } ); } );
Category和VoterID也是有效的类字段。但是ToJavascript()不会将它们序列化。相反,它只会序列化在第一次调用ToJavascript()中定义的字段…即使该调用发生在另一个cshtml文件中。就像SpecificFieldsResolver记住它创建的JsonProperty对象一样。
有什么想法吗?
更新资料
感谢dbc诊断确切的错误并提出解决方法。之所以略加适应,是因为我在几个解析器中依赖于Json.NET的驼峰案例名称解析:
public class CamelCaseNameMapper : CamelCasePropertyNamesContractResolver { public string ToCamelCase( string propertyName ) { return ResolvePropertyName( propertyName ); } } public class MaoDefaultContractResolver : DefaultContractResolver { private CamelCaseNameMapper _mapper = new CamelCaseNameMapper(); protected override string ResolvePropertyName( string propertyName ) { return _mapper.ToCamelCase( propertyName ); } }
现在,从MaoDefaultContractResolver派生的每个解析器(例如我的SpecificFieldsResolver)都会自动继承驼峰式大小写,但避免了dbc标识的缓存问题。
这似乎是的错误CamelCasePropertyNamesContractResolver。它的基类DefaultContractResolver拥有两个构造函数:无参数构造函数和DefaultContractResolver (Boolean)版本(在Json.NET 7.0中已作废)。该参数含义如下:
CamelCasePropertyNamesContractResolver
DefaultContractResolver
DefaultContractResolver (Boolean)
shareCache 类型:System.Boolean 如果设置为true,DefaultContractResolver将使用与相同类型的其他解析程序共享的缓存。共享缓存将显着提高多个解析器实例的性能,因为昂贵的反射将仅发生一次。如果假定解析程序的不同实例产生不同的结果,则此设置可能导致意外的行为。设置为false时,强烈建议使用重复使用DefaultContractResolver实例JsonSerializer。
shareCache
如果设置为true,DefaultContractResolver将使用与相同类型的其他解析程序共享的缓存。共享缓存将显着提高多个解析器实例的性能,因为昂贵的反射将仅发生一次。如果假定解析程序的不同实例产生不同的结果,则此设置可能导致意外的行为。设置为false时,强烈建议使用重复使用DefaultContractResolver实例JsonSerializer。
JsonSerializer
默认值为false。
false
不幸的是,默认构造函数用于CamelCasePropertyNamesContractResolver将值设置为 true :
true
public class CamelCasePropertyNamesContractResolver : DefaultContractResolver { public CamelCasePropertyNamesContractResolver() #pragma warning disable 612,618 : base(true) #pragma warning restore 612,618 { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true, OverrideSpecifiedNames = true }; } }
此外,没有第二个带有该shareCache选项的构造函数。这打断了你的SpecificFieldsResolver。
SpecificFieldsResolver
解决方法是,您可以从解析器派生DefaultContractResolver并用于CamelCaseNamingStrategy进行名称映射:
CamelCaseNamingStrategy
public class IndependentCamelCasePropertyNamesContractResolver : DefaultContractResolver { public IndependentCamelCasePropertyNamesContractResolver() : base() { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true, OverrideSpecifiedNames = true }; } } public class SpecificFieldsResolver : IndependentCamelCasePropertyNamesContractResolver { // Remainder unchanged }
请注意,如果您使用的是9.0之前的Json.NET版本,CamelCaseNamingStrategy则不存在。而是CamelCasePropertyNamesContractResolver可以使用嵌套的kludge 来映射名称:
public class IndependentCamelCasePropertyNamesContractResolver : DefaultContractResolver { class CamelCaseNameMapper : CamelCasePropertyNamesContractResolver { // Purely to make the protected method public. public string ToCamelCase(string propertyName) { return ResolvePropertyName(propertyName); } } readonly CamelCaseNameMapper nameMapper = new CamelCaseNameMapper(); protected override string ResolvePropertyName(string propertyName) { return nameMapper.ToCamelCase(propertyName); } }