Сервисные хосты WCF, сервисные конечные точки и привязки
В настоящее время я использую некоторые службы WCF REST в службе Windows (не IIS), используя WebServiceHost
, У меня есть отдельный интерфейс и класс, определенный для каждой службы, но у меня возникают некоторые проблемы с пониманием того, как WebServiceHost
, ServiceEndpoint
а также ServiceContracts
могут быть использованы вместе, чтобы создать самостоятельное решение.
Способ, которым я в настоящее время настраиваю вещи, состоит в том, что я создаю новый WebServiceHost
для каждого класса, который реализует службу и использует имя класса как часть URI, но затем определяет остальную часть URI в интерфейсе.
[ServiceContract]
public interface IEventsService
{
[System.ServiceModel.OperationContract]
[System.ServiceModel.Web.WebGet(UriTemplate = "EventType", ResponseFormat=WebMessageFormat.Json)]
List<EventType> GetEventTypes();
[System.ServiceModel.OperationContract]
[System.ServiceModel.Web.WebGet(UriTemplate = "Event")]
System.IO.Stream GetEventsAsStream();
}
public class EventsService: IEventsService
{
public List<EventType> GetEventTypes() { //code in here }
public System.IO.Stream GetEventsAsStream() { // code in here }
}
Код для создания сервисов выглядит так:
Type t = typeof(EventService);
Type interface = typeof(IEventService);
Uri newUri = new Uri(baseUri, "Events");
WebServicesHost host = new WebServiceHost(t, newUri);
Binding binding = New WebHttpBinding();
ServiceEndpoint ep = host.AddServiceEndpoint(interface, binding, newUri);
Это работает хорошо, и конечная точка службы для каждой службы создается по соответствующему URL.
http://XXX.YYY.ZZZ:portnum/Events/EventType
http://XXX.YYY.ZZZ:portnum/Events/Event
Затем я повторяю для другого интерфейса обслуживания и класса обслуживания. Я хотел бы удалить Events
в URL, хотя, но если я сделаю это и создать несколько WebServiceHosts
с тем же базовым URL я получаю ошибку:
The ChannelDispatcher at 'http://localhost:8085/' with contract(s) '"IOtherService"' is unable to open its IChannelListener
с внутренним исключением:
"A registration already exists for URI 'http://localhost:8085/'."
Я пытаюсь понять, как WebServiceHost
, ServiceEndpoint
а также ServiceContract
работать вместе, чтобы создать ChannelListener.
Нужен ли мне отдельный WebServiceHost
для каждого класса, который реализует услугу? Я не вижу способа зарегистрировать несколько типов с одним WebServiceHost
Во-вторых, я передаю интерфейс методу AddServceEndpoint и предполагаю, что метод проверяет объект на наличие всех членов OperationContract и добавляет их; проблема в том, как WebServiceHost узнает, какой класс должен сопоставляться с каким интерфейсом.
То, что я хотел бы, было бы примером создания собственной службы WCF, которая запускает несколько служб, сохраняя интерфейс и классы реализации раздельными.
4 ответа
Похоже, проблема в том, что вы пытаетесь зарегистрировать более одного сервиса в одном и том же URI сервиса. Это не будет работать, как вы заметили, каждая служба должна иметь уникальную конечную точку.
Уникальный By
- IP
- Домен
- Номер порта
- Полный URL
Примеры
http://someserver/foo -> IFoo Service
http://someserver/bar -> IBar Service
http://somedomain -> IFoo Service
http://someotherdomain -> IBar Service
http://somedomain:1 -> IFoo Service
http://somedomain:2 -> IBar Service
Вы поняли идею.
Таким образом, чтобы напрямую ответить на ваш вопрос, если вы хотите, чтобы сервис несколько раз находился в корневом URL-адресе вашего сайта, вам придется разместить их на разных портах. Таким образом, вы можете изменить свой код, чтобы быть что-то вроде
public class PortNumberAttribute : Attribute
{
public int PortNumber { get; set; }
public PortNumberAttribute(int port)
{
PortNumber = port;
}
}
[PortNumber(8085)]
public interface IEventsService
{
//service methods etc
}
string baseUri = "http://foo.com:{0}";
Type iface = typeof(IEventsService);
PortNumberAttribute pNumber = (PortNumberAttribute)iface.GetCustomAttribute(typeof(PortNumberAttribute));
Uri newUri = new Uri(string.Format(baseUri, pNumber.PortNumber));
//create host and all that
Я думаю, что вам может быть полезно переосмыслить свой подход URI. Uri - это уникальный идентификатор ресурса. Каждая ваша конечная точка говорит о том, что вы пытаетесь выставить за пределы различного вида ресурсов его "События" и "Другой ресурс". Таким образом, вам нужно немного изменить свои UriTemplates.
Я бы сделал это так:
[ServiceContract]
public interface IEventTypesService
{
[OperationContract]
[WebGet(UriTemplate = "", ResponseFormat=WebMessageFormat.Json)]
IList<EventType> GetEventTypes();
[OperationContract]
[WebGet(UriTemplate = "{id}")]
EventType GetEventType(string id);
}
[ServiceContract]
public interface IEventsService
{
[OperationContract]
[WebGet(UriTemplate = "")]
Stream GetEventsAsStream();
[OperationContract]
[WebGet(UriTemplate = "{id}")]
Event GetEvent(string id);
}
public class EventsService: IEventsService, IEventTypesService
{
public IList<EventType> GetEventTypes() { //code in here }
public EventType GetEventType(string id) { //code in here }
public Stream GetEventsAsStream() { // code in here }
public EventType GetEventType(string id) { // code in here }
}
Type t = typeof(EventService);
Type interface1 = typeof(IEventsService);
Type interface2 = typeof(IEventTypesService);
var baseUri = new Uri("http://localhost");
Uri eventsUri= new Uri(baseUri, "Events");
Uri eventTypesUri= new Uri(baseUri, "EventTypes");
WebServicesHost host = new WebServiceHost(t, baseUri);
Binding binding = New WebHttpBinding();
host.AddServiceEndpoint(interface1, binding, eventsUri);
host.AddServiceEndpoint(interface2, binding, eventTypesUri);
И да, вы правы - у вас должны быть разные адреса, но это действительно разные ресурсы. Чтобы лучше это понять, вы можете обратиться к: RESTful API Design, http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Чтобы закончить, есть способ использовать тот же адрес, но подход немного странный: использование того же адреса
Следующее решение:
- позволяет одному объекту обрабатывать конкретную конечную точку
- в шаблоне URI нет части пути
- использует один и тот же порт для всех сервисов
Для этого требуется более одного WebServiceHost - по одному на объект, который обрабатывает запросы. Другая трудность заключается в том, что добавление более глубоких конечных точек (например, /events/2014) означает, что они либо должны иметь уникальные параметры, либо шаблон URI должен включать часть пути, если вы соглашаетесь с конфигурацией, которая не должна быть проблемой.
WebServiceHost может содержать только одну вещь (класс), но этот объект может иметь несколько интерфейсов для обработки нескольких различных типов запросов по разным URL-адресам. Как разные WebServiceHosts могут быть привязаны к одному и тому же домену: порту? Они не могут, поэтому я предполагаю, что WebServiceHost оборачивает базовый статический объект, который направляет запросы к нужному объекту. Технически это не отвечает на ваш вопрос, но я думаю, что эта реализация позволяет вам делать то, что вы хотите, верно?
Консольное приложение, в котором размещаются веб-сервисы.
public class Program
{
static void Main (string[] args)
{
var venueHost = new WebServiceHost (typeof (Venues));
venueHost.AddServiceEndpoint (typeof (IVenues), new WebHttpBinding (), "http://localhost:12345/venues");
venueHost.Open ();
var eventHost = new WebServiceHost (typeof (Events));
eventHost.AddServiceEndpoint (typeof (IEvents), new WebHttpBinding (), "http://localhost:12345/events");
eventHost.Open ();
while (true)
{
var k = Console.ReadKey ();
if (k.KeyChar == 'q' || k.KeyChar == 'Q')
break;
}
}
}
Класс Venues реализует IVenues и обрабатывает любые запросы http://localhost:12345/venues/
[ServiceContract]
public interface IVenues
{
[WebInvoke (Method = "GET", UriTemplate = "?id={id}")]
string GetVenues (string id);
}
public class Venues : IVenues
{
public string GetVenues (string id)
{
return "This would contain venue data.";
}
}
Класс Events реализует IEvents и обрабатывает любые запросы к http://localhost:12345/events/
[ServiceContract]
public interface IEvents
{
[WebInvoke (Method = "GET", UriTemplate = "?venue={venue}")]
string GetEvents (string venue);
}
public class Events : IEvents
{
public string GetEvents (string venue)
{
return "This would contain event data.";
}
}
Самостоятельный хостинг WCF может быть выполнен разными способами, например, хостинг консольных приложений, хостинг служб Windows и т. Д.
Я пытался разместить два сервиса, используя одно консольное приложение. Структура сервисов была похожа на то, что вы упомянули, то есть отдельные классы и интерфейсы для обоих сервисов.
Возможно, вы захотите взглянуть на эту ссылку: размещение двух служб WCf с помощью одного консольного приложения