Skip to main content

Blog

Go Search
Home
  

Categories
Work
Personal
Other
Other Blogs
There are no items in this list.
Web Site or Web Application?

Here's a question I hear a lot: "Should I use a Web Site (WS) or Web Application (WAP) for my ASP.NET 2.0 project?" Certainly Web Site projects look attractive when you are using a free copy of Visual Web Developer and a just have a couple of pages in your site. But how do they work for a real application?

There are many differences between these two project types and the choice will often be made based on either developer preference or operations considerations.  Project type usually has no impact on the capabilities or performance of the application itself – except for any compilation you postpone to runtime.  But if you are a developer using VS.NET 2005 for a medium-to-large application, WAPs can save you a lot of time over WSs.  The full-site compilation tax for Web Site projects is very noticeable, and must be paid every time you:

  • Run the site (debug or not)
  • Use the refactoring tools
  • Run code analysis

I am that VS2005 developer and this is the deal-breaker for me. I just hate how much it slows down my coding. So, for all but the most trivial projects I use a WAP.

But not everyone feels uncomfortable with the compilation tax. Since some folks will choose to use a Web Site project for a productions web app, I'll add this tip: consider compiling and deploying the entire site as a single DLL to avoid the runtime recompilation vulnerability. ScottGu has a number of must-read compilation and deployment tips for Web Site project users.

Here's a checklist of differences between the project types:

 

Web Site (WS)

Web Application (WAP)

Comments

Tools support

VS.NET, Web Developer, notepad

Requires VS.NET 2005, SP1 and later

 

Supported page code models

Code-beside

Code-in-page

Code-behind

Code-beside

Code-in-page

All code models work in both models, including the code-in-ASPX model.  

Code-behind also generates a partial class for code declarations in a designer.cs file  

Time for full site compilation

Very high

Normal

The time to compile a WS scales in an almost linear fashion with the number of pages in the project.   For a medium-sized projects, a full-site compile for a WAP can be 10x faster than a full-site compile for a WS.

Full site compilation can have a significant impact on developers that use VS.NET refactoring features.

"Magic"

Mostly compile-time magic:

Namespaces can be random and generated at compile time

DLL are randomly named at compile time

Assembly references are inferred from #using statement and DLLs in the bin directory

Mostly design-time magic:

   

.designer files are generated for control declarations

Overall, WAPs are much less magical. 

Less magic can be helpful for cases where you want to exercise explicit control over how components appear to each other.

Magical assembly references in WSs can be harder to manage for a team project.

Production recompilation vulnerability

Utilities that touch source files in the production environment (like virus checkers) can cause recompilation, and may cause an app restart as well.

n/a

For WS projects, this vulnerability can be mitigated by compiling the entire site to a DLL.

Deploy changes to only part of the web application

Because the page DLLs are magically named at compilation time, it's often difficult to replace a single page without restarting the application.

Partial changes can be deployed by copying markup files (if needed) and the site DLL to the production server.

Contrary to what most folks would guess, the magical compilation of WSs makes it a bit more difficult to deploy partial site changes than for WAPs.

Team-wide code analysis rules supported?

no

yes

Code analysis rules are part of the project for WAPs, and can be standardized and checked in for the whole team.  WSs support unshared personal rules only. 

Resources

The WS model splits the majority of string resources in separate, per-page resource files.  It's possible to use Global resources, but more clunky. 

Per-page or explicit models supported.

Per-page file approach can increase the cost of localization (round-tripping multiple files to the translation vendors).

XML documentation support (VS.NET)

no

yes

 

Strongly-typed MasterPage support

no

yes

Using strongly-typed MasterPages, you can define members on the MasterPage that are available to all of the derived pages, without resorting to reflection.

Supported by Team System Build?

no

yes

 

Project to project references supported

no

yes

For WSs, you must copy the built DLLs into the WS's bin folder, either manually or by doing some fiddling with the build output path in the component project.

Cross-project debugging supported

sorta

yes

With project to project references (WAP), you can debug into a component project in the solution with no additional setup. It just works.
For WSs you must build a debug version of the component and manually attach to it during your debug session.

Quick Windows App Icons, even for Vista

I'm working on a little WPF application and I need to add an icon that plays well on Vista. There's great prescriptive guidance on MSDN here – well worth a read!

