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 Code Project 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 information).
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:
="1.0"="utf-8"
<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 possibly 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 delimited. 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 its incredible flexibility and power.
The Forecast Include File
So, what does the include file look like? Here's the markup:
="1.0"="utf-8"
<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 occurrences 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!).
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.