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-объекта.
- Преобразовать Newtonsoft.Json.Linq.JArray в список определенного типа объекта
- В чем разница между PreserveReferencesHandling и ReferenceLoopHandling в Json.Net?
- Настройка пользовательских дат WebApi Json.NET
- Удалить дескриптор объекта json в динамический объект с помощью Json.net
- Deserialize json, который имеет некоторое имя свойства, начиная с числа
Метод помощника 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.
- Регистрация пользовательского JsonConverter по всему миру в Json.Net
- Как добавить дополнительное свойство в сериализованную строку JSON с помощью json.net?
- JSON.NET - как десериализовать сборку интерфейсных экземпляров?
- Json.NET добавляет обратную косую черту при возврате сериализованной строки json
- Указание пользовательского формата DateTime при сериализации с помощью Json.Net
- Невозможно десериализовать текущий объект JSON (например, {"name": "value"}) в тип 'System.Collections.Generic.List`1
- Порядок сериализованных полей с использованием JSON.NET
- Как сериализовать словарь как часть его родительского объекта с помощью Json.Net
Это похоже на ошибку с 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); } }