Introduction
I wanted to play with web services with regards to how they might be
implemented in MyXaml, and I figured an interesting web service to try out is
one that reports the current weather conditions and a forecast.
It was interesting trying to even find a working weather web service.
Most of the ones I tried were dead or didn't work. I finally stumbled upon
the CP article "Writing a Web Service
Consumer using Borland C# Builder" by Sagar Regmi, and after investigating
the EJSE site, I discovered that it has
everything that I need:
- current conditions
- forecast
- nice bitmaps
The latter, nice bitmaps, I stumbled across a weblog that pointed out that
the icons are available from EJSE also (I think this is the weblog where I found the
info).
For our international community, sorry, but this web service only works in
the US.
How To Create The Web Service Consumer
Creating the web service consumer is trivial:
- Create a new project as a Class Library
- Right click on "References" and select "Add Web Reference"
- Copy the link to the web service's WSDL into the
URL textbox
- Click on "Go"
- Click on "Add Reference"
Visual Studio creates a nice set of wrapper classes for invoking the web
service, classes that encapsulate the return data, and enumerations.
Creating The MyXaml Application
The MyXaml application consists of several parts:
- The namespaces that reference the assemblies to be used in the application
- The form declaration
- The service invocations
- The current conditions controls
- The forecast controls
The following sections describe each of these.
Creating The Namespaces In MyXaml
There are four XML namespaces that need to be created:
- The default namespace, referencing System.Windows.Forms
- The namespace for the XWorkflow plug-in
- The namespace for the newly created web service consumer
- The namespace for the object definitions
The result looks like this:
<?xml version="1.0" encoding="utf-8"?>
<!-- (c) 2004 Marc Clifton
All Rights Reserved -->
<MyXaml
xmlns="System.Windows.Forms"
xmlns:def="Definition"
xmlns:wf="XWorkflow"
xmlns:ws="WeatherForecast.com.ejse.www, WeatherForecast">
The namespace declarations take the form:
Namespace
-or-
Namespace, Assembly
-or-
Namespace, Fully
Qualified Assembly Name
When the namespace and the assembly are the same, the first form is
sufficient. When the namespace is different from the assembly name, then
the second form must be used. The third form is useful if you want to
reference a specific assembly version or culture.
The Form Declaration
The form declaration is straight-forward:
<Form
def:Name='AppMainForm'
ClientSize='525, 460'
StartPosition='CenterScreen'
FormBorderStyle="FixedSingle"
Text='Weather Forecast'>
These are all properties of the .NET Form class. The "def:Name"
attribute instructs the parser to keep track of the instance in a key/value pair
list. The key is always the attribute value, and the value is the class
instance.
The Service Invocations
First, the service must be instantiated. MyXaml isn't just a UI markup
parser. It can instantiate any parameter-less class. In this
case:
<ws:Service def:Name="Service"/>
MyXaml is instantiating the service. The "ws:" prefix tells the parser
that the class is found in the assembly referenced by the previous namespace
declaration. Again, the "def:" prefix is used, which tells the parser to
save a reference to the instance just created. Even though the Service
class doesn't have a Name property, the above statement is still processed.
Next, two methods of the service are called:
<wf:Invoke DefList="{MyXamlDefs}"
Target="{Service}"
Method="GetWeatherInfo" Args="[int]02852" RetVal="Info"/>
<wf:Invoke
DefList="{MyXamlDefs}"
Target="{Service}"
Method="GetExtendedWeatherInfo" Args="[int]02852" RetVal="ExtInfo"/>
To get your local weather, edit the file and replace "02852" with
your zipcode.
While MyXaml supports inline and code-behind, sometimes that's
overkill. All we really want to do is call a method, passing some
parameters to it, and possible do something with the return value.
The above two lines instantiate the class Invoke, found in the XWorkflow
assembly (as determined by the "wf:" prefix). For every class that is
instantiated, the parser checks, via reflection, if it has a "Finisher"
method. if so, this method is invoked. In the case of the Invoke
class, the Finisher method is defined to invoke, via reflection, the method
initialized in the XML attributes:
DefList - this is the definition list to use to resolve references. In
this case, we're passing MyXaml's own definition list, which it adds to its own
key/value object list so that it can be referenced in the markup. The {}
braces tell the parser to look up the object in the object list.
Target - this is the object on which we are calling a method. The {}
braces tell the parser to look up the object in the object list, which is why we
used the "def:" format in naming the Service instance.
Method - this is the method to invoke
Args - these are the arguments, comma delimeted. Note the kludge to
convert the string to an "int".
RetVal - if defined, the return value (an object, we don't care about type)
is added to the definition list. The attribute value is the key in the
key/value pair.
In this particular case, the WeatherInfo and ExtendedWeatherInfo return
instances are being stored in the MyXaml definition list, using the name keys
"Info" and "ExtInfo".
What does the Invoke code look like? There are a couple interesting
parts:
Processing The Args Attribute
public Type[] ProcessParams(object[] parms)
{
ArrayList types=new ArrayList();
for (int i=0; i<parms.Length; i++)
{
string parm=parms[i].ToString();
if (parm.StartsWith("*"))
{
parm=Lib.StringHelpers.RightOf(parm, '*');
if (defList.Contains(parm))
{
object obj=defList[parm];
parms[i]=obj;
}
}
else if (parm.StartsWith("["))
{
string parmType=Lib.StringHelpers.Between(parm, '[', ']');
if (parmType=="int")
{
parms[i]=Convert.ToInt32(
Lib.StringHelpers.RightOf(parm, ']'));
}
}
types.Add(parms[i].GetType());
}
return (Type[])types.ToArray(typeof(Type));
}
Yes, there's a serious kludge in this code, regarding testing for the "[int]"
substring. I'll be fixing that in the next version of MyXaml. But
the interesting thing about this code is how it builds the type array, which is
needed to find the specific method that accepts the particular parameter types
(often, .NET can resolve this on its own. Under certain cases though, it
can't, which results in an exception being thrown, that ambiguous parameters are
resulting in two or more matching methods, hence I go through the pains of
getting the type information myself).
Invoking The Method Via Reflection
The following code calls the method and handles the return value:
public void Finisher()
{
Type t=target.GetType();
object[] parms=ArgList.ToArray();
Type[] types=ProcessParams(parms);
MethodInfo mi=t.GetMethod(method, types);
object ret=null;
if (mi != null)
{
try
{
ret=mi.Invoke(target, parms);
if (RetVal != null)
{
defList[RetVal]=ret;
}
}
catch(Exception e)
{
...
}
}
else
{
...
}
}
The Current Conditions Controls
The markup for the current conditions section of the UI is simple
enough. It's a bunch of labels:
<Controls>
<GroupBox
Location="10, 10" Size="500, 230" Text="Current
Conditions"
FlatStyle="System" Font="MS Sans Serif,
10pt">
<Controls>
<Label Location="10, 20" Size="100, 20"
Text="Location:"/>
<Label Location="10, 40" Size="100, 20" Text="Last
Updated:"/>
<Label Location="10, 80" Size="100, 20"
Text="Temperature:"/>
<Label Location="10, 100" Size="100, 20" Text="Feel
like:"/>
<Label Location="10, 120" Size="100, 20"
Text="Humidity:"/>
<Label Location="10, 140" Size="100, 20"
Text="Pressure:"/>
<Label Location="10, 160" Size="100, 20" Text="UV
Index:"/>
<Label Location="10, 180" Size="100, 20"
Text="Wind:"/>
<Label Location="10, 200" Size="100, 20"
Text="Forecast:"/>
<Label Location="110, 20" Size="200, 20"
Text="{Info.Location}"/>
<Label Location="110, 40" Size="380, 40"
Text="{Info.LastUpdated}"/>
<Label Location="110, 80" Size="200, 20"
Text="{Info.Temprature}"/>
<Label Location="110, 100" Size="200, 20"
Text="{Info.FeelsLike}"/>
<Label Location="110, 120" Size="200, 20"
Text="{Info.Humidity}"/>
<Label Location="110, 140" Size="200, 20"
Text="{Info.Pressure}"/>
<Label Location="110, 160" Size="200, 20"
Text="{Info.UVIndex}"/>
<Label Location="110, 180" Size="200, 20"
Text="{Info.Wind}"/>
<Label Location="110, 200" Size="200, 20"
Text="{Info.Forecast}"/>
</Controls>
</GroupBox>
The interesting thing here is the Text="{Info.Location}" and similar
attributes. This syntax instructs the parser to return the object
referenced inside the {} braces. However, instead of just returning the
object, the dot notation is used to tell the parser to return the value of the
object's property, as named on the right side of the ".". This syntax can
be used to drill down into properties to any depth, and is a very convenient way
of getting access to object property values.
The Forecast Controls
The five day forecast employs a really neat feature of MyXaml, the ability to
include repetitive blocks of XML. This feature is used to display the
Day1, Day2, Day3, Day4, and Day5 members of the ExtendedWeatherInfo instance,
all of which are DayForecastInfo objects. Because the web service is
nicely structured in this way, we can employ the prefix/postfix feature of the
Include processor to adjust for the specific member instance that we're
interested in.
Remember that the parser instantiates any kind of class. Again, the
Include tag is implemented with a class named Include. The core parser
doesn't know and doesn't care about includes, or anything else specific about
the class that it is instantiating. This is a very important thing.
Any "custom" parsing is handled by classes that are instantiated by the core
parser. Essentially, MyXaml is a plug-in framework.
Here's the rest of the markup:
<GroupBox Location="10, 250" Size="100, 190" Text="{ExtInfo.Day1.Day}"
FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="ExtendedForecast"
Postfix="1"/>
</Controls>
</GroupBox>
<GroupBox Location="110, 250" Size="100, 190" Text="{ExtInfo.Day2.Day}"
FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="ExtendedForecast"
Postfix="2"/>
</Controls>
</GroupBox>
<GroupBox Location="210, 250" Size="100, 190" Text="{ExtInfo.Day3.Day}"
FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="ExtendedForecast"
Postfix="3"/>
</Controls>
</GroupBox>
<GroupBox Location="310, 250" Size="100, 190" Text="{ExtInfo.Day4.Day}"
FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="ExtendedForecast"
Postfix="4"/>
</Controls>
</GroupBox>
<GroupBox Location="410, 250" Size="100, 190" Text="{ExtInfo.Day5.Day}"
FlatStyle="System">
<Controls>
<Include Src="ExtForecast.mx" ElementName="ExtendedForecast"
Postfix="5"/>
</Controls>
</GroupBox>
</Controls>
</Form>
</MyXaml>
For each GroupBox, note how the text is being set to
"{ExtInfo.Day<n>.Day}", where <n> is for days 1-5. The second
thing to note is the "Postfix" property, which specifies a value. The
Include class allows you to define a prefix and a postfix value, which can be
applied anywhere in the included markup, using the #prefix# and #postfix#
notation.
OK, the notation is a bit nutso, isn't it? Well, regardless, I've used
this feature on numerous occasions in the years of application development that
I've done with the Application Automation Layer, and I can attest to it's
incredible flexibility and power.
The Forecast Include File
So, what does the include file look like? Here's the markup:
<?xml version="1.0" encoding="utf-8"?>
<!-- (c) 2004
Marc Clifton All Rights Reserved -->
<MyXaml
xmlns="System.Windows.Forms"
xmlns:def="Definition"
xmlns:wf="XWorkflow"
xmlns:ws="WeatherForecast.com.ejse.www,
WeatherForecast">
<Element
Name="ExtendedForecast">
<PictureBox Location="22, 20" Size="56,
48">
<Image>
<Bitmap URL="http://www.ejse.com/WeatherService/images/52/
ExtInfo.Day#postfix#.IconIndex}.gif"/>
</Image>
</PictureBox>
<Label
Location="10, 80" Size="80,
25"
Text="{ExtInfo.Day#postfix#.Forecast}"
TextAlign="MiddleCenter"/>
<Label Location="10, 105" Size="80,
20"
Text="Precip. Prob.:"
TextAlign="MiddleCenter"/>
<Label Location="10, 125" Size="80,
20"
Text="{ExtInfo.Day#postfix#.PrecipChance}"
TextAlign="MiddleCenter"/>
<Label Location="10, 145" Size="80,
20"
Text="{ExtInfo.Day#postfix#.High}" TextAlign="MiddleCenter"
ForeColor="Red"
Font="MS Sans Serif, 10pt,
style=Bold"/>
<Label
Location="10, 165" Size="80,
20"
Text="{ExtInfo.Day#postfix#.Low}" TextAlign="MiddleCenter"
ForeColor="Blue"
Font="MS Sans Serif, 10pt, style=Bold"/>
</Element>
</MyXaml>
Here we see a few things. First off, is the PictureBox. MyXaml
complies with the concept of tags being in a "class-property-class" hierarchy
(although in cases where the property name and the property value, as
implemented by a class, are the same, you can use the "class-class" format):
- PictureBox is a class.
- Image is a property of that class, which just so happens to be implemented
by an Image class, but we can't use it directly because of the way it is
implemented.
- The Bitmap class in MyXaml returns an Image, and has some smarts in it--the
image can be obtained from a URL, file, or resource.
The parser, following the "class-property-class" rule, assigns the object
constructed by the Bitmap class to the Image property of the PictureBox
instance.
Note the URL. Using the #postfix# syntax, we can extract the IconIndex
referenced by the particular day. Similarly, the forecast, precipitation
probability, high and low temperature text is extracted for the instance
determined by the #postfix# contents.
When markup is included, a pre-processor implemented in the Include class
goes through the attribute values and replaces all occurances of #prefix# and
#postfix# with the values specified in the Include tag. Yes, this is time
consuming and could stand for a lot of optimization. Regardless, the
functionality that this feature provides is quite powerful. It keeps us
from having to write the same markup over and over (in this case, 5 times
over)!
Conclusion
Besides demonstrating a simple web service, I hope that primarily this
article gives you some interesting ideas regarding how XML can be used to
declaratively instantiate classes. Some of the techniques, such as the
Include feature, are really nifty--you can create a library of UI building
blocks, for example. Remember, this isn't limited to UI though. In
my blog, I demonstrate using the
Include tag to load in a DataTable of states and their abbreviations.
Using MyXaml, or any general purpose declarative instantiator engine, really
changes the way in which you think about programming. Besides helping to
decouple objects, this method of programming really helps to separate the UI
from the control logic. If you generalize that idea, you can see that it
creates a nice separation between the "passive" parts of the program and the
"active" parts. I've found that, a lot of times, the passive parts change
over time while the active parts remain the same. Being able to easily
modify the UI, data tables, or other declaratively initialized information ends
up making my applications more robust. I'm not recompiling code, the
application is more resilient to new and existing feature changes, and I can get
my work done faster (yes, even without a designer!).