Json.Net: Html Помощник не регенерирует

Я столкнулся с проблемой, когда созданный мной ASP.NET MVC html-метод-помощник не «регенерируется» при каждом вызове.

objective вспомогательного метода – создать объекты Javascript, которые будут использоваться в ракурсе angularjs. Например, вот fragment кода, в котором используется вспомогательный метод (вызываемый из тега скрипта на странице html):

var app = angular.module( "appName", ["ui.bootstrap"] ); app.controller( 'appCtrl', function( $scope ) { $scope.model = @Html.ToJavascript( Model, new string[] { "FirstName", "LastName", "ID", "Role" } ); } ); 

Модель – это экземпляр classа, который обладает множеством свойств, но я хочу, чтобы FirstName, LastName, ID и Role были сериализованы для javascript-объекта.

Метод помощника ToJavascript () определяется в classе 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" } ); } ); 

Категория и VoterID также являются действительными полями classов. Но ToJavascript () не серализовывает их. Вместо этого он только сериализует поля, определенные при первом вызове ToJavascript () … хотя этот вызов имеет место в другом файле cshtml. Это как если бы SpecificFieldsResolver запоминает созданные им объекты JsonProperty.

Мысли?

Обновить

Thanx to dbc для диагностики того, что было неправильно, и предлагая обходной путь. Я немного адаптировал его, потому что я полагаюсь на разрешение имени Jel.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.

Это похоже на ошибку с CamelCasePropertyNamesContractResolver . Его базовый class, DefaultContractResolver , имеет два конструктора: конструктор без параметров и версию DefaultContractResolver (Boolean) (только что устаревшая в Json.NET 7.0). Этот параметр имеет следующее значение:

shareCache

  • Тип: System.Boolean

    Если установлено значение true, DefaultContractResolver будет использовать кешированный общий доступ с другими преобразователями того же типа. Совместное использование кеша значительно улучшит производительность с помощью нескольких экземпляров resolver, потому что дорогое reflection произойдет только один раз. Этот параметр может вызвать неожиданное поведение, если разные экземпляры резольвера предполагают получение разных результатов. Если установлено значение false, настоятельно рекомендуется повторно использовать экземпляры DefaultContractResolver с помощью JsonSerializer .

По умолчанию используется значение false .

К сожалению, конструктор по умолчанию для CamelCasePropertyNamesContractResolver устанавливает значение в 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 и использовать CamelCaseNamingStrategy для сопоставления имен:

 public class IndependentCamelCasePropertyNamesContractResolver : DefaultContractResolver { public IndependentCamelCasePropertyNamesContractResolver() : base() { NamingStrategy = new CamelCaseNamingStrategy { ProcessDictionaryKeys = true, OverrideSpecifiedNames = true }; } } public class SpecificFieldsResolver : IndependentCamelCasePropertyNamesContractResolver { // Remainder unchanged } 

Обратите внимание: если вы используете версию Json.NET до 9.0, то CamelCaseNamingStrategy не существует. Вместо этого kladge inested CamelCasePropertyNamesContractResolver может использоваться для сопоставления имен:

 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); } } 
  • Можно ли указать путь в атрибуте для сопоставления свойства в моем classе с дочерним свойством в моем JSON?
  • JsonValueProviderFactory выбрасывает «запрос слишком большой»
  • Как десериализовать JSON с двойными именами свойств в одном и том же объекте
  • JToken: получить исходное / оригинальное значение JSON
  • Разбор массива JSON с использованием Json.Net
  • Json.Net добавляет $ id к объектам EF, несмотря на то, что PreserveReferencesHandling указывает на "None"
  • JSON.NET как сериализатор OAP для WebAPI 2 и ODataMediaTypeFormatter
  • Как использовать JSON.NET для десериализации в вложенный / рекурсивный словарь и список?
  • JSON.NET: зачем использовать JToken - когда-либо?
  • JSON.Net выдает StackOverflowException при использовании
  • Обеспечение json-ключей в .NET
  • Давайте будем гением компьютера.