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
 Wednesday, January 25, 2006

Yesterday I went out and picked up a new toy: a PSP (PlayStation Portable).  More than anything is was for the novelty of having it and tinkering.  I've got to say that I love it!  I'm not much of a gamer (though one might argue that looking at my XBox game library), but I enjoy them once in a while.  I picked up Frogger and Prince of Persia along with a 1 GB memory stick along with it.

Connecting it up to my WLAN was a breeze too (the PSP seamlessly connected to my G network, WPA-PSK (TKIP), MAC Filtered, etc :), so I can browse the internet, download updates (I'm on ver 2.60 now), play games against friends (though I don't yet know anyone with a PSP), watch movies, etc, etc.

It's TONS of fun...now if MS would only come out with an XBP (XBox portable)...something I've been wanting for quite sometime now...

Wednesday, January 25, 2006 7:10:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Sunday, January 22, 2006

There seems to be a lot of confusion floating around out there regarding the StrongNameIdentityPermission...and perhaps with good cause.  Perhaps you've seen (or written) code that resembles the following example which uses the permission declaratively via the StrongNameIdentityPermissionAttribute (SNIPA):

[StrongNameIdentityPermission(SecurityAction.Demand, PublicKey=".....")]
public sealed class TestClass { ... }

One might infer that a demand is placed on the consumer of TestClass such that it must be signed with the specified public key.  In fact, that assumption seemed to hold true in .NET 1.x.  Running within .NET 1.x you could effectively mark your types (or assemblies, constructors, or methods) with this attribute to prevent unknown callers from invoking those methods.  However, this was really just an illusion.  There were oh so many ways around this.  The best it really accomplishes is to present a facade of security (which may very well be worse than no security at all).

I'll be the first to admit that I've used that strategy in the past with smaller project to create a 'family of related applications'.

Basically what it comes down to is this:  In the .NET 1.x timeframe, all Identity permissions (except empty ones) were not considered to be a subset of the Unrestricted PermissionSet.  In essence this forced the various demands (e.g. Demand, LinkDemand, InheritanceDemand, etc) to take effect even if the assembly was running in FullTrust.  Effectively, the StrongNameIdentityPermission would block an application in FullTrust with an invalid public key.  This seemed hunky dory.

Things have changed, however, in the .NET 2.x world.  Hopefully these changes will affect only a small number of applications out there.  FullTrust now really means "Full Trust".  That is, Code Access Security (CAS) is not enforced if the assembly is running under FullTrust - ever.  Once an application, malignant or benign, is running in FullTrust, it can do whatever it darn well pleases.  Also, the various Identity Permissions have become a subset of the Unrestricted PermissionSet - they always pass in FullTrust.

One benefit of this is that performance of FullTrust applications will be increased because no stackwalks need to be executes for Demands.  Demands will automatically pass and LinkDemands are optimized out completely.

These changes are pretty dramatic and simply make apparent what was previously hidden: there is no real protection against FullTrust - though there seemingly was before.  Again, that was just a facade - a misnomer. Therefore, by way of example, if you've tried using the StrongNameIdentityPermission in a .NET 1.x application and seen it block a caller, but tried the same application in .NET 2.x to different results...that's probably the reason.

If you're a developer and marking your assemblies, types, and members with the SNIPA, you're probably seeing your calls pass on through your demands and may have been confused by that behavior.  Well, your applications are undoubtedly running in FullTrust.

You may wonder, then, "What's the purpose of such an attribute?  If it doesn't stop code from running, what's the point?"  Well, as it turns out, it does stop code from calling your code...just not code running in FullTrust.  Any application in the a lesser privileged permission set (such as the internet zone) if not found with the appropriate identity will not be able to execute your code.

So what can you do to prevent types from being publicly available in .NET 2.0?  Again, there's no real protection against FullTrust.

I have some ideas, but I'd like to get your feedback before divulging...
Thoughts?

Sunday, January 22, 2006 11:56:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Wednesday, January 18, 2006

