We will talk about each step sequentially. First we will create the API, then the VPN CSP. After this we will call the API from client code to show how it works.
1. CSP Actions
First, we will start by creating an interface that contains common actions for a CSP named IConfigurationServiceProviderActions:
public interface IConfigurationServiceProviderActions
string SerializeToXml();
void Provision();
}
Now we need to implement the implementer, ConfigurationServiceProviderActions. This class is common to which each CSP will derive to reduce boilerplate code throughout our CSPs.
public class ConfigurationServiceProviderActions : IConfigurationServiceProviderActions
{
#region Methods
/// <summary>
/// Provisions this CSP to the device.
/// </summary>
public void Provision()
{
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(SerializeToXml());
ConfigurationManager.ProcessConfiguration(xmlDoc, true);
}
/// <summary>
/// Serializes this CSP to XML.
/// </summary>
/// <returns></returns>
public string SerializeToXml()
{
StringWriter sw = null;
StringBuilder sb = null;
try
{
sb = new StringBuilder();
sw = new StringWriter(sb, CultureInfo.CurrentCulture);
var serializer =
new XmlSerializer(GetType());
var ns = new XmlSerializerNamespaces();
ns.Add("", "");
serializer.Serialize(sw, this, ns);
}
finally
{
if (sw != null)
sw.Close();
}
return sb.ToString();
}
#endregion
}
2. Building the CSP Boilerplate Code
Next, we will create the main IConfigurationServiceProvider interface:
public interface IConfigurationServiceProvider<T> : IConfigurationServiceProviderActions
{
ConfigurationServiceProviderBody<T> Body { get; set; }
}
As you can see, this interface also implements the IConfigurationServiceProviderActions interface that we created earlier. We do this for good practice reasons. It enables us to have access to the SerializeToXML and Provision methods (and any other methods we may want to implement for future versions of the API) when working with the interface.
Now we need to implement the implementer, ConfigurationServiceProvider:
[XmlRoot("wap-provisioningdoc")]
public class ConfigurationServiceProvider<T> : ConfigurationServiceProviderActions, IConfigurationServiceProvider<T>
{
public ConfigurationServiceProvider(Csp csp)
{
Body = new ConfigurationServiceProviderBody<T>
{
CspType = csp
};
}
[XmlElement("characteristic")]
public ConfigurationServiceProviderBody<T> Body
{
get;
set;
}
}
The above class is the main class we work by way of the interface. As you can see, no code will change when you implement new CSPs.
3. Building the CSP Body
Next, we will define the IConfigurationServiceProviderBody interface class, which contains each “characteristic” block as well as the type of the CSP we want to use.
First, let’s look at the interface:
public interface IConfigurationServiceProviderBody<T>
{
Collection<T> Items { get; }
Csp CspType { get; set; }
}
The interface is named generically so that we can support many types of CSPs. In this example, T will be of type Vpn.
Now for the implementer for IConfigurationServiceProviderBody:
public class ConfigurationServiceProviderBody<T> : IConfigurationServiceProviderBody<T>
{
[XmlElement("characteristic")]
public Collection<T> Items
{
get;
}
[XmlAttribute("type")]
public Csp CSPType
{
get;
set;
}
}
As you can see, the CSPType property is the property we set in the ConfigurationServiceProvider class to tell the API what CSP we are creating.
The above example is made up of fairly straightforward code. You’ll notice the XML declarations appended to the properties; when the model is serialized, we get the correct schema that the CSP expects. Also note the use of automatic properties (if you're new to C# 3.0, see my blog post on C# 3.0 feature).
4. Adding the CSP Enumerator
Next is the CSP enumerator, which is very simple and resembles the following:
public enum Csp
{
CM_VPNEntries
}
Although we currently only have one CSP, this is the point in the code where we add new CSPs to extend the API.
5. Looking at the Characteristic Class
So far, we've looked at the boilerplate code that makes up this API, but we haven’t looked at the guts of the characteristic we are working with.
For reusability, we have created a class named Characteristic. This class defines the core characteristic. It contains a list of parameters that tell the CSP what properties to set with what values. It also contains the Name property which has a generic meaning for each CSP in most cases, though; it is simply the name of the entity that is currently being created.
The Characteristic class is the most complex of all the classes, but fear not: the code doesn’t need to be written again and again. It is simply reused.
It looks like the following:
[XmlRoot("characteristic")]
public class Characteristic
{
private readonly ParameterCollection parameters = null;
public Characteristic()
{
parameters = new ParameterCollection();
}
[XmlAttribute("type")]
public string Name
{
get;
set;
}
[XmlElement("parm")]
public ParameterCollection Parameters
{
get
{
return parameters;
}
}
}
public class ParameterCollection : Collection<Parameter>
{
public Parameter this[string name]
{
get
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException("name");
var parameter = this.Where(p => p.Name.ToLower(CultureInfo.CurrentCulture) == name.ToLower(CultureInfo.CurrentCulture));
if (parameter.Count() > 0)
return parameter.ToList()[0];
else
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Parameter {0} is not valid as it wasn't found", name));
}
}
}
public class Parameter
{
[XmlAttribute("name")]
public string Name
{
get;
set;
}
[XmlAttribute("value")]
public string Value
{
get;
set;
}
}
The Name property is simply (in our VPN case) the name of the VPN. The Parameters collection is a custom collection (class ParameterCollection) that defines all the parameters that we are interested in. We define a custom collection so we can select parameters by name; we can also call the default indexer to select by index. We use the LINQ to Objects technology to do the actual selecting, which is another example of how LINQ makes coding easier.
The Parameter class contains the Name and Value properties that correspond to a parm type in the CSP. As in our VPN case, an example of this is the Username parm.
The last class is the Vpn class. This class simply derives the Characteristic class, and its constructor creates all the supported parameters:
public class Vpn : Characteristic
{
public Vpn()
{
base.Parameters.Add(new Parameter()
{ Name = "UserName", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "Password", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "SrcId", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "DestId", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "Phone", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "Domain", Value = "" });
base.Parameters.Add(new Parameter()
{ Name = "Type", Value = "1" });
base.Parameters.Add(new Parameter()
{ Name = "IPSecAuth", Value = "0" });
}
}
Our VPN class above creates the relevant parameter objects during construction. And as you will see the way to access them is done by way of the Parameters collection. We could if we wanted to, make this class type safe. Since this class is essentially wrapping the Characteristic class, you need to provide additional properties for each parameter. Something like the following would work:
public string UserName
{
get
{
Return Parameters["UserName"].Value;
}
set
{
Parameters["UserName"].Value = value;
}
}
6. Calling the API
The following code illustrates how we call this API:
IConfigurationServiceProvider<Vpn> vpn = new ConfigurationServiceProvider<Vpn>(Csp.CM_VPNEntries);
var vpn1 = new Vpn
{
Name = "MyVPN1",
};
var vpn2 = new Vpn
{
Name = "MyVPN2"
};
vpn1.Parameters["username"].Value = "simon";
vpn1.Parameters["password"].Value = "password";
vpn1.Parameters["domain"].Value = "domain";
vpn1.Parameters["phone"].Value = "1234";
vpn2.Parameters["username"].Value = "simon2";
vpn2.Parameters["password"].Value = "password2";
vpn2.Parameters["domain"].Value = "domain2";
vpn2.Parameters["phone"].Value = "5678";
vpn1.Parameters["srcId"].Value = "{8b06c75c-d628-4b58-8fcd-43af276755fc}";
vpn1.Parameters["destId"].Value = "{8b06c75c-d628-4b58-8fcd-43af276755fc}";
vpn2.Parameters["srcId"].Value = "{8b06c75c-d628-4b58-8fcd-43af276755fc}";
vpn2.Parameters["destId"].Value = "{8b06c75c-d628-4b58-8fcd-43af276755fc}";
vpn.Body.Items = new Collection<Vpn> {vpn1, vpn2};
vpn.Provision();
//Getting the XML:
string xml = vpn.SerializeToXml();
In the above client code, we have just added two VPN connections. One is named MyVPN1 and the other named MyVPN2.
It just sets a couple of the parameters and attaches both VPN connections to our previously created network (that is, Acme Corp).
Notice here the use of another new C# 3.0 feature (see my blog post: object collection initializers and object initializers).
Finally, we call the Provision method that actually provisions the device with the VPN connections. So what does this look like in Windows Mobile after you run the code? Here’s a screen shot:
As you can see, we have created two VPN connections and added them to our “Acme Corp” network.
The sample code that you can download with this article contains a Windows project named “Client” which shows how to call the managed CSP API—a library named Microsoft.WindowsMobile.ConfigurationServiceProvider. The sample code contains the “old-fashioned” XML builder code to create the network and connection for you, so you can run through the managed CSP and add VPNs without the worry of creating a network. Ideally, of course, you would create a managed network and connection characteristic so you can use the managed API instead of raw XML, but I’ll leave you to do that.