.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();
这种方式稍显复杂,但从架构模式上来说要更好一些。它隔绝了目标类型和客户端的联系,服务器可以更灵活地变化和升级。
点击下载该演示代码