August 2004 - Posts

Hey, SourceForge doesn't work sometimes either!!!

Note: this entry has moved.

Just to be fair to the complete fiasco that is GDN, today is the *first time ever* that I see SourceForge *partially* broken.

Just to be fair to SF, however, please notice that even if the admin features (which require a login) are not working, I don't get a completely useless page, but can continue to use the features that do work:

ASP.NET v2: how to remove an amazing feature and replace it with a bad, already tried and failed one

Note: this entry has moved.

Before I get to the point where I expect to convince you of what we're loosing with ASP.NET v2, I'll make a brief recap, just in case you haven't used the technique in question.

What are components?

Components, in the strict .NET sence, is anything that directly or indirectly implements IComponent. You'd be surprised that a lot of classes in .NET (specially visual ones, such as System.Web.UI.Control or System.Windows.Forms.Control) implement IComponent. One of the consequences of a class being a component is that the IDE can offer additional features to it at design time. The key property for the IDE to offer services to components is the IComponent.Site. A so-called sited component is one that has been placed in a Container. This containment is general and is not related to visual containment.

For example, an ASP.NET server control, when dropped in a Web Form, is said to be sited. Its Site property (part of the implementation of the IComponent interface), is set to the host where the component lives now, which inside VS .NET is an instance of the Microsoft.VisualStudio.Designer.Host.DesignSite class. Exactly the same object type is assigned as the Site property of a Windows Forms user control when dropped in the forms designer, and to a non-visual component at design-time.

The Site property, of type ISite, contains members that allow the component to communicate with other components, with its container (a logical container) and services provided by it. The container is an object that implements the System.ComponentModel.IContainer interface. At design-time, the container is always an instance of the Microsoft.VisualStudio.Designer.Host.DesignerHost. This object is the core of VS .NET IDE features for components.

Now, in ASP.NET 1.x, you have a clear distinction between visual components such as controls and non-visual ones, which appear in a separate area called the "component tray". In the highly unlikely event that you never saw it before, here's how it looks like:

Why are they cool?

One up-front benefit of non-visual components and the way ASP.NET *and* Windows Forms handle them (up to now, both handle them in the same uniform way), is that they don't clutter the visual design of the form with items that are not supposed to be rendered.

Other important benefits are:

  1. Component designers: you can assign a designer to a component, which will enhance the component behavior at design time. Many of the following points are related to designers.
  2. Designer verbs, wizards: a component designer can expose so called designer verbs so that the user can trigger pottentially complex actions or even a wizard that will aid him in configuring and using the component. A well-known case of this is the xxDataAdapter components, which allow you to do quite complex manipulation of the data access strategy. The following picture shows a component I developed that aids the user in checking bindings defined for a component, as well as editing them with a UI.


  3. Customize code generation: code generated for the InitializeComponent can be customized at will, up to the point where you can directly generate CodeDom statements for it. This means the complex component configuration can be translated into code (that will be compiled and very performant, therefore) that will be reloaded at run-time. And all the logic is encapsulated directly a separate class that acts as the CodeDomSerializer for the component, meaning that the component logic isn't even tied to the "serialization" format. You could even store the object's state in an XML file or anything else. Here's the code serialization for the component above:


  4. Through 3., participate in UI lifecycle: through the custom code that can be emited, a component can easily attach itself to relevant events in the UI containing it (i.e. a WebForm's Load or PreRender stages), and perform additional automatic processing, all without the user having to write a single line of code. In ASP.NET, this means for example, that a component can attach itself to the PreRender event, and add attributes and code to other controls on the page (for example, to setup required information for a client-side Javascript-based UI framework....). The component above, through the WebFormsAdapter connects the controller Init method with the page Init event, the controller RefreshModels method with the Load event, and the RefreshView method with the PreRender event.
  5. Revolutionary extension of existing components: we can even go further and implement AOP (Aspect Oriented Programmings) by directly adding "new" properties on other components/controls. This is the IExtenderProvider interface, which you could probably see applied in the Tooltip Windows Forms component. When you drop such a component on a WinForm, you see it in the component tray, and automatically all controls get a new property named after the name of the component. You can configure tooltip feature for all controls on the page on a single place, the component itself, and apply selective tooltip messages to each control you want.
    The component shown above, the publisherControler, adds view-model mapping capabilities to all built-in and third-party controls on a page automatically:


  6. Component root designers and authoring: components can be composed graphically by dragging and dropping other components on a default design surface area. This results in RAD development of composite components by assembling the features of several constituent parts. You can even go and implement your own root designer, providing a more-compelling design-time experience. Simple attributes allow you to control the ToolBox, so that, for example, only certain components can be dropped on your control design surface. The following picture shows the root designer for the controller component, which allows you to only drop models on it:


  7. You can go even further and hook into the serialization process of other components, by using the IDesignerSerializationManager to register your own IDesignerSerializationProvider. This way, you could for example add arbitrary attributes to all controls on the page, *at design time*, that is, by emitting additional code for the InitializeComponent method. This makes for an ultimate AOP-enabler, combined with IExtenderProvider.

What's going on in Whidbey?

You would naturally expect for such a cool technology to be extender, further supported and exploited in the new version, right? Well, it turns out that the ASP.NET team is conciounsly stopping to support the feature in the new Web Forms designer.

One of the reasons they've given in the public bug database (if you haven't used MSDN Product Feedback yet, you definitely should!!!) is:

The web forms designer has changed to support only ASP.NET controls. It no longer supports component style controls.

There's a fundamental flaw in this reasoning. Components are *not* controls. They are not visual things, neither they are intended to be laid out together with the controls on the page, which DO need to be arranged for visually coherence and UI rendering. MS's comment on the bug goes on and says:

The primary reason for this change is the newer architecture provides a cleaner and more robust model for persisting controls in the page

Again, the reason is that they see everything as a control. What this means for you as a web app developer, is that instead of having a clean UI surface for laying out controls and designing the graphics aspects of your page, you get something like the following:

This is not an unreasonable example. You have a SiteMapDataSource datasource, for a navigation control, next you have an XmlDataSource probably for a treeview, and a SqlDataSource to render data on the GridView. Finally, as there's a WebPart, you need to drop a WebPartManager too. The WebPartManager is the extreme case, as you even have to drop it *before* any webpart in document order in order for it to work!!!!

Now, is it just me or this sounds a lot like a *HUGE* step backwards to the Visual InterDev days and before? It's clear to me that this means I'll be moving those "non-visual-controls" around so they don't mess with my page design experience. But it's worse than you think, you can't even set their top/left to -100!!! As they are not visual controls, you can't set any of the regular visual attributes :S. Ups!

The reason for deprecating this feature goes on:

We no longer rely on an "InitializeComponent" section, and there is not "tools generated" code ever. In the past we had many bugs around InitializeComponent not working correctly and the model was very brittle.

As I noticed before, being able to generate code from a component is a very powerful feature, and the ability to extend other controls from the outside (Aspect Oriented Programming, does it ring a bell?) is amazing, despite it wasn't publicized enough. Now, if there are bugs around the InitializeComponent method, the reasonable thing to do seems to be to go fix them, don't you think?

I complained this is a breaking change on ASP.NET v2, but they say:

You can still use components within a webform, however you have to do so in code, and the tray is not supported. You can still used components within classes in your web project however. To do so you can create a class that inherits from IComponent and place it in the code directory. You should then be able to open that class using the component designer and place components on it.

So, in order not to break existing apps, they just left the runtime support, but removed the design-time experience on WebForms. What's important to realize, though, is that the design-time experience, and the aids they represent to the users is that makes components so compelling. Building applications by composing blocks (components) into bigger components (i.e. dropping models on controllers, and controllers on pages) is just what some MS architects have been preaching, yet we have to see the ASP.NET team deprecating one of the most revolutionary steps in that direction.

And just in case I didn't stress it enough: ASP.NET Controls are obviously an ASP.NET-only technology, unlike Components, which can be used without changed in both WebForms and WinForms!!!

Call to action

It's not too late. Many developers already voted on the bug to keep the feature. We're not still at Beta2, so I believe if we put enough preasure, we may be lucky enough to get it back.

Go to the MSDN Product Feedback bug page and vote NOW. Otherwise, don't complain next year if you see this feature missing!

Strongly-typed, event-rising, design-time generated custom XmlSerializers (even more than Whidbey sgen!)

Note: this entry has moved.

Summary

There are a couple known issues with the XmlSerializer:

  1. First-time use is painfully slow, because of the temporary assembly generation and compilation.
  2. If you want to do something else other than straight serialization, there's no easy way of customizing it.

Whidbey will bring a solution to the first, in the form of the "sgen" tool, which will generate at design-time the serialization assembly. I still haven't looked at the solutions it offers for the second issue.

So, are you condemned to waiting 'till Whidbey? Are you condemned to either choose between the straightforward-but-almost-impossible-to-customize XmlSerializer approach and endless lines of XmlReader/XmlDocument/XPath code to do almost the same yet generally with less performance just to gain the flexibility you need? Well, I turns out that I am a firmly believer on automating boring and repetitive tasks, so I'm not happy at all with people choosing the later, because I believe it leads to hard to maintain, inflexible, and really ugly code. You waste so much effort that would be better put to work on creative stuff, and making your app great. And performance is always a big issue, at least in my view.

This article explains how to achieve design-time XmlSerializer generation, how it's customized to allow for the stuff you always thought impossible, such as getting events for each kind of object being deserialized.

Recall that I wrote this code in 4 hours as I waited for my plane to Redmond on the airport :o), so it's not the most gorgeous piece of code you'll see coming from me, but it certainly gets a pretty cool job done!

Near the end of a previous article I said it was not possible to customize the XmlSerializer to use something else other than the built-in dynamically generated code. I was wrong. What follows is an explanation of how I achieved what seemed impossible.

Usage

Right now, this is a command-line utility. Its arguments are:

SGen.exe fullTypeName assemblyFile targetNamespace outputFileName

First argument is the namespace-qualified name of the type you'll be using with the XmlSerializer. The second argument is the file name of the assembly containing the type. Next follows the namespace you want to put the generated code into. And finally you can specify the file name to write the generated code to.

The output of the tool is a set of classes you can use for XML serialization, which not only allows you to avoid the run-time impact of temporary assembly generation, but also allows you to attach to events that are exposed for each element that will be deserialized. If you had a class called Order like the following (presumably generated from an XSD):

public class Customer {   // Members plus optional XML serialization attributes }

You would get the following classes:

  • CustomerReader: a class inheriting from the XmlSerializationReader-derived class generated by the XmlSerializer, also included in the file but as an inner class of the CustomerSerializer.
  • CustomerWriter:  a class inheriting from the XmlSerializationWriter-derived class generated by the XmlSerializer, also included in the file but as an inner class of the CustomerSerializer too.
  • CustomerSerializer: XmlSerializer-derived class that allows you to pass the two previous custom classes for serialization, in order to skip the dynamic code generation.
  • CustomerDeserializedHandler: handler for an event exposed by the OrderReader, called CustomerDeserialized, which you can attach to in order to perform additional processing when deserialization is done.

The custom serializer is generated basically to allow for the custom reader/writer classes to be passed-in, as the XmlSerializer class itself doesn't allow for this, but provides the hook methods CreateReader and CreateWriter, as well Serialize and Deserialize overloads receiving the result of those method calls. So in order to deserialize an order class, you instantiate the reader, pass it to the custom serializer, and call Deserialize as usual. You can avoid constructing the custom reader at all, as by default I'll create a new one when needed. You will need the reader variable, though, in order to attach to the events it fires.

 CustomerSerializer os = new CustomerSerializer(); 
object customer = os.Deserialize(inputReader);

The same process would be done for serializing the object. At this point you already saved a *huge* amount of processing for the initial hit on this class upon deserialization. And this impact is higher as the object to deserialize is more complex.

But there's more to this generation process than just performance boost. Let's say the Customer class, among other properties, has one of type Order, and then a collection of Item. The custom reader would expose an event for each of them, so you can perform additional processing. So you could do the following:

 public void Test() 
{
XmlTextReader tr = new XmlTextReader(GetInputStream());
// Typed reader.
CustomerReader reader = new CustomerReader();
// Attach to events for each object!
reader.CustomerDeserialized += new CustomerDeserializedHandler(OnCustomerDeserialized);
reader.OrderDeserialized += new OrderDeserializedHandler(OnOrderDeserialized);
reader.ItemDeserialized += new ItemDeserializedHandler(OnItemDeserialized);

// Custom serializer receiving the custom reader.
CustomerSerializer serializer = new CustomerSerializer(reader);
// Deserialize as usual, but all event handlers called while deserializing!
object customer = serializer.Deserialize(tr);
// Do something with the order...
}

private void OnCustomerDeserialized(Customer customer)
{
Console.WriteLine(customer.FirstName);
}
private void OnOrderDeserialized(Order order)
{
Console.WriteLine(order.Id);
}
private void OnItemDeserialized(Item item)
{
Console.WriteLine(item.Price);
}

You can even use this event callbacks as a more programmer-friendly approach to XML processing than the lower-level XmlReader. You just have to create the XSD, generate classes with xsd.exe or something better, and use the SGen generated reader. You just attach to the events for each element you're interested in, and process it using friendly properties/fields instead of XmlReader.Value, XmlReader.GetAttribute and the like.

You can easily set this tool to run as a post-build event, by setting appropriate project field to:

..\..\..\SGen\bin\Debug\SGen SGen.Tests.Customer SGen.Tests.dll SGen.Tests.Serialization ..\..\CustomerSerialization.cs

Implementation

The code generated by the XmlSerializer can be kept around by using the technique explained in a previous post. Using that approach, the SGen utility instantiates an XmlSerializer passing the type you specify as arguments. After that, using hacky reflection, it retrieves the temporary code location burned deep inside the XmlSerializer private members and internal classes. Afterwards, using a mix of CodeDom and raw string manipulation, the final code is generated. I've done quite a bit of regular expressions-based parsing too, and there's a region called "Code templates" that provide the skeleton for the generation process.

I didn't create everything using CodeDom because the XmlSerializer generates just C# output, therefore, there wasn't much benefit in trying to do everything the "right" way. The code and the regular expressions are pretty nasty for the non-accustomed eye, so I'll save you the trouble and instead point you to the respective code download.

What's worth seeing is how the XmlSerializer is being extended:

 /// Custom serializer for Order type.Constructs the serializer with a pre-built reader. 
public OrderSerializer(OrderReader reader)
{
_reader = reader;
}

 /// Constructs the serializer with a pre-writer reader.
public OrderSerializer(OrderWriter writer)
{
_writer = writer;
}

 /// Constructs the serializer with pre-built reader and writer.
public OrderSerializer(OrderReader reader, OrderWriter writer)
{
_reader = reader;
_writer = writer;
}

/// <summary>See <see cref="XmlSerializer.CreateReader"/>.</summary>
protected override XmlSerializationReader CreateReader()
{
if (_reader != null)
return _reader;
else
return new CustomerReader();
}

/// <summary>See <see cref="XmlSerializer.CreateWriter"/>.</summary>
protected override XmlSerializationWriter CreateWriter()
{
if (_writer != null)
return _writer;
else
return new CustomerWriter();
}

/// <summary>See <see cref="XmlSerializer.Deserialize"/>.</summary>
protected override object Deserialize(XmlSerializationReader reader)
{
if (!(reader is CustomerReader))
throw new ArgumentException("reader");

return ((CustomerReader)reader).Read();
}

/// <summary>See <see cref="XmlSerializer.Serialize"/>.</summary>
protected override void Serialize(object o, XmlSerializationWriter writer)
{
if (!(writer is CustomerWriter))
throw new ArgumentException("writer");

((CustomerWriter)writer).Write((Mvp.Xml.Design.Tests.Customer)o);
}

// Inner XmlSerializer-generated reader and writer classes go on

So it *was* extensible in the end, right? It's a little bit cumbersome, but it's possible to extend it as you can see.

Don't forget to download the tool and play with it!

Update: now you can simply use a Custom Tool and get the generated class automatically and easily!

Behave properly on your posts

Note: this entry has moved.

It's rewarding to see other bloggers using the same approach I've been using for quite a while for collapsing code regions that maybe be of interest of those who want a closer look at what you've done, but not to all readers, specially on the aggregated site.
If we could just get everyone else using the same, the main page would be much more pleasant. If you want to join this crusade, start using the following for your collapsible HTML regions: <div> <span style="font-weight: bold; color: white; background-color: blue; cursor: pointer" onclick="var div=document.getElementById('YourCodeListingDivId');if(div.style.display=='none'){div.style.display='block';this.innerHTML='&nbsp;-&nbsp;';}else{div.style.display='none';this.innerHTML='+';}">+</span> <!-- Some text next to the +|- "link" explaining what's on the hidden region --> Expand for some cool code. <div id="YourCodeListingDivId" style="display:none"> ...your code... </div> </div>
More Posts

Search

Go

This Blog

News

     

      Microsoft MVP Profile

Syndication