One of the tools the article recommends is IconWorkshop from Axialis (say that 5 times fast). It's a pretty amazing tool. I'm no genius with graphics or graphics tools, and IconWorkshop would have to be amazing indeed to fix that. But it does have nice support for 256x256 RBG/A 32-bit icons, including an option to save them in a compressed format supported by Vista.

Best of all is the "Add Several Image Formats from This Image…" option. I created my 256x256 image first, then generated a 48x48 image which I edited to remove the countdown numbers. Then I generated all the other formats from the 48x48 image. The total size of the resulting .ICO file: 50 KB.

Drag-to-Scroll in WPF

In my previous post I discussed Drag/Drop in WPF. Often drag/drop is a user gesture to exchange data between two applications, or between parts of an application. But sometimes dragging alone is used as a gesture to scroll some content that's too big to see all at once. Most online map sites let you drag the map image to reveal a different part of the map – in effect, scrolling it.

In the Family.Show reference app, Ralph Arvesen enabled the drag-to-scroll gesture to position the family genealogy graph using the ScrollViewer control and mouse events. It's a nifty approach that I've extracted out into a bare bones example.

The WPF ScrollViewer control already handles positioning content using the scrollbars and clipping of content within its viewport. All that's left is for your code to do is to handle the mouse events and position the content programmatically. Mouse events are implemented in WPF as attached events, and to make things even easier UIElement exposes an alias event for each mouse event. This sample just overrides the event handler methods on Page, which derives from UIElement.

The ScrollViewer's extent is sized to hold the content (in this example, an explicitly-sized Canvas), and the Scrollviewer's viewport is sized to fit within a window, page or some other XAML content control. When the viewport is smaller than the content, the drag-to-scroll behavior is enabled.

On PreviewMouseDown, the original scroll position of the content within the viewport is noted. Each time PreviewMouseMove fires the scroll position is updated, giving the user constant feedback.

XAML

<Page x:Class="WindowsApplication1.Page1
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
  
  <ScrollViewer x:Name="myScrollViewer"
    HorizontalScrollBarVisibility="Hidden"
    VerticalScrollBarVisibility="Hidden" 
    >
    <Canvas Height="2000" Width="3000"
     
<Label x:Name="myLabel">Here's my label.</Label
   
</Canvas>
 
</ScrollViewer>
</Page>

Code

public partial class Page1 : System.Windows.Controls.Page
{
    private Point mouseDragStartPoint;
    private Point scrollStartOffset; 

    public Page1() 
   
        InitializeComponent(); 
        myLabel.Loaded += new RoutedEventHandler(myLabel_Loaded); 
   

    protected override void OnPreviewLeftMouseDown(MouseButtonEventArgs e) 
   
        mouseDragStartPoint = e.GetPosition(this); 
        scrollStartOffset.X = myScrollViewer.HorizontalOffset; 
        scrollStartOffset.Y = myScrollViewer.VerticalOffset; 

        // Update the cursor if scrolling is possible 
        this.Cursor = (myScrollViewer.ExtentWidth > myScrollViewer.ViewportWidth) || 
            (myScrollViewer.ExtentHeight > myScrollViewer.ViewportHeight) ? 
            Cursors.ScrollAll : Cursors.Arrow; 

        this.CaptureMouse(); 
        base.OnPreviewMouseDown(e); 
   

    protected override void OnPreviewMouseMove(MouseEventArgs e) 
   
        if (this.IsMouseCaptured) 
       
            // Get the new mouse position. 
            Point mouseDragCurrentPoint = e.GetPosition(this); 

            // Determine the new amount to scroll. 
            Point delta = new Point
                (mouseDragCurrentPoint.X > this.mouseDragStartPoint.X) ? 
                -(mouseDragCurrentPoint.X - this.mouseDragStartPoint.X) : 
                (this.mouseDragStartPoint.X - mouseDragCurrentPoint.X), 
                (mouseDragCurrentPoint.Y > this.mouseDragStartPoint.Y) ? 
                -(mouseDragCurrentPoint.Y - this.mouseDragStartPoint.Y) : 
                (this.mouseDragStartPoint.Y - mouseDragCurrentPoint.Y)); 

            // Scroll to the new position. 
            myScrollViewer.ScrollToHorizontalOffset(this.scrollStartOffset.X + delta.X); 
            myScrollViewer.ScrollToVerticalOffset(this.scrollStartOffset.Y + delta.Y); 
       
        base.OnPreviewMouseMove(e); 
   

    protected override void OnPreviewMouseUp(MouseButtonEventArgs e) 
   
        if (this.IsMouseCaptured) 
       
            this.Cursor = Cursors.Arrow; 
            this.ReleaseMouseCapture(); 
       
        base.OnPreviewMouseUp(e); 
   

    void myLabel_Loaded(object sender, RoutedEventArgs e) 
   
        //center the label initially 
        myLabel.SetValue(Canvas.LeftProperty, 
            ((myScrollViewer.ExtentWidth / 2) - (myLabel.ActualWidth / 2))); 
        myLabel.SetValue(Canvas.TopProperty, 
            ((myScrollViewer.ExtentHeight / 2) - (myLabel.ActualHeight / 2))); 
        myScrollViewer.ScrollToHorizontalOffset( 
            (myScrollViewer.ExtentWidth / 2) - (myScrollViewer.ViewportWidth / 2)); 
        myScrollViewer.ScrollToVerticalOffset( 
            (myScrollViewer.ExtentHeight / 2) - (myScrollViewer.ViewportHeight / 2)); 
    }
}

Enjoy ~ Susan

Drag/Drop in WPF

Recently I had my first opportunity to add drag/drop functionality to a WPF application and I learned that WPF doesn't change the recipe for drag/drop much from previous Windows client technologies. It's not too surprising, really, since most of us will want our WPF applications to "play nice" with Windows and other applications your user is running. And mostly, that means sharing data in a standard way between applications.

The data exchange mechanism between the "drag source" application and the "drop target" application is an object that implements IDataObject. (The drag source and drop target can be the same application, of course.) In WPF the IDataObject is passed from the drag source by the System.Windows.DragDrop.DoDragDrop() method, and received by the drop target as the Data member of DragEventArgs. The IDataObject also provides some methods for inspecting what data is available and which UX gesture was should be applied to the data (copy, move, etc.).

In WPF, as with earlier technologies, you'll encounter the "three bears" of drag/drop implementation complexity. Here are some simple examples that illustrate the differences.

Baby Bear: Your app is a drop target for another app's drag data

The simplest drag/drop implementation only has to read the data passed by another application (or Windows) when a user drops and object on your application. This example lets a user drag a file from the desktop and drop it on a StackPanel in your application. The creation of the IDataObject is the job of the drag source, so Windows has already taken care of that part for you. Your app just handles the WPF Drop event and queries e.Data for the file that was dragged.

XAML

<Page x:Class="SimpleDropTarget
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
    > 
    <StackPanel AllowDrop="True" Drop="FileDropHandler"
        <Label x:Name="myLabel" >Drop a file here</Label
    </StackPanel>
</Page>

Code

public partial class SimpleDropTarget : System.Windows.Controls.Page
{
    + default constructor

    private void FileDropHandler(object sender, DragEventArgs e) 
   
        if (e.Data.GetDataPresent(DataFormats.FileDrop)) 
       
            //Do work. Can special-case logic based on Copy, Move, etc. 
            string[] fileNames = e.Data.GetData(DataFormats.FileDrop, true) as string[]; 
            myLabel.Content = fileNames[0] as string
       

        e.Handled = true
    }
}

Mama Bear: Your application is a drag source

When your application wants to act as the source of some dragged data, things start to get a bit more involved. Now, you must handle mouse events to detect when dragging is happening and figure out what UIElement is being dragged. And your code must create an IDataObject with the data you'd like to pass to the drop target.

Note that when your application is both the drag source on the drop target it's often useful to pass data in a custom format that you define, rather than using one of the canned ones defined by the DataFormats object.

Here's a bare-bones code example that passes a file name:

XAML

<Page x:Class="WindowsApplication1.SimpleDragSource
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
    > 
    <Canvas
        <ListBox x:Name ="fileList
            PreviewMouseDown="ListBoxItemDragBegin" 
            PreviewMouseMove="ListBoxItemDragMove" 
        /> 
    </Canvas>
</Page>

Code

public partial class SimpleDragSource : System.Windows.Controls.Page

    bool dragStarted; 

    public
SimpleDragSource() 
   
        InitializeComponent(); 

        //bind a list of files to the ListBox 
        DirectoryInfo windowsDir = new DirectoryInfo("c:\\windows"); 
        fileList.ItemsSource = windowsDir.GetFiles(); 
   

    private
