Silverlight Event Tokens

Thursday, September 27, 2007 by scott
0 comments

Events are easy to add and remove when using the CLR and C#. Just sprinkle some += and -= in the right places and the runtime does the rest. Silverlight programming with JavaScript is a bit different, however, and although the addEventListener and removeEventListener appear similar to a DOM element's event registration APIs, these two methods have a twist.

As an example, let's say we wanted to listen to the MediaElement's BufferingProgressChanged event, but we want to stop listening once buffering has exceeded 50%. The following code does work:

function onLoad(rootElement)
{
    
var mediaElement = rootElement.findName("_media");
    mediaElement.addEventListener(
"BufferingProgressChanged",
                                  
"onBufferProgress");
}

function onBufferProgress(sender, eventArgs)
{      
    
// .. do something

    if(sender.BufferingProgress > 0.50)
    {
        sender.removeEventListener(
"BufferingProgressChanged",
                                    
"onBufferProgress");
    }
}

.. but this code will never unsubscribe from the event:

function onLoad(rootElement)
{
    
var mediaElement = rootElement.findName("_media");
    mediaElement.addEventListener(
"BufferingProgressChanged",
                                   onBufferProgress);
}

function onBufferProgress(sender, eventArgs)
{      
    
// .. do something

    if(sender.BufferingProgress > 0.50)
    {
        sender.removeEventListener(
"BufferingProgressChanged",
                                    onBufferProgress);
    }
}

When not using a quoted string to specify the event handler, you have to save the value returned from addEventListener. The return value is a token that is "just an integer value to track the order in which handlers were added for each object-event combination" (read MSDN for other subtleties in the event registration methods).

A proper unsubscribe looks like the following:

var token;

function onLoad(rootElement)
{
    
var mediaElement = rootElement.findName("_media");
    token = mediaElement.addEventListener(
                          
"BufferingProgressChanged",
                           onBufferProgress);
}

function onBufferProgress(sender, eventArgs)
{      
    
// .. do something

    if(sender.BufferingProgress > 0.50)
    {
        sender.removeEventListener(
"BufferingProgressChanged",
                                    token);
    }
}

Mice, Usability, and Silverlight

Wednesday, September 26, 2007 by scott
3 comments

There is no mouse double-click event on UIElements in Silverlight.

Some people think this is an odd omission. Silverlight's cousin WPF can fire a double-click event, so why not Silverlight? Even JavaScript can catch double-click events inside a web page (although in true web fashion, the exact behavior depends on the combination of browser version, operating system, and the current phase of the moon – see Jan Wolter's Javascript Madness: Mouse Events for all the gory details).

You can still detect a double click using a mouse up event handler and a timestamp - but should you? The omission of a double-click event wasn't because of a technical limitation – the omission is a deliberate design decision. Silverlight is built for the web, and many consider double-clicking on the web to be a usability no-no.

Here is what Peter Chng has to say:

Take this comment by about the new Yahoo! Photos site; the user laments about the interface requiring a double-click to open a full-size image rather than just a traditional single-click as on most other websites. (The double-click detection is done via JavaScript)

I'll admit that while I was first confused by this action, I thought little of that user's comment - I mean, how hard can it be to learn a simple action like that? But, after some more thinking, I've come to agree with the comment - breaking the pattern of how a user navigates on a website is not a good idea, even if it's done to try to make your website feel more like a desktop application.

The New York Times website includes a feature that launches a search (in a pop-up window) when you double click a word inside an article. Digital Inspiration believes this behavior is user unfriendly, while Dion thinks the feature is a bug.

