Friday, February 10, 2006

As I arrived home from the Utah .NET User Group meeting last night I was greeted with a very pleasant surprise.  My brother had been by and left a paper on my desk which read:

Hi Aaron!
Happy Birthday!
Your Pal,
”Weird Al” Yankovic

My brother drives a Park City shuttle, frequently taking people to and from the airport.  Well, it turns out that Al and his wife, Suzanne, and their daughter were heading to Park City on my brother's shuttle.  He called me later in the day, informing me of his passenger.  In terms of being a fan, I don't know of a greater “Weird Al” fan than myself.  I absolutely LOVE his music and creativity.  I'm jealous and wich I could have been there.

Apparently, and this is why my brother needs a phone so badly, he went to check his phone to see if Al would call me, but had no service.  Of all the times to not have service!

I do thank him, however, for being thoughtful and providing me with a personalized note.  What a treat!

(The picture, for those uninitiated, is a capture I made from his 'All About the Pentiums' video - one of my all-time favorites; and a frequent MSN Messenger avatar that I employ)

Friday, February 10, 2006 10:37:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Thursday, February 09, 2006

I'm very happy with how the Utah .NET User Group went tonight, despite obstacles.  We've had some issues with our mail server.  Apparently a very small percentage of the emails sent out were actually received.  Despite not receiving their emails, we had about 50 people show up which was fantastic :)

We had a group of students/developers from Neumont University demonstrate and talk about their ORM solution (named NORMA - Neumont ORM Architect) which will be released as an open source beta on Feb 17th.  Their solution was very impressive.

I then had the opportunity to talk about creating WinForms controls.  We created a simple control, touched on several attributes, created an Editor, a Designer, and an Extender.  It was lots of fun and I think it was well received.  I really look forward to following it up with a talk on creating ASP.NET controls - maybe next month or the month after depending on demand and enthusiasm.  It was very reassuring to have several developers come up to me afterwards expressing how much they enjoyed the presentation and how much work it saved them.  Awesome!

Thursday, February 09, 2006 3:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Wednesday, February 08, 2006

Boy! Time sure does fly!  One more month has simply flown by and here we are again at the doorstep of the next Utah .NET User Group (UtahDNUG) monthly meeting.  Tomorrow's meeting (technically today's - but I've not yet gone to bed) looks to be pretty exciting.  We have two presentations slated.  First off, a few students from Neumont University will take a few minutes (about 20-30 minutes) to show off their ORM (Object-Role Modeling) solution and talk about it.  Following that, I have the distinct opportunity to present to the user group on the topic of Component Development.  This presentation will hopefully be the first in a series of 4 or 5 presentations focusing on components, WinForms controls, ASP.NET controls, assemblies, libraries, patterns, et al.

I'm looking forward to it.  The meeting is, of course, free to attend and open to all.  We'll get started right around 6:00 PM.  Directions and more information is found on the group website.

See you there!

Wednesday, February 08, 2006 7:13:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Microsoft has recently released a least-priviliged user account (LUA) whitepaper that's a great read if you're contemplating running as LUA (which you should be) or about to.  As any follower of my blog knows, I have been successfully running as a LUA for about 3 years now and absolutely loving it.  In that timeframe I have had ZERO viruses and ZERO mal-/ad-/spy-ware.  I've been a long-time supporter of Aaron Margosis' MakeMeAdmin, having customized it so that I have an admin console for VS 2003 and one for VS 2005.

At any rate, read it - it's good for you.

Wednesday, February 08, 2006 10:52:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I don't usually post comics (though I happened to just a few days ago), but this one was simply too good to pass up.  Dilbert is hilarious.

Wednesday, February 08, 2006 10:05:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, February 07, 2006

Tonight I had the opportunity to present at the Northern Utah .NET User Group.  The topic was Threading.  Last year I gave a similar presentation on Threading and it went very well.  This time around I took a slightly different tact and talked substantially more about .NET 2.0 Threading enhancements.  It was a lot of fun and went very well and was very well received.  I wish we had had more time because I had a lot of information that I wanted to cover that I didn't get a chance to except briefly.  Thanks to the user group for giving me the opportunity to present - I'd like to again in the near future :)