Last night we had a fantastic event - our third Utah Geek Dinner.  Especially compared to last month (which in some ways was cool, others a fiasco), we had a wonderful evening.  By far we had our greatest attendance as well with over 80 people showing up and participating.  We had two brief educational presentations by some attorneys focusing on legal issues of which geeks should be aware.  Nathan Nelson (who also helped procure the Larry H. Miller SLCC Campus and the Panache Catering) spoke on starting your own company.  David McKenzie then addressed us on software patents, copyrights, etc.

Following both of those presentations we had a mini-presentation by Dave Turnbull of SoftwareFor.org who discussed a very cool initiative they have in getting quality software to students worldwide on a student budget (i.e. FREE) called “Software for Starving Students”.

We had a great time and a great turnout.  The next Geek Dinner will be on February 22nd, 2006 and (probably) at the same place at 6:00 PM.  If you haven't had a chance to make it out to one of these events you're missing out!  It's a wonderful opportunity to do some business networking and help promote the cross pollenation of technologies and technologists here in Utah.

Wednesday, January 18, 2006 4:32:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Monday, January 16, 2006

Don't you hate it when you're debugging an application only to discover that the issue was a stupid typo that was sitting right in front of you?  Well, that happened to me tonight.  First time...honest ;-).  The error message had me off on a wild goose chase when the problem was right in front of me :'(

I am working of some software for a client that requires, alas, that an Access .MDB database be used (the reasons behind this I won't discuss herein, but they are valid).  Upon attempting to connect to the database I would receive an error message that said:

Could not find installable ISAM

Initially I thought I didn't have the latest MDAC (ver 2.81) installed, but that didn't really make sense.  Then I thought maybe it was a permissions issue.   I was googling around and browsing support.microsoft.com for any assistance. Turns out I simply mistyped my connection string.  It's “Data Source” not “DataSource”.  The issue was exacerbated by the fact that the debugger would not hit my breakpoint where the connection string is assigned for some odd reason so I didn't think to look there initially. Well, there went about 20 minutes.  All was not lost however.  I'm recording this little note for me and my posterity (and anyone else) so I might be able to avoid the issue if it crops up again - the resolution might be as simple as fixing a connection string and not having to reinstall any software. :)

Monday, January 16, 2006 8:42:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Friday, January 13, 2006

A short while ago I purchased a license for an Email server called EmailArchitect by Admin Systems.  I can't say how impressed and pleased I am with the software.  While there are a few very minor things that I still take issue with (mostly because I'm such a hard-nosed perfectionist), I very much like the software.

I had an interesting experience with them this week that I'd like to relate.  I sent an email request to their support on Tuesday expressing a few issues (mostly typos, and the like) and I got an email back later that night indicating that they'd look into it and make the corrections in their next version.  At the time, I was running Email Architect 6.0.1.1.  Right now, I'm running 6.0.1.2 - two and a half days later and they have incorporated all of the changes that I requested and published it on their website!

That's awesome!  If you're in the market for a great email server application, check 'em out!

Friday, January 13, 2006 4:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Extra! Extra!

** January Geek Dinner **

DevUtah's next Geek Dinner will be held at 6:00 this Tuesday night (Jan 17) at the Miller Business Innovation Center on the Salt Lake Community College campus (9690 South 300 West, Sandy).

We'll have two short educational presentations by accomplished attorneys who will discuss legal topics for geeks.  Nathan Nelson will talk about legal aspects of selling your software concepts, starting your own software development company (code shop), or becoming an independent developer.  David McKenzie will discuss popular myths about software patents and copyright ownership relating to contractors.

We will also have a brief mini-presentation by Dave Turnbull from SoftwareFor.org, who will talk about some of the successes and failures of the last release of “Software for Starving Students”, which had over 25,000 downloads in just a few days.

The event will be catered by Panache Catering.  Each attendee is required to contribute $7 to help cover the costs.  Attendees are strongly encouraged to prepay burnsadria@hotmail.com via PayPal, although cash and checks will also be accepted at the door.

