Configuration Files

.NET Remoting configuration files allow you to specify parameters for most aspects of the remoting framework. These files can define tasks as simple as registering a channel and specifying a Type as a server-activated object, or can be as complex as defining a whole chain of IMessageSinks with custom properties.

Instead of writing code like this on the server:

 HttpChannel chnl = new HttpChannel(1234); ChannelServices.RegisterChannel(chnl); RemotingConfiguration.RegisterWellKnownServiceType(                  typeof(CustomerManager),                  "CustomerManager.soap",                  WellKnownObjectMode.Singleton); 

you can use a configuration file that contains the following XML document to specify the same behavior:

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="1234" />       </channels>       <service>         <wellknown mode="Singleton"                    type="Server.CustomerManager, Server"                    objectUri="CustomerManager.soap" />       </service>      </application>    </system.runtime.remoting> </configuration> 

To employ this configuration file in your application, you have to call RemotingConfiguration.Configure() and pass the filename of your *.config file to it.

 String filename = "server.exe.config"; RemotingConfiguration.Configure(filename); 
Note 

As a convention for .NET applications, the configuration filename should be <applicationname>.config, whereas an application filename includes the extension .exe or .dll.

When using this code in the IDE, remember to put the *.config files in the directory where the application will be built!

Watch for the Metadata!

Instead of using Activator.GetObject() and passing a URL to it, you can use the new operator after loading the configuration file with RemotingConfiguration.Configure().

In terms of the sample application in Chapter 2, this means that instead of the following call:

 ICustomerManager mgr = (ICustomerManager) Activator.GetObject(           typeof(ICustomerManager),           "http://localhost:1234/CustomerManager.soap"); 

you might simply use this statement after the configuration file has been loaded:

 CustomerManager mgr = new CustomerManager() 

And here the problem starts: you need the definition of the class CustomerManager on the client. The interface is not sufficient anymore, because you cannot use IInterface x = new IInterface(), as this would represent the instantiation of an interface, which is not possible.

In Chapter 3, I show you several tools for supplying the necessary metadata in a shared assembly: interfaces, abstract base classes, and SoapSuds-generated metadata-only assemblies. When using configuration files, you won't be able to employ abstract base classes or interfaces—you simply have to resort to SoapSuds-generated metadata.

When your application includes only SAOs/CAOs (and no [Serializable] objects), you're fine with using soapsuds -ia:<assembly> -nowp -oa:<meta_data.dll> to generate the necessary metadata. However, when you are using [Serializable] objects, which not only hold some data but also have methods defined, you need to provide the implementation (the General.dll in the examples) to the client as well.

To see the problem and its solution, take a look at Listing 4-1. This code shows you a [Serializable] class in a shared assembly that will be called General.dll.

Listing 4-1: A Shared [Serializable] Class

