Tuesday, November 30, 2004

As I had mentioned previously, I spent some time recently creating a control that would provide a facility to allow users to rate blog content (posts, comments, stories, pictures, you name it) and provide their own two cents.  This control is presently implemented on my own blog as you can see.  I’m making a few late breaking changes and creating the documentation.  When completed, I will post it here for use on your own .Text blog if you’d like.  If you simply MUST have it sooner, contact me and we’ll get it set up.

Getting the control implemented on a .Text site wasn’t extremely easy, but it wasn’t difficult either…you just have to know what to do.  If you recall, the entire control (and its associated pages) is encapsulated within a single assembly: Devstone.Web.Blog.Controls.Rating.dll.  This enables you to deploy the control to your blog by simply dropping the .dll in the \bin folder, tweaking the web.config, and running a database script (which simply adds a table and a few stored procs).

I have not tested this with .Text versions other than 0.95.2004.102.  Also, my blogs have been the multi-user/community blogs rather than a single-user blog, but I imagine that getting it setup on a single-user blog would be pretty painless and follow much of the same procedure.

And now for the nitty gritty...

There are two components to the rating control:  a Rating WebControl and a Rating Page.  In order for the server to process requests I decided to handle the requests using an HttpHandler (technically, an HttpHandlerFactory and an HttpHandler for the dynamic images).

Within IIS I mapped the .dspx extension to the aspnet_isapi.dll, checking to not enforce that the file exist (because it doesn’t exist in this case) and allowing the verbs GET and POST.  The .dspx extension is of my own creating (meaning Devstone Server Page).

Then I updated the web.config file.  Some care had to be made here.  .Text usurps ASP.NET in how requests are handled by defining its own suite of HttpHandlers depending on how the client request comes in.  Therefore, I could not add my handler to the <httpHandlers /> section as one would traditionally do – I tried and got erratic results; sometimes it worked and other times it didn’t.  Instead, I had to add my own <HttpHandler /> to the HandlerConfiguration/HttpHandlers node.

In fact this is the trick to making the whole thing work.  You just have to make sure the regular expression is valid.  And because I’m using a ‘Factory’ rather than ‘Handler’ to handle the requests my statements ended up looking more or less like this:

<HttpHandler pattern="^(?:/(\w|\s|\.)+/img/\d\.\d{2}/(?:Star|Dot|Bar)\.dspx)$" type="Devstone.Web.Blog.Controls.RatingHandlerFactory, Devstone.Web.Blog.Controls.Rating" handlerType="Factory" />

<HttpHandler pattern="^(?:/(\w|\s|\.)+/rate/(?:Star|Dot|Bar)/[12345]/\d+\.dspx)$" type="Devstone.Web.Blog.Controls.RatingHandlerFactory, Devstone.Web.Blog.Controls.Rating" handlerType="Factory" />


If I had created HttpHandlers, rather than HttpHandlerFactories, I would have specified a handlerType of ‘Direct’.  Also, of note:  depending on how your blog is set up, the regular expression will differ. 
http://blog.devstone.com/ for example, is at the root so my regex starts ^(?:/(\w|\s|\.)+/….  If the blog is rooted in a virtual directory, your regex may resemble ^(?:/\w+\/(\w|\s|\.)+/…

Additionally, my control relies on a few custom configuration values that I added to the <appSettings /> section for ease – though I could have just as easily created my own configuration section.

Once those steps were accomplished, all I had to do was add the <%@ Register %> directive to each of the .ascx controls on which the control would be rendered.  For example, I wanted the control to sit along side each post on the blog’s main page.  Therefore, I opened the Day.ascx control and added the following lines:

<%@ Register tagPrefix="devstone" namespace="Devstone.Web.Blog.Controls" assembly="Devstone.Web.Blog.Controls.Rating" %>
<%@ Import Namespace="Dottext.Framework" %>
<%@ Import Namespace="Dottext.Framework.Components" %>


Then navigated down to within the <ItemTemplate /> element of the DayList Repeater control and added the following line of code:

<devstone:ratingsummary id=rs runat=server alignment=right postId="<%# ((Entry)Container.DataItem).EntryID %>" postLink="<%# ((Entry)Container.DataItem).Link %>" />


That’s all there is to it.  This associates the proper id and url with the rating control.  The link isn’t strictly necessary, but is included as a courtesy for when the post is rated, the owner of the blog is sent an email with a link to the post within it.

There are a slew of other properties that can be assigned which I will discuss in the next post when I provide the final documentation and product for download.

Tuesday, November 30, 2004 1:35:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [9]  |  Trackback
 Monday, November 29, 2004

All too often we have to post data to a web server and/or navigate to another page and then sit twiddling our thumbs waiting for the response to come back.  Impatient users will click the 'submit' button again or simply leave the site.  The usual approach to solve this dilemma is to introduce a 'Please wait' page that actually initiates the real request to the server, redirecting upon completion.

Usually, this works fine, but there's got to be something better and easier to implement, right?  Well, without any ingenuity there's nothing out of the box.  Over the years, I've implemented various solutions to this issue, some more elaborate than others.  Perhaps someday I'll blog about what I've done that (I feel) pretty danged awesome to borrow an associate's terminology.

I was perusing (read: catching up on) my RSS feeds today and found a post by Mark Wagner and his proposed solution to the issue - which in many ways is pretty elegant and slick.  His solution (yet to be better packaged as a control) implements a script file and a hidden DIV element on the page that gets rendered in the onbeforeunload event.

One advantage of an intermediate page is that it can effectively prevent the user from 'resubmitting' the page because the user is no longer on the page in question.  Does Mark's solution address issues such as these?  I'm not sure yet, I haven't given it too much investigation.  Is this a valid concern?  I think so.

With a little javascript, a developer can definitly deter and mitigate many of these issues and I believe Mark's approach definitely has merit and is a great solution...I'm excited to test it out!

Monday, November 29, 2004 9:53:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, November 28, 2004

For the longest time (well, at least since April 2004 when I first installed .Text) I have wanted to add the ability to rate a web posting on my blog.  It hasn’t been until recently, however, that I decided to do something about it.

 

It wasn’t until I was almost done with the project that I decided to do a Google search for other blog rating controls.  I happened upon a Content Rater control by Scott Mitchell (of aspnet.4guysfromrolla.com.  Scott’s control presents a UI reminiscent of the rating boxes found on Microsoft’s MSDN sites.  Funny – that initially provided me with some of my original inspiration as well.  My rating control’s UI, however, ended up completely different.

 

As I started in on the project I had an idea of where I wanted to end up and what functionality was desired, so I layed out some design goals:

 

  1. Take advantage of .Text’s ability to be functionally extended without needing to be recompiled.  This one was pretty easy – I really didn’t have to think too hard to make this one work ;-)
  2. Utilize the .Text framework classes as much as possible, providing deeper integration and consistency with how the blog behaves.  This was accomplished simply by referencing the various Dottext.*.dll libraries and building against them
  3. Minimize external dependencies.  I wanted to make sure that all controls and pages were completely contained within my .dll with functionality that was homegrown or natively available in the .NET framework.
  4. Minimize the impact of the installation and deployment.  I wanted to make the touch points as small and few as possible.  This greatly increases the ease of adoption.
  5. Ensure proper functionality within cached parent controls.  .Text will perform PartialCaching controls (i.e. @OutputCache or [PartialCaching]) to optimize server performance and minimize database hits.  This isn’t without its few drawbacks.  One of my primary goals was to allow my rating control to be placed on the blog’s main page along side each post (via .Text’s Day.ascx control).  This control gets cached and therefore does not accept postbacks (unless the postback just happens to correspond with a cache timeout).
  6. Support dynamic graphics for rendering the rating (stars, dots, bars to start with).  I didn’t want to have a plain-ol’ ordinary rating on my blog, but rather a nice, professional presentation.  This called for writing a custom HttpHandler to take the graphics and stream them down to the client browser.  If, however, this turned out to be too lengthy an operation, I could just as easily pre-gen the images and have them on-hand, ready to go.
  7. Be able to rate anything on the site: blog posts, comments, stories, images, etc.  I didn’t want to be able to rate only blog posts.  Instead, anything on the site is fair game and should be ratable – even other people’s comments!  Ratings with comments would, in addition, become comments themselves.
  8. Be hostable anywhere on the site.  As mentioned earlier, I greatly desired to be able to present the rating along side each post on the blog main page.  This required that I support some properties that otherwise I wouldn’t have; properties that allow you to specify the post link (e.g. permalink) and id of the post in question.

 

I had a general idea of how I wanted it presented, but I could flesh that out in time and wanted to firstly ensure that I would be able to achieve the functionality desired before diving too deeply into the UI.

 

Ultimately, I decided to implement it as an ASP.NET WebControl rather than as a UserControl.  This allowed me to distribute a single .dll and not have to worry about an additional .ascx file (or set of files).  Though, perhaps, this limits me to how the control is to be rendered on the client, I feel that it wasn’t that great a sacrifice to make.

 

Due to how I envisioned the control being used I could not have it postback to itself directly, despite all desires to have such functionality.  Therefore, I decided to have the control pop up a dialog box for the user to rate the post.  I’m not a huge fan of popups my any means, but I feel that in this case they are warranted.  First of all, I didn’t want to redirect the user simply to rate the page, and second, it should be a really quick operation.

 

In order to have a page to display (without distributing any additional files) I had to create a class that inherited from System.Web.UI.Page and drive the UI fully from within the class.

 

Once this functionality had been defined and the behaviors established, it was time to integrate it with my .Text blog – which will be the topic for my next post.

Sunday, November 28, 2004 9:18:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Tuesday, November 23, 2004

I found out about this a few months ago and it worked once for me then, but it's failed ever since then until today.  Granted, I've not been checking every day, but occasionally.  Microsoft has created the MSN Web Messenger that allows you to IM w/o any download of an executable.  A particularly beneficial advantage of the WebMessenger is that it enables individuals that are behind blocking firewalls to exchange messages as by using port 80.  I had occasion, back when I first learned about it, while at a client site to use the WebMessenger.  This particular client, a military site, had all the messenger ports blocked on the firewall, but the WebMessenger allowed me to communicate with my contacts which was a life saver (especially since I was out of cell-phone range and email was simply too slow for the information I needed).

Simply login using your MSN passport and you have full access to your contacts, email, etc.  The WebMessenger isn't a fully featured utility app; it only allows for basic chat functionality, but the support it offers is fantastic.  I'm digging it!

Tuesday, November 23, 2004 6:17:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [5]  |  Trackback
 Sunday, November 21, 2004

Well, it's been a long drought since I last posted here on the blog - for a variety of reasons, but I'm glad to be back at it.  The past weeks (mostly the last one) have been a bit disheartening on several levels; personally, professionally, and extracurricularly, the details of which I will not persue herein.

On a different note, I recently glanced at my blog's web statistics and noticed a significant drop in RSS and Web reads of the blog over the course of several days which left me wondering a few things:

1)  was my blog not accessible?
2)  did a majority of readers simply unsubscribe because the content was not valuable / pertinent/ etc?
3)  are the users simply not opening their RSS readers and downloading subscription data?

3)  insert miscellaneous, random thoughts here

Anyway, I'm back at it, and determined to not let anything get me down or affect the blog.  I am sincerely hoping that the information that thus far I have posted here has been useful in some way to someone other than solely myself.

Sunday, November 21, 2004 7:24:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, November 12, 2004

[Update 11/16/2004 - Fixed grammar, etc]

Following last night's inspiring .NET User Group presented by Scott Golightly (our local Microsoft RD), I've decided to take the plunge.  That is, start developing software (and hence, using my computer) as a non-administrator.  It's a precept that I have long understood and preached, but never practiced - the hypocrisy is over.

All in all, the transition has been painless - nothing has broken in my code which is reassuring.  There were a couple of things, nonetheless, that I had to do to ensure that my development experience remained seameless.

These were the steps that I underwent (more or less):

1.  Removed my domain account from the local built-in Administrators group.
2.  Ensured that my domain account is a member of Debugger Users and VS Developers
3.  In order to properly debug ASP.NET applications I have to ensure that my user account owns the process, so I had to change the credentials under which the aspnet_wp runs.  To accomplish this, I downloaded a tool from Microsoft called aspnet_setreg.exe which allows me to encrypt credentials and such and place them in the registry.  The interesting aspect here is that I have a domain policy in which credentials expire frequently, so I'll have to be updating the registry setting accordingly.
4.  I changed the credentials for the ASP.NET process by editing the machine.config file's tag, updating the username and password attributes to reflect the registry settings.

From that point on, it's all working seamlessly as a matter of fact - which pleases me greatly.

Sure, there are things I have to constantly remember to do, like using RunAs (either via command prompt or context menus) to execute certain actions in an administrative capacity.

A tool that was demonstrated last night that will be extremely helpful is a tool by Aaron Margosis called MakeMeAdmin.  There are certain tasks that need to be executed administratively but not as Administrator.  At times files get installed into user-specific folders or settings need to be made that are user-specific.  Running under different credentials is not adequate.  The MakeMeAdmin (and its counterpart, MakeMePU) temporarily elevates the current user to be a member of the Administrators group to install/change settings.  Very cool!

Friday, November 12, 2004 1:59:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, November 11, 2004

I ran into an interesting problem that I hadn't seen before, but it was pretty simple to solve.  The problem was manifest by having the Page_Load event firing twice.  At first I was a bit disconcerted.  However after a little thought, I figured it out.

As it turns out, there is an AutoEventWireup parameter on the @Page directive of an ASP.NET page.  The common practice is to explicitly set this to false, thereby enabling developers to be more flexible in naming their methods.  If the parameter is true (or omitted - it defaults to true), then the ASP.NET runtime will automatically 'wire-up' method calls by name (e.g. Page_Load, btnSave_Click, etc).

Well, it turned out that I had both omitted the AutoEventWireup attribute (defaulting to true) and had an explicit event handler (via this.Load += new EventHandler(this.Page_Load);).  Therefore, the event was being called once by the runtime (by virtue of the AutoEventWireup) and once explicitly (by virtue of my explicit event handler).

The reason I never experienced this before was because I either always have the AutoEventWireup=”false” in my @Page directive or, more frequently, I override the OnLoad() method on the page and bypass the event mechanism all together to achieve greater performance.

Thursday, November 11, 2004 5:54:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, November 10, 2004

Today is a pretty exciting day - on a variety of levels.

First (and most importantly) I received my copy of Halo 2.  Now I realize that many enjoyed this thrill yesterday, but my patience has paid off.  The downside is I won't be able to play until later tonight (around 10:00 PM) but it's sitting here on my desk as a constant reminder that I have a date tonight.  I absolutely LOVE the Collector's Edition case - in particular the image on the back cover.  I'm pumped to play it; if you know me, you've known this for a while too ;-).

I'm not a hard-core gamer by trade - never really have been.  But despite the fact that I've owned Halo since I bought my XBox a few years ago, I hadn't passed the stinkin' game until just a couple of weeks ago in anticipation of being ready for part 2.  Now I'm ready!

Second, Mozilla released their very popular Firefox 1.0 browser today.  I've used their 0.9 version for some time as a 'testing' tool rather than my primary browser, but I've always been pleased with it.  Now, I'm not one to back down from my IE devotion.  I've enjoyed being able to take advantage of some of the IE-only extensions in order to create more fully featured, powerful browser-based web applications with great success.  That said, I'm pleased to find that much (but not all) of my code works as expected on this new browser.  I'll continue to use it, and I'd encourage you to do you same, but not as my primary browser - at least not yet.  However, IE 7 is long overdue; IE has been without any significant upgrades or enhancements (aside from the periodic security patch) far too long and I'd like to see something else, something exciting come out of Redmond on the browser front soon.

Hopefully, Firefox is an eye opener for the apparent complacent behavior on part of the MS team.

Wednesday, November 10, 2004 7:29:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Tuesday, November 09, 2004
Sorry if my blog has been acting up lately.  In making some modifications to it recently I erroneously left some invalid text in the web.config file.  I have since fixed it and things should be back to running order...thanks for your patience.
Tuesday, November 09, 2004 4:22:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Sunday, November 07, 2004

Though perhaps I'm not as affected as others I know by it, spam is a plague on the net.  I have had my share of garbage spam showing up in my blog and it's been pretty upsettings to find it there.

Today, I was browsing around a bit and found an anti-spam control that I am now fond of.  Miguel Jimenez has put together a small control and an Http Handler that provides a HIP (Human Interaction Proof) mechanism in the form of obfuscated text within an image.  When filling out a form the user must enter the text that appears in the image or else the submittal will not be accepted.

The installation was painless and I had it up an running in less than 2 minutes.  Basically it consists of added a .dll to your \bin, editing the web.config to register the Http Handler (the image generator), and add a pair of tags to the page on which you want to validate the submittal.  The two things that I wasn't really that excited about was 1) that the anti-spam control uses Session state so I had to enable it in the web.config where previously it was off and 2) sometimes the text can be quite difficult to read (e.g. discerning the difference between '$' and 's' can be quite difficult).

All in all, however, the process was seamless and I'm happy about the results.  Good job Miguel!

Sunday, November 07, 2004 6:10:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, November 05, 2004

Several months ago I wrote an article on enabling XP Visual Styles in your .NET 1.1 applications via a method called EnableVisualStyles().  This method, when coupled with a subsequent call to DoEvents() to work around a bug in the implementation of EnableVisualStyles() provided the nice, attractive Windows XP look and feel to which we've all grown accustomed.  If you fail to provide the DoEvents(), you will be greeted with an SEHException() that is not quite informative and can leave you scratching your head.

The alternative to this approach is to fall back to what was required in the good old days of .NET 1.0: that is, we must create a .manifest file.  Including the .manifest file along with your application is not very desirable.  A convenient, but painstaking remedy, is to embed the .manifest file xml directly into your executable post-build.  Simply open your executable in Visual Studio, add a new resource of the type RT_MANIFEST and paste your content.  Assign a resource id of 1 and change the Language to Neutral.  At this point you'd be good to go.  This process can be automated via code if you like as well.

These approaches are documented ad nauseum across the internet, so I won't consume any more time eating bandwidth on how they work (a simple Google search will provide all the answered you could want - and more).

One might ask, however, why on earth would I want to go through all the trouble of using a .manifest file (embedded or otherwise) when I have the option of using EnableVisualStyles()?  Well, the short answer is that EnableVisualStyles() doesn't fix the whole problem.  As it turns out, the issue resurfaced for me again this evening.

There is still an issue with respect to ComboBoxes and XP styles.  If you call a DoEvents() or show a dialog from a ComboBox event (such as KeyPress or SelectedIndexChanged) the SEHException will be thrown.  For example, consider the following code:

private void cboSearch_KeyPress(object sender, KeyPressEventArgs e) {
   if ( e.KeyChar == (char)Keys.Enter ) {
      e.Handled = true;
      doSearch();
   }
}

private void doSearch() {
   pnlStatus.Visible = true;
   Application.DoEvents();   // make sure the panel is fully visible before proceeding
   try {
      // ...search logic here...
   }
   finally {
      pnlStatus.Visible = false;
   }
}


The previous code will throw because of the call to DoEvents().  However, when a .manifest file is present this is a non-issue.

In fact, I think I'd be fine with the solution of using the .manifest exception for one BIG issue.  If your assembly is signed with a strong name (as mine is), you can't go changing the .exe file after it has been compiled.  Doing so will cause the runtime to throw a nasty “Strong name validation failed“ error message due to the fact that the checksums don't match up any more.  Upon seeing this error, I tried several solutions:

  • First, I created my own Win32 .res file in C++ with my .manifest file embedded as an RT_MANIFEST resource.  Then I wanted to add the .res file to my .csproj file but because of how .NET embeds resources that wouldn't work.
  • Second, I went to the command line to compile via csc.  The csc compiler accepts a switch /win32res that allows you to designate the .res file to embed within your assembly...this looked promising.  In fact it worked fine.  However, I have a solution of 5+ projects with 175+ files, several of which are forms that have .resx files associated with them...this complicates the build process as I do most of my compiling within the IDE.
    • I wish I could designate a .sln (or even a .csproj) file in the csc command line
    • ...looking forward to the MSBuild tool in Visual Studio 2005.

If you designate the /win32res switch on the csc commandline you will also experience an unfortunate pair of side effects:

  1. You trump VS's ability to create a VERSION resource based off of your Assembly attributes, so your .res file will need to include this if you want Explorer to show a 'Version' tab in the properties dialog for your executable and you'll have to maintain it manually versus allowing VS to update it.  Not to mention that VS adds some properties to the VERSION resource that aren't a part of the initial version block (such as Assembly Version).
  2. You cannot specify the /win32icon switch together with /win32res so you need to embed your app.ico file into your resource file properly, thereby eliminating VS's automatic generation of the Icon resource.

All in all it's a pain, and it's definitely not worth it...I'd rather distribute a .manifest file that have to deal with those headaches.

This exception was frustrating the first time I ran into the EnableVisualStyles() bug because I didn't know at first what was causing the problem.  It's even more frustrating & enfuriating now because I know that it's causing the problems and I can seemingly not do anything about it in code....or can I?

As it turns out, I can perform some magic...being the prestidigitator that I'd like to believe I am.  The remedy that I found, and yes, it is a sort of a hack, is to defer control to a Timer temporarily and have the timer invoke the call that has the DoEvents().  For example:

private void cboSearch_KeyPress(object sender, KeyPressEventArgs e) {
   if ( e.KeyChar == (char)Keys.Enter ) {
      e.Handled = true;
      tmrSearch.Interval = 10;   // make it quick so the user doesn't see any delay
      tmrSearch.Enabled = true;
   }
}

private void tmrSearch_Tick(object sender, EventArgs e) {
   // work around a bug in the ComboBox/EnableVisualStyles() with respect
   // to calling DoEvents from within a ComboBox event.
   tmrSearch.Enabled = false;   // only allow the event to run once
   doSearch();
}

private void doSearch() {
   pnlStatus.Visible = true;
   Application.DoEvents();   // make sure the panel is fully visible before proceeding
   try {
      // ...search logic here...
   }
   finally {
      pnlStatus.Visible = false;
   }
}


So there seems to be a remedy in code, but I can't say that I really recommend it as a best practice.  The best way (i.e. the least error prone) is to go the route of the .manifest file, but it's not without its complications and gotchas.  I would much prefer to use the EnableVisualStyles(), and now that I know of a way to work around the issues until fixed I can live with it.

Friday, November 05, 2004 8:29:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, November 01, 2004

If you're like me in your GDI+ adventures you've run across the Image.GetThumbnailImage(...) method and been disappointed with its results.  This method opts for performance over quality when rendering your thumbnail image and as a result your thumbnails can look discolored or distorted.  I wanted to have a routine that provided a high performance alternative but would render a better quality image.

The way I implemented this at first was to iterate through the pixels of the source image, performing averages of pixel color over an area of pixels, and writing that resultant pixel to the output (thumbnail) image.  I wrote this a couple of years ago and it did the trick nicely and for a small number of small pictures it was more than adequate; with larger pictures, on the other hand, the performance degradation was noticeable.  I've attached the code for your viewing pleasure:

public static Bitmap CreateThumbnail(Bitmap source, int thumbWi, int thumbHi, IProgress progressCallback) {
   if ( thumbWi > source.Width || thumbHi > source.Height ) return null;

   Bitmap ret = new Bitmap(thumbWi, thumbHi);
  
   float xScale = (float)thumbWi / ((float)source.Width - 1);
   float yScale = (float)thumbHi / ((float)source.Height - 1);

   for ( int i = 0; i < thumbHi; i++ ) {
      for ( int j = 0; j < thumbWi; j++ ) {
         int x1 = (int)( j / xScale );
         int x2 = (int)( ( ( j + 1 ) / xScale ) - 1 );
         int y1 = (int)( i / yScale );
         int y2 = (int)( ( ( i + 1 ) / yScale ) - 1 );
         float r = 0, g = 0, b = 0;
         for ( int x = x1; x <= x2; x++ )
            for ( int y = y1; y <= y2; y++ ) {
               int c = source.GetPixel(x, y).ToArgb();
               r += ( c & 0xff0000 ) / 0xff01;
               g += ( c & 0xff00 ) / 256;
               b += ( c & 0xff );
            }

         int numPixels = ( x2 - x1 + 1 ) * ( y2 - y1 + 1 );
         ret.SetPixel(j, i, Color.FromArgb((int)( r/numPixels ), (int)( g/numPixels ), (int)( b/numPixels )));
      }

      if ( null != progressCallback ) progressCallback.Report(i / thumbHi * 100);
   }

   return ret;
}


Recently, however, it became important to have a faster algorithm to generate image thumbnails.  To meet these new demands, I produced the following code which results in as good a thumbnail as my previous code but MUCH faster - though not quite as fast as the GetThumbnailImage(...) method.  Here is its code:

public static Bitmap CreateThumbnail(Bitmap source, int thumbWi, int thumbHi) {
   // return the source image if it's smaller than the designated thumbnail
   if ( source.Width < thumbWi && source.Height < thumbHi ) return source;

   System.Drawing.Bitmap ret = null;
   try {
      int wi, hi;

      // maintain the aspect ratio despite the thumbnail size parameters
      if ( source.Width > source.Height ) {
         wi = thumbWi;
         hi = (int)(source.Height * ((decimal)thumbWi / source.Width));
      }
      else {
         hi = thumbHi;
         wi = (int)(source.Width * ((decimal)thumbHi / source.Height));
      }

      // original code that creates lousy thumbnails
      // System.Drawing.Image ret = source.GetThumbnailImage(wi,hi,null,IntPtr.Zero);
      ret = new Bitmap(wi, hi);
      using ( Graphics g = Graphics.FromImage(ret) ) {
         g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
         g.FillRectangle(Brushes.White, 0, 0, wi, hi);
         g.DrawImage(source, 0, 0, wi, hi);
      }
   }
   catch {
      ret = null;
   }

   return ret;
}


Enjoy!...and happy thumbnailing!

Monday, November 01, 2004 9:05:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [7]  |  Trackback

In developing pages and controls in ASP.NET I've come across situations in which I need to bind against properties that are not implemented on the default interface of the object in question.  For the most part, this is really never an issue.  Typically you have an object (be it a user-defined class instance, or something more data-centric such as a DataSet or a DataReader) and you want to bind against it.  Your syntax might frequently resemble the following:

<%# DataBinder.Eval(Container.DataItem, "UserName") %>


where this code will evaluate (via Reflection) the item being bound (identified by Container.DataItem) and extract the value contained within the UserName property/column/etc.  This is all well and good and usually all that you need.  However, only properties (and methods) on the default interface are sought.  That is, if your object (Container.DataItem) implements and interface and maps to the interface methods directly rather than implementing them generically by name, the runtime will not be able to resolve to them.

For example, suppose you have an IUser interface defined and implemented within a User class:

public interface IUser {
   string UserName { get; set; }
}

public sealed class User : IUser {
   private string _userName;

   public string UserName {
      get { return _userName; }
      set { _userName = value; }
   }
}


In this scenario, your databinding would work perfectly.  Yes, UserName maps to the IUser.UserName property implicitly, but is also defined on the default interface for User.  Suppose, however, that the implementation were explicit:

public sealed class User : IUser {
   private string _userName;

   string IUser.UserName {
      get { return _userName; }
      set { _userName = value; }
   }
}


Now, the databinding would fail because there is no property called UserName on the default interface of the User class.  In this circumstance, it is important to take matters into our own hands and perform the casting on the ASP.NET page/control ourselves.  This may also require that we fully qualify the interface name (using the proper namespace) or use an <%@ Import namespace="..." %>directive.

<%@ Import namespace="..." %>

<%# ((IUser)Container.DataItem).UserName %>


There, works like a charm.  Note, that depending on the content of the property, you might be best served by enclosing the operation in a Server.HtmlEncode() function call to prevent any misuse, XSS (Cross Site Scripting), or HTML/Script injection.

Monday, November 01, 2004 3:25:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback