У меня есть служба WCF, размещенная в IIS. Эта служба определяется универсальным интерфейсом с типами интерфейсов в качестве аргументов или возвращаемых типов, поэтому мы используем атрибут ServiceKnownType для определения доступных реализаций указанных интерфейсов во время выполнения.
Все это, кажется, работает нормально, однако иногда мы видим, что все запросы к этим службам завершаются с ошибкой CommunicationException; "Произошла ошибка при попытке сериализации параметра http://tempuri.org/:arg . Сообщение InnerException было "Тип" MyNamespace.SomeInterfaceImplementation" с именем контракта данных "SomeInterfaceImplementation:http://schemas.datacontract.org/2004/07/MyNamespace" не ожидается. Добавляйте в список известных типов любые неизвестные статически типы, например, используя атрибут KnownTypeAttribute или добавляя их в список известных типов, передаваемых в DataContractSerializer.'. Дополнительные сведения см. в разделе InnerException."
Я не могу надежно воспроизвести эту ошибку, однако она регулярно появляется после того, как службы остаются запущенными в течение некоторого времени (например, в выходные дни). Сначала я предположил, что это происходит из-за перезапуска пула приложений IIS, однако перезапуск вручную или плановый перезапуск с небольшими интервалами (например, 2 минуты) не может воспроизвести проблему.
Я могу надежно воспроизвести исключение, исключив 'MyNamespace.MyType' из поставщика 'ServiceKnownTypes', однако это на самом деле не говорит мне, почему это происходит с перерывами.
В следующем фрагменте показано объявление службы. Это общая служба для различных типов реализации . Обратите внимание, что именно аргумент операции 'arg' создает исключение CommunicationException.
[ServiceKnownType("GetKnownTypes", typeof(KnownTypesCache))]
[ServiceContract]
public interface IMyService<T>
where T : class, IMyServiceTypeInterface
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
MyReturnType HandleRequest(T element, ISomeInterfaceArgumentType arg);
}
Теперь реализация KnownTypesCache, предоставляющая известные типы для ISomeInterfaceArgumentType, выглядит так;
public static class KnownTypesCache
{
private static readonly List<Assembly> queriedAssemblies = new List<Assembly>();
private static readonly List<Type> knownTypes = new List<Type>();
static KnownTypesCache()
{
// get all available assemblies at this time
List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
// find all available known types publishers
IEnumerable<Type> knownTypesPublisherTypes = assemblies
.Where(a => !queriedAssemblies.Contains(a)) // exclude already queried assemblies to speed things up
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IKnownTypesPublisher).IsAssignableFrom(p) && p.HasAttribute(typeof(KnownTypesPublisherAttribute)));
// add all known types
foreach (Type type in knownTypesPublisherTypes)
{
IKnownTypesPublisher publisher = (IKnownTypesPublisher)Activator.CreateInstance(type);
AddRange(publisher.GetKnownTypes());
}
}
// record the assemblies we've already loaded to avoid relookup
queriedAssemblies.AddRange(assemblies);
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
return knownTypes;
}
}
По сути, этот глобальный статический кэш запрашивает все загруженные сборки в AppDomain для разработчиков IKnownTypesPublisher, которые, в свою очередь, предоставляют доступные типы сериализации для каждой сборки. Таким образом, на каждую сборку приходится один IKnownTypesPublisher, который отвечает за идентификацию типов только в этой сборке, а KnownTypesCache просто объединяет все это и возвращает в DataContractSerializer во время выполнения.
Как я уже упоминал, этот подход работает нормально в 99% случаев. А затем по неизвестной мне причине он перестает работать и может быть разрешен только вызовом iisreset.
Сейчас я в замешательстве, я пробовал всевозможные решения, но поскольку я могу надежно воспроизвести эту ошибку, только дождавшись понедельника, это немного сложно!
Моя последняя оставшаяся мысль заключается в том, что статический конструктор KnownTypesCache может быть вызван до того, как все сборки будут загружены в AppDomain, и поэтому кеш будет пуст на время существования экземпляра...?