start example
 using System; namespace General {    [Serializable]    public class Customer    {       public String FirstName;       public String LastName;       public DateTime DateOfBirth;       public int getAge()       {          TimeSpan tmp = DateTime.Today.Subtract(DateOfBirth);          return tmp.Days / 365; // rough estimation       }    } } 
end example

On the server side you use the following configuration file, which allows you to write CustomerManager obj = new CustomerManager to acquire a reference to the remote object.

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="1234" />       </channels>       <service>         <wellknown mode="Singleton"                    type="Server.CustomerManager, Server"                    objectUri="CustomerManager.soap" />       </service>     </application>   </system.runtime.remoting> </configuration> 

The server itself, which is shown in Listing 4-2, implements aMarshalByRefObject that provides a getCustomer() method, which will return a Customer object by value.

Listing 4-2: The Server-Side Implementation of CustomerManager

start example
 using System; using System.Runtime.Remoting; using General; namespace Server {    class CustomerManager: MarshalByRefObject    {       public Customer getCustomer(int id)       {          Customer tmp = new Customer();          tmp.FirstName = "John";          tmp.LastName = "Doe";          tmp.DateOfBirth = new DateTime(1970,7,4);          return tmp;       }    }    class ServerStartup    {       static void Main(string[] args)       {          Console.WriteLine ("ServerStartup.Main(): Server started");         String filename = "server.exe.config";         RemotingConfiguration.Configure(filename);          // the server will keep running until keypress.          Console.WriteLine("Server is running, Press <return> to exit.");          Console.ReadLine();       }    } } 
end example

After compiling, starting the server, and running the SoapSuds command shown in Figure 4-1, you'll unfortunately end up with a little bit more output than expected.

click to expand
Figure 4-1: SoapSuds command line for extracting the metadata

The Generated_General.dll file (which you can see in Figure 4-2) contains not only the metadata for Server.CustomerManager, but also the definition for General.Customer. This is because SoapSuds generates metadata-only assemblies for all classes and assemblies that are referenced by your server. You'll run into the same problems when referencing parts from mscorlib.dll or other classes from the System.* namespaces.

click to expand
Figure 4-2: The Generated_General.dll that has been created by SoapSuds

Comparing Generated_Meta.dll to the original General.Customer in General.dll (the one that has been created by compiling the shared project) in Figure 4-3, you can see that although the generated Customer class contains all defined fields, it does not include the getAge() method.

click to expand
Figure 4-3: The original General.dll contains the method Customer.GetAge().

You can now safely assume that using the Generated_General.dll will not be sufficient for the application; after all, you want access to the getAge() method.

If you try to reference both General.dll and Generated_General.dll in your client application, you will end up with a namespace clash. Both assemblies contain the same namespace and the same class (General.Customer). Depending on the order of referencing the two DLLs, you'll end up with either a compile-time error message telling you that the getAge() method is missing or an InvalidCastException when calling CustomerManager.getCustomer().

Using SoapSuds to Generate Source Code

To further convince you that shipping the implementation to the client is not the best idea, even if it does solve the preceding problems posed by sharing metadata, I present an alternative solution.

Although SoapSuds can be used to generate DLLs from the WSDL information provided by your server, it can also generate C# code files, including not only the definition but also the required SoapMethodAttributes, so that the remoting framework will know the server's namespace identifier.

To generate code instead of DLLs, you have to specify the parameter -gc instead of -oa:<someassembly>.dll. This command, shown in Figure 4-4, generates one C# source code file for each server-side assembly. You will therefore end up with one file called general.cs and another called server.cs (both are placed in the current directory). The generated server.cs file is shown in Listing 4-3.

click to expand
Figure 4-4: SoapSuds command line for generating C# code

Listing 4-3: The SoapSuds-Generated server.cs File

start example
 using System; using System.Runtime.Remoting.Messaging; using System.Runtime.Remoting.Metadata; using System.Runtime.Remoting.Metadata.W3cXsd2001; namespace Server {      [Serializable, SoapType(XmlNamespace="http://schemas.microsoft.com/clr/n   sassem/Server/Server%2C%20Version%3D1.0.678.38058%2C%20Culture%3Dneutral%2C%    20PublicKeyToken%3Dnull", XmlTypeNamespace="http://schemas.microsoft.com/clr    /nsassem/Server/Server%2C%20Version%3D1.0.678.38058%2C%20Culture%3Dneutral%2    C%20PublicKeyToken%3Dnull")]      public class CustomerManager : System.MarshalByRefObject      {          [SoapMethod(SoapAction="http://schemas.microsoft.com/clr/nsassem/Ser   ver.CustomerManager/Server#getCustomer")]          public General.Customer getCustomer(Int32 id)          {              return((General.Customer) (Object) null);          }     } } 
end example

Generally speaking, these lines represent the interface and the attributes necessary to clearly resolve this remoting call to the server.

Instead of including the Generated_General.dll file (which also contains the namespace General), you can include this C# file in the client-side project or compile it (using the csc.exe stand-alone command line compiler) to a DLL.

Note 

When using the command-line compiler csc.exe, you have to specify the /r parameter to add a reference to General.dll. A possible compilation command might be csc /t:library /out:new_generated_meta.dll /r:general server.cs.

You can now safely reference the shared General.dll, without any namespace clashes, in your project to have access to the Customer class' implementation.

Porting the Sample to Use Configuration Files

Taking the first sample application in Chapter 2 (the CustomerManager SAO that returns a Customer object by value), I'll show you here how to enhance it to use configuration files.

Assume that on the server side of this application you want an HTTP channel to listen on port 1234 and provide remote access to a well-known Singleton object. You can do this with the following configuration file:

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="1234" />       </channels>       <service>         <wellknown mode="Singleton"                    type="Server.CustomerManager, Server"                    objectUri="CustomerManager.soap" />       </service>     </application>   </system.runtime.remoting> </configuration> 

The server-side implementation will simply load the configuration file and wait for <Return> to be pressed before exiting the application. The implementation of CustomerManager is the same as shown previously, and only the server's startup code is reproduced here:

 using System; using System.Runtime.Remoting; using General; namespace Server {    class ServerStartup    {       static void Main(string[] args)       {         String filename = "server.exe.config";         RemotingConfiguration.Configure(filename);          Console.WriteLine("Server is running. Press <Return> to exit.");          Console.ReadLine();       }    } } 

Creating the Client

The client will consist of two source files, one of them being the previously mentioned SoapSuds-generated server.cs and the second will contain the real client's implementation. It will also have a reference to the shared General.dll.

To allow the client to access the server-side Singleton, you can use the following configuration file to avoid hard coding of URLs:

 <configuration>   <system.runtime.remoting>     <application>       <client>         <wellknown type="Server.CustomerManager, Client"                    url="http://localhost:1234/CustomerManager.soap" />       </client>     </application>   </system.runtime.remoting> </configuration> 

Even before I get to the details of configuration files, I want to mention what the attribute "type" in the XML tag <wellknown> contains. In the previous example, you can see that on the server side the tag includes "Server.CustomerManager, Server" and on the client-side it includes "Server.CustomerManager, Client".

The format is generally "<namespace>.<class>, <assembly>", so when a call to the remote object is received at the server, it will create the class Server.CustomerManager from its Server assembly.

Caution 

Make sure you do not include the .dll or .exe extension here as the .NET Remoting Framework will not give you any error messages in this case. Instead, your application just won't work as expected. If you want to be sure that you're dealing with a remote object, you can call your object's IsTransparentProxy() method right after creation of the remote reference.

On the client side the format is a little bit different. The preceding entry, translated into plain English, more or less reads, "If someone creates an instance of the class Server.CustomerManager, which is located in the Client assembly, then generate a remote reference pointing to http://localhost:1234/CustomerManager.soap." You therefore have to specify the client's assembly name in the type attribute of this tag. This differs when using aSoapSuds-generated DLL, in which case you would have to include the name of this generated assembly there.

Caution 

When a typo occurs in your configuration file, such as when as when you misspell he assmbly name or the class name, there won't be an exception during "x = new Something()". Instead, you will get a reference to the local object. When you subseuently call methods on it, they will return null.

The C# code of the client application (excluding the SoapSuds-generated server.cs) is shown in Listing 4-4.

Listing 4-4: The Working Client Application (Excluding server.cs)

start example
 using System; using System.Runtime.Remoting; using General; // from General.DLL using Server; // from server.cs namespace Client {    class Client    {       static void Main(string[] args)       {         String filename = "client.exe.config";         RemotingConfiguration.Configure(filename);         CustomerManager mgr = new CustomerManager();          Console.WriteLine("Client.Main(): Reference to CustomerManager" +                               " acquired");          Customer cust = mgr.getCustomer(4711);          int age = cust.getAge();          Console.WriteLine("Client.Main(): Customer {0} {1} is {2} years old.",             cust.FirstName,             cust.LastName,             age);          Console.ReadLine();       }    } } 
end example

When server and client are started, you will see the familiar output shown in Figures 4-5 and 4-6.

click to expand
Figure 4-5: Client's output when using the configuration file

click to expand
Figure 4-6: Servers's output when using the configuration file

Standard Configuration Options

All .NET configuration files start with <configuration>, and this applies to remoting configuration files as well.

A remoting configuration file basically contains the following structure:

 <configuration>    <system.runtime.remoting>       <application>         <lifetime />         <channels />         <service />         <client />       </application>    </system.runtime.remoting> </configuration> 

Lifetime

Use the <lifetime> tag to configure your object's default lifetime (as discussed in Chapter 3). Valid attributes for the <lifetime> tag are listed here:


ATTRIBUTE

DESCRIPTION

leaseTime

The initial time to live (TTL) for your objects (default is 5 minutes)

sponsorshipTimeout

The time to wait for a sponsor's reply (default is 2 minutes)

renewOnCallTime

The time to add to an object's TTL when a method is called (default is 2 minutes)

leaseManagerPollTime

The interval in which your object's TTL will be checked (default is 10 seconds)


All attributes are optional and may be specified in different time units. Valid units are D for days, H for hours, M for minutes, S for seconds, and MS for milliseconds. When no unit is specified, the system will default to S. Combinations such as 1H5M are not supported.

Here is an example for a very short-lived object:

 <lifetime    leaseTime="90MS"    renewOnCallTime="90MS"    leaseManagerPollTime="100MS" /> 

Channels

The <channels> tag contains one or more channel entries. It only serves as a collection for these and doesn't have any XML attributes assigned to it.

To register a server-side TCP channel that listens on port 1234, you can specify the following configuration section:

 <channels>    <channel ref="tcp" port="1234"> </channels> 

Channel

The <channel> tag allows you to specify a port number for the server application, to reference custom channels, and to set additional attributes on channels. When you want to use the default HTTP channel or TCP channel, this tag does not have to be specified on the client because these channels will be registered automatically by the framework. On the server, you have to specify at least a port number on which the server-side channel will listen.

You have basically two ways of referencing channels: using a named reference for a predeclared channel or specifying the exact type (namespace, classname, and assembly) for the channel's implementation. Valid attributes for the <channel> tag are as follows:


ATTRIBUTE

DESCRIPTION

ref

Reference for a predefined channel ("tcp" or "http") or reference to a channel that has been defined in a configuration file.

displayName

Attribute only used for the .NET Framework Configuration Tool.

type

Attribute that is mandatory when ref has not been specified. Contains the exact type (namespace, classname, assembly) of the channel's implementation. When the assembly is in the GAC, you have to specify version, culture, and public key information as well. For an example of this, see the default definition of HTTP channel in your machine.conf file (which is located in %WINDIR%\Microsoft.NET\Framework\v1.0.3705\CONFIG).

port

Server side port number. When using this attribute on a client, 0 should be specified if you want your client-side objects to be able to receive callbacks from the server.


In addition to the preceding configuration properties, the HTTP channel, which is created by specifying <channel ref="http">, supports the following entries:


ATTRIBUTE

DESCRIPTION

name

Name of the channel (default is "http"). When registering more than one channel, these names have to be unique or an empty string ("") has to be specified. The value of this attribute can be used when calling ChannelServices.GetChannel().

priority

Indicator of the likelihood for this channel to be chosen by the framework to transfer data (default is 1). The higher the integer, the greater the possibility. Negative numbers are allowed.

clientConnectionLimit

Number of connections that can be simultaneously opened to a given server (default is 2).

proxyName

Name of the proxy server.

proxyPort

Port number for your proxy server.

suppressChannelData

Directive specifying whether the channel will contribute to the ChannelData that is used when creating an ObjRef. Takes a value of true or false (default is false).

useIpAddress

Directive specifying whether the channel shall use IP addresses in the given URLs instead of using the hostname of the server computer. Takes a value of true or false (default is true).

listen

Directive specifying whether activation shall be allowed to hook into the listener service. Takes a value of true or false (default is true).

bindTo

IP address on which the server will listen. Used only on computers with more than one IP address.

machineName

A string that specifies the machine name used with the current channel. This property overrides the useIpAddress property.


The TCP channel, which is created by specifying <channel ref="tcp"> supports the same properties as the HTTP channel and the following additional property:


ATTRIBUTE

DESCRIPTION

rejectRemoteRequests

Indicator specifying whether the server will accept requests from remote systems. When set to true, the server will not accept such requests, only allowing interapplication communication from the local machine.


On the server side, the following entry can be used to specify an HTTP channel listening on port 1234:

 <channels>    <channel ref="http" port="1234"> </channels> 

On the client, you can specify an increased connection limit using the following section:

 <channels>    <channel ref="http" port="0" clientConnectionLimit="100"> </channels> 

ClientProviders/ServerProviders

Underneath each channel property, you can configure nondefault client-side and server-side sink providers and formatter providers.

Caution 

When any of these elements are specified, it's important to note that no default providers will be created by the system. This means that appending ?WSDL to the URL will only work if you explicitly specify <provider ref="wsdl" />.

The .NET Remoting Framework is based on messages that travel through various layers. Those layers can be extended or replaced and additional layers can be added. (I discuss layers in more detail in Chapter 7.)

These layers are implemented using so-called message sinks. A message will pass a chain of sinks, each of which will have the possibility to work with the message's content or to even change the message.

Using the ClientProviders and ServerProviders properties in the configuration file, you can specify this chain of sinks through which you want a message to travel and the formatter with which a message will be serialized.

The structure for this property for the server side is as follows:

 <channels>   <channel ref="http" port="1234">     <serverProviders>       <formatter />      <provider />     </serverProviders>   </channel> </channels> 

You may only have one formatter entry but several provider properties. Also note that sequence does matter.

The following attributes are common between formatters and providers:


ATTRIBUTE

DESCRIPTION

ref

Reference for a predefined SinkProvider ("soap", "binary", or "wsdl") or reference to a SinkProvider that has been defined in a configuration file.

type

Attribute that is mandatory when ref has not been specified. Contains the exact type (namespace, classname, assembly) of the SinkProvider's implementation. When the assembly is in the GAC, you have to specify version, culture, and public key information as well.


Here are additional attributes that are optional for formatters:


ATTRIBUTE

DESCRIPTION

includeVersions

Indicator of whether version information should be included in the requests. Takes a value of true or false (defaults to true for built-in formatters). This attribute changes behavior on the client side.

strictBinding

Indicator of whether the server will look for the exact type (including version) or any type with the given name. Takes a value of true or false (defaults to false for built-in formatters).


In addition to these attributes, both formatters and providers can accept custom attributes, as shown in the following example. You have to check the documentation of your custom sink provider for the names and possible values of such properties:

 <channels>   <channel ref="http" port="1234">     <serverProviders>       <provider type="MySinks.SampleProvider, Server" myAttribute="myValue" />           <sampleProp>This is a Sample</sampleProp>          <sampleProp>This is another Sample</sampleProp>       </provider>       <formatter ref="soap" />     </serverProviders>   </channel> </channels> 

Versioning Behavior

Depending on the setting of the includeVersion attribute on the client-side for-matter and the strictBinding attribute on the server-side formatter, different methods for creating instances of the given types are employed:


INCLUDEVERSIONS

STRICTBINDING

RESULTING BEHAVIOR

true

true

The exact type is loaded, or a TypeLoadException is thrown.

false

true

The type is loaded using only the type name and the assembly name. A TypeLoadException is thrown if this type doesn't exist.

true

false

The exact type is loaded if present; if not, the type is loaded using only the type name and the assembly name. If the type doesn't exist, aTypeLoadException is thrown.

false

false

The type is loaded using only the type name and the assembly name. A TypeLoadException is thrown if this type doesn't exist.


Binary Encoding via HTTP

As you already know, the default HTTP channel will use a SoapFormatter to encode the messages. Using configuration files and the previously mentioned properties, you can easily switch to a BinaryFormatter for an HTTP channel.

On the server side, you use the following section in your configuration file:

 <channels>   <channel ref="http" port="1234">     <serverProviders>       <formatter ref="binary" />     </serverProviders>   </channel> </channels> 

And on the client side, you can take the following configuration file snippet:

 <channels>   <channel ref="http>     <clientProviders>       <formatter ref=”binary” />     </serverProviders>   </channel> </channels> 
Note 

The server-side entry is not strictly necessary, because the server-side HTTP channel automatically uses both formatters and detects which encoding has been chosen at the client side.

Service

The <service> property in the configuration file allows you to register SAOs and CAOs that will be made accessible by your server application. This section may contain a number of <wellknown> and <activated> properties.

The main structure of these entries is as follows:

 <configuration>   <system.runtime.remoting>     <application>       <service>           <wellknown />           <activated />       </service>     </application>   </system.runtime.remoting> </configuration> 

Wellknown

Using the <wellknown> property in the server-side configuration file, you can specify SingleCall and Singleton objects that will be provided by your server. This property supports the same attributes that can also be specified when calling RemotingConfiguration.RegisterWellKnownServiceType(), as listed here:


ATTRIBUTE

DESCRIPTION

type

The type information of the published class in the form "<namespace>.<classname>, <assembly>". When the assembly is in the GAC, you have to specify version, culture, and public key information as well.

mode

Indicator specifying object type. Can take "Singleton" or "SingleCall".

objectUri

The endpoint URI for calls to this object. When the object is hosted in IIS (shown later in this chapter), the URI has to end with .soap or .rem to be processed correctly, as those extensions are mapped to the .NET Remoting Framework in the IIS metabase.

displayName

Optional attribute that specifies the name that will be used inside the .NET Framework Configuration Tool.


Using the following configuration file, the server will allow access to aCustomerManager object via the URI http://<host>:1234/CustomerManager.soap.

 <configuration>   <system.runtime.remoting>     <application>       <channels>         <channel ref="http" port="1234" />       </channels>       <service>         <wellknown mode="Singleton"                    type="Server.CustomerManager, Server"                    objectUri="CustomerManager.soap" />       </service>     </application>   </system.runtime.remoting> </configuration> 

Activated

The <activated> property allows you to specify CAOs in the server-side configuration file. As the full URI to this object is determined by the application name, the only attribute that has to be specified is the type to be published.


ATTRIBUTE

DESCRIPTION

type

The type information of the published class in the form "<namespace>.<classname>, <assembly>". When the assembly is in the GAC, you have to specify version, culture, and public key information as well.


The following example allows a client to create an instance of MyClass at http://<hostname>:1234/.

 <configuration>    <system.runtime.remoting>       <application>          <channels>             <channel ref="http" port="1234" />          </channels>          <service>             <activated type="MyObject, MyAssembly"/>          </service>       </application>    </system.runtime.remoting> </configuration> 

Client

The client-side counterpart to the <service> property is the <client> configuration entry. Its primary structure is designed to look quite similar to the <service> entry:

 <configuration>    <system.runtime.remoting>       <application>          <client>             <wellknown />             <activated />         </client>       </application>    </system.runtime.remoting> </configuration> 

When using CAOs, the <client> property has to specify the URI to the server for all underlying <activated> entries.

Note 

When using CAOs from more than one server, you have to create several <client> properties in your configuration file.


ATTRIBUTE

DESCRIPTION

url

The URL to the server, mandatory when using CAOs.

displayName

Attribute that is used in the .NET Framework Configuration Tool.


Wellknown

The <wellknown> property is used to register SAOs on the client and allows you to use the new operator to instantiate references to remote objects. The client-side <wellknown> entry has the same attributes as the call to Activator.GetObject(), as listed here:


ATTRIBUTE

DESCRIPTION

url

The full URL to the server's registered object.

type

Type information in the form "<namespace>.<classname>, <assembly>". When the target assembly is registered in the GAC, you have to specify version, culture, and public key information as well.

displayName

Optional attribute that is used in the .NET Framework Configuration Tool.


When registering a type to be remote, the behavior of the new operator will be changed. The framework will intercept each call to this operator and check if it's for a registered remote object. If this is the case, a reference to the server will be created instead of an instance of the local type.

When the following configuration file is in place, you can simply write CustomerManager x = new CustomerManager() to obtain a remote reference.

 <configuration>   <system.runtime.remoting>     <application>       <client>         <wellknown type="Server.CustomerManager, Client"                    url="http://localhost:1234/CustomerManager.soap" />       </client>     </application>   </system.runtime.remoting> </configuration> 

Activated

This is the client-side counterpart to the <activated> property on the server. As the URL to the server has already been specified in the <client> entry, the only attribute to specify is the type of the remote object.


ATTRIBUTE

DESCRIPTION

type

The type information in the form "<namespace>.<classname>, <assembly>". When the target assembly is registered in the GAC, you have to specify version, culture, and public key information as well.


Data from this entry will also be used to intercept the call to the new operator. With a configuration file like the following, you can just write MyRemote x = new MyRemote() to instantiate a server-side CAO.

 <configuration>   <system.runtime.remoting>     <application>       <client url="http://localhost:1234/MyServer>         <activated type="Server.MyRemote, Client" />       </client>     </application>   </system.runtime.remoting> </configuration> 




Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net