【问题标题】:WCF ServiceHosts, ServiceEndpoints and BindingsWCF 服务主机、服务端点和绑定
【发布时间】:2014-04-07 15:42:57
【问题描述】:

我目前正在使用WebServiceHost 在 Windows 服务(不是 IIS)中运行一些 WCF REST 服务。我为每个服务定义了一个单独的接口和类,但我在理解如何将WebServiceHostServiceEndpointServiceContracts 一起使用来创建自托管解决方案时遇到了一些问题。

我目前的设置方式是为每个实现服务的类创建一个新的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,但如果我这样做并使用相同的基本网址创建多个WebServiceHosts,我会收到错误:

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/'."

我试图了解WebServiceHostServiceEndpointServiceContract 如何协同工作来创建 ChannelListener。

是否需要为每个实现服务的类提供单独的WebServiceHost?我看不到用单个 WebServiceHost 注册多种类型的方法

其次,我将接口传递给 AddServceEndpoint 方法,我假设该方法检查对象的所有 OperationContract 成员并添加它们,问题是 WebServiceHost 如何知道哪个类应该映射到哪个接口。

我希望创建一个 WCF 自托管服务的示例,该服务运行多个服务,同时保持接口和实现类分开。

【问题讨论】:

    标签: c# wcf rest webservicehost


    【解决方案1】:

    在我看来,您遇到的问题是您试图在同一个服务 URI 上注册多个服务。这行不通,正如您所注意到的,每个服务都必须有一个唯一的端点。

    独一无二

    • IP
    • 端口号
    • 完整网址

    例子

    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
    

    【讨论】:

    • 感谢您的解释。对答案并不感到兴奋,但至少现在很清楚。问题是我有一个单独的类来处理每种类型的资源,即 IVenueService 定义了一个带有 UriTemplate =“Venues”的函数 GetVenues,而 IUserService 定义了一个带有 UriTemplate =“Users”的函数 GetUsers。我遇到的问题是我不想通过“foo.com:123/Something/Venues”和“foo.com:123/SomethingElse/Users”访问它们。我想我可以删除 UriTemplates 并依赖启动所有 WebServiceHosts 的代码来设置 baseURL。
    【解决方案2】:

    我认为重新考虑 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 Designbest-practices-for-a-pragmatic-restful-api

    最后,有一种方法可以使用相同的地址,但方法有点奇怪: Using the same address

    【讨论】:

    • 这就是我害怕的。我希望在界面中保留更多定义的地址,但似乎我需要有多个 WebServicesHost 并在代码中为每个资源定义基础以设置服务而不是在界面中。
    • 您可以尝试Web API,它可以更好地满足您的需求。许多人避免将 WCF 用于 REST,而使用专门为此目的设计的 Web API。这是一个链接:asp.net/web-api/overview/web-api-routing-and-actions/…
    • 感谢您的链接。我查看了 Web API,但是当我尝试启动一个自托管 Web API 项目时,添加了太多不需要的代码。这似乎有点矫枉过正,考虑到我将它与 WCF 进行比较,这是一件奇怪的事情,它本身似乎有点沉重。
    【解决方案3】:

    以下解决方案:

    • 允许单个对象处理特定端点
    • URI 模板中没有路径的任何部分
    • 为所有服务使用相同的端口

    它确实需要一个以上的 WebServiceHost - 每个处理请求的对象一个。另一个困难是,添加更深层次的端点(如 /events/2014)意味着它们要么需要具有唯一参数,要么 URI 模板必须包含路径的一部分,如果您采用约定而不是配置的话,这应该不是问题。

    WebServiceHost 只能托管一个事物(类),但该对象可以有多个接口来处理不同 URL 上的多种不同类型的请求。不同的 WebServiceHosts 如何绑定到同一个域:端口?他们不能,所以我猜 WebServiceHost 包装了一个底层静态对象,该对象将请求路由到正确的对象。从技术上讲,这并不能回答你的问题,但我认为这个实现可以让你做你想做的事吗?

    托管 Web 服务的控制台应用程序。

    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.";
            }
        }
    

    【讨论】:

    • 这样操作,需要app.config文件吗?
    【解决方案4】:

    WCF 自托管可以通过多种方式完成,例如控制台应用程序托管、Windows 服务托管等。

    我曾尝试使用单个控制台应用程序托管两个服务。服务的结构与您提到的类似,即两种服务的单独类和接口。

    您可能想查看此链接: Hosting two WCf services using one console app

    【讨论】:

    • 我有一个托管多个 WCF 服务类的 Windows 服务。我遇到的问题是WebServiceHostServiceEndpoint 如何使用我的ServiceContract 接口中的信息来创建ChannelDispatchers。我不是简单地问如何设置 WCF 自托管。
    猜你喜欢
    • 2013-03-03
    • 2011-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多