A familiar question in the ASP.NET world is: "How do I show the progress of a long-running background operation?"
ASP.NET AJAX has given us new tools to answer this question. It's easy to drop an UpdatePanel and a Timer inside a web form, and partially update the page when the Timer control posts back. There are still some fundamentals to remember when taking this approach, however. For example:
protected
void Button1_Click(object sender, EventArgs e)
{
HeavyDutyWorker
worker = new
HeavyDutyWorker
();
worker.ProgressNotification +=
new
ProgressEventHandler
(worker_ProgressNotification);
worker.DoWork();
}
void worker_ProgressNotification(object
sender, ProgressEventArgs args)
{
Session
[ProgressKey] = args.PercentComplete;
}
protected
void Timer1_Tick(object sender, EventArgs e)
{
Label1.Text = String.Format("Percent Complete = {0}",
Session[ProgressKey]);
}
The button click event handler creates an object whose DoWork method is going to work asynchronously on some task. Let's pretend the object fires an event each time it completes 10% of the work. The Page subscribes to this event, and the event handler (worker_ProgressNotification) updates a session variable with the percentage of work complete.
To update the page with the completion percentage, the AJAX Timer fires a Tick event on the server. The Tick event handler (Timer1_Tick) tries to read the completion percentage out of the session variable and update a Label control.
This approach will fail.
The problem lies in the worker_ProgressNotification event handler. The Page class uses HttpContext.Current to retrieve the current Session object, but the asynch worker will raise this event on a background thread that is not associated with an HttpContext. The line of code in the event handler will throw a System.Web.HttpException. Even if the event fired on the right thread, the event will fire long after the Page has rendered and the original request that started the asynch work is complete. All bets on having a valid HttpContext would be off at that point.
Interestingly, this approach does work if you use the Cache object (with a unique key for the user / work item, of course). A Page always uses HttpContent.Current to fetch the Session object, but always carries a direct reference to the Cache object. This is, of course, an implementation detail that could change in the future.
A good rule of thumb is to never let a Page subscribe to an event that doesn't originate from inside the Page (where user controls and master pages are considered inside the Page). Anything else is asking for trouble.
A couple times this year I've wanted to plug some electronics into a hotel TV. For instance, when I'm on the road and want to watch a movie from my Creative Vision M. I also unexpectedly came into possession of an XBOX 360 during a recent trip to Seattle (thank you, Party with Palermo and PreEmptive).
Side note: My XBOX Live Gamertag thingy is:
The last couple hotel TVs I've looked at offer no media inputs on the front. No problem, I'll think to myself. If there are no inputs on the front, there must be inputs on the back. Getting to the back of a hotel TV, however, is a problem.
You see, here in the United States a television is a prized commodity. Watch carefully next time you see news footage of looting in the U.S. After a natural disaster, you won't see looters plundering grocery stores for canned food and bottled water, but you will see them carrying 32 inch televisions through streets of rubble. As soon as electricity is restored, American Idol can alleviate the dehydration and malnourishment.
To protect their assets from rogue bands of Idol depraved looters, hotels lock their TVs into place using the strongest, heat-tempered metals known to mankind. Depending on how the TV is mounted, it's often physically impossible to look at the back of a television. No problem, I think, I'll reach around and take a picture of the back, and find some inputs.
The proverbial "black box" is here, bolted onto the TV. All inputs are hard wired. I think the plastic twisty thing is some sort of tamper detection "seal". Turns out hotels don't want you to watch movies unless you watch their pay-for-view movies.
If this TV was a Visual Basic class, it would have the MustNeverInheritOrOverideAnything keyword attached.
For lucky #13, I want to know what can go terribly wrong with the following code, and why.
class
HardWorker
{
public
void DoMultiThreadedWork(object someParameter)
{
lock (lockObject)
{
// ...
lots of work ...
}
}
private
string lockObject = "lockit";
}
Hint: Think about memory optimizations in the CLR.
The Themes feature in ASP.NET 2.0 requires a server-side <head> tag. In other words, if you don't have runat="server" in the <head> tag, like the following:
<head
runat="server">
<!-- head stuff -->
</head>
… then trying to use css files from a theme will create a runtime exception with a clear error message:
Exception Details: System.InvalidOperationException: Using themed css files requires a header control on the page. (e.g. <head runat="server" />).
Not every component yields such a direct message, however.
I've been updating pages in a 5 year old application by adding some AJAX features and using some flair from the Control Toolkit. It's been some time since I've been in the UI layer of an application, but I've restrained myself and haven't gotten too crazy. Still, when I saw the Calendar extender I knew it was a perfect fit. After adding the control, the page threw an exception.
System.NullReferenceException: Object reference not set to an instance of an object.
at AjaxControlToolkit.ScriptObjectBuilder.RegisterCssReferences(Control control) ...
at AjaxControlToolkit.ExtenderControlBase.OnPreRender(EventArgs e) ...
at System.Web.UI.Control.PreRenderRecursiveInternal() ...
...
Because I knew that the themes feature in ASP.NET 2.0 relies on an active header control to inject style sheets, I figured the ScriptObjectBuilder used the same trick to inject new script tags. Just adding runat="server" in the head tag of the page was enough to fix the problem.
Yesterday, I was giving an AJAX facelift to some old, old .aspx files. These files were created in the first version of Visual Studio.NET and have been running smoothly for years. Inside I found:
<meta
content="Microsoft Visual Studio 7.0"
name="GENERATOR">
<meta
content="C#"
name="CODE_LANGUAGE">
<meta
content="JavaScript"
name="vs_defaultClientScript">
<meta
content="http://schemas.microsoft.com/intellisense/ie5"
name="vs_targetSchema">
...
<body
MS_POSITIONING="GridLayout">
ASP.NET has come a long way since the days when MS_POSITIONING appeared. GridLayout meant that every control dropped on the form used absolute positioning by default. GridLayout was a thrill for new web developers who came from a VB6 background, but the feature didn't fly so well with anyone who took HTML seriously.
Ironically, Microsoft's web tools are now closer to the metal than they've ever been. The VS2005 editor is XHTML-compliant. The CSS Friendly Control Adapters can customize a control's HTML output. This trend continues in Orcas where the split screen view (see a screen shot in ScottGu's post) keeps the web form designer and the HTML editor on screen at the same time.
I think it's safe to say we aren't abstracting HTML away anytime soon. While it's OK to hide XmlHttpRequests and client side event handling behind frameworks and script libraries, everyone wants to see, feel, touch, and taste the HTML.
Windows Workflow Foundation is a multi-faceted technology. Some people will look at WF and see a tool to manage long-running business processes, while others see a rules engine. Yet another perspective is to look at WF as a tool for building domain specific languages.
Last week I had numerous conversations with talented individuals who reinforced this theme of WF as a DSL tool. For example- Kathleen Dollard builds code-generation tools for developers, and Sam Gentile builds applications for financial analysts. Both of them are giving their end users the ability to create tailored software using a domain specific language. The DSLs themselves are composed from custom activities in Windows Workflow Foundation. Microsoft has built a few DSLs on top of WF too, most notably in the latest release of Sharepoint Server and in Speech Server 2007.
Here's a few interesting resources on custom activity development in WF:
Build Custom Activities To Extend The Reach Of Your Workflows – Matt Milner
Speaker Verification Activity for Speech Server 2007 – Casey Chestnut
Custom Activity Samples in the SDK
HOW TO: Create a custom Windows Workflow Activity and make it available in SharePoint Designer – Todd Baginski
Creating a Custom Composite Activity – Morgan Skinner
I'm still hoping the WF team will one day publish and document the source code to the base activity library in WF. This would be a great starting point for developers who need to build robust, production ready custom activities.
Microsoft Word 2007 produces relatively clean HTML when you use the Publish feature to create a blog post. Although the XHTML purist will still be unhappy with anything they don't write themselves, the HTML far surpasses anything we've seen from Word in previous versions. Unfortunately, this feature is only available for blog posting, as far as I can tell. The "Web Page" and "Web Page, Filtered" options in the "Save As" menu still produce the same .mso littered HTML that makes Word impossible to use as a serious HTML editor. I'd like to use the HTML output by the Publish feature for purposes other than blogging.
I wasn't sure how to get to this Publish feature, but after looking at the MetaBlog API that Word can consume, I decided it wouldn't be too hard to write something in ASP.NET that would run on localhost and give me exactly what I wanted from Word. Specifically – convert a document into clean HTML and PNG graphics and drop the files into a local directory. I decided this job was even easier when I snooped around the SubText subversion repository and discovered that Cook Computing's XML-RPC library does all the heavy lifting and XML parsing.
It took a bit of debugging, but the IMetaWeblog interface defined in the XML-RPC library (interfaces\MetaWeblogAPI.cs) needs a few tweaks to work with Word. First, Word invokes a blogger.getUserBlogs method that isn't defined in the interface, but is easy to add:
[XmlRpcMethod("blogger.getUsersBlogs", Description = "...")]
BlogInfo[] getUsersBlogs(string blogid, string username, string password);
Secondly, Word appear to pass an integer for the blogid parameter of the newMediaObject method. The service expects a string. I don't know enough about the history of the MetaBlog API to know who is wrong in this scenario, but it's easy to fix the method definition in the interface.
[XmlRpcMethod("metaWeblog.newMediaObject",
Description = "Makes a new file to a designated blog using the "
+ "metaWeblog API. Returns url as a string of a struct.")]
MediaObjectInfo newMediaObject(
int blogid, // this was a string, but that doesn't work
with Word...
string username,
string password, FileData file);
One last change is to Refactor -> Rename the UrlInfo struct in MetaWeblogAPI.cs to MediaObjectInfo. The rename allows Word and the MetaBlog service to agree on the name of the struct.
public
struct
MediaObjectInfo
// this used to be called UrlInfo
{
public
string url;
}
Once all this is done it's a simple matter to implement that interface in an HttpHandler (ashx file).
public
class
MetaWebLogging : XmlRpcService, IMetaWeblog
{
// ...
}
Each method needs an implementation. For my workflow, I'm moving files around on the hard drive, but here is a sample implementation for the newPost method that will dump the incoming HTML into a file in the root directory of the application.
public
string newPost(string blogid, string
username, string password,
Post post, bool
publish)
{
string fileName = Path.Combine(
HttpContext.Current.Server.MapPath("~"),
post.title + ".htm");
using (FileStream fs = File.OpenWrite(fileName))
using (StreamWriter writer = new
StreamWriter(fs))
{
writer.Write(post.description);
}
return
Path.GetFileName(fileName);
}
Now I just point Word 2007's Publish feature to my local metablog.ashx file and export documents as HTML. For what I needed to do, this little hack was a huge time saver. Hopefully, future versions of Word will make this even easier.