Как реализовать пользовательскую сериализацию JSON из веб-сервиса ASP.NET?

Какие существуют опции для сериализации при возврате экземпляров пользовательских классов из WebService?

У нас есть несколько классов с рядом свойств класса дочерней коллекции, а также другие свойства, которые могут устанавливаться или не устанавливаться в зависимости от использования. Эти объекты возвращаются из ASP.NET.asmx WebService, украшенного атрибутом ScriptService, поэтому сериализуются через сериализацию JSON при возврате различными WebMethods.

Проблема заключается в том, что внесетевая сериализация возвращает все общедоступные свойства независимо от того, используются они или нет, а также возвращает имя класса и другую информацию более подробно, чем это было бы желательно, если бы вы хотели ограничить количество трафика.

В настоящее время для возвращаемых классов мы добавили пользовательские javascript-конвертеры, которые обрабатывают сериализацию JSON, и добавили их в web.config, как показано ниже:

<system.web.extensions>
  <scripting>
    <webServices>
      <jsonSerialization>
        <converters>
          <add name="CustomClassConverter" type="Namespace.CustomClassConverter" />
        </converters>
      </jsonSerialization>
    </webServices>
  </scripting>
</system.web.extensions>

Но для этого требуется специальный конвертер для каждого класса. Есть ли другой способ изменить сериализацию JSON из коробки, либо путем расширения службы, создания пользовательского сериализатора или тому подобного?

Follow Up
@marxidad:

Мы используем класс DataContractJsonSerializer в других приложениях, однако мне не удалось выяснить, как применить его к этим службам. Вот пример настройки служб:

[ScriptService]
public class MyService : System.Web.Services.WebService
{
    [WebMethod]
    public CustomClass GetCustomClassMethod
    {
        return new customClass();
    }
}

WebMethods вызывается javascript и возвращает данные, сериализованные в JSON. Единственный способ, которым мы смогли изменить сериализацию, - использовать конвертеры javascript, как указано выше?

Есть ли способ сказать WebService использовать пользовательский DataContractJsonSerializer? Будь то конфигурация web.config, украшая сервис атрибутами и т.д.?

Update
Ну, мы не смогли найти какой-либо способ переключить JavaScript-сериализатор, за исключением создания отдельных JavaScriptConverters, как указано выше.

Что мы сделали с этой целью, чтобы предотвратить создание отдельного конвертера, был создан общий JavaScriptConverter. Мы добавили пустой интерфейс к классам, которые мы хотели обработать, и SupportedTypes, который вызывается при запуске веб-сервиса, использует отражение, чтобы найти любые типы, которые реализуют вид интерфейса следующим образом:

public override IEnumerable<Type> SupportedTypes
{
  get
  {
    foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    {
      AssemblyBuilder dynamicAssemblyCheck = assembly as AssemblyBuilder;
      if (dynamicAssemblyCheck == null)
      {
        foreach (Type type in assembly.GetExportedTypes())
        {
          if (typeof(ICustomClass).IsAssignableFrom(type))
          {
            yield return type;
          }
        }
      }
    }
  }
}

Фактическая реализация немного отличается, поэтому тип кэшируется, и мы, скорее всего, реорганизуем его для использования пользовательских атрибутов, а не пустого интерфейса.

Однако при этом мы столкнулись с несколько иной проблемой при работе с пользовательскими коллекциями. Обычно они просто расширяют общий список, но пользовательские классы используются вместо самого List < > , потому что в классах коллекции обычно есть обычная логика, сортировка и т.д.

Проблема заключается в том, что метод Serialize для JavaScriptConverter возвращает словарь, который сериализуется в JSON как пары значений имени с ассоциированным типом, тогда как список возвращается как массив. Таким образом, сборники не могли быть легко сериализованы с помощью конвертера. Решением для этого было просто не включать эти типы в конвертер SupportedTypes, и они идеально сериализуются как списки.

Итак, сериализация работает, но когда вы пытаетесь передать эти объекты другим способом в качестве параметра для вызова веб-службы, десериализация прерывается, потому что они не могут быть входными данными, рассматриваются как список словарей строки/объекта, который не может быть преобразован в список любого настраиваемого класса, содержащегося в коллекции. Единственный способ, с помощью которого мы могли бы справиться с этим, - создать общий класс, который представляет собой список словарей строки/объекта, который затем преобразует список в соответствующий пользовательский класс коллекции, а затем изменяет любые параметры веб-службы, чтобы вместо этого использовать общий класс.

Я уверен, что здесь есть множество проблем и нарушений "лучших практик", но это делает работу для нас без создания тонны пользовательских классов конвертеров.

Ответы

Ответ 1

Если вы не используете классы, генерируемые с помощью кода, вы можете украсить свои свойства с помощью ScriptIgnoreAttribute, чтобы сказать, что сериализатор игнорирует определенные свойства. Сериализация Xml имеет аналогичный атрибут.

Конечно, вы не можете использовать этот подход, если хотите вернуть некоторые свойства класса на один вызов метода службы и разные свойства одного и того же класса при вызове метода другой службы. Если вы хотите сделать это, верните анонимный тип в методе службы.

[WebMethod]
[ScriptMethod]
public object GimmieData()
{
    var dalEntity = dal.GimmieEntity(); //However yours works...

    return new
       {
           id = dalEntity.Id,
           description = dalEntity.Desc
       };

}

Сериализатор может не беспокоиться о типе объекта, который вы ему отправляете, поскольку он все равно превращает его в текст.

Я также считаю, что вы могли бы реализовать ISerializable в своем объекте данных (как частичный класс, если у вас есть код-gen'd данных), чтобы получить мелкозернистый контроль над процессом сериализации, но я не пробовал его.

Ответ 2

Я знаю, что эта тема немного помолчала, но я подумал, что если вы переопределите свойство SupportedTypes JavaScriptConverter в своем пользовательском конвертере, вы можете добавить типы, которые должны использовать конвертер. Это может привести к необходимости в файле конфигурации. Таким образом, для каждого класса вам не понадобится настраиваемый конвертер.

Я попытался создать общий конвертер, но не мог понять, как его идентифицировать в web.config. Хотелось бы узнать, сумел ли кто-нибудь другой.

У меня возникла идея, когда пыталась решить вышеупомянутую проблему и наткнулась на Ника Берарди "Создание более точного JSON.NET Serializer" (google it).

Работал для меня:)

Спасибо всем.

Ответ 3

Если вы используете .NET 3.x(или можете), вам будет лучше всего работать служба WCF.

Вы можете выборочно контролировать, какие свойства сериализуются клиенту с атрибутом [DataMember]. WCF также допускает более мелкозернистый контроль над сериализацией JSON и десерилизацией, если вы этого хотите.

Это хороший пример для начала работы: http://blogs.msdn.com/kaevans/archive/2007/09/04/using-wcf-json-linq-and-ajax-passing-complex-types-to-wcf-services-with-json-encoding.aspx

Ответ 4

Вы можете использовать класс System.Runtime.Serialization.Json. DataContractJsonSerializer в сборке System.ServiceModel.Web.dll.

Ответ 5

Не цитируйте меня на эту работу наверняка, но я считаю, что это то, что вы ищете.

[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public XmlDocument GetXmlDocument()
{
    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(_xmlString);
    return xmlDoc;
}