[Remoting] 八:元数据(收藏转帖http://www.rainsts.net/article.asp?id=415)

.NET Remoting 基础结构需要正确的元数据,以便将一个应用程序域中的对象连接到另一个域中的对象。通常我们将包含远程类型的程序集同时发布到服务器和客户端,但这并不是一个好主意。有太多的原因阻止我们这么做:

1. 我们并不想客户端开发人员知道远程对象的内部细节,诸如私有成员内容等。
2. 我们不希望每次升级都更新客户端文件。

Soapsuds

Remoting 为我们提供了一个工具 "Soapsuds"。不要被它的名字所迷惑,它同样适用于二进制序列化的远程对象,因为我们只是用它来创建一个远程代理而已。假设远程类型存放在 RemoteLibrary 中,类型全名是 RemoteLibrary.Data。

RemoteLibrary.csproj
namespace RemoteLibrary
{
  public class Data : MarshalByRefObject
  {
    public void Test()
    {
      Console.WriteLine("Test AppDomain:{0}", AppDomain.CurrentDomain.FriendlyName);
    }
  }
}

在 "Visual Studio 2005 命令提示" 窗口中用 Soapsuds 创建客户端代理类型。
c:/> Soapsuds -types:RemoteLibrary.Data,RemoteLibrary -gc

你会看到生成的 RemoteLibrary.cs 文件,打开看一下。
using System;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Metadata;
using System.Runtime.Remoting.Metadata.W3cXsd2001;
using System.Runtime.InteropServices;

namespace RemoteLibrary
{
  [SoapType(...)]
  [ComVisible(true)]
  public class Data : System.Runtime.Remoting.Services.RemotingClientProxy
  {
    // Constructor
    public Data()
    {
    }

    public Object RemotingReference
    {
      get{return(_tp);}
    }

    [SoapMethod(...)]
    public void Test()
    {
      ((Data) _tp).Test();
    }
  }
}

将这个文件拷贝到客户端目录,并添加到项目中。开始编写客户端代码。
TcpClientChannel channel = new TcpClientChannel();
ChannelServices.RegisterChannel(channel, false);
RemotingConfiguration.RegisterActivatedClientType(typeof(RemoteLibrary.Data), "tcp://localhost:801/test");

RemoteLibrary.Data data = new RemoteLibrary.Data();
data.Test();

其实还可以使用 System.Runtime.Remoting.MetadataServices 名字空间中的相关类用代码来创建这些代理源码或者程序集。
using System.IO;
using System.Runtime.Remoting.MetadataServices;

ArrayList list = new ArrayList();
FileStream fs = new FileStream("test.xml", FileMode.OpenOrCreate);

// 将远程类型转换为 XML 架构。
MetaData.ConvertTypesToSchemaToStream(new Type[] { typeof(Data) }, SdlType.Wsdl, fs);

// 将 XML 架构流中转换为代理源码文件。
fs.Seek(0, SeekOrigin.Begin);
MetaData.ConvertSchemaStreamToCodeSourceStream(true, AppDomain.CurrentDomain.BaseDirectory, fs, list);

// 显示所生成的代理源码文件名。
foreach (string s in list) Console.WriteLine(s);

// 将生成的源码转换为程序集文件。
MetaData.ConvertCodeSourceStreamToAssemblyFile(list, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.dll"), null);

接口隔离

还有一个方式就是使用接口进行隔离,这样客户端就不会看到远程类型。我们继续用上面的例子做演示。

1. 创建接口类型项目。项目中包含接口和一个工厂类型,由工厂类型负责创建目标类型。(注意为避免循环引用,我们通过配置文件读取目标类型信息。)

RemoteInterface.projc
namespace RemoteInterface
{
  public interface IData
  {
    void Test();
  }

  public class Factory : MarshalByRefObject
  {
    public IData NewData()
    {
      return (IData)Activator.CreateInstance(Type.GetType(ConfigurationManager.AppSettings["data"]));
    }
  }
}

配置文件


  
    
  


2. 修改原类型使其实现 IData 接口,注意添加 RemoteInterface.dll 引用。

RemoteLibrary.csproj
namespace RemoteLibrary
{
  public class Data : MarshalByRefObject, RemoteInterface.IData
  {
    public void Test()
    {
      Console.WriteLine("Test AppDomain:{0}", AppDomain.CurrentDomain.FriendlyName);
    }
  }
}

3. 在服务器端,除了 RemoteLibrary.Data,还要注册 RemoteInterace.Factory。

Server.csproj
TcpServerChannel channel = new TcpServerChannel(801);
ChannelServices.RegisterChannel(channel, false);

RemotingConfiguration.ApplicationName = "test";
RemotingConfiguration.RegisterActivatedServiceType(typeof(Factory));
RemotingConfiguration.RegisterActivatedServiceType(typeof(Data));

4. 将 RemoteInterface.dll 提供给客户端,添加引用后开始编码。

Client.csproj
RemotingConfiguration.RegisterActivatedClientType(typeof(Facade), "tcp://localhost:801/test");
RemotingConfiguration.RegisterActivatedClientType(typeof(IData), "tcp://localhost:801/test");

Factory factory = (Facade)Activator.CreateInstance(typeof(Factory), null);
IData data = factory.NewData();
data.Test();

这种方式稍显复杂,但从架构模式上来说要更好一些。它隔绝了目标类型和客户端的联系,服务器可以更灵活地变化和升级。

点击下载该演示代码

你可能感兴趣的:(C#_Remoting)