Interpreting WCF
In Aaron's latest MSDN Magazine article Learn The ABCs Of Programming Windows Communication Foundation, he covers building and hosting a service using WCF and then building a client to consume the service. Often one of the reasons you build a client so you can do some quick testing of your brand new service.
Aaron's article shows a couple of ways to generate a proxy that you can then use in a program to do some testing of your web service. Sometimes, though, you aren't really sure what to put in your test program... you just want to poke at your new web service. The Microsoft Command Shell, also referred to Monad or msh, that is part of the WinFX SDK is a great way to do this.
Part of Aaron's article shows how to make a service that has an Echo operation and host it on a multiple different endpoints. Figure 1 shows using msh to a quick test of the Echo operation on three of these endpoints.
Pretty easy to do, huh? For a couple of days there Aaron just repeated everything you said to him... now we can see he was just working on getting that Echo operation perfect.
In a previous blog article, msh+SMO, I discussed using msh as a tool to manage SQL Server. That article has information on where you can obtain msh and some of the basics of using it. You can download msh from http://www.microsoft.com/technet/scriptcenter/topics/msh/download.mspx or install the WinFX SDK. This article is going to continue that theme and discuss using msh to work with web services.
The purpose article has a number of purposes; one is to show how to setup msh to do ad-hoc testing of a web service. Another is to show a little bit about the information available from a svcutil generated proxy. And lastly it is meant to show some of the features of msh.
makeProxies is a function but isn't one of the functions included with msh, we are going to look at that next. There is a pdf version of this article and sample code at http://www.pluralsight.com/dan/arts/interpretingwcf.zip. It includes the wsproxies.msh script that creates the makeProxies function.
Basically makeProxies does about what you would do to create proxies; it runs svcutil, then runs the C# compiler to build an assembly, loads the assembly into msh, then uses the config file created by svcutil to get the information it needs to create instances of the proxies.
The wsproxies.msh script creates the makeProxies function. You run this script inside of msh. Figure 2 shows this. One important point about running this script is that it must be run in the scope of the shell you are interacting with. The "." a at the beginning of the command line tells msh to run the script in the file b following in the current scope..
Depending on the configuration of msh you may not be able to run this script. The latest builds of msh have their ExecutionPolicy by default set to "Restricted" which limits msh to loading only signed scripts. You can change this to "RemoteSigned" by making a change to the registry. Set The "ExecutionPolicy" value of the registry "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSH\Microsoft.Management.Automation.msh" to "RemoteSigned" and then you will be able to load local scripts into msh.
The first thing that wsproxies.msh does is a little bookkeeping as shown in Figure 3. It creates aliases a for svcutil and csc so the later part of the script will be less cluttered. More important though is that it loads b the System.ServiceModel assembly into msh. By default msh loads a number of assemblies from the .NET Framework, but not this one. Note that the @junk variable is there just to capture the text that would appear in the msh console window, so it can run silently.
a set-alias svcutil "C:\Program Files\Microsoft SDKs\Windows\v1.0\Bin\SvcUtil.exe"
set-alias csc "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\csc.exe"
b $junk = [reflection.assembly]::LoadWithPartialName("System.ServiceModel");
The next thing that wsproxies.msh is to make the makeProxies function. Figure 4 shows the beginning of the function definition. makeProxies takes a single parameter a which is a url for a WSDL file. This url is passed to svcutil b which uses it. Note that svcutil is pretty smart about how it uses that url, as long there is some way for the url to produce a WSDL file svcutil will figure it out. Note that the $junk variable is used to capture the output of scvutil that would normally go to the screen.
The svcutil utility produces two outputs, a configuration file and a source code file. This script generates a random name for each of those. The [IO.Path]::GetRandomfilename() tells msh to run the static GetRandomFileName method of the System.IO.Path class and captures what this returns in the $CSFileName variable. The svcutil directory creates the files in the present working directory which in msh is in the $PWD variable. On of the nice things about msh is that you can use just about anything from the.NET Framework in your scripts.
Note that, just to keep things simple, this script is not bullet-proof; it is not doing any error checking.
Once svcutil has created the configuration file and the source code the C# compiler is used c to make an assembly that holds the proxies. Note that this assembly needs a reference to the System.Runtime.Serialization.dll assembly. The $dllName variable holds the random file name used for the proxy assembly.
Once the assembly has been created it is loaded d into msh. Note that a bit of slight of hand is used to do this. The assembly could have been loaded directly from its file name, but instead this script reads the assembly into an array of bytes, then loads that array of bytes into msh. This is done this way so there will be no reference held on the assembly file itself that would prevent it from being cleaned up later on.
function makeProxies ($url) a
{
$CSFileName = [IO.Path]::GetRandomFileName();
$config = $CSFilename + ".xml"
b $junk=svcutil /noLogo /directory:"$PWD" /config:"$config" /o:"$CSFileName" "$url";
$CSFileName += ".cs";
$CSFileName = [IO.Path]::Combine($PWD, $CSFileName)
$dllName = [IO.Path]::Combine($PWD, [IO.Path]::GetRandomFileName());
c $junk=csc /nologo /r:System.ServiceModel.dll /r:System.Runtime.Serialization.dll / out:"$dllName" /target:library "$CSFileName"
$bytes = [System.IO.File]::ReadAllBytes("$dllName");
d $junk=[reflection.assembly]::Load($bytes);
Now we have an assembly that can make proxies for the web services at the url passed into makeProxies loaded into msh. Next the makeProxies function creates a binding for each of the bindings in the configuration file. It will need these bindings to create the proxies for the endpoints listed in the configuration file. There is a binding element for binding that will be needed.
Figure 5 shows a fragment of a typical configuration file produced by svcutil. There is a binding element a for each of the bindings that will be needed for the endpoints listed in the configuration file. We will be looking at the endpoints shortly.
configuration>
<system.serviceModel>
<client>
...
</client>
<bindings>
<customBinding>
a <binding name="BasicHttpBinding_YEchoService2a">
<textMessageEncoding maxReadPoolSize="64"
maxWritePoolSize="16"
messageVersion="Soap11Addressing1" writeEncoding="utf-8" />
<httpTransport manualAddressing="false" maxBufferPoolSize="524288"
maxMessageSize="65536" allowCookies="false"
authenticationScheme="Anonymous"
bypassProxyOnLocal="false"
hostNameComparisonMode="StrongWildcard"
mapAddressingHeadersToHttpHeaders="true"
proxyAuthenticationScheme="Anonymous"
realm="" transferMode="Buffered"
unsafeConnectionNtlmAuthentication="false"
useDefaultWebProxy="true" />
</binding>
Figure 6 shows the part of the makeProxies that builds the bindings. We could use one of the XML stacks from the .NET Framework to pull the information out of this configuration file if we wanted to, but instead we will used the XML support built into msh itself. The $xdoc variable a is assigned the content of the configuration file. The [xml] tells msh that the data returned by get-content should be treated as xml.
The $bindings variable b is an empty hashtable that the rest of the script will fill with bindings. The @{} syntax creates an empty hashtable.
The foreach statement iterates though each of the binding elements in the configuration file. Because $xdoc is XML the in clause of the foreach statement can use a dotted syntax to select the binding elements. Figure 7 shows how this syntax winds through the configuration file to select the binding elements.
Inside the foreach statement the script tests to see if the binding is an http or tcp binding. It does this by using a dotted syntax with the $binding variable d to extract the httpTransport element, if there is one in this binding. If there is it creates an instance e of the BasicHttpBinding class then fills out its properties with values from the configuration file. Once the binding is intialized it is added to the $bindings hashtable and indexed by the value of the name attribute of the binding element in the configuration file. It does likewise for the tcpTransport element.
It does likewise if it finds a tcpTransport element.
a $xdoc = [xml]$(get-content $config)
b $bindings = @{};
c foreach($binding in $xdoc.configuration."system.serviceModel".bindings.customBind ing.binding)
{
d $httpTransport = $binding.httpTransport;
$tcpTransport = $binding.tcpTransport;
if($httpTransport -ne $null)
{
e $b = new-object System.ServiceModel.BasicHttpBinding;
if($httpTransport.maxMessageSize -ne $null)
{
$b.maxMessageSize = $httpTransport.maxMessageSize;
}
if($httpTransport.maxBufferPoolSize -ne $null)
{
$b.maxBufferPoolSize = $httpTransport.maxBufferPoolSize;
}
if($httpTransport.hostNameComparisonMode -ne $null)
{
$b.hostNameComparisonMode = $httpTransport.hostNameComparisonMode;
}
if($httpTransport.bypassProxyOnLocal -ne $null)
{
$b.bypassProxyOnLocal = $httpTransport.bypassProxyOnLocal;
}
if($httpTransport.transferMode -ne $null)
{
$b.transferMode = $httpTransport.transferMode;
}
f $bindings[$binding.name] = $b;
}
if($tcpTransport -ne $null)
{
$b = new-object System.ServiceModel.NetTcpBinding
$bindings[$binding.name] = $b;
}
}
Note that the script that builds the bindings could be more complete, but it does show the basic technique you would use.
Once all the bindings have been built the script can build proxies for the endpoints. This is the easiest part. Figure 8 shows a typical endpoint element from a configuration file created by svcutil.
<endpoint address="http://localhost:8080/echo/svs"
bindingConfiguration="BasicHttpBinding_YEchoService2a"
binding="customBinding"
name="YEchoService2a"
contract="YEchoService2a" />
Figure 9 shows the foreach statement that uses a dotted syntax again, but this time to extract the endpoints from the configuration file. It uses the name of the contract for the endpoint with the suffix "Proxy" as the name of the class a for the proxy because that is the way that svcutil generates the name of the proxy class. It makes an instance b of that class using the binding indexed by the address attribute of the endpoint element.
If you are used to using a language like C# or Visual Basic it might seem strange to just make an instance of an object and seemingly throw it away. With msh when you make an instance of an object but don't assign it to a variable it is just returned to the caller of the function. If you make more than one object this way then msh returns an array of all the object made this way.
foreach($endpoint in $xdoc.configuration."system.serviceModel".client.endpoint)
{
a $class = $endpoint.contract + "Proxy";
$address = new-object System.ServiceModel.EndpointAddress $endpoint.address;
$b = $bindings[$endpoint.bindingConfiguration]
b new-object $class ($b, $address);
}
The last thing the script does is clean up after itself. This is shown in Figure 10. The remove-item does a delete in the file system. Note that we are able to delete the dll for the assembly because we used that slight of hand and loaded its bytes instead of the file itself.
remove-item $PWD/$config
remove-item $CSFileName
remove-item $dllName
Now we have the makeProxies function that we used in Figure 1 to try out the Echo operations on the endpoint from http://localhost:8080/echo. Of course I knew there were three endpoints and each had an echo operation, but you wouldn't necessarily know that for an arbitrary endpoint. However msh will tell you everything you need to know about the proxies. In fact the easiest thing you can do with the proxies returned by makeProxies is just let msh to tell you about them.
Figure 11 shows the format-list filter being used to list the properties of each of the proxies in $p. A filter in msh processes any object passed into it. The format-list filter lists the properties of the objects it processes. The $p variable, which is an array, is passed to the format-list filter though a pipe, that's the "|" symbol, so that each object it contain is processed individually by format-list. From this we can see that $p contains three endpoints.
What can the endpoints in $p do? We can use the same technique to drill into endpoints. Figure 12 shows the properties of the first endpoint. As you see with this technique you can interactively discover the information about a proxy.
If you drill a bit further you will find the operations associated with the proxy. Figure 13 shows that the first proxy has two operations, Echo and EchoMessage.
This seems to operate well for WCF, but what about non-WCF web services. Well, first of all there are web services and how they are implemented doesn't really matter. We'll try using makeProxies on an ancient web service; TerraService. Figure 14 shows makeProxies being used to find the Longitude and Latitude of Boston, Massachusetts. I'll leave you, the reader, to do the drilling into the TerraService proxy to find out it capabilites.
I hope this very brief introduction to using msh to do ad hoc testing of web services leads to dig deeper into msh. We have barely scratched the surface of what is possible with it. We used it to do ad hoc testing of web services, but really what were doing was testing some classes that happened to be proxies for web services; this technique can be used to do ad hoc testing on any class from the .NET Framework or those you design.