You might also have noticed the only mouse button events in Silverlight are LEFT mouse buttons events. Edit (I haven't kept up with changes in the other lifestyle...) Macs don't have a right mouse button, and my guess is they never will (adding one now would be like admitting defeat). Exposing a right-click event could only invite more usability problems.

Silverlight has some wonderful potential. Just remember to innovate – don't aggravate.

More on Conditional Compilation in ASP.NET

Tuesday, September 25, 2007 by scott
1 comment

Phil dug up an old post of mine on conditional compilation, but defining a constant in web.config didn't appear to work for him. I didn't see anything wrong with his approach, so I downloaded the solution and did some spelunking.

With this page ...

<%@ Page Language="C#" 
    
CompilerOptions="/d:QUUX" %>
...
    <div>
       <% #if BAZ %>
         BAZ in the aspx file.
      <%
#endif %>
      <%
#if QUUX %>
         QUUX in the aspx file.
      <%
#endif %>
    
</div>
...

... and this web.config ...

<system.codedom>
   <
compilers>
      <
compiler
         language="c#;cs;CSharp" extension=".cs"
         compilerOptions="/d:BAZ"
         type="Microsoft.CSharp.CSharpCodeProvider, System,
             Version=2.0.0.0, Culture=neutral,
             PublicKeyToken=b77a5c561934e089
" />
   </
compilers>
</
system.codedom>

... then the page behaves as if only the QUUX is defined.

To understand the scenario I added <compilation debug="true"> to the web.config. Debug settings leave behind a .cmdline file in the temporary ASP.NET files directory. The .cmdline file contains the exact commands to invoke the C# compiler, and the abbreviated form looked like this:

...
/t:library /utf8output
/D:DEBUG /debug+ /optimize- /nowarn:1659;1699
/d:QUUX  
...

I went in thinking the compilerOptions would be additive, but after a smack on the forehead, I realized the compiler options in the @ Page directive override the web.config compiler options. Remove the compilerOptions attribute from the @ Page directive and BAZ becomes defined.

The behavior does seem to follow the principle of least surprise, even if it did catch us off guard.

Nested Selects in LINQ to SQL

Monday, September 24, 2007 by scott
3 comments

Consider the following diagram to track the votes in a contest. Each voter can register only one vote (a score from 1 to 4) for each contest entry.

For any given voter, you'll want to present a screen showing every entry in the competition, along with the score assigned to the entry by the voter (if a score exists). To fetch the data with SQL, I'd write something like:

SELECT E.EntryID, E.EntryName, E.EntryDescription, @VoterID AS VoterID,
       (SELECT V.Score
        FROM   Votes AS V
        WHERE (V.VoterID = @VoterID) AND (V.EntryID = E.EntryID)) AS Score
FROM Entries AS E

The query should yield a resultset like the following:

...
EntryID EntryName VoterID Score 13 EntryA 1 4 14 EntryB 1 2 15 EntryC 1 NULL ...

I thought the query would be difficult to express in LINQ, but I stumbled into a solution just by keeping a "SQL" mindset:

var q = from e in Entries
         
select new
         
{
               EntryID = e.EntryID,
               EntryName = e.EntryName,
               EntryDescription = e.EntryDescription,
               VoterID = voterID,
               Score = (
int?)(from v in e.Votes
                             
where v.VoterID == voterID
                             
select v.Score).FirstOrDefault()
};

The LINQ generated SQL looks amazingly similar to the hand generated SQL, which is a comforting sight!

10 Tips for Shrink-wrapping ASP.NET Applications

Tuesday, September 11, 2007 by scott
7 comments

Most server-side applications run on machines under our control. We know what the application's environment will look like long before deployment.

Building and packaging an ASP.NET application to deploy inside of any corporate firewall is a bit more challenging. Here are a few lessons I've learned from five years of building a commercial web app.

  1. Document your required configuration. Use this document in the sales cycle and again before delivering the software to make sure there are no surprises.
  2. Verify the environment. Even though your document says you require SQL 2005 SP2, someone will point your software to a server with SQL Server 7.0. Small mistakes like this can eat up a surprising amount of time. Write some software that will verify versions, service packs, CPU, memory, and other environmental requirements.
  3. Two words: least privilege. In today's world, someone will challenge you over all privileges you request. You need to be in the db_creator role? Why? You need to do what on our Active Directory? Why? It's best to clearly document these required privileges and explain how you use them.
  4. Look at the licensing of third party components before looking at the feature list. As soon as you find the perfect grid control, you'll find it's too expensive to deploy. Many of the commercial controls and components you can buy for server-side software are geared less towards redistribution and more towards "per server" or "per CPU" licensing.
  5. Be creative about getting diagnostics. Log errors to help debug problems, but don't count on using the event log as it may not be well maintained. The file system is generally more reliable, and many logging components offer rolling log file options. To be proactive, consider sending diagnostic information directly back to your company – just don't use email. In my experience, sending email from a web server inside the firewall out over the Internet is only going to work about 30% of the time – corporate IT departments are just too leery. Consider hosting a web service at your place that the web app can contact over port 80, but be prepared to use a proxy server.
  6. Create and install a database maintenance plan. No one else will.
  7. Ruthlessly automate the installation. Every manual step in the installation is a problem waiting to happen.
  8. Be wary of virus scanners. Many corporate departments require them, but unfortunately, they can do some funky stuff to an ASP.NET application - frequent restarts and lock assemblies. Try to impress on the new owners of your application that they can exclude your directories from scanning.
  9. Be wary of impersonation. Let's say you need access to a network resource, so you place the encrypted username and password of a domain account into web.config. At runtime, you use the credentials to impersonate a domain account and reach the resource. This strategy will work well for about three months, then that corporate password change policy is enforced, and your application starts throwing mysterious errors.
  10. Stay out of the default AppPool in IIS6. Because you don't know who your neighbors are going to be. They might like to stay up late, use too much memory, play loud music, and run with a different version of the CLR.

Check Back In A Couple Days

Friday, September 7, 2007 by scott
7 comments

I'll blog again as soon as this little operation finishes....

by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!