воскресенье, 7 декабря 2014 г.

Обработка исключений при работе с WCF-сервисами

Наверняка вы сталкивались с ситуацией, когда в случае ошибки в WCF-сервисе нужно передать клиенту какие-либо данные либо, например, подробное описание что случилось. Очень часто встречаются реализации WCF- сервисов, использующие возвращаемые клиенту объекты с дополнительными, используемыми только в случае ошибки, полями, даже если реализация метода не требует возврата какого-либо результата клиенту. Так делают потому, что если просто выбросить исключение, то клиент не получит практически никакой информации о том, что случилось. Представьте, что у нас есть WCF сервис вот с таким простым контрактом и реализацией.


  
    //контракт
    [ServiceContract]
    public interface IDataService
    {        
        [OperationContract]        
        void TestError();
    }

    //реализация
    public void TestError()
    {
       throw new Exception("Error!");
    }


В если реализовать сервис именно так, то на клиент придет исключение, которое не дает никаких сведений о случившемся:

Можно, конечно, как написано в ошибке, включить IncludeExceptionDetailInFaults, как написано в сообщении об ошибке. Но это свойство не рекомендуется использовать кроме как для отладки, так как при этом любой клиент в случае ошибки получить все данные о стеке вызовов и структуре вашего сервиса, что очень нехорошо с точки зрения безопасности.
Но, помимо варианта для отладки и архитектурно не самого лучшего варианта с дополнительным полем в результате вызова, есть и третий вариант - более удобный и более правильный.

Это использования FaultContract и FaultException. Давайте сделаем контракт и реализацию нашего сервиса с использованием этих возможностей WCF.
 
[ServiceContract]
    //контракт
    public interface IDataService
    {        
        [OperationContract]
        [FaultContract(typeof(ServiceError))]
        void TestError();
    }

    [DataContract]
    public class ServiceError
    {
        [DataMember]
        public int ErrorCode { get; set; }
        [DataMember]
        public string Message { get; set; }        
    }

    //реализация
    public void TestError()
    {
        ServiceError srvError = new ServiceError();
        srvError.ErrorCode = 123;
        srvError.Message = "Error!";
        throw new FaultException<ServiceError>(srvError);
    }

Теперь, если ловить на клиенте не просто исключение а FaultException<>, то можно получить все те сведения, которые мы передали в сервисе.
                using (TestService.TestServiceClient client = new TestService.TestServiceClient())
                {
                    try
                    {                                                
                        client.TestError();
                    }
                    catch (FaultException<TestService.ServiceError> ex)
                    {
                        //поля вашего класса, объявленного как FaultContract, будут находится в свойстве исключения Datail 
                        //в данном случае появится сообщение "123Error!";
                        MessageBox.Show(ex.Detail.ErrorCode.ToString()+ex.Detail.Message);
                    }
                }

В принципе, все должно работать нормально, за одним исключением - при отладке в Visual Studio у вас может появляться странная ошибка System.ServiceModel.FaultException`1 was unhandled by user code на то месте в сервисе, где вы выбрасываете исключение:

Одни советы в интернете говорят, что чтобы починить это надо полностью отключить Exception assistant (после чего, вообще никакие ошибки не будет ловится студией), другие что в настройках этого самого ассистента вообще отключить все CLR Exceptions (что, в контексте разработки на .Net равнозначно предыдущему варианту). На самом деле, достаточно отключить одно это исключение и все будет работать. Идем в Debug -> Exception, открываем категорию Common Language Runtime Exceptions, затем System.ServiceModel и убираем галочку напротив System.ServiceModel.FaultException`1.
И все будет работать нормально.


Комментариев нет:

Отправить комментарий