|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI 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:
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 ConsumerCreating the web service consumer is trivial:
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 ApplicationThe MyXaml application consists of several parts:
The following sections describe each of these. Creating The Namespaces In MyXamlThere are four XML namespaces that need to be created:
The result looks like this: <?xml version="1.0" encoding="utf-8"?> The namespace declarations take the form: Namespace 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 DeclarationThe form declaration is straight-forward: <Form
def:Name='AppMainForm' 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 InvocationsFirst, 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}" 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 Attributepublic 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 ReflectionThe 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 ControlsThe markup for the current conditions section of the UI is simple enough. It's a bunch of labels: <Controls> 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 ControlsThe 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"> 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 FileSo, what does the include file look like? Here's the markup: <?xml version="1.0" encoding="utf-8"?> 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):
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)! ConclusionBesides 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!).
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||