Exceptions through WCF

    Давайте поговорим про передачу исключений через WCF сервис.

    Для начала давайте представим ситуацию – у нас есть метод RegisterUser, который соответственно производит регистрацию пользователя. И в случае ошибки в вводе каких-либо данных кидает CustomException.
    Выглядит код регистрации примерно так:
    /// <summary>  
    /// Registers the user.  
    /// </summary>  
    /// <param name="username">The username.</param>  
    /// <param name="password">The password.</param>  
    /// <param name="email">The email.</param>  
    public void RegisterUser(string username, string password, string email)  
    {  
        if (/*проверяем username*/)  
            throw new InvalidUsernameException();  
        if (/*проверяем password*/)  
            throw new InvalidPasswordException();  
        if (/*проверяем email*/)  
            throw new InvalidEmailException();
        ...  
    }
    * This source code was highlighted with Source Code Highlighter.

    А так его использование:
    /// <summary>  
    /// Handles the Click event of the btnRegister control.  
    /// </summary>  
    /// <param name="sender">The source of the event.</param>  
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>  
    protected void btnRegister_Click(object sender, EventArgs e)  
    {  
        try  
        {  
            RegisterUser(txtUsernamt.Text, txtPassword.Text, txtEmail.Text);  
        }  
        catch (InvalidUsernameException usernameException)  
        {  
            // даем знать пользователю произошла ошибка связанная с Username  
        }  
        catch (InvalidPasswordException passwordException)  
        {  
            // даем знать пользователю произошла ошибка связанная с Password  
        }  
        catch (InvalidEmailException emailException)  
        {  
            // даем знать пользователю что произошла ошибка связанная с Email  
        }  
        ...  
    * This source code was highlighted with Source Code Highlighter.
     
    Всё отлично работает, программист знает как обработать ошибку, пользователь знает где ошибка.
    А теперь представим, что метод RegisterUser у нас находится в WCF сервисе.
    Вот что говорит MSDN по поводу исключений в WCF:

    In all managed applications, processing errors are represented by Exception objects. In SOAP-based applications such as WCF applications, service methods communicate processing error information using SOAP fault messages. SOAP faults are message types that are included in the metadata for a service operation and therefore create a fault contract that clients can use to make their operation more robust or interactive. In addition, because SOAP faults are expressed to clients in XML form, it is a highly interoperable type system that clients on any SOAP platform can use, increasing the reach of your WCF application.

    Это означает, что для передачи исключения используется SOAP fault message и с ними надо работать несколько иначе.
    Т.е. мы можем бросать только FaultException.
    Но не все так скучно, у FaultException есть generic перегрузка FaultException<T>. Ей мы и воспользуемся.
     
    Создадим специальные классы для наших ошибок:
    /// <summary>  
    ///  указывает на ошибку в Username  
    /// </summary>
    [DataContract]  
    class InvalidUsernameFault  
    {  
        [DataMember]  
        public string CustomError;
        public InvalidUsernameFault()  
        {  
        }
        public InvalidUsernameFault(string error)  
        {  
            CustomError = error;  
        }  
    }
     /// <summary> 
    ///  указывает на ошибку в Password 
    /// </summary>  [DataContract]  
    class InvalidPasswordFault  
    {  
        [DataMember]  
        public string CustomError;
        public InvalidPasswordFault()  
        {  
        }
        public InvalidPasswordFault(string error)  
        {  
            CustomError = error;  
        }  
    }
     
    /// <summary>  
    ///  указывает на ошибку в Email  
    /// </summary>
    [DataContract]  
    class InvalidEmailFault  
    {  
        [DataMember]  
        public string CustomError;
        public InvalidEmailFault()  
        {  
        }
        public InvalidEmailFault(string error)  
        {  
            CustomError = error;  
        }  
    }
    * This source code was highlighted with Source Code Highlighter.
     
    Теперь вернемся к нашему WCF сервису.
    В интерфейсе мы должны указать FaultContract для нашего метода:
    [OperationContract]
    [FaultContract(typeof(InvalidUsernameFault))]
    [FaultContract(typeof(InvalidPasswordFault))]
    [FaultContract(typeof(InvalidEmailFault))]
    void RegisterUser(string username, string password, string email);


    * This source code was highlighted with Source Code Highlighter.

    Ну а теперь мы может быть уверены что наши исключения дойдут до клиента:
    /// <summary>  
    /// Registers the user.  
    /// </summary>  
    /// <param name="username">The username.</param>  
    /// <param name="password">The password.</param>  
    /// <param name="email">The email.</param>  
    public void RegisterUser(string username, string password, string email)  
    {  
        if (/*проверяем username*/)  
            throw new FaultException<InvalidUsernameFault>(new InvalidUsernameFault());  
        if (/*проверяем password*/)  
            throw new FaultException<InvalidPasswordFault>(new InvalidPasswordFault());  
        if (/*проверяем email*/)  
            throw new FaultException<InvalidEmailFault>(new InvalidEmailFault());
        ...  
    }Или можно бросать FaultException указывая подробное описание ошибки. Удобно, например для логгирования:/// <summary>  
    /// Registers the user.  
    /// </summary>  
    /// <param name="username">The username.</param>  
    /// <param name="password">The password.</param>  
    /// <param name="email">The email.</param>  
    public void RegisterUser(string username, string password, string email)  
    {  
        if (/*проверяем username*/)  
            throw new FaultException<InvalidUsernameFault>(new InvalidUsernameFault(“пользователь Medved уже зарегистрирован”));  
        if (/*проверяем password*/)  
            throw new FaultException<InvalidPasswordFault>(new InvalidPasswordFault(“пароль ’12345’ недопустим”));  
        if (/*проверяем email*/)  
            throw new FaultException<InvalidEmailFault>(new InvalidEmailFault(“уже зарегистрирован пользователь с адресом ya@krasafcheg.ru”));
        ...  
    }
    * This source code was highlighted with Source Code Highlighter.

     
    Обрабатывать такие исключения тоже проще простого:
    /// <summary>  
    /// Handles the Click event of the btnRegister control.  
    /// </summary>  
    /// <param name="sender">The source of the event.</param>  
    /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>  
    protected void btnRegister_Click(object sender, EventArgs e)  
    {  
        try  
        {
            … 
            wcfclient.RegisterUser(txtUsernamt.Text, txtPassword.Text, txtEmail.Text);  
        }  
        catch (FaultException<InvalidUsernameFault> usernameException)  
        {  
            // даем знать пользователю произошла ошибка связанная с Username  
        }  
        catch (FaultException<InvalidPasswordFault> passwordException)  
        {  
            // даем знать пользователю произошла ошибка связанная с Password  
        }  
        catch (FaultException<InvalidEmailFault> emailException)  
        {  
            // даем знать пользователю что произошла ошибка связанная с Email  
        }
        catch (FaultException faultEx)  
        {  
            // обрабатывает нераспознанную ошибку, например произошла ошибка авторизации или
            // ошибка соединения с сервером нашего WCF сервиса 
        }     ...  
    }

    * This source code was highlighted with Source Code Highlighter.
     
    Как видим, ничего сложного нет! Есть вопросы? напишите, обязательно разберемся ;-)
    Hope this helps!
    Кросспост

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 18

    • UFO just landed and posted this here
        +1
        Согласен! использование try catch для обработки бизнес логики не самый красивый вариант.
        +3
        Считаю, что использование Exception'ов для таких операций, как проверка логина и пароля при регистрации пользователя не совсем корректна. Для этих целей лучше использовать какие-нибудь кастомные классы, например OperationResult и ResultCode примерно так:

        public OperationResult RegisterUser(string username, string password, string email) {
        var operationResult = OperationResult.SuccessfulResult;

        if (/*проверяем username*/)
        operationResult.AddResultCode(ResultCode.UserNameError);

        if (/*проверяем password*/)
        operationResult.AddResultCode(ResultCode.PasswordError);

        if (/*проверяем email*/)
        operationResult.AddResultCode(ResultCode.EmailError);
        }


        На выходе метода мы получаем результат операции и ошибки, если они были. Как видно из кода OperationResult содержит в себе коллекцию возможных ошибок и по умолчанию результат выполнения операции успешен.

        Мне такой способ нравится больше, хотя бы потому, что если вдруг на сервере забыли поймать Exception, то всё свалится. А разве невалидный логин является критической ситуацией? Думаю нет. Это ошибка валидации. Тут нужно чётко отделять валидацию данных и критические ситуации (нет доступа к бд, null exception и т. д.).

        И ещё немного про предложенную валидацию:
        Считаю, что лучший вариант — это использование фабрики валидаторов, построенных по паттерну композит. Каждый валидатор проверяет на свои условия и возвращает результат проверки главному объекту. В итоге мы получим результат проверки со всеми ошибками, если они произошли.
          +2
          Возможно вы и правильно считаете, но пост я написал не про то, что в таких операциях круто использовать Exceptions, а про то, как этот Exception можно отправить из WCF сервиса и поймать на клиенте.

          А в остальном — я с вами соглашусь.
          Валидаторы вообще в принципе должны быть написаны на клиенте и в логике представления.
          А вот по поводу метода тут думать надо.
          Чаще всего хочется, чтобы RegisterUser возвращал сущность зарегистрированного юзера

          public User RegisterUser(string username, string password, string email)

          Тогда сложнее нам OperationResult собирать. И приходится думать…
          Но топик действительно не об этом, просто это был первый попавшийся более-менее подходящий пример :)
            0
            Мы в проекте используем OperationResultдля передачи сущностей. Очень удобно.
            К примеру я прицепился сильно, просто наболело)
              0
              хабр схавал OperationResult <T>
                0
                Бывает :) Джуниоров гоняете?
                  0
                  если б джуниоров. Господа со статусом «сеньёр девелопер» иногда такое выкидывают…
                  0
                  Наследуете все результаты всех методов от класса OperationResult, в котором хранится свойство с одноименным enum'ом?
                0
                вы не сможете сделать проверку Login,Password если WCF сервис имеет SecurityContext, т.к. все запросы к нему должны быть авторизованы до того, как ваш метод вызовется, поэтому в этом случае только FaultException
                0
                catch (InvalidUsernameException usernameException)
                {
                // даем знать пользователю что Password произошла ошибка связанная с Email
                }
                Похоже у вас небольшая опечатка в комментариях, Email вместо Username. Подправьте если не сложно для целостности ). А вообще-с интересом прочитал, метод для меня, в принципе, нов, с wcf только начинаю работать, спасибо за статью.
                  0
                  Ох уж эти опечатки…
                  Исправил!

                  Всегда пожалуйста! :)
                  –5
                  почему сегодня нет темы про ямабилко?
                  • UFO just landed and posted this here
                      +2
                      Насколько я понял, здесь описан процесс регистрации, а не аутентификации.
                      А при регистрации очень даже полезно знать, что именно ты ввел неправильно.
                      0
                      а я по простоте душевной, когда впервые пробовал работать с WCF, посылал исключения как просто так, черех trow и сервис после этого менял статус на fault :) так я вместо того чтобы найти иной способ бросать исключения, просто после срабатывания исключения менял статус сервиса :)

                      А вообще, как тут уже некоторые говорили, лучше всего использовать свои классы результатов типа CallResult и т. п.
                        0
                        Раз уже вы затронули такую тему, то что произойдет на клиенте, когда в методе RegisterUser на сервере произойдет неучтенный exception? В каком состояние будет channel? Раз уже упомянули FaultContract, тогда расскажите и про MapExceptionToFault и IErrorHandler. Хотелось бы еще услышать, как клиенту узнать что его channel faulted, и что делать в этом случае.
                          0
                          состоянии*

                        Only users with full accounts can post comments. Log in, please.