четверг, 18 марта 2010 г.

Настройка оповещений в MS Outlook

Давно пользуюсь Outlook для чтения корпоративной почты. Для личной переписки вполне хватает веб интерфейса Gmail + оповещение о новых письмах в браузере. Т.е. о получении новых личных писем я узнаю быстро.

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

Почему разработчики Outlook реализовали такое поведения не знаю, но достает это реально. Раньше все мои попытки, что либо настроить терпели неудачу. Все что удавалось это показывать оповещения о новых письмах, обработанных правилами в модальном окне, что не совсем удобно. И вот я наконец нашел решение, оказалось, что все таки нужно донастроить правила.

Допустим у нас есть набор готовых правил сортировки писем.

  1. Открываем Outlook, выбираем в меню Сервис -> Правила и оповещения
  2. Выбираем вкладку "Правила для электронной почты"
  3. Выбираем правило и в меню "Изменить параметры правила"
  4. Пропускаем "Какие сообщения следует отбирать?" и жмем "Далее"
  5. В разделе "Что следует сделать с сообщениями?" находим действие "вывести оповещение на рабочий стол", у меня это в самом низу списка
  6. Жмем "Готово"
В результате при получения сообщений, которые обрабатываются этим правилом, мы получим маленькое всплывающее окно с информацией о письме. Это позволит реагировать на письма быстрее.

среда, 3 марта 2010 г.

Руководство Microsoft по проектированию архитектуры приложений

Недавно был на конференции Microsoft посвященной VS2010 там порекламировали перевод руководства по проектированию архитектуры приложений.

Ребята из PATTERNS & PRACTICES выпустили новую версию, а наши перевели его на русский. Начал его читать и всем советую.

Цель данного руководства – помочь разработчикам и архитекторам решений создавать эффективные высококачественные приложения на платформе Microsoft и .NET Framework в более сжатые сроки и с меньшими рисками благодаря использованию проверенных и снискавших доверие архитектурных принципов и шаблонов проектирования.
В руководстве предлагается обзор основных принципов и шаблонов,
которые обеспечивают прочную базу для создания хорошей архитектуры и дизайна
приложения. В дополнение к этой базе даются общеприменимые рекомендации по
разделению функциональности приложения на слои, компоненты и сервисы. Далее приводятся советы по определению и реализации ключевых характеристик дизайна, основных атрибутов качества (таких как производительность, безопасность и масштабируемость) и сквозной функциональности (такой как кэширование и протоколирование). В завершение руководство предлагает рекомендации по архитектуре и дизайну наиболее общих типов приложений, таких как Веб-приложения, насыщенные Интернет-приложения (RIA), насыщенные клиентские приложения, сервисы и мобильные приложения.
Руководство разделено на части соответственно основным аспектам архитектуры и дизайна. Оно скомпоновано таким образом, чтобы могло использоваться и как справочник, и как учебное пособие.

Скачать его можно тут:

среда, 17 февраля 2010 г.

Преобразование Website в Web Application Projects

С появлением ASP.NET 2.0 и VS 2005 у разработчиков веб приложений появился новый тип проектов Website, который позволял изменять динамически не только дизайн страниц, но и  c# код. Этой возможностью многие стали пользоваться, но все это удобство достигается за счет того, что каждая страница и контрол компилируются по отдельности. При довольно большом размере проекта, компиляция занимает очень много времени. К тому же модель Website по умолчанию не добавляет namespace для страниц, что ведет к путанице и даже странным ошибкам компиляции.

У нас на проекте решили преобразовать существующий огромный Website в Web Application Projects. На первый взгляд казалось, что достаточно будет запустить визард конвертации, немного протестировать и все будет в порядке.
Открываю проект и ищу что то типа "Convert to web application", но не нахожу. Оказывается все намного сложнее.

Решение я нашел тут:
MSDN: Converting a Web Site Project to a Web Application Project in Visual Studio

Converting VS 2008 Website to Web Application

пятница, 5 февраля 2010 г.

Работа с Visual Studio 2008 на windows 7.

Пришлось переустановить window и опять встала проблема доустановки компонентов системы, которые нужны для разработки asp.net и wcf приложений.

1) Нужно установить компоненты для IIS 7
Подробней можно почитать тут
http://learn.iis.net/page.aspx/387/using-visual-studio-2008-with-iis-70/
Теперь наши веб проекты будут работать

2) Для wcf нужно же поставить еще и другие компоненты.
Все там же в разделе Microsoft .Net Framework выбираем Windows Communication  Foundation

А теперь уже заработает WCF и можно работать дальше.

пятница, 25 декабря 2009 г.

Логирование сообщений в WCF

Работая с WCF редко приходится задумываться "А в каком виде сообщения передаются между клиентом и сервером". В общем случае, достаточно настроить web.config, что бы изменить протокол передачи данных.

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

1. Воспользоваться приемами трассировки.
2. Реализовать заказное поведение оконечной точки.

Трассировка и протоколирование сообщений в WCF

Трассировка используется для вывода информации о потоке выполнения и отдельных действий различных компонентов распределенного приложения. Механизм же протоколирования сообщений предназначен для сохранения содержимого сообщений, которыми обмениваются клиент и служба.
Протоколирование сообщений основано на классах из пространства имен System.Diagnostics и по умолчанию выключено. Что бы его включить, необходимо сначала добавить прослушиватель трассировки (например, XmlWriterTraceListener) для обработки информации от источника трассировки System.ServiceModel.MessageLogging.

Давайте изменим App.config клиента.
<configuration>
    <system.serviceModel>
        <behaviors .../>
        <diagnostics>
            <messageLogging 
                logEntireMessage="true" 
                logMessagesAtTransportLevel="true"
                maxMessagesToLog="4000" />

        </diagnostics>
    </system.serviceModel>

<system.diagnostics>
        <sources>
            <source name="System.ServiceModel.MessageLogging">
                <listeners>
                    <add type="System.Diagnostics.XmlWriterTraceListener" name="messages" initializeData="messages.svclog">
                    </add>
                </listeners>
            </source>

        </sources>
    </system.diagnostics>
</configuration>

Мы добавили источник System.ServiceModel.MessageLogging, с помощью которого выводятся сообщения для протоколирования. Обрабатываем же получаемую от него информацию прослушивателем XmlWriterTraceListener.
Теперь в файл messages.svclog пишутся логи сообщений. Для чтения и анализа этого файла можно воспользоваться программой Service Trace Viewer. Она может использоваться для импорта протоколов сообщений, сформированных одним или несколькими компонентами распределенного приложения. Эта программа входит в пакет Windows SDK.

Реализация заказного поведения для трассировки.

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

Реализация заказного поведения состоит из трех шагов:
Шаг 1. Создать класс, который реализует интерфейс Inspector, Selector, Formater или Invocer. В нашем случае это IClientMessageInspector

internal class LogMessageInspector : IClientMessageInspector
    {
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            Console.WriteLine("---request---");
            Console.WriteLine(request.ToString());
            Console.WriteLine("---end---");
            return request;
        }

        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
            Console.WriteLine("---Reply---");
            Console.WriteLine(reply.ToString());
            Console.WriteLine("---end---");
        }
    }

Шаг 2. создать класс, который реализует один из интерфейсов поведения: IServiceBehavior, IEndpointBehavior, IOperationBehavior или IContractBehavior. У нас это IEndpointBehavior.

public class LogEndpointBehavior : IEndpointBehavior
    {
        public void Validate(ServiceEndpoint endpoint)
        {
        }

        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new LogMessageInspector());
        }
    }

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

            ChannelFactory<IFilms> factory = new ChannelFactory<IFilms>("poxWcfService");
            //Add logging of message to console
            factory.Endpoint.Behaviors.Add(new LogEndpointBehavior());
            IFilms client = factory.CreateChannel();

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

понедельник, 21 декабря 2009 г.

Опыт использования Microsoft Security Essentials

Месяц назад попробовал поставить дома Wondows 7 64bit, в качестве антивируса решил попробовать Security Essentials от Микрософта, благо он тоже зарелизился да и бесплатный.


Одновременно сменил интернет провайдера и роутер.
Провайдером стал Триолан, скорость 100Мбит всего за 90 грн.
Роутер поставил гигабитный Linksys WRT320N

В результате HD рипы 1080p, по 8 гиг качаются всего за 15 минут из интернета, в общем счастье наступило.


Но, через некоторое время стала временами отваливаться сеть, запускаю диагностику сети, выполняется сброс сетевого адаптера и сеть поднимается. Долго думал, кто виноват, сетевая, роутер или провайдер.


Похоже виноватым оказался антивирус от Микрософт - Security Essentials.
Как то копировал 40 гигабайтный файл на внешний винчестер, но он что то очень медленно  читался, начал искать виноватого, оказалось антивирус съел всю память, пытаясь проанализировать этот файл.


Снес антивирус от Микрософта, поставил nod32 и о чудо сеть больше не падает и память не кончается.


Вот такой антивирус написан Микрософт, что и сеть от него падает и файлы большие не скопировать.


вторник, 27 октября 2009 г.

Обработка ошибок в WCF при использовании REST подхода.


WCF позволяет реализовать обработку ошибок при помощи контрактов ошибок. Подробней об этой методике можно почитать на RSDN http://www.rsdn.ru/article/dotnet/FaultsWCF.xml или в книге “Основы WCF ” Стива Резника, глава 10 - Обработка исключений. Но там рассматривается только обработка исключений при SOAP коммуникациях, для REST нужна дополнительная реализация.
Ниже я покажу, как можно реализовать обработку ошибок для REST сервиса, который реализует две точки доступа POX и JSON.
Давайте создадим WCF службу ReliableService с единственным сервисом Films, который реализует каталог домашних фильмов. Сервис будет содержать 5 методов:
·  Counts – количество фильмов в каталоге
·  GetFilm – вернуть описания фильма по ID
·  GetFilmNames – вернуть последние добавленные фильмы
·  GetFilmsByGenre – вернуть фильмы по жанру
·  AddFilm – добавить фильм в каталог
Создаем новый проект WCF Service Application, удаляем существующий сервис из проекта и из web.config. И добавим новый сервис Films. Добавим в интерфейс этого сервиса 5 методов, которые можно вызывать через REST.
[ServiceContract]
public interface IFilms
{
      [OperationContract]
      [WebGet]
      int Count();

      [OperationContract]
      [WebGet]
      FilmEntity GetFilm(string filmID);

      [OperationContract]
      [WebGet]
      List<string> GetFilmNames();

      [OperationContract]
      [WebGet]
      List<FilmEntity> GetFilmsByGenre(FilmGenre genre);

      [OperationContract]
      [WebInvoke]
      void AddFilm(FilmEntity film);
}
[DataContract]
public enum FilmGenre
{
    [EnumMember]Action,
    [EnumMember]Comedy,
    [EnumMember]Drama,
    [EnumMember]War
}
[DataContract]
public class FilmEntity
{
    [DataMember]public int ID { get; set; }
    [DataMember]public string Guid { get; set; }
    [DataMember]public string Title { get; set; }
    [DataMember]public int Year { get; set; }
    [DataMember]public FilmGenre Genre { get; set; }
    [DataMember]public string Description { get; set; }
}
Реализуем этот сервис:
public class Films : IFilms
{
      private readonly SortedDictionary<string, FilmEntity> _films;
      public Films()
      {
            _films = GetFilms();
      }

      public int Count()
      {
            return _films.Count;
      }

      public FilmEntity GetFilm(string filmGuid)
      {
            return _films[filmGuid];
      }

      public List<string> GetFilmNames()
      {
            List<string> filmNames = new List<string>(_films.Count);
            foreach (FilmEntity film in _films.Values)
            {
                  filmNames.Add(film.Title);
            }
            return filmNames;
      }

      public List<FilmEntity> GetFilmsByGenre(FilmGenre genre)
      {
            List<FilmEntity> filmsByGenre = new List<FilmEntity>(_films.Count);
            foreach (FilmEntity film in _films.Values)
            {
                  if (film.Genre == genre)
                  {
                        filmsByGenre.Add(film);
                  }
            }
            return filmsByGenre;
      }

      public void AddFilm(FilmEntity film)
      {
            if (string.IsNullOrEmpty(film.Guid))
            {
                  film.Guid = Guid.NewGuid().ToString();
            }
            _films.Add(film.Guid, film);
      }

      private SortedDictionary<string, FilmEntity> GetFilms()
      {
            SortedDictionary<string, FilmEntity> films = new SortedDictionary<string, FilmEntity>();
            //fill test data here
            return films;
      }
}
Приведем секцию system.serviceModel в web.config к следующему виду:
<system.serviceModel>
  <services>
    <service behaviorConfiguration="ReliableService.FilmsBehavior" name="ReliableService.Films">
    <endpoint address="" binding="webHttpBinding" contract="ReliableService.IFilms" behaviorConfiguration="poxBehavior" />
    <endpoint address="json" binding="webHttpBinding" contract="ReliableService.IFilms" behaviorConfiguration="jsonBehavior" />
    <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
    service>
  services>
  <behaviors>
    <serviceBehaviors>
      <behavior name="ReliableService.FilmsBehavior">
        <serviceMetadata httpGetEnabled="true" />
        <serviceDebug includeExceptionDetailInFaults="true" />
      behavior>
    serviceBehaviors>
    <endpointBehaviors>
     
      <behavior name="poxBehavior">
        <webHttp/>
      behavior>
     
      <behavior name="jsonBehavior">
        <enableWebScript/>
      behavior>
    endpointBehaviors>
  behaviors>
system.serviceModel>
Осталось захостить нашу слудбу на ISS, скомпилироваться и можно смотреть, что у нас получилось.
Правый клик по сервису и выбираем View in Browser или просто открываем http://localhost/ReliableService/Films.svc. Вы должны увидеть:
Films Service.
You have created a service.
Хорошо, попробуем вызвать любой метод, например
Итак, мы имеем REST сервис, который может возвращать ответ в виде XML или JSON. Теперь давайте посмотрим, что будет если во время работы серсива произойдет ошибка. К примеру если метод GetFilm вызвать без параметра
Request Error
The server encountered an error processing the request. The exception message is 'Value cannot be null. Parameter name: key'. See server logs for more details. The exception stack trace is:
Пользователю лучше не видеть наших внутренних ошибок, давайте для начала отключим показ деталей ошибок в конфигурации, для этого нужно установить
<serviceDebug includeExceptionDetailInFaults="false" />
Теперь мы увидим следующее сообщение:
The server encountered an error processing the request. See server logs for more details.
Лучше, но не информативно. Разработчик, который будет использовать наш сервис, не сможет понять, в чем ошибка и будет просить нас проверить наши логии. Давайте попробуем реализовать контракты ошибок, как это описано в ссылках, которые я приводил выше. Неплохо бы разделить ошибки, в которых виноват клиент и в которых виноват сервер. Для этого будем возвращать типизированные контракты ошибок FaultContract.
Для начала нужно в интерфейсе каждому методу добавить пару атрибутов:
[OperationContract]
[WebGet]
[FaultContract(typeof(ClientError))]
[FaultContract(typeof(ServerError))]
FilmEntity GetFilm(string filmID);
Во вторых реализовать два класса ClientError и ServerError, они будут содержать подробное описание ошибки, к примеру, дата ошибки и сообщения об ошибке для разработчика и конечного пользователя. Т.е. разработчик клиента для нашего сервиса может не придумывать сообщения для пользователя, а использовать готовое сообщение.
[DataContract]
public abstract class CatalogError
{
      [DataMember(Order = 1, IsRequired = true)]
      public DateTime Date { get; set; }
      [DataMember(Order = 2, IsRequired = true)]
      public string SystemMessage { get; set; }
      [DataMember(Order = 3, IsRequired = true)]
      public string CustomerMessage { get; set; }
}
[DataContract]
public class ClientError : CatalogError
{
      public ClientError(string systemMessage, string customerMessage)
      {
            Date = DateTime.Now;
            SystemMessage = systemMessage;
            CustomerMessage = customerMessage;
      }
}

[DataContract(Namespace = GlobalConstants.WebServiceNamespace)]
public class ServerError : CatalogError
{
      public ServerError(string systemMessage, string customerMessage)
      {
            Date = DateTime.Now;
            SystemMessage = systemMessage;
            CustomerMessage = customerMessage;
      }
}

Осталось научить наш сервис возвращать ошибки в таком виде. Для этого нужно создать класс реализующий IErrorHandler, атрибут ErrorHandlerBehavior и добавить атрибут в реализацию сервиса.

public class CatalogErrorHandler : IErrorHandler
{
      private const string InternalErrorCustomerMessage = "We are having technical issuse at the moment, please try again.";

      public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
      {
            FaultException faultException = CreateFaultException(ex);

            MessageFault messageFault = faultException.CreateMessageFault();
            fault = Message.CreateMessage(version, messageFault, faultException.Action);
      }

      public bool HandleError(Exception error)
      {
            //TODO Add saving error here
            return true;
      }

      private static FaultException CreateFaultException(Exception ex)
      {
            FaultException faultException = new FaultException<ServerError>(
                  new ServerError(ex.Message, InternalErrorCustomerMessage), InternalErrorCustomerMessage);
            return faultException;
      }
}

public class ErrorHandlerBehaviorAttribute : Attribute, IServiceBehavior
{
      private IErrorHandler _errorHandler;

      public ErrorHandlerBehaviorAttribute(Type typeErrorHandler)
      {
            if (typeErrorHandler == null)
            {
                  throw new ArgumentNullException();
            }

            _errorHandler = (IErrorHandler)Activator.CreateInstance(typeErrorHandler);
      }

