65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 WCF 服务而不添加服务引用或代理

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (15投票s)

2012 年 6 月 28 日

CPOL

5分钟阅读

viewsIcon

222108

downloadIcon

3254

展示如何松散耦合您的 WCF 服务。

引言

在我最近的一次面试中,我被问到如何在 ASP.NET 项目中使用 WCF 服务而不添加服务引用或代理。当时我只知道通过 Visual Studio 添加服务引用或使用 svcutil.exe 生成代理类来实现。当时我没有答案(当然我通过了面试)。但过了一段时间,我找到了解决方案,这个解决方案可能会让其他人受益。

背景

假设您正在开发一个网站项目,并且您使用 WCF 服务来获取数据或其他原因,而您的客户需求变化非常迅速;为了满足新需求,您可能需要在 WCF 服务中添加新功能,但您无法承担添加新的服务引用、重新生成代理类或停止应用程序并重新启动。但是,如果我告诉您,无需进行所有这些操作,您仍然可以修改 WCF 服务来添加新功能,并且无需中断正在运行的应用程序呢?

使用代码

让我们详细了解一下这个解决方案。您可以通过两种方式在 Visual Studio 中创建 ASP.NET 用户界面项目。您可以创建 ASP.NET Web 应用程序ASP.NET 网站。互联网上有很多关于 ASP.NET Web 应用程序与 ASP.NET 网站的文章,因此我不会深入探讨细节,只需知道网站是动态编译的,源代码随其分发,而 Web 应用程序可以编译成单个 DLL 并进行部署。因此,使用 ASP.NET Web 应用程序或 ASP.NET Web 网站都有其优缺点。我推荐的解决方案仅适用于 ASP.NET 网站,您很快就会知道为什么它只适用于 ASP.NET 网站。那么,让我们开始吧……

  1. 我们将创建三个不同的项目:ISampleServiceWCFSampleServiceSampleServiceHost
    1. ISampleService:我们将把所有接口单独放在这个项目中。
    2. WCFSampleService:我们将定义服务公开的所有函数。
    3. SampleServiceHost:这将托管我们的 WCFSampleService。
  2. 创建一个空的解决方案 SampleServiceSolution,并向其中添加一个 WCF 服务库项目 ISampleService。项目创建完成后,从其中删除 Service1.csApp.config 文件,因为我们只希望在此项目中包含接口。
  3. 以下是 IService1 的代码。我们将定义一个服务契约。在本文中,我将只定义一个简单的服务契约 GetMessage,稍后我们将在 IService1 中添加更多服务契约。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace SampleService
    {
        [ServiceContract]
        public interface IService1
        {
            [OperationContract]
            string GetMessage();  
        }
    }
  4. 向解决方案 SampleServiceSolution 中添加第二个 WCF 服务库项目 WCFSampleService。项目创建完成后,从中删除 IService1.csApp.config 文件。向其中添加 ISampleService 的引用。
  5. [注意:两个项目都使用命名空间 SampleService。]

    以下是 Service1 的代码。现在我们将实现我们在 IService1 中定义的服务契约。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.Text;
    
    namespace SampleService
    {
        public class Service1 : IService1
        {
            public string GetMessage()
            {
                return "Hello World";
            }
        }
    }
  6. 由于我们的 WCFSampleService 已经准备就绪,现在我们将创建一个宿主应用程序来托管该服务。为了本文的需要,我们将把服务托管在 IIS 上,为此我们将创建一个新的网站项目 SampleServiceHost 并将其添加到 SampleServiceSolution 中。这里会有点棘手,我将一步一步地向您展示。
  7. 在 Visual Studio 中,右键单击 SampleServiceSolution,然后单击“添加”,再单击“新建网站”。

    选择项目模板 WCF Service,然后单击“确定”。

    这将会在 SampleServiceSolution 解决方案中创建一个 WCF 服务网站 SampleServiceHost。我们已经在单独的项目中创建了 WCFSampleService。我们将在 Service.svc 中引用它。我们将从 App_Code 文件夹中删除 IService.csService.cs 文件。右键单击 SampleServiceHost,添加对其的 WCFSampleService 引用。

    打开 SampleServiceHost 中的 Service.svc 文件并进行修改,使其看起来像这样

    <%@ ServiceHost Language="C#" Debug="true" Service="SampleService.Service1""%>

    现在让我们检查一下我们到目前为止所做的,让我们将 SampleServiceHost 设置为启动项目,并将 Service.svc 文件设置为启动页,然后运行 SampleServiceHost

    如果一切正常,您的屏幕应该看起来像这样

    现在让我们修改 SampleServiceHostWeb.Config,如下所示。您可以根据项目需求修改 System.ServiceModel 部分。

    例如,您的绑定、端点地址等。

    //
    // Web.Config of SampleServiceHost 
    //
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.web>
        <compilation debug="true" targetFramework="4.0" />
      </system.web>
      <system.serviceModel>
        <bindings>
          <basicHttpBinding>
            <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00" 
                  openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 
                  allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" 
                  maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" 
                  messageEncoding="Text" textEncoding="utf-8" 
                  transferMode="Buffered" useDefaultWebProxy="true">
              <readerQuotas maxDepth="32" maxStringContentLength="8192" 
                 maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
              <security mode="None">
                <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
                <message clientCredentialType="UserName" algorithmSuite="Default" />
              </security>
            </binding>
          </basicHttpBinding>
        </bindings>
        <services>
          <service behaviorConfiguration="mexBehavior" name="EnterpriseService.Service1">
            <endpoint address="https:///SampleService/Service.svc" 
               binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" 
               contract="EnterpriseService.IService1" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="false" />
            </behavior>
            <behavior name="mexBehavior">
              <serviceMetadata httpGetEnabled="true" />
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="false" />
      </system.serviceModel>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
        <defaultDocument>
          <files>
            <add value="Service.svc" />
          </files>
        </defaultDocument>
      </system.webServer>
    </configuration>

现在是时候将我们的 SampleServiceHost 网站部署到 IIS 了,这样它就可以被外部世界访问。我将其部署在 IIS 上的 https:///SampleService/Service.svc

部署 SampleServiceHost 后,就可以测试我们的 WCFSampleService 了。我们将创建一个单独的网站项目 TestClientForSampleService。向其添加 ISampleService 的引用。

接下来,我们将修改 TestClientForSampleService 网站的 Web.Config,如下所示

//
// modify Web.Config of TestClientForSampleService
// add servicemodel section
//
<system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="BasicHttpBinding_IService1" closeTimeout="00:01:00"
            openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
            allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
            maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
            messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
            useDefaultWebProxy="true">
          <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
              maxBytesPerRead="4096" maxNameTableCharCount="16384" />
          <security mode="None">
            <transport clientCredentialType="None" proxyCredentialType="None"
                realm="" />
            <message clientCredentialType="UserName" algorithmSuite="Default" />
          </security>
        </binding>
      </basicHttpBinding>
    </bindings>
    <client>
      <endpoint address="https:///SampleService/Service.svc"
          binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
          contract="SampleService.IService1" name="BasicHttpBinding_IService1" />
    </client>
</system.serviceModel>

接下来,我们将在 App_Code 文件夹下创建一个 ServiceClient 类,如下所示

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ServiceModel;
using System.Configuration;

public class ServiceClient<T> : ClientBase<T> where T : class
{
    private bool _disposed = false;
    public ServiceClient()
        : base(typeof(T).FullName)
    {
    }
    public ServiceClient(string endpointConfigurationName)
        : base(endpointConfigurationName)
    {
    }
    public T Proxy
    {
        get { return this.Channel; }
    }
    protected virtual void Dispose(bool disposing)
    {
        if (!this._disposed)
        {
            if (disposing)
            {
                if (this.State == CommunicationState.Faulted)
                {
                    base.Abort();
                }
                else
                {
                    try
                    {
                        base.Close();
                    }
                    catch
                    {
                        base.Abort();
                    }
                }
                _disposed = true;
            }
        }
    }
}

接下来,我们将在 default.aspx 上添加两个 ASP.NET Label 控件,如下所示

<%@ Page Title="Home Page" Language="C#" 
    MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeFile="Default.aspx.cs" Inherits="_Default" %>
<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Welcome to ASP.NET!
    </h2>
    <div>
        <asp:Label ID="Label1" runat="server" /><br />
        <asp:Label ID="Label2" runat="server" /><br />
    </div>
</asp:Content>

接下来,在代码隐藏文件 Default.aspx.csPage_Load 事件中,我们将使用我们的 ServiceClient 类来调用 WCFSampleService

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using SampleService;
public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        using (ServiceClient<IService1> ServiceClient = new ServiceClient<IService1>("BasicHttpBinding_IService1"))
        {
            this.Label1.Text = ServiceClient.Proxy.GetMessage();
        }
    }
}

现在是运行我们的 TestClientForSampleService 网站的时候了。您的屏幕应该看起来像这样。

现在是真正的神奇之处了,假设我的客户希望我添加一个显示客户地址的新功能,我所要做的就是修改我的 ISampleService 来添加一个新的服务契约,并修改我的 WCFSampleService 来添加一个实现新服务契约的新函数。在我的 TestClientForSampleService 网站中,我不需要再次添加 ISampleService 引用,这是因为 TestClientForSampleService 网站会自动获取更改后的引用的 DLL,因此,无需修改对其的 ISampleService 引用,我仍然可以在我的 TestClientForSampleService 网站中使用新功能。

因此,让我们修改 ISampleService 来添加一个新的服务契约 GetAddress,如下所示,然后构建项目。

namespace SampleService
{
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetMessage();
        [OperationContract]
        string GetAddress();  
    }
}

修改 WCFSampleService 来实现一个新的服务契约,然后构建项目

namespace SampleService
{
    public class Service1 : IService1
    {
        public string GetMessage()
        {
            return "Hello World";
        }
        public string GetAddress()
        {
            return "123 New Street, New York, NY 12345";
        }
    }
}

最后,构建 SampleServiceHost 项目。接下来,构建 TestClientForSampleService 项目,您无需更改对其的 ISampleService 引用。然后,修改 TestClientForSampleService 网站的 default.aspx.cs 文件,如下所示

protected void Page_Load(object sender, EventArgs e)
{
    using (ServiceClient<IService1> ServiceClient = 
           new ServiceClient<IService1>("BasicHttpBinding_IService1"))
    {
        this.Label1.Text = ServiceClient.Proxy.GetMessage();
        //once you have done the build inteli sense 
            //will automatically gets the new function
        this.Label2.Text = ServiceClient.Proxy.GetAddress();
    }
}

关注点

我在这里所做的是向您展示如何松散耦合您的 WCF 服务。

历史

  • 2012 年 6 月 1 日:第一个版本。
© . All rights reserved.