Внедрение контракта ошибки WCF с использованием Castle Dynamic Proxy Generation

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

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

Наша реализация WCF немного более надежна, чем готовый метод для установки ссылки на службу. Мы реализовали динамическую генерацию прокси на клиенте. Мы достигли этого путем создания интерфейсов веб-служб, которые совместно используются клиентом и сервером, и использовали класс Castle.DynamicProxy.ProxyGenerator и метод CreateInterfaceProxyWithoutTarget для создания прокси. Кроме того, когда мы вызываем метод CreateInterfaceProxyWithoutTarget, мы указываем реализацию IInterceptor. На сервере есть три основных класса, которые используются для отслеживания ошибок и поведения:

FaultOperationInvoker (реализует IOperationInvoker): Пытается вызвать метод службы с помощью IOperationInvoker.Invoke. Если это тип исключения ошибки, он отбрасывает его, если это исключение, он пытается определить, существует ли контракт ошибки, имеющий конкретный тип детализации, и если да, то выполнить перенос, а затем вызвать новое исключение ошибки с подробной информацией.

internal class FaultOperationInvoker : IOperationInvoker
    {
        IOperationInvoker innerOperationInvoker;
        FaultDescription[] faults;

        public FaultOperationInvoker(IOperationInvoker invoker, FaultDescription[] faults)
        {
            this.innerOperationInvoker = invoker;
            this.faults = faults;
        }

        #region IOperationInvoker Members

        object[] IOperationInvoker.AllocateInputs()
        {
            return this.innerOperationInvoker.AllocateInputs();
        }

        object IOperationInvoker.Invoke(object instance, object[] inputs, out object[] outputs)
        {
            try
            {
                return this.innerOperationInvoker.Invoke(instance, inputs, out outputs);
            }
            catch (FaultException e)
            {
                ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");

                //allow fault exceptions to bubble out
                throw;
            }          
            catch (Exception e)
            {
                Type exceptionType = e.GetType();

                ServerLogger.GetLogger(instance.GetType().FullName).LogException(e, "Unhandled exception in service operation.");

                //if the excpetion is serializable and there operation is tagged to support fault contracts of type WcfSerivceFaultDetail
                if (faults != null && (faults.OfType<WcfServiceFaultDetail>().Any() || faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) > 0))
                {
                    throw new FaultException<WcfServiceFaultDetail>(new WcfServiceFaultDetail(true, e), "Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
                }
                else
                {
                    throw new FaultException("Unhandled exception during web service call.", WcfFaultCustomExceptionFactory.GetFaultCodeForExceptionType(exceptionType));
                }

            }
        }

FaultOperationBehavior (реализует IOperationBehavior) указывает вызывающую операцию отправки на вызывающую операцию ошибки, описанную выше.

[AttributeUsage(AttributeTargets.Method)]
    public class FaultOperationBehavior : System.Attribute, IOperationBehavior
    {
        #region IOperationBehavior Members

        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription,
            System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }


        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription,
            System.ServiceModel.Dispatcher.ClientOperation clientOperation) { }

        void IOperationBehavior.ApplyDispatchBehavior(OperationDescription operationDescription,
            System.ServiceModel.Dispatcher.DispatchOperation dispatchOperation)
        {          
            dispatchOperation.Invoker = new FaultOperationInvoker(dispatchOperation.Invoker, operationDescription.Faults.ToArray());
        }

        void IOperationBehavior.Validate(OperationDescription operationDescription) { }

        #endregion
    }

ExceptionTraceBehavior (наследует атрибут, реализует IServiceBehavior) для обработки исключений, которые реализуют IServiceBehavior. У нас также есть класс (FaultOperationBehavior)

[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = true)]
public class ExceptionTraceBehavior : Attribute, IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        foreach (var ep in serviceDescription.Endpoints)
        {
            foreach (var operation in ep.Contract.Operations)
            {
                if (operation.Behaviors.Contains(typeof(FaultOperationBehavior)))
                    continue;

                operation.Behaviors.Add(new FaultOperationBehavior());

                //Check to see if this operation description contains a wcf service fault detail operation.  If it doesn't, add one.
                if (operation.Faults != null && (operation.Faults.Count == 0 || operation.Faults.Count > 0 && operation.Faults.Count(x => x.DetailType == typeof(WcfServiceFaultDetail)) == 0))
                {
                    FaultDescription faultDescription = new FaultDescription(operation.Name);
                    //faultDescription.Name = "WcfServiceFaultDetail";
                    //faultDescription.Namespace = "";
                    faultDescription.DetailType = typeof(WcfServiceFaultDetail);
                    operation.Faults.Add(faultDescription);
                }
            }
        }
    }

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
    }
}

Каждый из наших сервисных интерфейсов имеет конкретную реализацию. Все наши сервисы также наследуют наш базовый класс обслуживания, который украшен атрибутом ExceptionTrace.

Итак, теперь с фоновой информацией, вот проблема. Я хочу, чтобы у каждой сервисной операции был контракт отказа с подробным типом WCFServiceFaultDetail, но я не хочу ставить атрибут FaultContract для каждой сервисной операции. Как вы можете видеть в ExceptionTraceBehavior, я выяснил, как программно добавить контракт отказа, и он отлично работает для добавления отказа в операцию. Когда в вызывающем операторе обнаруживается обычное старое исключение, он обнаруживает, что существует правильный контракт ошибки, и выдает новое FaultExcption. Однако, когда исключение возвращается клиенту, оно попадает в код catch (FaultExcection fe) вместо кода catch (FaultException fe).

Однако, если удалить код для программного добавления в контракт на сбой, я декорирую каждую операцию службы с помощью [FaultContract(typeof(WcfServiceFaultDetail))], клиент перехватывает исключение, как и ожидалось.

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

С этой мыслью я попытался выяснить, как добавить контракт на ошибку в реализации IInterceptor, но безуспешно.

Поэтому я надеюсь, что кто-то уже сделал это и может предоставить некоторые детали. Любая помощь приветствуется.

1 ответ

Я не эксперт WCF, но мои руки немного испачкались.

Я думаю, вы правы. Контракты о сбоях разрешаются из метаданных сборки при создании ChannelFactory некоторого типа. Поскольку ваши интерфейсы не украшены соответствующими атрибутами FaultContract, ваш клиент использует контракт отказа по умолчанию без подробностей.

Добавление атрибутов FaultContract к интерфейсным методам во время выполнения также, вероятно, не будет работать.

Одним из решений может быть динамическое создание и использование типов во время выполнения для генерации фабрик каналов.

Я никогда этого не делал, но думаю, что это можно сделать, начиная с DefineDynamicAssembly в AppDomain.

Другие вопросы по тегам