Сервисные хосты 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/EventTypehttp://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 с помощью одного консольного приложения

Другие вопросы по тегам