void ListBoxItemDragBegin(object sender, MouseButtonEventArgs e) 
   
        dragStarted = true
        base.OnPreviewMouseDown(e); 
   

    private
void ListBoxItemDragMove(object sender, MouseEventArgs e) 
   
        if (dragStarted && sender is ListBox
       
            //create data object 
            ListBox theList = sender as ListBox
            DataObject data = CreateDataObjectFromList(theList); 

            //trap mouse events for the list, and perform drag/drop 
            Mouse.Capture(sender as UIElement); 
            System.Windows.DragDrop.DoDragDrop(theList, data, DragDropEffects.Copy); 
            Mouse.Capture(null); 

            dragStarted = false
            base.OnPreviewMouseMove(e); 
       
   

    private
DataObject CreateDataObjectFromList(ListBox list) 
   
        DataObject data = new DataObject(); 
        FileSystemInfo current = list.SelectedItem as FileSystemInfo
        if (current != null
            data.SetData(DataFormats.FileDrop, new string[] { current.FullName as string }); 
        return data; 
    }
}

Papa Bear: Full Blown Drag/Drop

The examples above are instructive, but the approaches they show are effective for only the most basic drag/drop scenarios. For applications that make more extensive use of drag/drop you'll want to consider:

  1. Refactoring the event handling and DataObject creation logic so it can be used for multiple drag sources and drop targets in your application. Otherwise, you'll find yourself writing a lot of redundant code.
  2. Adding user feedback for dragging. At a minimum change the cursor to something that indicates "drag in progress" or – since this is WPF and it's easy to do spiffy visualizations – show a preview of the item to be dropped under the cursor.

Pavan Podila's library tackles both of these requirements in a nice, reuseable way. Lester Lobo cleaned it up a little and shares his version here.

Enjoy ~ Susan

Adventures in CSS, Redux

We changed over our blogs platform recently (was Community Server, now SharePoint 2007), and a few of my recent posts about CSS and Windows Marketplace are buried in the old blog. So, reposting some links because someone at Mix asked me about them.

Adventures in CSS: Windows Marketplace 2006

Adventures in CSS: Stretchy Frame

Adventures in CSS: Authoring for Multiple Browsers

Windows Marketplace

That Pesky Indent in my Office 2007 Emails

For quite a while now (like, 20 years) I've been sending new emails every day. And never, until Outlook 2007, have I had emails indent each new paragraph by default. Perhaps it's more correct than having flush-left paragraphs, but it just feels wrong to me.

But getting rid of the indent was trickier than I would have guessed. Changing this meant changing the new email template (that part I guessed), specifically I had to change the template's styles. (Simply changing the formatting of the template's first paragraph didn't do the trick.) Here are the steps:

  1. Close Outlook.
  2. Edit the new email template using Word 2007: C:\Documents and Settings\user name\Application Data\Microsoft\Templates\NormalEmail.dotm. (Your path may vary, but this is the default.)
  3. Right-click the Normal style in the Styles group, and Modify.
  4. Make your changes to Normal as desired. In my case, I changed the Paragraph settings to remove the indent.
  5. Save your changes, and close Word.
  6. Re-open Outlook.

Now new messages will be formatted with your changes.

Photoshop’s Save for Web feature for Web JPEGs

I'm just a code monkey, so there's a truckload of stuff about image formats I've never bothered to try to understand… bit depths, palettes, embedded profiles. If you want to see my eyes lose focus and my mouth go slack, just start talking about chroma subsampling.

Thankfully, Vertigo has an excellent team of designers to shield me from such stuff most days. During yesterday's 10th anniversary festivities I found myself on my own trying to compress some images as JPEGs. I learned a trick for getting smaller, good looking images for a web site– without have to really understand what's going on:

  • Use Photoshop's (or Photoshop Elements) "Save for Web" feature

For each quality setting I used (I ended up going with the "JPEG High" preset) the size of the resulting image was significantly smaller than images created using "Save As JPEG" in Photoshop or Paint Shop Pro – 7k vs. 24k for one of the images on our People page. But the quality was identical.

So why would that be? I'm no expert but some explanations suggest it's likely due to simply removing the extraneous information from the file, like EXIF data, embedded profiles and the like. Save for Web apparently also converts the image to 8-bit depth, sRBG as well.

 ‭(Hidden)‬ Admin Links