Please RSVP here:

http://tinyurl.com/cgmv2

Friday, January 13, 2006 6:31:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, January 12, 2006

Here's cool Visual Studio tip...

You may be familiar with the F12 keyboard shortcut (or its more graphical, context-menu couterpart 'Go To Definition').  You may be intimate with Ctrl+Alt+J (Object Browser).  But did you know this...?

How about the 'Code Definition Window'?  Via the View menu in Visual Studio 2005 you can select the Code Definition Window option (or use the shortcut combination Ctrl+\, Ctrl+D) to cause a paned window to appear, similar to the Solution Explorer, the Properties Window, et al.  This window is pretty slick.  You simply position your insertion point (the keyboard cursor) within an identifier in your code (such as an object, property, variable, method, class, etc) and it will 'find' its definition for you, highlight the appropriate line where it's declared, and show it to you.

If the item isn't defined in your code (that is, if it can't find the source) it will reverse engineer the object for you from its meta data and show you.  I like it because it saves having to look in the classname.Designer.cs file.

One aspect of the Code Definition Window is that it's readonly - you can't go changing code therein...but it sure makes it easier to find out more information about what you're using.  If you want to make changes, however, there is a context menu option to 'Edit Definition' which will open a code pane at the selected spot for your editing pleasure.

I like it :)

Thursday, January 12, 2006 5:55:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, January 10, 2006

By and large, I'm not a user of emoticons in MSN Messenger (or any Instant Messenger-like application - though I use the keystrokes frequently).  I copy and paste too many code snippets in the window (though purportedly Skype does a better job of handling code, I can't stand Skype's interface) and it gets wearisome to interpret the code sequences rendered as graphics.  I was reading a bit today on the next version of MSN Messenger and saw mention of a hidden MSN Messenger emoticon, so I thought I'd try it out.  Sure enough, it worked!  I then hunted these down.  I believe that they been in MSN Messenger since about version 6 (though the goat may be new to 7.5).  I present them here for your viewing (and using) pleasure:

Image Shortcut Description
(brb) Be Right Back
(ci) Cigarette
(h5) High Five
(nah) Goat
(%) Handcuffs
(tu) Turtle
(xx) XBox
(yn) Fingers Crossed

A couple of years ago I set out to create some of my own (feel free to use them as well for yourself).  The majority of these derive from my passion around board games, in this case Settlers of Catan.  I may take up to creating some more time permitting.  [Update 01/18/2006: I've added a few new icons below (ellipsis and arrow).  Additionally, I have added a zip file (click for link) that provides all of these custom icons in a single download.]

Image Shortcut Description
(bowl) Bowling
($) Dollars / Money (I never liked the money symbol in Messenger)
(scrab) Scrabble
(brick) Settlers of Catan - Brick Tile
(desrt) Settlers of Catan - Desert Tile
(ore) Settlers of Catan - Ore Tile
(wheat) Settlers of Catan - Wheat Tile
(wood) Settlers of Catan - Wood Tile
(wool) Settlers of Catan - Wool Tile
... Animated ellipsis (NEW: 01/18/2006)
--> Right Arrow (NEW: 01/18/2006)
Tuesday, January 10, 2006 4:24:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [36]  |  Trackback

I'm sure many out there get a bit irked when a product doesn't do what you want it to, or it does too much.  This may be one of the most frequent complaints users have with Microsoft Office.  While I don't usually share this sentiment in general, I do agree with respect to it's 'Save As HTML' options.  Word, for example, will inject a TON of unnecessary markup in the end result if all you're wanting is pure, clean HTMl.  This may not always be bad, but if you're intending to paste the HTML somewhere not Office, then you have your work cut out for you.

Jeff Atwood has created a slick little utility that cleans up the HTML within a Word document, resulting in a much cleaner result.  Go check it out...and don't forget to subscribe to his blog - Jeff has some pretty amazing insights and commentary.  A good read all around.

Tuesday, January 10, 2006 9:00:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback