Scott Hanselman

Back to Basics: Using Fusion Log Viewer to Debug Obscure Loader Errors

May 21, 2009 Comment on this post [4] Posted in Back to Basics | Learning .NET | Tools
Sponsored By

UPDATE: There is a new modern open source alternative to Fusion Log Viewer called Fusion you should check out!

It's time to remind people about Fusion. Mostly because I don't see as many people using it as should. I mentioned it as long as six (!) years ago and it's still useful. I used it just this week with .NET 4.

Sometimes when an assembly doesn't load, there's still a sense/feeling that "something in the black box has broken." For some folks, the black box is larger and obscures more, than for others. My point is, if you know where to look, there is no box at all.

When it comes to assemblies there's three "times" to know about:

  • Binding before it happens - What do you want?
    • ILDASM or Reflector will tell you what your assembly wants (what it was compiled against)
  • Binding as it happens - Where does it look?
    • Fusion (the Assembly Binding Log Viewer) will show you all assembly binds if you set the HKLM\Software\Microsoft\Fusion\ForceLog registry value to 1
  • Binding after it happens - What did you get?
    • Process Explorer will tell you what DLL (assembly) is loaded in memory and from where it came.

Here's an example of how a tiny bit of digging saved me hours of confusion recently when I hit an unusual edge case. I was doing a build of sample that was showing C# interop-ing with IronPython, but I was using a daily build of .NET 4 and a random build of IronPython.

Assembly Binding Log ViewerI made a mistake and had some really old DLLs floating around that I shouldn't have had. My symptom was a FileNotFoundException for the file "System.Dynamic.dll." I KNEW it was in the GAC (Global Assembly Cache) and I could SEE it as a reference DLL in my directory. Can't find the file? Dude, it's right there!

Turning on Fusion Logging

You probably have a tool to help on your development system already. Type "Fusion" in the Start Menu. The Assembly Binding Log Viewer, or "Fusion Log Viewer" will tell the CLR to load assembling binding/loading activities to a folder, then let you see them.

Be sure to run it as Administrator if you want to change the Settings, otherwise they'll be grayed out.

Alternatively, just set the Registry keys your self. (I just memorized them, as I set them all the time.) Set HKLM\Software\Microsoft\Fusion\ForceLog registry value to 1 and HKLM\Software\Microsoft\Fusion\LogPath registry value to C:\FusionLogs or some path that exists.

Personally, I leave this on all the time on my dev machines (there's a small speed hit) and just clean the folder out every once in a while.

Solving My Binding Problem

Once I turned on Fusion Logging I could immediately see a failure in my folder:

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from: C:\Windows\Microsoft.NET\Framework\v4.0.xxxxx\clr.dll
Running under executable C:\Users\Scott\Desktop\TechEd09\FX4\Demo 5 - DLR\Two.IronPythonInterop\bin\Debug\Two.IronPythonInterop.exe
--- A detailed error log follows.

=== Pre-bind state information ===
LOG: User = HANSELMAN-DEV10\Scott
LOG: DisplayName = System.Dynamic, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
LOG: Appbase = file:///C:/Users/Scott/Desktop/TechEd09/FX4/Demo 5 - DLR/Two.IronPythonInterop/bin/Debug/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = Two.IronPythonInterop.exe
Calling assembly : IronPython, Version=2.6.0.1, Culture=neutral, PublicKeyToken=31bf3856ad364e35.
===
LOG: This bind starts in default load context.
LOG: No application configuration file found.
LOG: Using host configuration file:
LOG: Using machine configuration file from C:\Windows\Microsoft.NET\Framework\v4.0.xxxxx\config\machine.config.
LOG: Post-policy reference: System.Dynamic, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
LOG: GAC Lookup was unsuccessful.
LOG: Attempting download of new URL file:///C:/Users/Scott/Desktop/TechEd09/FX4/Demo 5 - DLR/Two.IronPythonInterop/bin/Debug/System.Dynamic.DLL.
LOG: Attempting download of new URL file:///C:/Users/Scott/Desktop/TechEd09/FX4/Demo 5 - DLR/Two.IronPythonInterop/bin/Debug/System.Dynamic/System.Dynamic.DLL.
LOG: Attempting download of new URL file:///C:/Users/Scott/Desktop/TechEd09/FX4/Demo 5 - DLR/Two.IronPythonInterop/bin/Debug/System.Dynamic.EXE.
LOG: Attempting download of new URL file:///C:/Users/Scott/Desktop/TechEd09/FX4/Demo 5 - DLR/Two.IronPythonInterop/bin/Debug/System.Dynamic/System.Dynamic.EXE.
LOG: All probing URLs attempted and failed.

You can see that it's looking all over for the file, first in the GAC< then all over the local folders before it gives up. Hm. Why isn't this working? I can see the file sitting right there.

Well, what's the public key token for this signed assembly? I can run "sn -T" on the file:

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0>
sn -T System.Dynamic.dll

Microsoft (R) .NET Framework Strong Name Utility Version 4.0.xxxxx.1
Copyright (c) Microsoft Corporation. All rights reserved.

Public key token is b03f5f7f11d50a3a

Wha? What's b03whatever? That's not b77whatever like the one my app is looking for! Looks like my sample app had reference not only an old version of System.Dynamic, but one with a completely different public key. That's what I get for not cleaning out my obj directories between daily builds.

This could have been just by setting the registry keys and watching the c:\fusionlogs folder, but the Fusion Log Viewer makes the process more user-friendly.

If you're debugging version number mismatches or strong-name mismatches, Fusion will ALWAYS tell you what's really going on. There is no Black Box.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

C# 4 and the dynamic keyword - Whirlwind Tour around .NET 4 (and Visual Studio 2010) Beta 1

May 21, 2009 Comment on this post [15] Posted in DLR | Learning .NET | TechEd
Sponsored By

I've posted twice so far on .NET 4, first on ASP.NET 4, then on improvements in C# around dynamism and PIAs as well as the COM Binder. Now "dynamic."

So I asked this guy, what's up with the dynamic keyword, and what type was it exactly? I mean, C# isn't dynamic, right? He says:

"Oh, well it's statically-typed as a dynamic type."

Then my brain exploded and began to leak out my ears. Honestly, though, it took a second. Here's a good example from some of Ander's slides:

Calculator calc = GetCalculator();
int sum = calc.Add(10, 20);

That's the creation of an object, invokation of a method, and the collection of a return value. This is the exact same code, as the "var" type is figured out at compile time.

var calc = GetCalculator();
int sum = calc.Add(10, 20);

If you wanted to do the exact same thing, except with Reflection (like if it were some other class, maybe old-COM interop, or something where the compiler didn't know a priori that Add() was available, etc) you'd do this:

object calc = GetCalculator();
Type calcType = calc.GetType();
object res = calcType.InvokeMember("Add",
BindingFlags.InvokeMethod, null,
new object[] { 10, 20 });
int sum = Convert.ToInt32(res);

It's pretty horrible to look at, of course. If the object is some dynamic thing (from any number of sources), we can do this:

dynamic calc = GetCalculator();
int sum = calc.Add(10, 20);

And get the dynamic method invocation and conversion of the return type. Basically it looks just like we're calling any other object.

Dynamism?

Here's the differences you see while coding. Hovering over the keyword gives me this nice tooltip.

image

When I hit the "." expecting intellisense to save me from my ignorance:

image 

I'm told this is a dynamic expression that will be resolved at runtime.

Here's a C# program calling a method in a python (.py) file:

ScriptRuntime py = Python.CreateRuntime();
dynamic random = py.UseFile("random.py");

//Make an array of numbers
var items = Enumerable.Range(1, 7).ToArray();

random.shuffle(items);

Here we're passing in an array if ints (System.Int32[]) into the Python 'shuffle' method and it works just fine.

image

The DLR basically enables everyone to talk to everyone. That includes not just Python and Ruby, but Silverlight, Office/COM, and others.

What price REPL?

John Lam has a great post about his TechEd talk where he took a spin on a traditional REPL (READ-EVAL-PRINT-LOOP) using the DLR. He even allows switching back and forth between languages, which is odd/interesting.

John's even put the code for his REPL up on GitHub. Why is this interesting? Well...

Screenshot of John Lam's REPL/editor

He took his REPL and embedded it into an example Open Source app, specifically Witty, a WPF Twitter Client. Why he didn't use BabySmash is beyond me. ;) Check it out, as well as the source code diff for Witty on John's blog.

It'll be nice to have this kind of dynamic stuff just baked in and waiting for me to use it.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

The Weekly Source Code 42 - Tree Trim, Plugins, and MEF

May 20, 2009 Comment on this post [5] Posted in Open Source | Source Code
Sponsored By

image I really advocate folks reading as much source as they can because you become a better writer by reading as much as writing. That's the whole point of the Weekly Source Code - reading code to be a better developer.

Reading code in Open Source projects is a good way to learn, especially if the project has been around a while and been successful, or if you already respect the team of people working on it. Less reliably, you can find snippets of code by searching and sharing code.

Tree Trim == CleanSources++

Many years ago, Omar Shahine wrote a great little app called Clean Sources. It added a right-click menu to the Windows Explorer that would delete your bin, obj and setup folders.

Later, Jeff wrote Clean Sources Plus. It added a "Clean and Zip" option as well as support for removing source control bindings.

Now, Steve Dunn has extended these to create Tree Trim, a command-line tool to do all this and more. He's extended it to include a plugin model that creates a little pipeline of plugins. You can chain them together and extend the command-line with your own plugins, and MEF (Managed Extensibility Framework) is at its core.

Tree Trim is convenient for build servers where you want to, for example, make a working copy, delete source control bindings, zip stuff up, email it, etc.

Each command line argument is a "task" and each command line arg (moniker) maps to a object.

treetrim.console.exe c:\dev\myproject -workingCopy -deleteFromDisk -zip -email

It's significant that order of arguments matter, so the args cause the plugins to run in order, like "make working copy, delete bin/obj, zip up, email."

Make your own Plugin and be MEFy

He's got an IPlugin interface:

public interface IPlugin
{
string Moniker { get ; }
string WorkingPath { get ; }
void Cleanup( ) ;
void Run(IPluginRuntimeSettings settings, IPlugin lastPlugin);
}

When you make a plugin, you need to let MEF know that it's available by Exporting the type:

[Export(typeof(IPlugin))]
public class SomePlugin : IPlugin
{
...
public string Moniker
{
get { return @"newPluginArgument" ; }
}
...
}

Then any plugin in the same directory gets pulled into a list of plugins...

public DiscoverPluginsInAssemblyDirectory( )
{

var catalog = new DirectoryCatalog(disk.DirectoryOfExecutingAssembly);

var container = new CompositionContainer(catalog);

var batch = new CompositionBatch();
batch.AddPart(this);

container.Compose(batch);
}

[Import( typeof( IPlugin ) )]
public IList<IPlugin> Plugins
{
get;
set;
}

The app kicks off this little pipeline by passing the command line args in along with the plugins found:

Trimmer.TrimTree(
new TaskCollection( pluginDiscoverer.DiscoveredPlugins, commandLineArgs ),
path );

...then...

public static void TrimTree(ITaskCollection tasks, string sourceTreeRoot)
{
ITask lastTask = new Task { Plugin = new NullPlugin( sourceTreeRoot ) } ;

foreach ( ITask eachTask in tasks )
{
eachTask.Run( lastTask );

lastTask = eachTask ;
}

IEnumerable<ITask> reversedTasks = tasks.Reverse( ) ;

foreach (ITask eachTask in reversedTasks)
{
eachTask.Cleanup();
}
}

The code is actually very easy to read and is up at TreeTrim on Google Code and check out the FAQ. Plugins are super easy with MEF and Tree Trim has some good examples of a number of things. First, just plugins in general, but also a technique for passing settings to plugins.

Add Context Menus to Explorer

You can easily create context menus in Explorer for this tool (or any tool). Add a Key to the Registry like below.

HKEY_CLASSES_ROOT\Folder\shell\<WHATEVER TEXT YOU WANT>\command

Then in the (Default) string value, put it something like this (for example):

"C:\Program Files (x86)\Tree Trim\TreeTrim.Gui.exe" "%1"  -workingcopy -deletefromdisk -zip:writeTo:"c:\users\scott\desktop\justzipped.zip"+dontCleanUp

Here's how it looks in registry:

Registry Editor (2)

Steve is also starting to setup tests using XUnit. He's starting to use the ContextSpecification Pattern for his tests, so it'll be interesting to see if he completes the tests. That part is pretty basic so far. Check out the TaskCollectionSpecs.cs as an example.

All in all, fun to read, and an interesting tool I'll use to quickly clean and send code samples around. I may extend it with a plugin to upload to my blog, then put the link in the clipboard. That could make blogging samples easier.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

CLR and DLR and BCL, oh my! - Whirlwind Tour around .NET 4 (and Visual Studio 2010) Beta 1

May 20, 2009 Comment on this post [18] Posted in BCL | Learning .NET | TechEd
Sponsored By

Just got a great tweet from Jeremiah Morrill about .NET 4.

"Office and COM Interop that is actually fun to do" Show me! Until then, I'm calling shenanigans.

I love a challenge! In my first Whirlwind Tour post on ASP I mentioned how COM Interop and Office Interop was fun with 4.

I've done a lot of COM Interop with C# and a LOT of Office Automation. Once upon a time, I worked at a company called Chrome Data, and we created a Fax Server with a Digiboard. Folks would call into a number, and the person who took the order would pick the make/model/style/year of the car and "instantly" fax a complete report about the vehicle. It used VB3, SQL Server 4.21 and Word 6.0 and a magical thing called "OLE Automation."

Fast forward 15 years and I sent an email to Mads Torgerson, a PM on C# that said:

I’m doing a sample for a friend where I’m simply spinning through an Automation API over a Word Doc to get and change some CustomDocumentProperties.

I’m really surprised at how current C# sucks at this. Of course, it makes sense, given all the IDispatch code in Word, but still. Dim != var as they say. Fix it!

Well, everything except "fix it!" is true. I added that just now. ;) I did a post on this a while back showing how scary the C# code was. This is/was somewhere where Visual Basic truly excels. I vowed to only use VB for Office Automation code after this fiasco.

If you want to melt your brain, check out the old code. No joke. I've collapsed the block because it's too scary. See the "ref missings"? The reflection? The Get/Sets? Scandalous!

{
ApplicationClass WordApp = new ApplicationClass();
WordApp.Visible = true;
object missing = System.Reflection.Missing.Value;
object readOnly = false;
object isVisible = true;
object fileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"..\..\..\NewTest.doc");
Microsoft.Office.Interop.Word.Document aDoc = WordApp.Documents.Open(
ref fileName,ref missing,ref readOnly,ref missing,
ref missing,ref missing,ref missing,ref missing,
ref missing,ref missing,ref missing,ref isVisible,
ref missing,ref missing,ref missing,ref missing);

aDoc.Activate();

string propertyValue = GetCustomPropertyValue(aDoc, "CustomProperty1");
SetCustomPropertyValue(aDoc, "CustomProperty1", "Hanselman");

foreach (Range r in aDoc.StoryRanges)
{
r.Fields.Update();
}
}

public string GetCustomPropertyValue(Document doc, string propertyName)
{
object oDocCustomProps = doc.CustomDocumentProperties;
Type typeDocCustomProps = oDocCustomProps.GetType();
object oCustomProp = typeDocCustomProps.InvokeMember("Item",
BindingFlags.Default |
BindingFlags.GetProperty,
null, oDocCustomProps,
new object[] { propertyName });

Type typePropertyValue = oCustomProp.GetType();
string propertyValue = typePropertyValue.InvokeMember("Value",
BindingFlags.Default |
BindingFlags.GetProperty,
null, oCustomProp,
new object[] { }).ToString();

return propertyValue;
}

public void SetCustomPropertyValue(Document doc, string propertyName, string propertyValue)
{
object oDocCustomProps = doc.CustomDocumentProperties;
Type typeDocCustomProps = oDocCustomProps.GetType();
typeDocCustomProps.InvokeMember("Item",
BindingFlags.Default |
BindingFlags.SetProperty,
null, oDocCustomProps,
new object[] { propertyName, propertyValue });
}

Fast forward to C# under .NET 4.

var WordApp = new ApplicationClass();
WordApp.Visible = true;
string fileName = @"NewTest.doc";
Document aDoc = WordApp.Documents.Open(fileName, ReadOnly: true, Visible: true);
aDoc.Activate();

string propertyValue = aDoc.CustomDocumentProperties["FISHORTNAME"].Value;
aDoc.CustomDocumentProperties["FISHORTNAME"].Value = "HanselBank";
string newPropertyValue = aDoc.CustomDocumentProperties["FISHORTNAME"].Value

foreach (Range r in aDoc.StoryRanges)
{
foreach (Field b in r.Fields)
{
b.Update();
}
}

See how all the crap that was originally reflection gets dispatched dynamically? But that's not even that great an example.

Word and Excel Automation with C# 4

That's just an example from Jonathan Carter and Jason Olson that gets running processes (using LINQ, woot) then makes a chart in Excel, then puts the chart in Word.

using System;
using System.Diagnostics;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;

namespace One.SimplifyingYourCodeWithCSharp
{
class Program
{
static void Main(string[] args)
{
GenerateChart(copyToWord: true);
}

static void GenerateChart(bool copyToWord = false)
{
var excel = new Excel.Application();
excel.Visible = true;
excel.Workbooks.Add();

excel.get_Range("A1").Value2 = "Process Name";
excel.get_Range("B1").Value2 = "Memory Usage";

var processes = Process.GetProcesses()
.OrderByDescending(p => p.WorkingSet64)
.Take(10);
int i = 2;
foreach (var p in processes)
{
excel.get_Range("A" + i).Value2 = p.ProcessName;
excel.get_Range("B" + i).Value2 = p.WorkingSet64;
i++;
}

Excel.Range range = excel.get_Range("A1");
Excel.Chart chart = (Excel.Chart)excel.ActiveWorkbook.Charts.Add(
After: excel.ActiveSheet);

chart.ChartWizard(Source: range.CurrentRegion,
Title: "Memory Usage in " + Environment.MachineName);

chart.ChartStyle = 45;
chart.CopyPicture(Excel.XlPictureAppearance.xlScreen,
Excel.XlCopyPictureFormat.xlBitmap,
Excel.XlPictureAppearance.xlScreen);

if (copyToWord)
{
var word = new Word.Application();
word.Visible = true;
word.Documents.Add();

word.Selection.Paste();
}
}
}
}

Notice the named parameters in C#, like 'Title: "whatever"' and "copyToWord: true"?

PIAs no long stand for Pain in the *ss - Type Equivalence and Embedded Interop Assemblies

Primary Interop Assemblies are .NET assemblies that bridge the gap between a .NET app and a COM server. They are also a PIA, ahem. When there's articles about your technology on the web called "Common Pitfalls With ______" you know there's trouble.

Typically you reference these Interop assemblies in Visual Studio and they show up, predictably, as references in your assembly. Here's a screenshot with the project on the right, and the assembly under Reflector on the left.

image

This means that those PIAs better be deployed on the end user's machine. Some PIAs can be as large as 20 megs or more! That's because for each COM interface, struct, enum, etc, there is a managed equivalent for marshalling data. That sucks, especially if I just want to make a chart and I'm not using any other types. It's great that Office has thousands of types, but don't make me carry them all around.

However, now I can click on Properties for these references and click Embed Interop Types = true. Now, check the screenshot. The references are gone and new types have appeared.

 image

Just the types I was using are now embedded within my application. However, since the types are equivalent, the runtime handles this fact and we don't have to do anything.

This all adds up to Office and COM Interop that is actually fun to do. Ok, maybe not fun, but better than a really bad paper cut. Huh, Jeremiah? ;)

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

ASP 4 - Whirlwind Tour around .NET 4 (and Visual Studio 2010) Beta 1

May 20, 2009 Comment on this post [14] Posted in ASP.NET | ASP.NET Dynamic Data | ASP.NET MVC | TechEd
Sponsored By

Hey, we released Visual Studio 2010 Beta 1. JasonZ has a great post with piles of details and a metric crapload of screenshots. Definitely take a moment (or eight) and check out his very detailed post. You can get it NOW if you're an MSDN Subscriber, or after the general announcement on Weds if you're not.

UPDATE: My bad, of course it's called ASP.NET 4 and not ASP 4. Sorry, typo.

Should You Freak Out?

I don't think so. There's a lot of stuff that's new and added in .NET 4, but not in that "overwhelming-I-need-to-relearn-everything" way. More in that, "oh, this is way easier/simpler now" way. Like, moving data around with ADO.NET DataServices is easy, binding with client templates is easy, there's F# if you need it, the "dynamic" keyword if you need it, Silverlight's already installed, oh, and the parallel stuff will freak you out, but in a good way.

I'll do a series of posts on what I think is cool with lots of details. I'll put code samples up also as I can. I will also point out where you can already do much of this with 3.5 SP1, as well.

Here's a general outline of a small part of the goodness:

  • New Shell, new File|New, new Extensions Manager
  • Lots of focus on "Code First" (TDD-friendly, etc)
  • Office and COM Interop that is actually fun to do.
  • Automatic Properties for VB, no more "_" for multi-line, inline subs, collection initializers
  • C# gets the dynamic keyword
  • F# is included out of the box
  • WPF Databinding in the Designer
  • Silverlight included out of the box
  • Piles of HTML Snippets for non-designer people
  • Better JavaScript Intellisense
  • MSBuild for C++
  • UML for VS Team Architecture
  • TFS Branch and Changeset visualizers
  • Parallel computing
  • Workflow speedup, new designer
  • SharePoint tooling

Rather than try to top Jason with a laundry list of what's new and what not, here's some details on a few things that I find particularly interesting. They may be obscure to you, and perhaps not deeply interesting, but they were interesting enough to me that I used them for demos at TechEd last week (with a hat tip to Jonathan Carter and Jason Olson).

This is just a smattering of the features (hence: "Whirlwind" although "totally random cool stuff" would have also worked) coming in .NET 4. These are features I'm interested in because they've solved problems I've had in the past.

I'm realizing I'll definitely do these as separate posts because they're going to get long. First, ASP.NET and Ajax.

ASP.NET 4

There is an excellent whitepaper at http://www.asp.net/learn/whitepapers/aspnet40/ with a lot of detail on the changes in ASP.NET 4.

There's lots new in ASP.NET 4, but at TechEd09 I showed two. First was control over my controls' client ids. Rather than getting a generated ID like ctl09_list45_whatever99, I can make my control ids more predictable.

More Control over ClientIDs in WebForms

For example, here's a ListView (ol/li) of Television shows. It uses jQuery to be sortable. Notice the new attributes in asp:ListView, specifically ClientIDRowSuffix and ClientIDMode.

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Television Series Picker</title>
<link href="Default.css" rel="stylesheet" type="text/css" />
<script src="jquery-1.3.2.min.js" type="text/javascript"></script>
<script src="ui.core.js" type="text/javascript"></script>
<script src="ui.sortable.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function() {
$("#televisionList li").append("<div><span>Click</span></div>");
$("#televisionList").sortable({ handle: $("#televisionList li div") });
});
</script>
</head>
<body>
<form runat="server">
<h1>Television Series Picker</h1>
<p>Order the following television series' based on which you think is most awesome:</p>
<asp:ListView
DataSourceID="televisionDataSource"
ClientIDRowSuffix="ID"
ClientIDMode="Predictable"
runat="server">
<LayoutTemplate>
<ol id="televisionList">
<asp:PlaceHolder ID="itemPlaceHolder" runat="server" />
</ol>
</LayoutTemplate>
<ItemTemplate>
<li id="televisionItem" runat="server">
<%# Eval("Name") %>
</li>
</ItemTemplate>
</asp:ListView>
<asp:ObjectDataSource
ID="televisionDataSource"
TypeName="_01_ClientId.TelevisionDataProvider"
SelectMethod="GetTelevisionSeries"
runat="server" />
</form>
</body>
</html>

The list is populated using an ObjectDataSource and I want the client id's to be created with a suffix using the ID property from the Television object.

Routing and WebForms

You probably know that System.Web.Routing is a big part of ASP.NET MVC, and you may know it's in .NET 3.5 SP1 as well. In .NET 4 it'll be even more easy to use with better support for WebForms. For example:

public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add("Product",
new Route("Products/{category}",
new PageRouteHandler("~/Products.aspx")));
}
}

Here I'm setting update a route such that /Products/SomeCategory routes to the /Products.aspx WebForms Page. The Category parameter is a RouteParameter and can be retrieved (of course) and used. System.Web.UI.Page has a RouteData parameter now, or you can refer to RouteValues in your ASPX markup. For example:

<p>The following are all the products within this category:</p>
<h2><asp:Literal Text="<%$ RouteValue:Category %>" runat="server" /></h2>

or into a Data Source via <RouteParameter>:

<asp:EntityDataSource
ID="productsDataSource"
ContextTypeName="AdventureWorksLTEntities"
EntitySetName="Products"
OrderBy="it.Name"
Where="it.Category.Name = @Category"
runat="server">
<WhereParameters>
<asp:RouteParameter
Name="Category"
RouteKey="Category"
Type="String" />
</WhereParameters>
</asp:EntityDataSource>

There will also be Routing Extension Methods like GetUrlForRoute or the like, in Beta 2, as well as IgnoreRoute. Basically anything that makes it simple, simple, simple to use Routes with WebForms. This'll be nice for hybrid WebForms/MVC apps as well.

Ajax 4

The Client Templates stuff in Ajax 4 is pretty sweet. This allows for 2-way data-binding using only JavaScript. For example, this template pulls from a JSON DataService at customers.svc. The JavaScript is below. We create a DataContext pointing to that endpoint, two DataViews that take the "customers-template" and "customer-template" HTML elements and creates a binding between them.

Sys.Application.add_init(function()
{
var dataContext = $create(Sys.Data.AdoNetDataContext,
{
serviceUri: "Customers.svc"
});

var customersTemplate = $create(Sys.UI.DataView,
{
dataProvider: dataContext,
fetchOperation: "Customers",
fetchParameters: { $top: 20, $orderby: "FirstName", $expand: "Orders" },
initialSelectedIndex: 0,
selectedItemClass: "selected"
},
null, null,
$get("customers-template"));

var customerTemplate = $create(Sys.UI.DataView,
null, null, null,
$get("customer-template"));

// This imperatively creates the binding
// between the customer grid and the customer
// detail form. When you select a record in
// the grid, it will automatically re-bind
// the detail view as well.
$create(Sys.Binding,
{
defaultValue: null,
source: customersTemplate,
path: "selectedData",
target: customerTemplate,
targetProperty: "data"
});

// This uses jQuery live event bindings to hook up
// the click event for the update button that will be
// "generated" when the customer detail form is bound.
$("#update-button").live("click", function()
{
dataContext.saveChanges();
});
});

Then we use jQuery live events, and then save the changes back to the dataContext. The changes that are made on the client side are tracked automatically, and those changes are sent back via JSON and commited. Note the source: and target: in the $create() call above that sets the master/detail relationship between customers and customer (singular.)

The client-side templates are similar to the server-side templates you probably already know how to use. You can even use expressions like to conditionally apply CSS.

image

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>AdventureWorks AJAX</title>
<link href="Content/Styles/reset-min.css" rel="stylesheet" type="text/css" />
<link href="Content/Styles/Default.css" rel="stylesheet" type="text/css" />

<script src="Content/Scripts/MicrosoftAjax.debug.js" type="text/javascript"></script>
<script src="Content/Scripts/MicrosoftAjaxTemplates.debug.js" type="text/javascript"></script>
<script src="Content/Scripts/MicrosoftAjaxAdoNet.debug.js" type="text/javascript"></script>
<script src="Content/Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>

<script src="Content/Scripts/Default.js" type="text/javascript"></script>
</head>
<body
xmlns:sys="javascript:Sys"
xmlns:class="http://schemas.microsoft.com/aspnet/class">
<h1>Customer Directory</h1>
<table cellspacing="0">
<thead>
<tr>
<th>Title</th>
<th>First Name</th>
<th>Middle Name</th>
<th>Last Name</th>
<th>Suffix</th>
</tr>
</thead>
<tbody id="customers-template" class="sys-template">
<tr sys:command="select" class:odd="{{ $index % 2 != 0 }}">
<td>{binding Title, defaultValue=}</td>
<td>{binding FirstName}</td>
<td>{binding MiddleName, defaultValue=}</td>
<td>{binding LastName}</td>
<td>{binding Suffix, defaultValue=}</td>
</tr>
</tbody>
</table>
<fieldset id="customer-template" class="sys-template">
<legend>{{ FirstName + " " + LastName }}</legend>
<label for="first-name">
<span>First Name:</span>
<input type="text" id="first-name" value="{binding FirstName}" />
</label>
<label for="middle-name">
<span>Middle Name:</span>
<input type="text" id="middle-name" value="{binding MiddleName, defaultValue=}" />
</label>
<label for="last-name">
<span>Last Name:</span>
<input type="text" id="last-name" value="{binding LastName}" />
</label>
<input id="update-button" type="button" value="Update" />
</fieldset>
</body>
</html>

This exact scenario can also be done declaratively with you having to write ANY JavaScript at all. The "declarations" from the JavaScript file above are replaced with declarative namespace statements like:

<fieldset
class="sys-template"
sys:attach="dataview"
dataview:data="{binding selectedData, source={{customersTemplate}}}"
dataview:sys-key="customerTemplate">
<legend>{{ FirstName + " " + LastName }}</legend>
...

And the result is the same as above:

image

It's important to point out that while you can go get the VS 2010 Beta 1, you can also play with this stuff TODAY running on .NET 3.5 SP1. Go over to the ASP.NET 4 Ajax Preview site on CodePlex.

Also, don't freak out that ASP.NET MVC isn't baked into VS2010 Beta 1. Phil explains here.

Related Links

Have fun!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.