Tuesday, February 07, 2006 2:58:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, February 06, 2006
I've decided to make the source code for my rating control (version 1.5.0.0) available.  I am in the process of making a few more performance enhancing changes to it, so I'll have an update soon, but I've been getting a lot of pressure to get this out so here it is.  You can download the tool from the links on the blog webpage.
Monday, February 06, 2006 7:28:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I have the opportunity tomorrow to speak at the Northern Utah .NET User Group (NUNUG) meeting.  The topic is Threading.  If you had a chance to attend my discussion on Threading several months ago for the Utah .NET User Group, much of the material will be similar, but I'll be throwing in a few more tidbits, so it should be good!

Meeting time is 6:00 PM at the offices of Atmedica

405 South Main Street
Suite 500 (5th floor)
Salt Lake City, Utah 84111

If you're in the neighborhood, please stop by - we'd love to see you there!

Monday, February 06, 2006 2:52:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, February 04, 2006
Saturday, February 04, 2006 12:59:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, February 03, 2006

Well, some might say it's about time and, frankly, I would agree.  I've updated my .Text Rating Control to include a few new features and enhancements.

Most notable of the updates are the following:

  • The rating control can now be placed on the root of a community blog website, allowing users to rate content directly on the root, rather than from the individual's blog.
  • Added a new 'smiley' rating image to complement the star, bar, and dot rating images.
  • The 'top rated posts' list can, like the rating control itself, be placed on the root of the blog providing a top rated posts across the entire blog.
  • The 'top rated posts' list can now be filtered by post type, allowing you to create a list of 'top comments' vs 'top posts', etc.
  • Users' name and url is now saved across ratings so they don't have to reenter the next time (via a cookie).

In addition to the aforementioned items, several other smaller enhancements and bug fixes have been made.

If you're upgrading from the previous version (i.e. 1.0.1.0 or 1.0.1.2) you'll need to update your web.config and add some new sections as well as run a database upgrade script.  This information is all presented for you in an attached readme.txt as well as the full help .CHM file.

Thanks all for your continued support.  Please let me know how it works for you and what other features you'd like in addition to those already present.

To download the latest version, please click the link on the left.

[UPDATE: 02/07/2005]
Version 1.5.1.0 has been released and is now available for download (and I'm already working on 1.5.2.0).

Updates in 1.5.1.0 include the following:

  • Added AllowAnonymousRatings boolean property to RatingSettings class.
  • Added RequireComments boolean property to RatingSettings class.
  • Changed RequiredFieldValidators on DownloadPageContents to Dynamic rather than Static.
  • Updated control to render a single script block and function calls rather than repetitive inline statements.
  • Fixed minor window positioning bug.
  • Added cookie to store the user's name/url upon rating a post.

Stay tuned for some new, cool enhancements in 1.5.2.0 :)

Friday, February 03, 2006 4:52:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [3]  |  Trackback
 Friday, January 27, 2006

I had an interesting dilemma today involving web services which I'd like to share.  One might argue that the vast majority of web services out there are well defined.  By that, I mean that they have expected parameters (inputs) and return expected, predefined, structured results (outputs).  These inputs and outputs (i.e. Requests and Responses) are defined within the WSDL associated with the web services.  Generally speaking you can create a proxy to a web service either in Visual Studio via the 'Add Web Reference...' dialog or manually via the WSDL.exe utility and feel pretty confident that that is all you have to do.  Beyond that, you generally don't need to worry whether or not you're actually dealing with a web service because it's just a function call and you get the expected results serialized to you.

I have a different situation, however.  Allow me to give a little background that might help illuminate the issue at hand.

I have an application that I'm developing that interfaces with a web service that has a single point of entry called 'GetEntity'.  This method will return one of 91 (yes 91!) object types depending on the parameters provided.  Of these 91 objects I really only care about 4 (so far).  Each of these objects ultimately derives from a class called 'Entity'.  None of this is the issue - I in fact LOVE the web service as it's defined - it's VERY cool.  Here's the issue:  Each Entity has a predefined set of properties, but users of the system can extend the business entities as they see fit (even create their own, custom entities).  One company may add a 'TotalWeight' field to a Quote object for instance, where another may add a 'LineNumber' field to a QuoteDetail object.

This makes it a little more difficult for me who distributes an application to the disparate users of the system.  I have no control over the return schema, but my application needs to potentially update any of these fields based on a completely flexible and arbitrary set of rules and formulas.  It is pretty much ruled out that I am unable to use WSDL.exe to create the proxy class and schemas because the schemas will differ from client to client.  So here's the dilemma, how do I take the result of an XML Web Service call (over which I can exert ZERO control) and still access the fields for which my schema has no definition?

I might try to cheat and alter the proxy class's method that calls the Web Method to return a different type.  If I could just retrieve the XML from the Response I could parse it out and read the custom properties into a collection of some sort.  I might, therefore, try to change the method to return a string rather than an Entity...or an XmlDocument.  There are issues with this however.

Suppose I have the following:

Sample web method result

<soap:Envelope mlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <GetEntityResult xmlns="
http://schemas.devstone.com/WebServices" xsi:type="person">
         <id>{B14C25BC-AB83-DA11-93D2-0003FF770FD4}</id>
         <name>R. Aaron Zupancic</name>
         <hairColor>Purple</hairColor>
      </GetEntityResult>
   </soap:Body>
</soap:Envelope>

Sample proxy class method to invoke the web method

[SoapDocumentMethod("http://schemas.devstone.com/WebServices/GetEntity", Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Bar)]
[return: XmlElement(Namespace="
http://schemas.devstone.com/WebServices")]
public Entity GetEntity([XmlElement(Namespace="
http://schemas.devstone.com/WebServices")] Guid id, [XmlElement(Namespace="http://schemas.devstone.com/WebServices")] string entityType) {
   object[] results = this.Invoke("GetEntity", new object[] { id, entityType });
   return (Entity)results[0];
}

Sample classes

[XmlInclude(typeof(person))]
public abstract class Entity { }

[XmlType(Namespace="http://www.devstone.com/WebServices")]
public class person : Entity {
   public int    id;
   public string name;
   public string hairColor;
}

This looks pretty typical.  However, if a customer were to customize the person entity schema to include an 'age' field, my proxy definition would not match and I'd not be able to retrieve the age property after the response is deserialized into a person object...my proxy class would be useless except for these known fields, which is of little use to me.  So, as mentioned above I might change the proxy method to return a string rather than an Entity.  If I do that I get the value of the first Xml element (e.g. '{B14C25BC-AB83-DA11-93D2-0003FF770FD4}').  Hmm, well make an XmlDocument.  If I do that, I get the first result element (e.g. '<id>{B14C25BC-AB83-DA11-93D2-0003FF770FD4}</id>').  The result is pretty flat...it's a series of nodes and that doesn't constitute a valid document - a valid document has only one root node.

My solution, therefore is to make it so that before the result gets back to my application for deserialization each of the nodes is placed within a parent node.  However, as mentioned above, I have no control over the web service nor its WSDL.

To make this work, this I create a custom SoapExtension-derived class.  This class will intercept and rewrite the XML as it flows back from the server.  My class might resemble the following:

public sealed class EntityTransformExtension : SoapExtension {
   private Stream _origStream;
   private String _customStream;
  
   public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) { return null; }
   public override object GetInitializer(Type serviceType) { return null; }
   public override void Initialize(object initializer) { }
  
  
   public override Stream ChainStream(Stream stream) {
      // cache off the original stream provided
      _origStream = stream;
      // create a custom stream and return it
      // this is the stream to which the request will be written first
      // and the stream to which the response will be written last
      _customStream = new MemoryStream();
      return _customStream;
   }
  
  
   public override void ProcessMessage(SoapMessage message) {
      switch ( message.Stage ) {
         case SoapMessageStage.AfterSerialize:
            // the request message is about to be sent to the web server
            // copy the information from our custom stream to the original to be
            // sent to the server

            _customStream.Position = 0;
            copyStream(_customStream, _origStream);
            break;
           
         case SoapMessageStage.BeforeDeserialize:
            // the response message just arrived from the web server
            // delegate control off to a helper method to modify the XML, inserting
            // each of the child XML elements into a new parent node.
            rewriteResponse();
            break;
           
         case SoapMessageStage.BeforeSerialize:
         case SoapMessageStage.AfterDeserialize:
         default:
            // we're not concerned with these message stages as they don't apply to what we're doing client-side.
            break;
      }
   }
  
  
   private void copyStream(Stream src, Stream dst) {
      // UPDATE: I inadvertently had the TextReader and TextWriter in using blocks
      // while this worked with this particular example, it's not generally a good
      // idea to do it in the SoapExtension because it has the nasty side-effect
      // of closing down the stream being read from/written to.
      TextReader reader = new StreamReader(src);
      TextWriter writer = new StreamWriter(dst);
      writer.WriteLine(reader.ReadToEnd());
      writer.Flush();
   }
  
  
   private void rewriteResponse() {
      // rather than load the document directly into an XmlDocument, or load it in an XPathDocument for an XSLT
      // transformation (which are both possible), simply read the source XML in and spit it out with a writer
      // to the destination stream with our own custom transformation (which simply takes all the returned nodes
      // and embeds them into another node).

      _customStream.Position = 0;
      XmlTextReader reader = new XmlTextReader(_origStream);
      XmlTextWriter writer = new XmlTextWriter(_customStream, UTF8Encoding.UTF8);
      writer.WriterStartDocument();
      while ( reader.Read() ) {
         if ( reader.IsStartElement("GetEntityResult") ) {
            writeNode(reader, writer);
            writer.WriteStartElement(reader.Prefix, "entity", reader.NamespaceURI);
         }
         else if ( XmlNodeType.EndElement == reader.NodeType && "GetEntityResult" == reader.Name ) {
            writer.WriteEndElement();
            writeNode(reader, writer);
         }
         else
            writeNode(reader, writer);
      }
      writer.Flush();
      _customStream.Position = 0;
   }
  
  
   private void writeNode(XmlReader reader, XmlWriter writer) {
      switch ( reader.NodeType ) {
         case XmlNodeType.Element:
            writer.WriteStartElement(reader.Prefix, reader.Localname, reader.NamespaceURI);
            writer.WriteAttributes(reader, true);
            if ( reader.IsEmptyElement ) writer.WriteEndElement();
            break;
           
         case XmlNodeType.Text:
            writer.WriteString(reader.Value);
            break;
           
         case XmlNodeText.EndElement:
            writer.WriteFullEndElement();
            break;
           
         default:
            // there are many more, but this covers the very basic, and works well enough for this
            // web service for now.
    
      }
   }
}

Once the SoapExtension class is created, I simply need to create a SoapExtensionAttribute that I can apply to my web method caller function that inserts the SoapExtension onto the stack.  Once in place I should get back an altered result set that I can then parse independent of the Entity class as defined above.

[AttributeUsage(AttributeTargets.Method)]
public sealed class EntityTransformationExtensionAttribute : SoapExtensionAttribute {
   private int _priority = 1;
  
   public override int Priority {
      get { return _priority; }
      set { _priority = value; }
   }
  
  
   public override Type ExtensionType {
      get { return typeof(EntityTransformationExtension); }
   }
}

I then alter my proxy class to return an XmlDocument and I tack this new attribute to it as follows:

[EntityTransformationExtension()]
[SoapDocumentMethod("
http://schemas.devstone.com/WebServices/GetEntity", Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Bar)]
[return: XmlElement(Namespace="
http://schemas.devstone.com/WebServices")]
public XmlDocument GetEntity([XmlElement(Namespace="
http://schemas.devstone.com/WebServices")] Guid id, [XmlElement(Namespace="http://schemas.devstone.com/WebServices")] string entityType) {
   object[] results = this.Invoke("GetEntity", new object[] { id, entityType });
   return (XmlDocument)results[0];
}

My resulting XML now has a new structure:

<soap:Envelope mlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <soap:Body>
      <GetEntityResult xmlns="
http://schemas.devstone.com/WebServices" xsi:type="person">
         <entity>
            <id>{B14C25BC-AB83-DA11-93D2-0003FF770FD4}</id>
            <name>R. Aaron Zupancic</name>
            <hairColor>Purple</hairColor>
         </entity>
      </GetEntityResult>
   </soap:Body>
</soap:Envelope>

This slick little trick allows me to take control of the results of the web method (without touching the web service) and adapt my application to its ever-varying resultset.

Friday, January 27, 2006 7:32:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback