Friday, December 10, 2004

A good friend of mine, Scott Golightly, is now blogging!  Employed at Keane, Inc, Scott has been a consultant for over 12 years.  As an alter-ego, Scott parades around as our local Microsoft Regional Director, giving the occasional talk at the .NET User Group or even speaking at Tech Ed.

Glad you joined the blog party!  Welcome aboard!

Friday, December 10, 2004 5:10:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

Just last night I had need to create a server-side repeater control programmatically.  This is something that, thus far, I have not had much need to do.  Most of the time, I've resorted to embedding <asp:repeater /> controls (or some other control such as a DataList or a DataGrid) on an .aspx or .ascx control and establishing templates as in the following:

<ul>
<asp:repeater id=repeater runat=server>
   <itemtemplate>
   <li><%# DataBinder.Eval(Container.DataItem, "DataField")%></li>
   </itemtemplate>
</asp:repeater>
</ul>

This, I believe, is a pretty popular route that developers take.  My circumstances, however, proved to be different this time.  I needed to create a server control (derived from WebControl) that utilized a templated Repeater control.  The trick here wasn't so much getting a Repeater control on the page, but rather to create a data-bindable template without the 'code-in-front'.

This was, in fact, extremely easy to accomplish despite all of the useless help examples in MSDN.  In fact the MSDN help only added hot coals to the fire of confusion so I abandoned it to figure it out manually.

Basically, what it came down to was that I had to create a class that derived from ITemplate and assign an instance of this class to the ItemTemplate, AlternatingItemTemplate, or whatever property of the Repeater control.  By implementing the ITemplate.InstantiateIn() method, we provide the runtime the opportunity to hook our template into the proper parent control.  In order to render the output html we have to use server controls (such as the Literal control) because we cannot add plain text directly to the Controls collection of the parent control.

That's all there is to it to create a simple template, but for the most part that isn't enough.  One of the most powerful and luring capabilities of templates is their ability to data bind.  In this case we cannot simply embed the databinding tags <%#...%> within our text because those expressions are parsed out of an .aspx/.ascx and turned to code before the template is ever created.  Therefore, in order to accomplish this, we must attach to the DataBinding event of the control outputting the data (the Literal control in our case).  This is where things get a bit more involved - but not by much.

The DataBinding event has the signature of a typical EventHandler (object sender, EventArgs e).  The sender parameter can be cast to the Literal control as it is the control on which the event is firing.  Then, to get at the data that is being bound to our template instance we must acquire a reference to the RepeaterItem (because we're dealing with a Repeater control).  The RepeaterItem has a property called DataItem from which we can extract the data being bound.  After that, it's merely a matter of dumping the data to the control.

Suppose that we had the following Customer class:

public class Customer {
   private string _id;
   private string _name;
  
   public Customer(string id, string name) {
      _id = id;
      _name = name;
   }
  
   public string Id {
      get { return _id; }
      set { _id = value; }
   }
  
   public string Name {
      get { return _name; }
      set { _name = value; }
   }
}

Now an array of Customer instances will be provided to the fictitious control for data binding.  Our control might resemble the following in the server-side code:

public class CustomerListing : WebControl {
   private Repeater repeater;
  
   protected override void OnInit(EventArgs e) {
      base.OnInit(e);
     
      // create the repeater
      repeater = new Repeater();
      this.Controls.Add(repeater);
     
      // assign the template instance to the repeater
      repeater.ItemTemplate = new CustomerTemplate();
   }
  
  
   protected override void OnLoad(EventArgs e) {
      base.OnLoad(e);
     
      Customer[] customers = BusinessLayerSomethingOrOther.GetCustomers();
      repeater.DataSource = customers;
      repeater.DataBind();
   }
  
  
   protected override void Render(HtmlTextWriter writer) {
      writer.Write("<b>Customer List</b>");
      writer.Write("<ul>");
      base.Render(writer);
      writer.Write("</ul>");
   }
}

Now it's time to get into the template.

internal class CustomerTemplate : ITemplate {
   Literal lit;
  
   void ITemplate.InstantiateIn(Control container) {
      lit = new Literal();
      lit.DataBinding += new EventHandler(dataBind);
      container.Controls.Add(lit);
   }
  
  
   private void dataBind(object sender, EventArgs e) {
      // get a reference to the Literal control
      Literal lit = sender as Literal;
      // get a reference to the templated item
      RepeaterItem container = lit.NamingContainer as RepeaterItem;
      // note: we could use DataBinder.Eval(...) but I'd rather not take the perf hit
      Customer cust = ((Customer)container.DataItem);
      lit.Text = string.Format("<li>{0} - {1}</li>", cust.Id, cust.Name);
   }
}

As you can see, it's pretty easy to accomplish this!  There is so much more than can be accomplished, but this is merely a first step into the greater world of server-side templating.

Friday, December 10, 2004 4:16:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [14]  |  Trackback

Boy!  This has been a slow month for blogging!  I've been travelling so much and working so hard that once again my blog has become secondary.

I am out of town at the moment and regrettably missed our .NET User Group meeting last night.  My good friend and associate, Matt Smith, from our local Salt Lake City Microsoft offices gave what I'm sure was a excellent talk on Sql Server Reporting Services - Matt never fails to impress me on any level.  As perhaps one of the best developers I have ever met, I'm always impressed.

In my experience, I routinely run into developers that have never even heard of SRS which is regrettable.  I hope that the presentation was not only an eye-opener but replete with technical goodies that developers crave.

Friday, December 10, 2004 3:28:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Thursday, December 02, 2004

This hit me pretty good today - it took about an hour and a half to solve.  The whole time I spent almost pulling my hair out...though I did have a glass of apple juice which whet the appetite nicely.

Ever since I removed myself from my local Administrators group, I've been wanting to test a particular website that I developed (and have been developing for some time).  The website uses impersonation, thereby allowing me to effectively run as a different user.  I didn't want, even for testing/development purposes, to have direct access to databases or other objects.  Rather I've wanted to write the software in an environment more like the one into which it will be deployed.

Well, because I removed myself from the Administrators group, I have had to run ASP.NET as a user other than ASPNET in order to debug the websites.  Of course, this was not a problem at all.  I simply encrypted my credentials via aspnet_setreg.exe and updated the machine.config file's to point to the registry as the source for its credentials.  All has been great - until today, well, yesterday, but that's a different story all together.

Upon attempting to run this website, I was greeted with the not-so-friendly-but-somewhat-informative message of “Access Denied to 'C:\...\Index.aspx'.  Failed to start monitoring file changes.” Therefore, I attempted to run the project from within Visual Studio, to see if I could further debug the problem and isolate any issues.  I got a different message all together: “Configuration Error.  Unable to load c:\windows\...\machine.config.  Either a required impersonation level was not provided, or the provided impersonation level is invalid.”

This seemed a simple, permissions settings.  I proceeded, therefore, to grant my impersonated user rights to the folders and files in question.  No dice.  Same message.  Hmmm, this was getting interesting - and a tad frustrating.

I tried granting the user Full Control at the root C:\ level to see if that took care of things - a big fat nope!

Interestingly, if I set the impersonated user to be my domain administrator it still didn't work.  It ONLY worked under my account.  Then I started to think - I probably don't have rights to impersonate.  When I was in the Administrators group I had that right by membership, but now that I'm a lowly User, that privilege had been stripped.

The solution?  Well, I opened my Local Security Policy (running it as the administrator), expanded Local Policies\User Rights Assignment and navigated to the 'Impersonate a client after authentication' policy setting.  Sure enough only three accounts by default were granted this right:  Administrators, ASPNET, and SERVICE.  I simply had to add my account (because that's the account the ASP worker process is running as) to the list.  Then I opened the command prompt and ran gpupdate /force to ensure that all policy changes took effect, then ran iisreset.

Now it works and I'm ready to ride off into the sunset and retire - at least for the evening.

Thursday, December 02, 2004 6:12:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [14]  |  Trackback
 Wednesday, December 01, 2004

It just goes to show you!  When you feel you know and understand a product pretty well, some new nook or cranny appears, allowing you into previously unexplored territories.

I never had explored this facet of IE, but Nikhil Kothari identifies that you can create your own stylesheets and apply them to Internet Explorer in general.  Specifically, he points out simply enlarging (zooming) the contents of the browser by creating a simple stylesheet containing nothing but body { zoom: xxx% }.  This is fantastic - especially for presentations!

One thing of note, because I had never looked into the zoom attribute before:  not only does it enlarge what the browser is presenting (text, images), but also its scrollbars.  Likewise, it does not zoom contents for which HTML (for one reason or another in IE) has no control over, such as your caret or combo boxes.

Additionally, this stylesheet, should you choose to apply it, affects ALL Internet Explorer-based applications on your system universally (Outlook, Web Browser controls, etc), so choose your styles with care.

Wednesday, December 01, 2004 2:09:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 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