知识大全 .NET Remoting程序开发入门篇
Posted 知
篇首语:只有上不去的天,没有过不去的山。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 .NET Remoting程序开发入门篇相关的知识,希望对你有一定的参考价值。
.NET Remoting程序开发入门篇 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
一 Remoting基础 什么是Remoting 简而言之 我们可以将其看作是一种分布式处理方式 从微软的产品角度来看 可以说Remoting就是D的一种升级 它改善了很多功能 并极好的融合到 Net平台下 Microsoft? NET Remoting 提供了一种允许对象通过应用程序域与另一对象进行交互的框架 这也正是我们使用Remoting的原因 为什么呢?在Windows操作系统中 是将应用程序分离为单独的进程 这个进程形成了应用程序代码和数据周围的一道边界 如果不采用进程间通信(RPC)机制 则在一个进程中执行的代码就不能访问另一进程 这是一种操作系统对应用程序的保护机制 然而在某些情况下 我们需要跨过应用程序域 与另外的应用程序域进行通信 即穿越边界 在Remoting中是通过通道(channel)来实现两个应用程序域之间对象的通信的 如图所示首先 客户端通过Remoting 访问通道以获得服务端对象 再通过代理解析为客户端对象 这就提供一种可能性 即以服务的方式来发布服务器对象 远程对象代码可以运行在服务器上(如服务器激活的对象和客户端激活的对象) 然后客户端再通过Remoting连接服务器 获得该服务对象并通过序列化在客户端运行 在Remoting中 对于要传递的对象 设计者除了需要了解通道的类型和端口号之外 无需再了解数据包的格式 但必须注意的是 客户端在获取服务器端对象时 并不是获得实际的服务端对象 而是获得它的引用 这既保证了客户端和服务器端有关对象的松散耦合 同时也优化了通信的性能 Remoting的两种通道 Remoting的通道主要有两种 Tcp和Http 在 Net中 System Runtime Remoting Channel中定义了IChannel接口 IChannel接口包括了TcpChannel通道类型和Http通道类型 它们分别对应Remoting通道的这两种类型 TcpChannel类型放在名字空间System Runtime Remoting Channel Tcp中 Tcp通道提供了基于Socket的传输工具 使用Tcp协议来跨越Remoting边界传输序列化的消息流 TcpChannel类型默认使用二进制格式序列化消息对象 因此它具有更高的传输性能 HttpChannel类型放在名字空间System Runtime Remoting Channel Http中 它提供了一种使用Http协议 使其能在Internet上穿越防火墙传输序列化消息流 默认情况下 HttpChannel类型使用Soap格式序列化消息对象 因此它具有更好的互操作性 通常在局域网内 我们更多地使用TcpChannel 如果要穿越防火墙 则使用HttpChannel 远程对象的激活方式 在访问远程类型的一个对象实例之前 必须通过一个名为Activation的进程创建它并进行初始化 这种客户端通过通道来创建远程对象 称为对象的激活 在Remoting中 远程对象的激活分为两大类 服务器端激活和客户端激活 ( ) 服务器端激活 又叫做WellKnow方式 很多又翻译为知名对象 为什么称为知名对象激活模式呢?是因为服务器应用程序在激活对象实例之前会在一个众所周知的统一资源标识符(URI)上来发布这个类型 然后该服务器进程会为此类型配置一个WellKnown对象 并根据指定的端口或地址来发布对象 Net Remoting把服务器端激活又分为SingleTon模式和SingleCall模式两种 SingleTon模式 此为有状态模式 如果设置为SingleTon激活方式 则Remoting将为所有客户端建立同一个对象实例 当对象处于活动状态时 SingleTon实例会处理所有后来的客户端访问请求 而不管它们是同一个客户端 还是其他客户端 SingleTon实例将在方法调用中一直维持其状态 举例来说 如果一个远程对象有一个累加方法(i= ++i) 被多个客户端(例如两个)调用 如果设置为SingleTon方式 则第一个客户获得值为 第二个客户获得值为 因为他们获得的对象实例是相同的 如果熟悉Asp Net的状态管理 我们可以认为它是一种Application状态 SingleCall模式 SingleCall是一种无状态模式 一旦设置为SingleCall模式 则当客户端调用远程对象的方法时 Remoting会为每一个客户端建立一个远程对象实例 至于对象实例的销毁则是由GC自动管理的 同上一个例子而言 则访问远程对象的两个客户获得的都是 我们仍然可以借鉴Asp Net的状态管理 认为它是一种Session状态 ( ) 客户端激活 与WellKnown模式不同 Remoting在激活每个对象实例的时候 会给每个客户端激活的类型指派一个URI 客户端激活模式一旦获得客户端的请求 将为每一个客户端都建立一个实例引用 SingleCall模式和客户端激活模式是有区别的 首先 对象实例创建的时间不一样 客户端激活方式是客户一旦发出调用的请求 就实例化 而SingleCall则是要等到调用对象方法时再创建 其次 SingleCall模式激活的对象是无状态的 对象生命期的管理是由GC管理的 而客户端激活的对象则有状态 其生命周期可自定义 其三 两种激活模式在服务器端和客户端实现的方法不一样 尤其是在客户端 SingleCall模式是由GetObject()来激活 它调用对象默认的构造函数 而客户端激活模式 则通过CreateInstance()来激活 它可以传递参数 所以可以调用自定义的构造函数来创建实例
二 远程对象的定义 前面讲到 客户端在获取服务器端对象时 并不是获得实际的服务端对象 而是获得它的引用 因此在Remoting中 对于远程对象有一些必须的定义规范要遵循 由于Remoting传递的对象是以引用的方式 因此所传递的远程对象类必须继承MarshalByRefObject MSDN对MarshalByRefObject的说明是 MarshalByRefObject 是那些通过使用代理交换消息来跨越应用程序域边界进行通信的对象的基类 不是从 MarshalByRefObject 继承的对象会以隐式方式按值封送 当远程应用程序引用一个按值封送的对象时 将跨越远程处理边界传递该对象的副本 因为您希望使用代理方法而不是副本方法进行通信 因此需要继承MarshallByRefObject 以下是一个远程对象类的定义
public class ServerObject:MarshalByRefObject public Person GetPersonInfo(string name string sex int age) Person person = new Person(); person Name = name; person Sex = sex; person Age = age; return person;
这个类只实现了最简单的方法 就是设置一个人的基本信息 并返回一个Person类对象 注意这里返回的Person类 由于这里所传递的Person则是以传值的方式来完成的 而Remoting要求必须是引用的对象 所以必须将Person类序列化 因此 在Remoting中的远程对象中 如果还要调用或传递某个对象 例如类 或者结构 则该类或结构则必须实现串行化Attribute[SerializableAttribute]
[Serializable]public class Person public Person() private string name; private string sex; private int age; public string Name get return name; set name = value; public string Sex get return sex; set sex = value; public int Age get return age; set age = value;
将该远程对象以类库的方式编译成Dll 这个Dll将分别放在服务器端和客户端 以添加引用 在Remoting中能够传递的远程对象可以是各种类型 包括复杂的DataSet对象 只要它能够被序列化 远程对象也可以包含事件 但服务器端对于事件的处理比较特殊 我将在本系列之三中介绍
三 服务器端 根据第一部分所述 根据激活模式的不同 通道类型的不同服务器端的实现方式也有所不同 大体上说 服务器端应分为三步 注册通道 要跨越应用程序域进行通信 必须实现通道 如前所述 Remoting提供了IChannel接口 分别包含TcpChannel和HttpChannel两种类型的通道 这两种类型除了性能和序列化数据的格式不同外 实现的方式完全一致 因此下面我们就以TcpChannel为例 注册TcpChannel 首先要在项目中添加引用 System Runtime Remoting 然后using名字空间 System Runtime Remoting Channel Tcp 代码如下
TcpChannel channel = new TcpChannel( );ChannelServices RegisterChannel(channel);
在实例化通道对象时 将端口号作为参数传递 然后再调用静态方法RegisterChannel()来注册该通道对象即可 注册远程对象 注册了通道后 要能激活远程对象 必须在通道中注册该对象 根据激活模式的不同 注册对象的方法也不同 ( ) SingleTon模式 对于WellKnown对象 可以通过静态方法RemotingConfiguration RegisterWellKnownServiceType()来实现
RemotingConfiguration RegisterWellKnownServiceType(typeof(ServerRemoteObject ServerObject) ServiceMessage WellKnownObjectMode SingleTon);
( )SingleCall模式 注册对象的方法基本上和SingleTon模式相同 只需要将枚举参数WellKnownObjectMode改为SingleCall就可以了
RemotingConfiguration RegisterWellKnownServiceType(typeof(ServerRemoteObject ServerObject) ServiceMessage WellKnownObjectMode SingleCall);
( )客户端激活模式 对于客户端激活模式 使用的方法又有不同 但区别不大 看了代码就一目了然
RemotingConfiguration ApplicationName = ServiceMessage ;RemotingConfiguration RegisterActivatedServiceType(typeof(ServerRemoteObject ServerObject));
为什么要在注册对象方法前设置ApplicationName属性呢?其实这个属性就是该对象的URI 对于WellKnown模式 URI是放在RegisterWellKnownServiceType()方法的参数中 当然也可以拿出来专门对ApplicationName属性赋值 而RegisterActivatedServiceType()方法的重载中 没有ApplicationName的参数 所以必须分开 注销通道 如果要关闭Remoting的服务 则需要注销通道 也可以关闭对通道的监听 在Remoting中当我们注册通道的时候 就自动开启了通道的监听 而如果关闭了对通道的监听 则该通道就无法接受客户端的请求 但通道仍然存在 如果你想再一次注册该通道 会抛出异常
//获得当前已注册的通道 IChannel[] channels = ChannelServices RegisteredChannels;//关闭指定名为MyTcp的通道 foreach (IChannel eachChannel in channels) if (eachChannel ChannelName == MyTcp ) TcpChannel tcpChannel = (TcpChannel)eachChannel; //关闭监听 tcpChannel StopListening(null); //注销通道 ChannelServices UnregisterChannel(tcpChannel);
代码中 RegisterdChannel属性获得的是当前已注册的通道 在Remoting中 是允许同时注册多个通道的 这一点会在后面说明
四 客户端 客户端主要做两件事 一是注册通道 这一点从图一就可以看出 Remoting中服务器端和客户端都必须通过通道来传递消息 以获得远程对象 第二步则是获得该远程对象 注册通道 TcpChannel channel = new TcpChannel();ChannelServices RegisterChannel(channel); 注意在客户端实例化通道时 是调用的默认构造函数 即没有传递端口号 事实上 这个端口号是缺一不可的 只不过它的指定被放在后面作为了Uri的一部分 获得远程对象 与服务器端相同 不同的激活模式决定了客户端的实现方式也将不同 不过这个区别仅仅是WellKnown激活模式和客户端激活模式之间的区别 而对于SingleTon和SingleCall模式 客户端的实现完全相同 ( ) WellKnown激活模式 要获得服务器端的知名远程对象 可通过Activator进程的GetObject()方法来获得
ServerRemoteObject ServerObject serverObj = (ServerRemoteObject ServerObject)Activator GetObject(typeof(ServerRemoteObject ServerObject) tcp://localhost: /ServiceMessage );
首先以WellKnown模式激活 客户端获得对象的方法是使用GetObject() 其中参数第一个是远程对象的类型 第二个参数就是服务器端的uri 如果是通道 自然是//localhost: /ServiceMessage了 因为我是用本地机 所以这里是localhost 你可以用具体的服务器IP地址来代替它 端口必须和服务器端的端口一致 后面则是服务器定义的远程对象服务名 即ApplicationName属性的内容 ( ) 客户端激活模式 如前所述 WellKnown模式在客户端创建对象时 只能调用默认的构造函数 上面的代码就说明了这一点 因为GetObject()方法不能传递构造函数的参数 而客户端激活模式则可以通过自定义的构造函数来创建远程对象 客户端激活模式有两种方法 ) 调用RemotingConfiguration的静态方法RegisterActivatedClientType() 这个方法返回值为Void 它只是将远程对象注册在客户端而已 具体的实例化还需要调用对象类的构造函数
RemotingConfiguration RegisterActivatedClientType( typeof(ServerRemoteObject ServerObject) tcp://localhost: /ServiceMessage );ServerRemoteObject ServerObject serverObj = new ServerRemoteObject ServerObject();
) 调用进程Activator的CreateInstance()方法 这个方法将创建方法参数指定类型的类对象 它与前面的GetObject()不同的是 它要在客户端调用构造函数 而GetObject()只是获得对象 而创建实例是在服务器端完成的 CreateInstance()方法有很多个重载 我着重说一下其中常用的两个 a public static object CreateInstance(Type type object[] args object[] activationAttributes); 参数说明 type 要创建的对象的类型 args 与要调用构造函数的参数数量 顺序和类型匹配的参数数组 如果 args 为空数组或空引用(Visual Basic 中为 Nothing) 则调用不带任何参数的构造函数(默认构造函数) activationAttributes 包含一个或多个可以参与激活的属性的数组 这里的参数args是一个object[]数组类型 它可以传递要创建对象的构造函数中的参数 从这里其实可以得到一个结论 WellKnown激活模式所传递的远程对象类 只能使用默认的构造函数 而Activated模式则可以用户自定义构造函数 activationAttributes参数在这个方法中通常用来传递服务器的url 假设我们的远程对象类ServerObject有个构造函数
ServerObject(string pName string pSex int pAge) name = pName; sex = pSex; age = pAge;
那么实现的代码是
object[] attrs = new UrlAttribute( tcp://localhost: /ServiceMessage );object[] objs = new object[ ];objs[ ] = wayfarer ;objs[ ] = male ;objs[ ] = ;ServerRemoteObject ServerObject = Activator CreateInstance(typeof(ServerRemoteObject ServerObject) objs attrs);
可以看到 objs[]数组传递的就是构造函数的参数 b public static ObjectHandle CreateInstance(string assemblyName string typeName object[] activationAttribute); 参数说明 assemblyName 将在其中查找名为 typeName 的类型的程序集的名称 如果 assemblyName 为空引用(Visual Basic 中为 Nothing) 则搜索正在执行的程序集 typeName 首选类型的名称 activationAttributes 包含一个或多个可以参与激活的属性的数组 参数说明一目了然 注意这个方法返回值为ObjectHandle类型 因此代码与前不同
object[] attrs = new UrlAttribute( tcp://localhost: /EchoMessage ); ObjectHandle handle = Activator CreateInstance( ServerRemoteObject ServerRemoteObject ServerObject attrs);ServerRemoteObject ServerObject obj = (ServerRemoteObject ServerObject)handle Unwrap();
这个方法实际上是调用的默认构造函数 ObjectHandle Unwrap()方法是返回被包装的对象 说明 要使用UrlAttribute 还需要在命名空间中添加 using System Runtime Remoting Activation;
五 Remoting基础的补充 通过上面的描述 基本上已经完成了一个最简单的Remoting程序 这是一个标准的创建Remoting程序的方法 但在实际开发过程中 我们遇到的情况也许千奇百怪 如果只掌握一种所谓的 标准 就妄想可以 一招鲜 吃遍天 是不可能的 注册多个通道 在Remoting中 允许同时创建多个通道 即根据不同的端口创建不同的通道 但是 Remoting要求通道的名字必须不同 因为它要用来作为通道的唯一标识符 虽然IChannel有ChannelName属性 但这个属性是只读的 因此前面所述的创建通道的方法无法实现同时注册多个通道的要求 这个时候 我们必须用到System Collection中的IDictionary接口 注册Tcp通道
IDictionary tcpProp = new Hashtable();tcpProp[ name ] = tcp ;tcpProp[ port ] = ;IChannel channel = new TcpChannel(tcpProp new BinaryClientFormatterSinkProvider() new BinaryServerFormatterSinkProvider());ChannelServices RegisterChannel(channel);
注册Http通道
IDictionary Prop = new Hashtable();Prop[ name ] = ;Prop[ port ] = ;IChannel channel = new HttpChannel(Prop new SoapClientFormatterSinkProvider() new SoapServerFormatterSinkProvider());ChannelServices RegisterChannel(channel);
在name属性中 定义不同的通道名称就可以了 远程对象元数据相关性 由于服务器端和客户端都要用到远程对象 通常的方式是生成两份完全相同的对象Dll 分别添加引用 不过为了代码的安全性 且降低客户端对远程对象元数据的相关性 我们有必要对这种方式进行改动 即在服务器端实现远程对象 而在客户端则删除这些实现的元数据 由于激活模式的不同 在客户端创建对象的方法也不同 所以要分离元数据的相关性 也应分为两种情况 ( ) WellKnown激活模式 通过接口来实现 在服务器端 提供接口和具体类的实现 而在客户端仅提供接口
public interface IServerObject Person GetPersonInfo(string name string sex int age);public class ServerObject:MarshalByRefObject IServerObject
注意 两边生成该对象程序集的名字必须相同 严格地说 是命名空间的名字必须相同 ( ) 客户端激活模式 如前所述 对于客户端激活模式 不管是使用静态方法 还是使用CreateInstance()方法 都必须在客户端调用构造函数实例化对象 所以 在客户端我们提供的远程对象 就不能只提供接口 而没有类的实现 实际上 要做到与远程对象元数据的分离 可以由两种方法供选择 a 利用WellKnown激活模式模拟客户端激活模式 方法是利用设计模式中的 抽象工厂 下面的类图表描述了总体解决方案
我们在服务器端的远程对象中加上抽象工厂的接口和实现类
public interface IServerObject Person GetPersonInfo(string name string sex int age);public interface IServerObjFactory IServerObject CreateInstance(); public class ServerObject:MarshalByRefObject IServerObject public Person GetPersonInfo(string name string sex int age) Person person = new Person(); person Name = name; person Sex = sex; person Age = age; return person; public class ServerObjFactory:MarshalByRefObject IServerObjFactory public IServerObject CreateInstance() return new ServerObject();
然后再客户端的远程对象中只提供工厂接口和原来的对象接口
public interface IServerObject Person GetPersonInfo(string name string sex int age);public interface IServerObjFactory IServerObject CreateInstance();
我们用WellKnown激活模式注册远程对象 在服务器端
//传递对象 RemotingConfiguration RegisterWellKnownServiceType(typeof(ServerRemoteObject ServerObjFactory) ServiceMessage WellKnownObjectMode SingleCall);
注意这里注册的不是ServerObject类对象 而是ServerObjFactory类对象 客户端
ServerRemoteObject IServerObjFactory serverFactory = (ServerRemoteObject IServerObjFactory) Activator GetObject(typeof(ServerRemoteObject IServerObjFactory) tcp://localhost: /ServiceMessage );ServerRemoteObject IServerObject serverObj = serverFactory CreateInstance();
为什么说这是一种客户端激活模式的模拟呢?从激活的方法来看 我们是使用了SingleCall模式来激活对象 但此时激活的并非我们要传递的远程对象 而是工厂对象 如果客户端要创建远程对象 还应该通过工厂对象的CreateInstance()方法来获得 而这个方法正是在客户端调用的 因此它的实现方式就等同于客户端激活模式 b 利用替代类来取代远程对象的元数据 实际上 我们可以用一个trick 来欺骗Remoting 这里所说的替代类就是这个trick了 既然是提供服务 Remoting传递的远程对象其实现的细节当然是放在服务器端 而要在客户端放对象的副本 不过是因为客户端必须调用构造函数 而采取的无奈之举 既然具体的实现是在服务器端 又为了能在客户端实例化 那么在客户端就实现这些好了 至于实现的细节 就不用管了 如果远程对象有方法 服务器端则提供方法实现 而客户端就提供这个方法就OK了 至于里面的实现 你可以是抛出一个异常 或者return 一个null值 如果方法返回void 那么里面可以是空 关键是这个客户端类对象要有这个方法 这个方法的实现 其实和方法的声明差不多 所以我说是一个trick 方法如是 构造函数也如此 还是用代码来说明这种 阴谋 更直观 服务器端
public class ServerObject:MarshalByRefObjectpublic ServerObject()public Person GetPersonInfo(string name string sex int age) Person person = new Person(); person Name = name; person Sex = sex; person Age = age; return person;
客户端
public class ServerObject:MarshalByRefObject public ServerObj() throw new System NotImplementedException(); public Person GetPersonInfo(string name string sex int age) throw new System NotImplementedException();
比较客户端和服务器端 客户端的方法GetPersonInfo() 没有具体的实现细节 只是抛出了一个异常 或者直接写上语句return null 照样OK 我们称客户端的这个类为远程对象的替代类
利用配置文件实现 前面所述的方法 于服务器uri 端口 以及激活模式的设置是用代码来完成的 其实我们也可以用配置文件来设置 这样做有个好处 因为这个配置文件是Xml文档 如果需要改变端口或其他 我们就不需要修改程序 并重新编译 而是只需要改变这个配置文件即可 ( ) 服务器端的配置文件
<configuration><system runtime remoting><application name= ServerRemoting ><service><wellknown mode= Singleton type= ServerRemoteObject ServerObject objectUri= ServiceMessage /></service><channels><channel ref= tcp port= /></channels></application></system runtime remoting></configuration>
如果是客户端激活模式 则把wellknown改为activated 同时删除mode属性 把该配置文件放到服务器程序的应用程序文件夹中 命名为nfig 那么前面的服务器端程序直接用这条语句即可 RemotingConfiguration Configure( nfig ); ( ) 客户端配置文件 如果是客户端激活模式 修改和上面一样 调用也是使用RemotingConfiguration Configure()方法来调用存储在客户端的配置文件 配置文件还可以放在nfig中 如果客户端程序是web应用程序 则可以放在nfig中 启动/关闭指定远程对象 Remoting中没有提供类似UnregisterWellKnownServiceType()的方法 也即是说 一旦通过注册了远程对象 如果没有关闭通道的话 该对象就一直存在于通道中 只要客户端激活该对象 就会创建对象实例 如果Remoting传送的只有一个远程对象 这不存在问题 关闭通道就可以了 如果传送多个远程对象呢?要关闭指定的远程对象应该怎么做?关闭之后又需要启动又该如何? 我们注意到在Remoting中提供了Marshal()和Disconnect()方法 答案就在这里 Marshal()方法是将MarshalByRefObject类对象转化为ObjRef类对象 这个对象是存储生成代理以与远程对象通讯所需的所有相关信息 这样就可以将该实例序列化以便在应用程序域之间以及通过网络进行传输 客户端就可以调用了 而Disconnect()方法则将具体的实例对象从通道中断开 方法如下 首先注册通道
TcpChannel channel = new TcpChannel( );ChannelServices RegisterChannel(channel);
接着启动服务 先在服务器端实例化远程对象
ServerObject obj = new ServerObject();
然后 注册该对象 注意这里不用RemotingConfiguration RegisterWellKnownServiceType() 而是使用RemotingServices Marshal()
ObjRef objrefWellKnown = RemotingServices Marshal(obj ServiceMessage );
如果要注销对象 则
RemotingServices Disconnect(obj);
cha138/Article/program/net/201311/13914相关参考
知识大全 让.NET Remoting更快些-IPCChannel
让.NETRemoting更快些-IPCChannel 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧
.NETRemoting实现分布式数据库查询 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 开始
给.Net程序员和WEB程序员建议:.Net篇 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧! 好
知识大全 基于消息与.Net Remoting的分布式处理架构
基于消息与.NetRemoting的分布式处理架构 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
知识大全 Visual C#.Net 网络程序开发Socket篇
VisualC#.Net网络程序开发Socket篇 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
《风越代码生成器[FireCodeCreator]》是一款基于多种数据库的程序代码生成软件可快速建立数据信息的添加编辑查看列表搜索功能默认提供aspaspxWEB程序net普通三层框架web程序n
知识大全 ASP.NET入门教程 10.3.3 事件处理程序/函数
ASP.NET入门教程10.3.3事件处理程序/函数 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!
知识大全 ASP.NET入门教程 1.7 VWD Express——开发环境
ASP.NET入门教程1.7VWDExpress——开发环境 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看
知识大全 ASP.NET入门教程 10.5.2 三层应用程序
ASP.NET入门教程10.5.2三层应用程序 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!&nb
知识大全 ASP.NET入门教程 10.5.1 两层应用程序
ASP.NET入门教程10.5.1两层应用程序 以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!&nb