[Remoting专题系列] 八:元数据

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

  }

}


配置文件
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<appSettings>
    
<add key="data" value="RemoteLibrary.Data,RemoteLibrary"/>
  
</appSettings>
</configuration>

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();

这种方式稍显复杂,但从架构模式上来说要更好一些。它隔绝了目标类型和客户端的联系,服务器可以更灵活地变化和升级。
下载示例源代码
posted on 2007-06-05 09:16  编程山人  阅读(356)  评论(0编辑  收藏  举报