only for RuBoard |
Example 9-11 contains yet another version of ServerInfo . There are only two differences between this version and the last:
The class is marked <Serializable> instead of derived from MarshalByRefObj , which is required to marshal an object by value.
All data that is provided by the class is obtained in the constructor instead of at call time.
When ServerInfoFactory.CreateServerInfo is called, all remote machine properties are ascertained immediately. This is necessary because next , the entire class is marshaled by value back to the client; that is, a complete copy is transferred to the client. If the machine values were not obtained in the constructor, the class would return values relative to the client machine (because the object will be local to the client).
|
<Serializable( )> _ Public Class ServerInfo Implements IServerInfo Private machineTime As DateTime Private processorUsed As Single Private availableMemory As Long Private machine As String Private ip As IPAddress Public Sub New( ) 'Get machine info when object is created machine = Dns.GetHostName( ) Dim ipHost As IPHostEntry = Dns.GetHostByName(machine) ip = ipHost.AddressList(0) 'Machine date machineTime = DateTime.Now 'Processor used If PerformanceCounterCategory.Exists("Processor") Then Dim pc As New PerformanceCounter("Processor", _ "% Processor Time", "_Total", True) Dim sampleA As CounterSample Dim sampleB As CounterSample sampleA = pc.NextSample( ) Thread.Sleep(1000) sampleB = pc.NextSample( ) processorUsed = CounterSample.Calculate(sampleA, sampleB) End If 'Available memory If PerformanceCounterCategory.Exists("Memory") Then Dim pc As New PerformanceCounter("Memory", "Available MBytes") availableMemory = pc.RawValue( ) End If End Sub 'Shared method Public Function GetMachineTime( ) As DateTime _ Implements IServerInfo.GetMachineTime Return machineTime End Function 'Get % of process currently in use Public Function GetProcessorUsed( ) As Single _ Implements IServerInfo.GetProcessorUsed Return processorUsed End Function 'Get MBytes of free memory Public Function GetAvailableMemory( ) As Long _ Implements IServerInfo.GetAvailableMemory Return availableMemory End Function Public ReadOnly Property MachineName( ) As String _ Implements IServerInfo.MachineName Get Return machine End Get End Property Public ReadOnly Property IPAddress( ) As IPAddress _ Implements IServerInfo.IPAddress Get Return ip End Get End Property
Other than these two changes, everything is the same on the server sideeven the configuration file (which contains one well-known entry that exposes the object factory). Just stop the service, copy the assembly to the appropriate location, and restart.
Some interesting architectural changes must be made to accommodate the fact that ServerInfo is now an MBV object. The client must have access to the ServerInfo assembly to use the object, which raises several questions.
Can the client get the type information from the ServerInfo assembly without linking to it? Thankfully, yes. The client does not need to reference the object directly. Otherwise, the object factory concept would be rendered useless. ServerInfo just needs to be in the same directory as the client executable.
This still presents some problems, though. The client is not linked to the assembly, but what about distribution? If changes are made to the remote assembly, how is the client going to get it? Also, the source to the remote assembly will be on the client machine, where it can be disassembled by money-hungry corporate spies! IL is not difficult to pick up, and reverse engineering has never been easier.
One solution is to move the entire Windows Service into a virtual directory on the web server. The service can still provide the objects over TCP, and the client can now use a few lines of code to download the ServerInfo assembly to its location at runtime. This way, the client always has the latest version of the object. The assembly can be loaded into the client AppDomain , making the ServerInfo type available. Once this has occurred, the downloaded file can be deleted from the client directory, leaving no trace of its existence.
To make the ServerInfo assembly available from IIS, set up a directory called ServerInfo for the component under wwwroot (it can be set up anywhere local to the web server if a different location is desired):
Inetpub wwwroot ServerInfo bin <- server, assemblies, service .config file
In IIS, configure ServerInfo as a virtual directory. Make sure that the ServerInfo and bin directories use the following settings:
Execute Permissions = "Scripts Only"
Application Protection = "Low (IIS Process)"
Read Permission
Anonymous and Integrated Windows authentication (IWA)
|
All server files should be placed in the bin directory, while the upper-level ServerInfo directory contains nothing. The server should be started with everything configured as before. Remember that this configuration also applies when using IIS (versus a server) to remote objects.
On the client side, the location of the virtual directory and the object name can be stored in the client configuration file, which is shown in Example 9-12. For the .NET configuration class to find the file, it must have the same name (with file extension) as the client assembly, followed by .config (i.e., remoteclient.exe.config ). Custom settings can be added in <appSettings> , as illustrated in Example 9-12.
<configuration> <appSettings> <add key="location" value="http://192.168.1.100/ServerInfo/bin/" /> <add key="assembly" value="ServerInfo" /> </appSettings> <system.runtime.remoting> <!-- client side remoting settings are here --> </system.runtime.remoting> </configuration>
At runtime, these values can be retrieved (rather than hardcoded in the source) by using the System.Configuration.ConfigurationSettings class:
Dim location As String Dim remoteAssembly As String Dim fileName As String location = ConfigurationSettings.AppSettings("location") remoteAssembly = ConfigurationSettings.AppSettings("assembly") fileName = remoteAssembly + ".dll"
Example 9-13 contains the listing for the revised client that uses the MBV ServerInfo object. The ServerInfo assembly is downloaded to the client by calling WebClient.DownloadFile , which conveniently provides just what is needed here. The first parameter to the method is the remote file's URL, while the second is the name it should be saved to locally.
Once the file is downloaded, a call is made to AppDomain.SetShadowCopyFiles (for the current domain). This call is necessary to prevent ServerInfo.dll from being locked when the assembly is loaded into the AppDomain. After calling AppDomain.Load to inject the assembly into the current AppDomain, the file is deleted using File.Delete . However, the ServerInfo type is now available to the client. The rest of the client code behaves as it did previously, using Activator.GetObject to get the object factory interface. This time, when CreateServerInfo is called, the factory returns an MBV instance of the remote object.
Imports System Imports System.Configuration Imports System.IO Imports System.Net Imports System.Reflection Imports System.Runtime.Remoting Imports ServerInterfaces Public Class Client Public Shared Sub Main( ) Try '----- Download ServerInfo.dll Dim location As String Dim remoteAssembly As String Dim fileName As String location = ConfigurationSettings.AppSettings("location") remoteAssembly = ConfigurationSettings.AppSettings("assembly") fileName = remoteAssembly + ".dll " Dim wc As New WebClient( ) wc.DownloadFile(location + fileName, fileName) AppDomain.CurrentDomain.SetShadowCopyFiles( ) AppDomain.CurrentDomain.Load(remoteAssembly) File.Delete(fileName) '---------------------------- Dim factoryObj As Object = _ Activator.GetObject(GetType(IServerInfoFactory), _ "tcp://192.168.1.100:1969/RemotingTest/ServerInfoFactory.rem") Dim factory As IServerInfoFactory = _ CType(factoryObj, IServerInfoFactory) Dim si As IServerInfo = factory.CreateServerInfo( ) If Not (si Is Nothing) Then Console.WriteLine(si.MachineName) Console.WriteLine(si.IPAddress) Console.WriteLine(si.GetMachineTime) Console.WriteLine("{0}MB Free", si.GetAvailableMemory) Console.WriteLine("Processor Used: {0}", si.GetProcessorUsed) End If Catch e As Exception Console.WriteLine(e.GetType( ).FullName) Console.WriteLine(e.Message) End Try Console.WriteLine("Hit ENTER to continue...") Console.ReadLine( ) End Sub End Class
only for RuBoard |