Создать свой простенький сервис, не составляет труда. В интернете полно статей про это (раз, два, три).
Ну что же, создадим и мы свой сервис. Назовем его ListService и он будет содержать всего лишь один метод GetListID, принимающий параметром название списка. Не трудно догадаться, что он делает – мы ему название списка, а он нам его идентификатор.
Вот исходник сервиса:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class ListService : IListService
{
#region propery
private SPContext _spcontext;
private SPContext Context
{
get
{
_spcontext = _spcontext ?? SPContext.GetContext(HttpContext.Current);
return _spcontext;
}
}
private SPWeb _web;
private SPWeb Web
{
get
{
_web = _web ?? Context.Web;
return _web;
}
}
#endregion
public Guid GetListID(string title)
{
SPList list = Web.Lists.TryGetList(title);
if (list == null)
{
throw new SPException("List not found");
}
return list.ID;
}
}
И его интерфейс
[ServiceContract]
interface IListService
{
[OperationContract]
[WebInvoke(Method = "GET",
UriTemplate = "GetListID/{title}",
ResponseFormat = WebMessageFormat.Json,
RequestFormat = WebMessageFormat.Json,
BodyStyle = WebMessageBodyStyle.Bare)]
Guid GetListID(string title);
}
Ну вот результат результат его работы.Все работает. Так в чем же проблема? А что будет, если мы ошибемся в название списка? Давайте проверим..
Мда.. Это не то, что я ожидал. Это сообщение не дает никакой информации об ошибки и как ее исправить. Надо менять сие поведение!
Если вы работаете с .net
4 и выше – есть очень простой способ исправить это. Необходимо использовать
класс WebFaultException вместо обычного класса исключения. Сейчас
покажу как. Изменим метод GetLIstID нашего сервиса.
public Guid GetListID(string title)
{
SPList list = Web.Lists.TryGetList(title);
if (list == null)
{
throw new WebFaultException("List not found", HttpStatusCode.InternalServerError);
}
return list.ID;
}
И проверим результат.
Отлично, все работает, как нам надо. Правда, возник другой вопрос - а что
будет если ошибка возникнет там, где мы ее не ожидаем?
Еще раз поменяем код.
Попробуем с эмулировать эту ситуацию. Заменим SPList list = Web.Lists.TryGetList(title) на SPList list = Web.Lists[title]. Допустим мы не подозреваем, что теперь получим исключение,
если имя списка не правильное. Смотрим результат.. Ну и предсказуемо мы видим «Request Error».
Как один из вариантов исправить это -
обернуть все операции(ну или весь метод сервиса), которые могут
потенциально вызвать исключение, в блок try/catch и в catch выбрасывать WebFaultException. Но это лишний код и
вообще как-то нехорошо, к тому же не стоит забывать этот класс доступен только
для .net 4 и выше.
После быстрого поиска в гугле, попалась вот эта статья.
В двух словах для решения этой проблемы необходимо сделать следующее:
- Создать класс реализующий интерфейс IErrorHandler, который будет обрабатывать ошибки.
- Создать наследника WebHttpBehavior и переопределить стандартный обработчик ошибок, на созданный нами ранее.
- Создать наследника WebServiceHostFactory и добавить к нему поведение созданное в шаге 2.
Прежде всего нам необхожимо создать класс, который мы будем возвращать при ошибке. Назовем его, например, CustomFault.
[DataContract]
class CustomFault
{
[DataMember]
public string Message { get; set; }
}
Хорошо, а теперь создадим наш собственный обработчик ошибок.
class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
CustomFault ex = new CustomFault();
ex.Message = error.Message;
fault = Message.CreateMessage(MessageVersion.None, "",ex, new DataContractJsonSerializer(ex.GetType()));
var wbf = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, wbf);
WebOperationContext.Current.OutgoingResponse.ContentType = "application/json";
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError;
}
}
Нам необходимо реализовать свойство HandleError, определяющее будем ли мы обрабатывать ошибки. Просто возвращаем true. Ну и метод ProvideFault – где и обрабатываем ошибку и возвращаем JSON. Создаем CustomFault, инициализируем единственное свойство тестом исключения и добавляем наш объект к экземпляру класса Message, который и вернется клиенту. Также не забываем указать Content-Type и StatusCode ответа сервера.
Создадим потомка WebHttpBehavior и добавим наш обработчик ошибок.
class WebHttpBehaviorEx : WebHttpBehavior
{
protected override void AddServerErrorHandlers(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Clear();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(new ErrorHandler());
}
}
Остался последний шаг и вот тут нас ожидают проблемы. Когда мы создали наш сервис, мы в файле .svc указали какой класс использовать как фабрику, а именно класс Microsoft.SharePoint.Client.Services.MultipleBaseAddressWebServiceHostFactory. Почему именно его и зачем он вообще нужен почитайте здесь. Нам надо изменить его поведение, а именно переопределить создание экземпляра класса ServiceHost . Как вариант, попробуем унаследоваться и переопределить эти методы. Сказано сделано.
class CustomFactory : MultipleBaseAddressWebServiceHostFactory
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
var sh = new ServiceHost(typeof(ListService), baseAddresses);
sh.Description.Endpoints[0].Behaviors.Add(new WebHttpBehaviorEx());
return sh;
}
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return base.CreateServiceHost(serviceType, baseAddresses);
}
}
И не забудем поменять наш svc файл, приведем его к виду
<%@ ServiceHost Language="C#" Debug="true"
Service="CustomService.NVServices.ListService, $SharePoint.Project.AssemblyFullName$"
CodeBehind="ListService.svc.cs"
Factory="CustomService.NVServices.CustomFactory,
$SharePoint.Project.AssemblyFullName$"%>
Проверим, что получили.. Ошибка, но уже с CorrelationID, что радует. Находит
в ее в логах - This collection already
contains an address with scheme http. Ммм.. т.е сервис уже создан? Проверим, дебаг еще никто не отменял.
И в действительности, не получается создать экземпляр объекта ServiceHost. Причина скорее всего в том, что CreateServiceHost(Type serviceType, Uri[] baseAddresses) отрабатывает раньше и base.CreateServiceHost(serviceType, baseAddresses) уже создал нам экземпляр
ServiceHost. Придется капать глубже, а куда глубже, чем в
исходники?) К тому же идея была не ахти какая. Что пришлось бы делать, если
сервисов было несколько – создать для каждого класс фабрику? Ну нет уж.
Открываем ILSpy и посмотрим из чего состоит MultipleBaseAddressWebServiceHostFactory. Для этого нам понадобиться сборка Microsoft.SharePoint.Client.ServerRuntime.dll(C:\Program
Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI). Вот так вот выглядит этот класс.
Мы пытались переопределить не тот метод, которы нам нужен и наша фабрика работает с наследником ServiceHost. Посмотрим, что из
себя представляет MultipleBaseAddressWebServiceHost.
Вот и нашли
место, где мы сможем добавить наш WebHttpBehaviorEx.
А именно метод OnOpening класса MultipleBaseAddressWebServiceHost. Ну что ж сделаем это, создадим своего наследника класса MultipleBaseAddressWebServiceHost.
[ServiceFactoryUsingAuthSchemeInEndpointAddress]
class VirtoMultipleHttpBindingServiceHost : MultipleBaseAddressWebServiceHost
{
public VirtoMultipleHttpBindingServiceHost(Type serviceType, params Uri[] baseAddresses)
: base(serviceType, Utility.GetBaseAddressesWithUniqueScheme(baseAddresses))
{
}
protected override void OnOpening()
{
base.OnOpening();
Description.Endpoints[0].Behaviors.Add(new WebHttpBehaviorEx());
}
}
Utility.GetBaseAddressesWithUniqueScheme(baseAddresses) - это копия метода ServiceUtility.GetBaseAddressesWithUniqueScheme(baseAddresses). К сожалению класс ServiceUtility является internal и мы не сможем им воспользоваться.
И теперь осталось изменить наш класс CustomFactory.
class CustomFactory : WebServiceHostFactory
{
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
return new VirtoMultipleHttpBindingServiceHost(serviceType, baseAddresses);
}
}
Проверим результат
Вот и все. Теперь вы можете управлять обработкой исключения как вам угодно и возвращать их в любом формате.
Спасибо, за внимание.
Комментариев нет:
Отправить комментарий