      public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
      {
            foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
            {
                  dispatcher.ErrorHandlers.Add(_errorHandler);
            }
      }

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

      public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
      {
      }
}

Не забываем добавить атрибут к реализации сервиса:

[ErrorHandlerBehavior(typeof(CatalogErrorHandler))]
public class Films : IFilms

Если бы мы работали по SOAP, этого было бы достаточно, но для REST это не работает.
Для того что бы и для REST подхода заработала архитектура контрактов ошибок WCF, нам нужно сделать обработчики ошибок, которые сериализуют ошибку в нужный нам формат. Для того что бы служба знала как возвращать ошибки для различных точек доступа, также создадим расширения поведения точек доступа.
Для начал сделаем два дополнительных обработчика ошибок PoxErrorHandler и JsonErrorHandler:
public class PoxErrorHandler : IErrorHandler
{
      public bool HandleError(Exception error)
      {
            return true;
      }

      public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
      {
            FaultException faultException = CatalogErrorHandler.CreateFaultException(error);

            // extract the our FaultContract object from the exception object.
            var detail = faultException.GetType().GetProperty("Detail").GetGetMethod().Invoke(faultException, null);
            // create a fault message containing our FaultContract object
            fault = Message.CreateMessage(version, faultException.Action, detail, new DataContractSerializer(detail.GetType()));
            // tell WCF to use XML encoding
            var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Xml);
            fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
      }
}

public class JsonErrorHandler : IErrorHandler
{
      public bool HandleError(Exception error)
      {
            return true;
      }

      public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
      {
            FaultException faultException = CatalogErrorHandler.CreateFaultException(error);

            // extract the our FaultContract object from the exception object.
            var detail = faultException.GetType().GetProperty("Detail").GetGetMethod().Invoke(faultException, null);
            // create a fault message containing our FaultContract object
            fault = Message.CreateMessage(version, faultException.Action, detail, new DataContractJsonSerializer(detail.GetType()));
            //  tell WCF to use JSON encoding rather than default XML
            var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
            fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
      }
}
Теперь создадим два класа расширителя поведения точек доступа
public class FaultingJsonBehaviorElement : BehaviorExtensionElement
{
      public override Type BehaviorType
      {
            get { return typeof(FaultingJsonBehavior); }
      }

      protected override object CreateBehavior()
      {
            return new FaultingJsonBehavior();
      }
}

public class FaultingJsonBehavior : WebHttpBehavior
{
      protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
      {
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new JsonErrorHandler());
      }

      public override WebMessageFormat DefaultOutgoingResponseFormat
      {
            get
            {
                  return WebMessageFormat.Json;
            }
            set
            {
                  base.DefaultOutgoingResponseFormat = value;
            }
      }
}

public class FaultingPoxBehaviorElement : BehaviorExtensionElement
{
      public override Type BehaviorType
      {
            get { return typeof(FaultingPoxBehavior); }
      }

      protected override object CreateBehavior()
      {
            return new FaultingPoxBehavior();
      }
}

public class FaultingPoxBehavior : WebHttpBehavior
{
      protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
      {
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
            endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new PoxErrorHandler());
      }
}
Осталось прописать эти раширители в файле конфигурации, перед секцией behaviors добавим секцию extensions
<extensions>
  <behaviorExtensions>
    <add name="faultingPox" type="ReliableService.FaultingPoxBehaviorElement, ReliableService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
    <add name="faultingJson" type="ReliableService.FaultingJsonBehaviorElement, ReliableService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
  behaviorExtensions>
extensions>
Не забудем использовать эти расширители в точках доступа
<endpointBehaviors>
 
  <behavior name="poxBehavior">
    <webHttp/>
    <faultingPox/>
  behavior>
 
  <behavior name="jsonBehavior">
    <enableWebScript/>
    <faultingJson/>
  behavior>
endpointBehaviors>
Дело сделано, теперь в случае ошибки наши REST точки доступа будут возвращать контракты ошибок в нужном формате. Например для POX вызовем метод GetFilm без параметров
получим следующий результат
<ServerError xmlns="ReliableService" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Date>2009-10-27T13:26:07.5407249+02:00Date>
  <SystemMessage>Value cannot be null. Parameter name: keySystemMessage>
  <CustomerMessage>We are having technical issues at the moment, please try again.CustomerMessage>
ServerError>

Дело сделано, теперь и при REST подходе мы можем использовать единый механизм обработки ошибок WCF.

Исходный код проекта можно скачать по ссылке:

http://sites.google.com/site/mrdragonden/primery/ReliableService.rar?attredirects=0&d=1