Json.Net:Html Helper Method无法重新生成

时间:2015-06-09 19:07:56

标签: asp.net-mvc angularjs json.net

我遇到了一个问题,我创建的ASP.NET MVC html帮助器方法没有被重新生成"每次被召唤。

辅助方法的目的是创建要在angularjs框架中使用的Javascript对象。例如,这里是一个代码片段,其中使用了辅助方法(在html页面的脚本标记内调用):

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对象。

Tojavascript()帮助器方法在statis类中定义如下:

   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对象一样。

思想?

更新

Thanx到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 );
    }

}

现在每个解析器(例如我的SpecificFieldsResolver,派生自MaoDefaultContractResolver)都会自动继承驼峰套管,但避免了dbc识别的缓存问题。

1 个答案:

答案 0 :(得分:6)

这似乎是CamelCasePropertyNamesContractResolver的错误。它的基类DefaultContractResolver有两个构造函数:一个parameterless constructor和一个DefaultContractResolver (Boolean)版本(刚刚在Json.NET 7.0中过时)。该参数具有以下含义:

  

shareCache

     
      
  • 类型:System.Boolean

         

    如果设置为true,DefaultContractResolver将使用与其他相同类型的解析器共享的缓存。共享缓存将显着提高多个解析器实例的性能,因为昂贵的反射只会发生一次。如果假设解析器的不同实例产生不同的结果,则此设置可能会导致意外行为。设置为false时,强烈建议将DefaultContractResolver实例与JsonSerializer一起使用。

  •   

默认值为false

不幸的是,CamelCasePropertyNamesContractResolver的{​​{3}}将值设置为 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

作为一种变通方法,您可以从DefaultContractResolver派生您的解析程序并使用default constructor进行名称映射:

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);
    }
}