There have been a lot of
public articles and presentations about what’s coming in the Whidbey version of
the .NET frameworks and Visual Studio.NET 2005 for ASP.NET web forms. We have
exciting features such as Personalisation, Master Pages, Code-Beside (did I call
that the right thing?), HTML preservation, etc. etc.
What I’ve not seen as much
of is what’s new for the person writing ASMX web services. So, we know that
advanced web service scenarios are being targeted through the Web Service
Enhancements (WSE) and that lets us reach the Web Service Architecture stuff
(WS-*) in the medium term and in the longer term we’ll get Indigo but what can
we do with Whidbey that we couldn’t do before with ASMX web
services?
I thought I’d take a bit of
a look round what I could find on ASMX in Whidbey – this is not meant to be
“official” and nor is it meant to be “definitive”. I just wanted to take a tour
around the MSDN bits and see what I could see. Mistakes and ommssions are mine –
I’ll come back and fix them as I play more with these bits. Additionally, any
code samples are provided “as is” with no implied warranty or rights and are not
fit for any particular purpose – they’re just
samples.
What I’ve documented here
comes from the Whidbey Community Preview that was released at VSLive a month or
two ago and is based on the bits that are available from MSDN subscriber
downloads for people with MSDN subscriptions.
The
Project System
The first change when you
drop into the Community Preview and create a web services project is that the
project type isn’t there anymore under the File->New->Project menu. It’s
now under the File->New->Web Site menu. From here I can choose a language
and the ASP.NET Web Service project type and I get a new
project.
The nice thing that I
spotted is that you can place this project anywhere you like –
“c:\temp\testservice” was a good starting point for me rather than having to
point it at a live web server. Indeed, one of the big changes for the Community
Preview is that it comes with its own web server rather than requiring you to
have IIS on the machine.
So, if you create a new web
site (ASP.NET Web Service) then what you get is a folder that looks like
this;
TestService
Code
Service.asmx.cs
Data
Service.asmx
Just to check that my
sanity was ok, I instantly checked that I can still make an ASMX web service in
a single file and hacked the Service.asmx file to
be;
So, with no codebehind and just a
quick F5 on the keyboard I have my web service up and running just like VS.NET
2003 so I’m happy with that.
<%@
webservice language="C#" class="Test" %>
using
System;
using
System.Web.Services;
public class
Test
{
public Test()
{}
[WebMethod()]
public string
SayHello()
{
return("Hello");
}
}
So, what’s going on with
this Code folder and the .cs files with it? Well, ASP.NET has a couple of new
models for doing its dynamic compilation thing which leaves me with a few ways
to partition my web service code;
1.
Put all the code into the ASMX file and leave it there. This will get
compiled on an as-needs basis.
2.
Put no code in the ASMX file and have a codebehind file in the Code
folder and put all the code in there.
3.
Put no code in the ASMX file and no codebehind and have the ASMX file
reference a class I’ve built into a class library and deploy that in my bin or
GAC.
In all cases, any
“external” code is going to need to be found by the .NET assembly loader and so
would need to be in a bin folder or the GAC.
I also get a new option for
(1,2,3) above in that there’s a new WebSite->Publish menu option which will
pre-compile the web site up for me leaving me with a set of bits that just need
deploying to the web server. So, in this case there’s no visible code left in my
ASMX file and everything’s been pre-built into the contents of a bin folder that
VS.NET produces for me. It’s worth trying one of these and having a look at the
outputs that you get to see what the difference is between this and any of
(1,2,3) above.
Note that the project-less
project system is just working off folders on the disk so I can just walk up to
my Service.asmx file in a folder using Explorer and double-click on it to get
back into VS.NET and see the file and the “project” (in terms of the folder
structure) that it’s a part of. There’s no specific project or solution file
being built up here for me.
The
WebServiceBinding attribute and WsiClaims enumeration
However you choose to split
up your code, one of the most obvious changes when you create a new project with
the Whidbey Community Preview is that your web service class gets a shiny new
attribute placed on it;
WebServiceBinding
Now, this attribute was in
the framework V1.1 but it now has a new property named ConformanceClaims. This is an
enumeration of type WsiClaims and is
a way for your web service to state whether it claims to conform to the WS-I
basic profile 1.0 or not.
If you state that your
service is Base Profile 1.0 compliant then this will be checked for you at the
point where the test page for your web service is generated. So, a good way of
showing this is to change your web method that visual studio generates for you
and add another attribute to it;
SoapRpcMethod
If you do this and then run
the project you’ll see that the test page nicely flags for you that you no
longer conform to the WS-I Basic Profile V1.0 and, in particular, you violate
recommendation R2706 (cool!).
The WebServiceBinding attribute also has an
EmitConformanceClaims flag on it
which you can use to determine whether your WSDL should include the claims that
you’re making. For instance, if you specify that you want conformance claims
emitted then your WSDL operation will be decorated as below;
<wsdl:portType
name="Service_asmxSoap">
<wsdl:operation
name="HelloWorld">
<wsdl:documentation>
<wsi:Claim
conformsTo=http://ws-
i.org/profiles/basic/1.0 xmlns:wsi="http://ws-
i.org/schemas/conformanceClaim/" />
</wsdl:documentation>
<wsdl:input
message="tns:HelloWorldSoapIn"/>
<wsdl:output
message="tns:HelloWorldSoapOut"/>
</wsdl:operation>
</wsdl:portType>
You will also notice that
these two new properties appear on the WebMethod attribute to allow you
control these same properties at the method level rather than the binding
level.
Xml
Serialization
The XmlSerializer is a key
part of the ASMX infrastructure in that if you want to do the object<->XML
mapping that’s required in going from CLR types to XML types and back again then
XmlSerializer is your friend for doing this.
In V1.1 we had limited
control over how types were serialized using the XmlSerializer. We could
attribute types at design time and we could also override those attributes with
new values at run time but it was all attribute based and the developer wasn’t
really given full control as they were with (e.g.) remoting
serialization.
It was reasonably well
known that there was an interface called IXmlSerializable that certain .NET
framework classes (the DataSet) used to do custom Xml serialization through the
XmlSerializer but these interfaces weren’t documented for
use.
In the Whidbey frameworks
the IXmlSerializable interface is documented and supported for anyone to use if
they need full control over how their types are serialized.
So, how does this work with
ASMX web services? We’d hope that we can use types that implement
IXmlSerializable as return types and parameter types to our “web methods” and
we’re in luck because we can.
IXmlSerializable has 3
methods that you need to implement – ReadXml, WriteXml and GetSchema. I’m not 100% sure of the
status of GetSchema right now as it
doesn’t seem to be quite doing the right thing for me at the moment so I’m
working a different way. Here’s an example of a simple class that handles its
own serialization (in a very basic way);
[XmlSchemaProvider("ProducePersonSchema")]
public
class Person :
IXmlSerializable
{
public Person()
{
}
public Person(string
firstName, string
lastName)
{
_firstName = firstName;
_lastName = lastName;
}
public static
XmlQualifiedName ProducePersonSchema(
XmlSchemaSet
set)
{
XmlSchema s = new
XmlSchema();
s.Id
= "Test";
s.TargetNamespace = "urn:types-mt-com";
XmlSchemaComplexType t = new
XmlSchemaComplexType();
t.Name = "personType";
XmlSchemaAttribute a = new
XmlSchemaAttribute();
a.Name = "firstName";
t.Attributes.Add(a);
XmlSchemaAttribute b = new
XmlSchemaAttribute();
b.Name = "lastName";
t.Attributes.Add(b);
XmlSchemaElement e = new
XmlSchemaElement();
e.Name = "person";
XmlQualifiedName n = new
XmlQualifiedName("personType",
"urn:types-mt-com");
e.SchemaTypeName
= n;
s.Items.Add(t);
s.Items.Add(e);
set.Add(s);
return(n);
}
public XmlSchema GetSchema()
{
return(null);
}
public void
WriteXml(XmlWriter writer)
{
writer.WriteStartElement("person", "urn:mt-com");
writer.WriteAttributeString("firstName",
_firstName);
writer.WriteAttributeString("lastName", _lastName);
writer.WriteEndElement();
}
public void
ReadXml(XmlReader reader)
{
// Not very robust
this...
XmlNodeType type = reader.MoveToContent();
if ((type == XmlNodeType.Element)
&&
(reader.LocalName ==
"customer"))
{
_firstName = reader["firstName"];
_lastName = reader["lastName"];
}
}
public override
string ToString()
{
return(string.Format("Person [{0}
{1}]"));
}
private string
_firstName;
private string
_lastName;
}
So, in our class Person we have implemented IXmlSerializable and we read/write Xml
in the appropriate methods. Note that we have not implemented GetSchema but, instead, have attributed
the class with an XmlSchemaProvider
attribute which states that the schema for the serialized form of our class will
be provided by the ProducePersonSchema method where we
make up a schema each time and provide it back to the
framework.
With that in place we
should be able to use our type from a web service such
as;
[WebService(Namespace="urn:mt-com")]
public class Service_asmx
{
[WebMethod]
public Person
GetPerson()
{
return(new Person("Mike",
"Taulty"));
}
[WebMethod]
public void PutPerson(Person p)
{
}
}
}
Pre-Generating Serialization Assemblies
There’s some more stuff
relating to XML serialization in that the way that the serializer works in the
frameworks today is through compiling code at the point where we first come to
serialize a particular type and this can be an “unexpected” hit to the
performance of your system and has other implications around access to
compilers. In Whidbey it’s possible to produce serialization assemblies a priori
in order that you avoid this hit altogether (well, you take the hit at build
time which is probably ok as you can do it whilst grabbing a coffee J).
There’s a new tool included
with the SDK in the Whidbey preview named SGEN.EXE which performs this task for
you. Now, I must admit that in the download I have when I run SGEN.EXE I get an
exception saying that the strong name for SGEN.EXE can’t be verified. If this is
the same for you and you’re playing then my way around it was to skip
verification on SGEN.EXE by using the SN.EXE tool with
a;
SN –Vr SGEN.EXE
which allowed me to work
around this in order to experiment with it. So, in order to pre-generate a
serialization assembly for your assembly containing serializable types you can
do;
SGEN /assembly:myassembly.dll
And you can also specify /keep to keep the source code generated
and one or two other options (see SGEN /?). If you do keep the source then you
can have a poke around and see that you end up with classes derived from XmlSerializationReader and XmlSerializationWriter that are made
bespoke for your particular class(es).
So, having got this
assembly what do you do with it? How do you make sure that this thing is used
when your types needs serializing rather than the framework cooking up new
serialization readers and writers?
Well, there are certainly
overloads of Serialize and Deserialize on the XmlSerializer that allow you to
explicitly pass serialization readers and writers, but what if you don’t control
the code that’s doing the serialization? How does that work? How do we get an
implicit link between a type and a pre-generated Xml serialization assembly?
From what I can see (i.e.
from a bit of testing) at the point where the XmlSerializer is constructed the
framework goes out looking for a pre-built assembly that does the serialization
(there is an assembly-wide attribute XmlSeralizerAssembly placed on the Xml
serialization assembly) for the particular type being used. So, in my tests when
I construct an XmlSerializer for my type my pre-generated assembly is loaded if
it’s locatable in my bin path (or, presumably, GAC) and, if not, then a
temporary assembly is compiled and loaded for me.
Generics in
Serialization
For the Whidbey release,
the CLR gets a huge addition in expressive power in that Generic types get added
with full support in the runtime. Xml serialization has been extended in order
to support generic types for serialization and
de-serialization.
So, what can we do here? We
can write generic types such as the one here;
[XmlRoot("Key")]
public class DictionaryKey<K,
T>
{
public
DictionaryKey()
{
}
public T
Value
{
get { return(_value); }
set { _value = value; }
}
public K
Key
{
get { return(_key); }
set { _key = value; }
}
private K
_key;
private T
_value;
}
And we can use Xml
serialization to serialize and de-serialize particular specialisations of this
type by making a serializer for ourselves such as;
s = new
XmlSerializer(typeof(DictionaryKey<int,
string>));
So, we can write web
services where we use parameters and return-values of generic types – whilst I’m
pretty excited about generics in the run time I’m not (yet!) so excited about
the use of generic types in these circumstances as I’m thinking that the focus
is more on message-exchange as opposed to type-system representation. I might be
missing a trick here though J
Customising WSDL and Proxies
There’s a new concept of
a Service Description Format
Extension (SDFE) – we can use this to add elements to the WSDL generated for a
service and also to extend proxies as they are
generated.
In the Whidbey docs that come
with the Community Preview there’s a good explanation of how to build one of
these things so I won’t duplicate it here – look for the topic named “Customizing the Generation of Service
Descriptions and Proxy Classes”.
From what I can see of this
it’s pretty powerful (and can be used to build the /sqltypes stuff
below).
WSDL.EXE
and Proxies
The wsdl.exe tool is used
for two primary purposes – generating proxy classes to allow us to talk to a web
service that’s described by WSDL and generating server side classes which give
us a head start in implementing a web
service that matches a pre-written WSDL.
In the Community Preview
we’ve got a few new flags and parameters added to the WSDL tool, let’s take a
look.
WSDL.EXE
/sqltypes
In the build that I’ve got
of WSDL.EXE I couldn’t get the /sqltypes flag to do anything for me – possibly
my error or possibly the tool. My understanding is that this is analogous to the
“Customising WSDL and Proxies” section above.
WSDL.EXE
/sharetypes
There’s a scenario today
where you build a single type (say Customer) and you build 2 web services around
it. Say on web service 1 you’ve got a method named “GetCustomer” and on web
service 2 you’ve got one named “PutCustomer”. You create Customer as a common
type in a library and you reference that common type from your other 2 web
service projects. This is all fine until you get to the point where you create a
single client project and you want to add web references to both service 1 and
service 2 and you’re expecting to pass the Customer instance that you get back
from GetCustomer() into a call to PutCustomer(). At this point you’ll find that
the code that’s been built for you has created 2 different customer types in two
different namespaces so there’s now some manual editing stuff to do to make sure
that this works ok and that needs some care because the chances are that you’ll
regenerate those proxies at some point and lose your manual
changes.
I’m not quite sure how you
drive this from the development environment just yet. What I have found is that
if I’ve got 2 web services with a common type shared between them then I can
do;
WSDL.EXE
/sharetypes http://service1/service1.asmx?wsdl
http://service2/service2.asmx?wsdl
And that seems to build me
proxy code which will let me use that shared type across these two web services
without any issue. I’ve a feeling that there’s a little bit more to this than
I’ve written here so need to revisit.
WSDL.EXE
/fields
The /fields parameter seems
to do what it says on the tin in that it replaces public properties on your
proxy classes with public fields instead. So, if we have a customer type with a
firstname and lastname they will either be represented as a proxy class with
properties or (with /fields) by fields. I haven’t checked this against 1.1 but,
as I remember it, in 1.1 you got public fields whereas now you get public
properties but you can change this using the /fields
flag.
WSDL.EXE
/protocol
We can use the protocol to
switch how we talk to the web service. Choices are SOAP, SOAP12 (!), HttpGet, HttpPost.
Going with HttpGet gives
you a proxy class derived from HttpGetClientProtocol with methods attributed
with HttpMethod attributes that look
to use classes such as UrlParameterWriter to write the
“parameters” on to the Url presumably. Haven’t seen this stuff before – not 100%
sure that it’s brand new but I’m guessing that it
is.
Going with HttpPost gets
you a proxy class derived from HttpPostClientProtocol and this looks
to use classes such as HtmlFormParameterWriter to move your
XML data up to the web site on a form.
Going with SOAP uses the
familiar SoapHttpClientProtocol base
class for your proxy and going with SOAP12 seems to do the same but sets the SoapVersion property on the base class
to SoapProtocolVersion.Soap12.
Proxies
I had a bit of a look
around the proxy generated with WSDL.EXE (for the SOAP protocol). Firstly, it
seems to have a new asynchronous pattern implemented. For a “webmethod” named DoSomething we are used to
seeing;
BeginDoSomething()
EndDoSomething()
DoSomething()
generated for us on the
proxy side. In addition we now also see
DoSomethingAsync()
DoSomethingCompleted (event!)
On the proxy – the base
class seems to now have BeginInvoke
as previously and now also InvokeAsync – not sure how this is
working or what the difference is here right now but it’s a difference. The
proxy also has a CancelAsync method
to stop one of these calls proceeding.
Proxy also gets a ConformanceClaims property and a SoapVersion property – don’t notice any
other changes in there.
Configuration
I’m not sure if this is
something that already happened in the V1.1 framework but I noticed that the
default set of protocols that I was getting for my web services were SOAP and
SOAP12. If you want to see Http Get and Post then you need to go into your
configuration file and switch them back on.
Hosting
Web Services
Today, if you want to host
a web service you need a web server. So, web service work very well in a
scenario where you have a client that makes requests to a web service, gets some
data back and goes off to do something with it. Where it doesn’t work so well is
where you want to build a publisher/subscriber model where a client registers
interest in some data and the service later on notifies the client. This only
works today if our notion of a “client” is defined to be somewhere where we can
rely on the presence of a web server.
We can get around this
today by using the Web Service Enhancements (2) which allow us to send
bi-driectional SOAP messages without having a web server so we can have those
call backs to the “client”.
But, can we achieve the
same thing with ASMX? Both Windows Server 2003 and Windows XP Service Pack 2
have support for a kernel HTTP listener, HTTP.SYS and the frameworks already
expose some essential pieces from the ASP.NET runtime that should allow us to
put the two things together and host ASMX ourselves without a web server (well,
without IIS anyway) inside of an application such as a service, a Windows Forms
app or a Console app.
I need to come back to this
as I have half a solution working right now – will repost when I have something
working properly.