Jesse Johnston
.NET Dev. Good times with .NET and coffee

Silverlight 3 Navigation: My HyperlinkButtons don’t work!

Sunday, 17 January 2010 15:30 by jesse

The navigation framework of Silverlight 3 is great.  The URI mapping and browser history support add a lot of value.

When upgrading a Silverlight 2 site to use Silverlight 3 with navigation, I was surprised to see that all of my HyperlinkButtons that referenced external URLs were failing!

For example, a button with this XAML:

<HyperlinkButton Content="Microsoft" NavigateUri="http://www.microsoft.com"/>

when clicked yields this:

image

It turns out that once your HyperlinkButton is inside of a navigation frame (which it will be once you add navigation support to your application), you need to set the TargetName property of the button.  To preserve the Silverlight 2 behavior where the referenced URI loads in the same browser window, use “_self” as the TargetName:

<HyperlinkButton Content="Microsoft" NavigateUri="http://www.microsoft.com" TargetName="_self"/>

You can also use “_blank” to open up the page in a new browser window.

When using a HyperlinkButton to navigate to a Silverlight page internal to your application, TargetName should reference the name of the navigation frame:

<navigation:Frame x:Name="ContentFrame" Source="/home">
  <navigation:Frame.UriMapper>
    <uriMapper:UriMapper>
      <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
      <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/{pageName}.xaml"/>
    </uriMapper:UriMapper>
  </navigation:Frame.UriMapper>
</navigation:Frame>
 
<HyperlinkButton NavigateUri="/about" TargetName="ContentFrame" Content="About Us"/>
 

HyperlinkButtons fixed!

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET | Silverlight
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

.NET 101

Monday, 26 October 2009 05:10 by jesse

If you find yourself wanting yourself to share some C# brilliance related to…images

  • The difference between Object.Equals() and ==
  • How string comparison is an interesting special case
  • Why class instance memory is not necessarily reclaimed when an instance goes out of scope
  • Which of Stream.Close() and Stream.Dispose() should be called (or both)

Please remember that it is not 2001.  Everyone knows these things (unless you’re in a .NET 101 class).  If you don’t know these things…may I suggest .NET U?

Seriously, though – how about something interesting like harnessing the power of IObservable<T>?  Bring on the new!

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (1) | Comment RSSRSS comment feed

Where are the recent items in my Windows 7 jump list?

Thursday, 1 October 2009 11:49 by jesse

I just started adding support for the Windows 7 taskbar jumplist to my WPF application, using the excellent .NET wrappers in the Windows API Code Pack.

After creating the jump list, file selection via the Windows common file dialogs should automatically cause entries to appear in the Recent section of the jump list.

But no.  What's going on?

It turns out that everything is working as expected.  However, you need to have the "recent items" checkbox enabled for the taskbar:

 

 
Once clicked, the Recent items appear on the jump list. 
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET | WPF
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Scrolling TextBox in WPF

Thursday, 1 October 2009 03:03 by jesse

If you put a TextBox inside a ScrollViewer, the TextBox will take as much width as it needs, even if you set TextWrapping to Wrap.  That means that a long line of text will just stretch out to the right, beyond the right edge of the ScrollViewer, instead of wrapping.

Fortunately, you can bind the Width of the TextBox to the ScrollViewer ViewportWidth.  This makes the text wrap at the right edge of the ScrollViewer.  If you hide the horizontal scrollbar, you get just what you'd expect - vertical scrolling and horizontal wrapping.  This also allows the ScrollViewer to participate in a flow style layout and still have the text wrap correctly.

<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">

<TextBox IsReadOnly="True" FontSize="14" TextWrapping="Wrap" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollViewer}}, Path=ViewportWidth}"/>

</ScrollViewer>

 

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET | Silverlight | WPF
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

code2plan Beta Release

Wednesday, 7 January 2009 16:48 by jesse

As a few of you might have seen, my side project code2plan is finally in beta.

code2plan is a free agile software project management application that is available as a Visual Studio 2008 add-in, and also as a standalone Windows desktop application.  Both versions have the same feature set, and both are WPF applications.  The program supports single users as well as supporting a shared database for teams.  You can work offline (no network connection required), and when you do sync up, a very straightforward conflict resolution feature helps you manage changes that you and other team members have made to the same data.

If you're an agile .NET developer, I would really appreciate your feedback on the program - both good and bad.  We'll be releasing new versions of the application very frequently, so there's a good chance that if code2plan doesn't work for you right now, it may very soon.  Just let me know.  Please post your comments to the forums on the code2plan web site so that everyone can benefit from the discussion.

I'll be posting here regularly over the coming weeks about the code2plan architecture, design thinking, and future plans.

Happy New Year!

Jesse

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Tags:  
Categories:   .NET | code2plan
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Using SQL Server 2008 Express as a default instance

Saturday, 13 September 2008 17:46 by jesse

Time to upgrade!  After uninstalling SQL Server 2005, I decided that I would try running only the Express version of SQL Server 2008 on my laptop.  After installing, I found that running SQLCMD against the (local) server no longer worked.  For example,

sqlcmd -s (local) -i SomeScript.sql

Resulted in this error:

HResult 0x2, Level 16, State 1
Named Pipes Provider: Could not open a connection to SQL Server [2].
Sqlcmd: Error: Microsoft SQL Server Native Client 10.0 : A network-related or instance-specific error has occurred while establishing a connection to SQL Server. Server is not found or not accessible. Check if instance name is correct and if SQL Server is configured to allow remote connections. For more information see SQL Server Books Online.
Sqlcmd: Error: Microsoft SQL Server Native Client 10.0 : Login timeout expired.

I had installed SQL Server 2008 Express as the default instance (and I don't have any other version of SQL Server on this machine), and accepted the default instance name SQLEXPRESS.

SQLCMD is connecting to the instance through named pipes, and the pipe name it connects on for the default instance is \\.\pipe\sql\query

To connect to a named instance, it connects through the named pipe \\.\pipe\MSSQL$<instancename>\sql\query

Sure enough, after opening Sql Server Configuration Manager, I expanded the Sql Server Network Configuration, double-clicked on Named Pipes, and was able to see that the pipe name included the instance name SQLEXPRESS.  After changing the pipe name to \\.\pipe\sql\query, my sqlcmd reference to (local) worked great.  Whew.

Named Pipes Properties

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Tags:  
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (2) | Comment RSSRSS comment feed

ClickOnce Error after upgrading to VS 2008

Thursday, 29 November 2007 22:04 by jesse

We recently upgraded to VS 2008 RTM at work, and shortly after discovered that a new build of our ClickOnce-deployed WinForms application failed to install correctly!  During the download process, a message box would display, and the log details revealed an InvalidDeploymentException (RefDefValidation) in System.Deployment.Application.DownloadManager.ProcessDownloadedFile().

Let me just say that days of fruitless investigation followed.

I'm happy to report that I finally did find the solution (completely by accident).  A new option has been added to Visual Studio, allowing you to automatically create and embed a manifest in your application.  This manifest allows you to provide proper application behavior when running under UAC in Vista.  Here's the option, on the Application property page of the Visual Studio project:Manifest Options

When you upgrade a WinForms project from VS2005 to VS2008, this option is automatically set for you.  If you had identified the application as a ClickOnce application in VS 2005, the resulting deployment from a VS2008 build works correctly.

Real-world ClickOnce

If you're like me, you're developing in a professional environment where you can't just click the Publish Now button and send an app from your development box into production.  Is it just me, or does everyone think that's insane?  Do you think we might want to QA those phasers first, Captain?

We have a relatively complex build that builds our ClickOnce manifests based on the target environment (Dev, QA, or Production) so that we can test ClickOnce updates from previous versions before going to production.  Also, since we've had our ClickOnce app deployed for more than one year, we had to overcome the certificate renewal problem, which requires a special signing process using both the expired and the current signing certificate.  Needless to say, neither of these can be set up in Visual Studio.  What we ended up with is a build where the ClickOnce options are not set in the Visual Studio project, but externally in an MSBuild project file, using many of the built-in targets that VS uses.

Migrating from VS2005

When the project was converted to VS2008, it was upgraded by the wizard as if it wasn't a ClickOnce project.  This means that the manifest setting was set incorrectly.

Here's what the Visual Studio documentation has to say about the manifest setting:

 

Manifest
Selects a manifest generation option when the application runs on Windows Vista under User Account Control (UAC). This option can have the following values:
  • Embed manifest with default settings. Supports the typical manner in which Visual Studio operates on Windows Vista, which is to embed security information in the application's executable file, specifying that requestedExecutionLevel be AsInvoker. This is the default option.
  • Create application without a manifest. This method is known as virtualization. Use this option for compatibility with earlier applications.
  • Properties\app.manifest. This option is required for applications deployed by ClickOnce or Registration-Free COM. If you publish an application by using ClickOnce deployment, Manifest is automatically set to this option.

The only options I see in the combo box are the first and second.  In our application, "Embed manifest with default settings" was selected by the conversion.  As soon as I chose "Create application without a manifest", I was able to build, deploy, and download with no trouble at all.

I still need to investigate the manifest issue further.  I want my users to have a great UAC experience with our app, and that means getting the manifest right.  For now though, "create without a manifest" is your quick fix.

Click On!

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Tags:  
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Service Locator in Code?

Wednesday, 28 November 2007 03:04 by jesse

Phil Haack posed a question today about dependency injection frameworks:

Normally, I would just specify a type to instantiate as another PluginFamily entry. But what I really want to happen in this case is for StructureMap to call a method or delegate and use the value returned as the constructor argument.

...

Does anyone know if something like this is possible with any of the Dependency Injection frameworks out there? Whether via code or configuration?

I don't have much experience with the big three DI frameworks Castle Windsor, Spring.NET and StructureMap, but what I've seen suggests that these are strongly configuration based.

That means that I can specify a concrete type name in a configuration file that the framework will use to instantiate a particular interface type.  For example, in Spring.NET, you might specify the concrete type like this:

<objects xmlns="http://www.springframework.net">
    <object
        name="TheClassIWant"
        type="MyNamespace.MyConcreteClass, MyAssembly"
    </object>
</objects>

Then in your code, you can ask Spring to create an instance of MyConcreteClass like so:

IApplicationContext ctx = ContextRegistry.GetContext();
MyConcreteClass instance = (MyConcreteClass)ctx.GetObject("TheClassIWant");

That's all well and good.  In Phil's case, though, he wants to specify a way to create an instance of MyConcreteClass in code rather than configuration.  Let's take that requirement more generally and assert that we know what concrete classes we want to instantiate in our application, but just don't want the whole application to know.  Maybe I need an instance of ISomeInterface in some part of my code, but that code doesn't know how to get an ISomeInterface.  What I want is some global mechanism to define how to get an instance of some type, and then a way to get the type.  Now, how do we do that in code?

Simple Service Locator

I had some spare time while at DevConnections, and decided to create just that - a code-based service locator.  The basic idea is that we'll have a ServiceLocator class that maps interface types to concrete types.

At some point in your code, you register a concrete type for an interface:

ServiceLocator locator = new ServiceLocator();
locator.Register<ITest>(typeof(Test));

This means that when we ask for instance of the interface ITest, ServiceLocator will create and return an instance of Test (the implication of course is that Test implements ITest).  Here's how we ask for an ITest:

ITest instance = locator.Get<ITest>();

If you expose a singleton instance of ServiceLocator in your application, you can register your types at startup, and then use the locator to instantiate the concrete types from anywhere in the code.

What about parameterized constructors?

Default constructors are the trivial case.  How do we handle creation of concrete types that require constructor parameters?  We have two cases:

  1. The parameter is an interface that we want ServiceLocator to instantiate
  2. The parameter is a value that we want to provide explicitly.

Here's how we handle the first case:

locator.Register<ICat>(typeof(Cat), typeof(IOwner));
locator.Register<IOwner>(typeof(Owner));
 
ICat instance = locator.Get<ICat>();

Here we're saying that if we want an ICat, we need to create a Cat with the constructor Cat(IOwner).  By registering IOwner to create an Owner, ServiceLocator is able to create a Cat by first instantiating an Owner and passing that value to the Cat constructor.

In the second case, we have some value that ServiceLocator doesn't know how to create.  Let's suppose the Cat constructor takes an IOwner and a string:

locator.Register<IOwner>(typeof(Owner));
locator.Register<ICat>(typeof(Cat), new TypeParameter(typeof(IOwner)), new TypeParameter(typeof(string), "Kitty"));
ICat cat = locator.Get<ICat>();

In this case, ServiceLocator will call the constructor Cat(IOwner, string) with an IOwner it creates, and the string "Kitty".

And Singletons?

Sometimes we always want the same instance of an object:

Cat kitty = new Cat();
locator.Register<ICat>(kitty);
ICat cat = locator.Get<ICat>();

Here we always return the instance kitty when an ICat is requested.  We all know how hard it is to work with a large number of cats.

Le Delegate

Yes, there is a Santa Claus.  I might have some code that is responsible for creating an object.  Perhaps it's responsible for managing a pool of objects, or just has some complexity that would be difficult to express in configuration.  I want to register a delegate that will provide the instance.

locator.Register<ICat>(delegate() { return kitty.IsSleeping ? buffy : kitty; });
ICat cat = locator.Get<ICat>();

Here I provided an anonymous method in the Register() call, but I could just as easily register some other class method.

So there you have it - a configuration-free service locator with quite a bit of flexibility in object creation.

You can download the source for my ServiceLocator here.  The project was built in Visual Studio 2008.

Should I Use it?

If it works for you!  It's free of course.  However, I conjured this up in an hour or two, so it's not likely that ServiceLocator offers anywhere near the capabilities of a full-fledged dependency injection framework.  I'd encourage you to look closely at Castle Windsor, Spring.NET and StructureMap.  I'm sure that there are other great frameworks too.

I'd be grateful for any feedback as always.

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

ObjectListView Update (1.0.0.11): Find, Select, & Property Paths

Sunday, 6 May 2007 23:03 by jesse

Note: You can download the complete implementation here.

I just posted a new version of ObjectListView that fixes a couple of bugs and adds some minor usability enhancements.

I've been using ObjectListView more in different projects, and I'm feeling the pain points.  Foremost, a complete IBindingListView implementation means that interaction with Windows Forms controls works smoothly, but that doesn't mean it's easy for the developer to use.

PropertyDescriptors Everywhere

I don't like working with PropertyDescriptors as method parameters.  In order to find a list item via IBindingList,.Find(), you need to get the descriptors for the list item type from TypeDescriptor, and then look up the correct one by the property name.  Awkward.  Moreover, all that Find() gives you is the first item found for which one property equals a specified value.  I've added two more useful Find() overloads.  One takes a string expression that can be an arbitrarily complex set of property comparisons (using the same syntax as the Filter property).  The other allows you to specify a Predicate delegate to provide a comparison in code.  Given these, the original Find() required for IBindingList seems unlikely to be used, so I changed it to an explicit interface implementation.  This has the effect of hiding the overload from the publicly exposed class methods, but still making it available to consumers of IBindingList.

Select

One of the useful methods of DataTable is Select(), which returns the set of DataRows in the table that match a given criteria.  Now clearly, you could iterate over the items in the view yourself and evaluate each, but having this built in to ObjectListView is very convenient.  As with Find(), I added two versions, one that takes a string expression, and the other that takes a delegate.  The return value of Select is a list of items that match the criteria.

Property Paths

Something I've had in mind for a while is being able to filter on list items based on the values of properties of properties.  You could use FilterPredicate to specify such a filter with a delegate, but it would be even more convenient to use an expression like "Customer.AccountRep.Department = 2".  The new version 1.0.0.11 supports this kind of property path in the Filter property, Find() and Select().  Currently, changing the value of one of the sub-properties specified in a Filter property path will not cause the view to be updated.  I need to do more work before I have an efficient implementation of the notification logic.

Item Deletion Events

ObjectListView has always provided the ListChanged event, which specifies an action type (ListChangedType).  One of the actions reported is Deleted, indicating that a list item has been removed.  Unfortunately, only the index of the item deleted is available at the time the event is raised; the item has already been removed.  I've added the RemovingItem event, which is raised just before an item is removed from the list.  This event is only raised when an item is removed through a method of ObjectListView (for example, view.Remove() or view.RemoveAt()).  If an item is removed through a method of the underlying list, the deletion is reported to ObjectListView after the fact, so RemovingItem cannot be raised.

Bugs

As always, a few bugs are fixed, and some minor clean-up done.  See the change log for details.

What's Cooking?

As I mentioned, there's work yet to be done in keeping the view up to date as sub-properties specified in the Filter expression change.  I'm also working on dynamic properties that can be added to the view.  These would be analogous to the expression columns supported by DataTable.

I'm also excited to be presenting ObjectListView at the Portland Code Camp in two weeks!  I think it will be hard to explain it all in an hour.  I'll just have to talk really, really fast.

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Serialization Problems and Solutions

Sunday, 8 April 2007 22:11 by jesse

.NET serialization is a very simple concept, yet often fails to work the way we'd expect.  The basic idea is that you're converting objects from their in-memory format to some other form, and then back again.

Today I'll walk through some common scenarios that you might encounter when serializing objects to a file with the BinaryFormatter.  The complete set of code examples is provided for download here.

The Serializable Attribute

In order to enable serialization, you must annotate the class to be serialized with the Serializable attribute:

    [Serializable]

    public class CustomerSerializable

    {

        private string id;

        private string company;

        ...

To convert an object from it's in-memory form and write it to a file, we use a BinaryFormatter:

BinaryFormatter formatter = new BinaryFormatter();

 

CustomerSerializable customer1 = new CustomerSerializable();

customer1.City = "Bothell";

customer1.Region = "WA";

 

using (FileStream stream = File.Create("Serialized.dat"))

{

    formatter.Serialize(stream, customer1);

}

Once we have persisted an object to a file using the above code, we can reverse the process by reading the object from the file and rehydrating it in memory:

BinaryFormatter formatter = new BinaryFormatter();

CustomerSerializable customer2 = null;

using (FileStream stream = File.OpenRead("Serialized.dat"))

{

    customer2 = (CustomerSerializable)formatter.Deserialize(stream);

}

Pretty simple, huh?  Of course there are complications in the real world, as we shall see.

How Does it Work?

Essentially, in the simple case shown above, the formatter finds all of the fields of your class using Reflection, and reads their values during serialization.  During deserialization, the data is read from the file and restored to a new empty object.  Interestingly, in the simple case, no constructor of your serialized class is called during deserialization.

ISerializable

You might have heard about the ISerializable interface.  Isn't implementing that just another way of saying that my class is serializable?  Not quite.  You still have to apply the Serializable attribute to the class.  ISerializable just allows us to serialize and deserialize the data in a customized way.  Usually, this means saving only some of the data, or transforming the data before saving it.  Again, you must still apply the Serializable attribute to your class.  If you don't, BinaryFormatter.Serialize() will throw a SerializationException, even though your class supports ISerializable.

Unserializable Fields and Bases

Adding the Serializable attribute to your class is all well and good if the base class (and it's base, etc) and all of the fields of your class are also serializable.  But what if they aren't?  In either case, the Serializable attribute isn't enough - this is where ISerializable is needed.  Let's suppose the base class isn't serializable:

public class BaseNotSerializable

{

    private int totalOrders;

 

    public int OrderTotal

    {

        get { return totalOrders; }

        set { totalOrders = value; }

    }

}

 

[Serializable]

public class CustomerBaseNotSerializableISerializable : BaseNotSerializable, ISerializable

{

    private string id;

    private string company;

    ...

By declaring support for ISerializable, we're telling BinaryFormatter to do two things.  First, when serializing, our GetObjectData method should be called instead of using the Reflection-based mechanism for reading our object's data.  Secondly, during deserialization a special constructor should be called instead of rehydrating the object in the normal way.  In each case, we'll be provided with a SerializationInfo object that contains the data being transferred to or from the object.  Our job in GetObjectData() is to save the data from our object and from the unserializable base of our object:

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)

{

    // Serialize data from the base.

    info.AddValue("OrderTotal", this.OrderTotal);

 

    info.AddValue("Address", this.address);

    info.AddValue("City", this.city);

    info.AddValue("Company", this.company);

    info.AddValue("ContactName", this.contactName);

    info.AddValue("ContactTitle", this.contactTitle);

    info.AddValue("Country", this.country);

    info.AddValue("Fax", this.fax);

    info.AddValue("Id", this.id);

    info.AddValue("Phone", this.phone);

    info.AddValue("Region", this.region);

    info.AddValue("ZipCode", this.zipcode);

}

If our class contained a field that wasn't serializable, we could just skip it in GetObjectData(), or save some other piece of serializable information that we could use later to restore the value.

During deserialization, the special constructor is called, allowing us to read the data back into our object:

public CustomerBaseNotSerializableISerializable(SerializationInfo info, StreamingContext context)

{

    // Deserialize data to the base.

    this.OrderTotal = info.GetInt32("OrderTotal");

 

    this.address = info.GetString("Address");

    this.city = info.GetString("City");

    this.company = info.GetString("Company");

    this.contactName = info.GetString("ContactName");

    this.contactTitle = info.GetString("ContactTitle");

    this.country = info.GetString("Country");

    this.fax = info.GetString("Fax");

    this.id = info.GetString("Id");

    this.phone = info.GetString("Phone");

    this.region = info.GetString("Region");

    this.zipcode = info.GetString("ZipCode");

}

Something to note about this constructor is that it is required when ISerializable is implemented, but it is not part of the ISerializable interface, so it is easy to accidentally omit.  The compiler won't complain, because you have met the ISerializable implementation requirements by providing GetObjectData().  A SerializationException will be thrown at run-time if the constructor is not present.  As I mentioned earlier, no constructor is called in the simple case where ISerializable is not implemented.

But it's not my class!

Implementing ISerializable is a great solution when you're writing your own class.  Sometimes, however, you need to serialize an object provided by a third-party library or the .NET framework itself.  If the class wasn't implemented with the Serializable attribute, you might think that you're stuck.  Fortunately, there's a way around this obstacle, too.  Enter the serialization surrogate.

A serialization surrogate is a class that implements ISerializationSurrogate, and knows how to serialize and deserialize some other class.  We inject this knowledge into the serialization process by attaching the surrogate to the SurrogateSelector of the BinaryFormatter:

BinaryFormatter formatter = new BinaryFormatter();

 

SurrogateSelector selector = new SurrogateSelector();

selector.AddSurrogate(typeof(CustomerNotSerializable), new StreamingContext(StreamingContextStates.All), new CustomerNotSerializableSerializationSurrogate());

formatter.SurrogateSelector = selector;

 

CustomerNotSerializable customer1 = new CustomerNotSerializable();

 

using (FileStream stream = File.Create("Serialized.dat"))

{

    formatter.Serialize(stream, customer1);

}

 

CustomerNotSerializable customer2 = null;

using (FileStream stream = File.OpenRead("Serialized.dat"))

{

    customer2 = (CustomerNotSerializable)formatter.Deserialize(stream);

}

Now when formatter.Serialize() and formatter.Deserialize() are called, the SurrogateSelector is consulted, and methods of the provided CustomerNotSerializableSerializationSurrogate class are called to perform the needed operations:

public class CustomerNotSerializableSerializationSurrogate : ISerializationSurrogate

{

    // GetObjectData is called to serialize the object.

    void ISerializationSurrogate.GetObjectData(object obj, SerializationInfo info, StreamingContext context)

    {

        CustomerNotSerializable customer = (CustomerNotSerializable)obj;

 

        info.AddValue("Address", customer.Address);

        info.AddValue("City", customer.City);

        info.AddValue("Company", customer.Company);

        info.AddValue("ContactName", customer.ContactName);

        info.AddValue("ContactTitle", customer.ContactTitle);

        info.AddValue("Country", customer.Country);

        info.AddValue("Fax", customer.Fax);

        info.AddValue("Id", customer.Id);

        info.AddValue("Phone", customer.Phone);

        info.AddValue("Region", customer.Region);

        info.AddValue("ZipCode", customer.ZipCode);

    }

 

    // SetObjectData is called to deserialize the object.

    object ISerializationSurrogate.SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)

    {

        CustomerNotSerializable customer = (CustomerNotSerializable)obj;

 

        customer.Address = info.GetString("Address");

        customer.City = info.GetString("City");

        customer.Company = info.GetString("Company");

        customer.ContactName = info.GetString("ContactName");

        customer.ContactTitle = info.GetString("ContactTitle");

        customer.Country = info.GetString("Country");

        customer.Fax = info.GetString("Fax");

        customer.Id = info.GetString("Id");

        customer.Phone = info.GetString("Phone");

        customer.Region = info.GetString("Region");

        customer.ZipCode = info.GetString("ZipCode");

 

        return customer;

    }

}

You've probably noticed some similarity between the surrogate class and an implementation of ISerializable.  It's essentially the same thing, with the surrogate being decoupled from the class being serialized.

The Evil Conspiracy of Event Handlers

.NET events are a great architectural feature, but they can pose unexpected problems in serialization.  When you're serializing an object that contains an event, you potentially could be serializing objects of any .NET class.  How could this possibly be, you ask?

Events are sneaky.  Very sneaky.  Think about it for a minute.  An event is just a delegate field with some sugary syntax to make it more convenient.  A delegate is a list of zero or more methods to call when the event is raised.  Each method in the delegate's list references a specific object (if it is an instance method).  If any event handlers have been added to your event, these objects will be serialized when you serialize the object containing the event!

Typically, you just don't want to serialize the delegate field of an event.  You might also have other fields in your class that aren't serializable or that you just don't need to persist.  For these cases, the NonSerialized attribute can be applied to the field, and the serialization mechanism will ignore the field during serialization and deserialization.  Consider these fields of a class:

private string id;

private string company;

private string contactName;

 

[NonSerialized]

private PropertyDescriptor lastPropertyChanged;

 

[NonSerialized]

private PropertyDescriptorCollection properties;

 

[NonSerialized]

private PropertyChangedEventHandler propChanged;

 

public event PropertyChangedEventHandler PropertyChanged

{

    add { propChanged = (PropertyChangedEventHandler)Delegate.Combine(propChanged, value); }

    remove { propChanged = (PropertyChangedEventHandler)Delegate.Remove(propChanged, value); }

}

Here we have a couple of fields that aren't serializable; neither PropertyDescriptor nor PropertyDescriptorCollection can be serialized.  I've annotated these with [NonSerialized] so that the formatter won't attempt to serialize or deserialize them.  Making the PropertyChanged event non-serialized requires the event to be broken up into an declared delegate field and an event with explicit add and remove accessors.  The delegate field (propChanged above) is then annotated with [NonSerialized].  If I had declared the event in the conventional way, I couldn't have applied the NonSerialized attribute to the event.  The layout shown above, with an explicit delegate field (propChanged) and event accessors acting on the delegate mirrors exactly the structure that you would get by specifying the event conventionally:

public event PropertyChangedEventHandler PropertyChanged;

The code above is perfect for ignoring certain fields during serialization and deserialization.  However, it may be the case that after deserialization, these fields really do need valid values.  There are a few ways to handle this.  We could implement ISerializable and restore the values in the special deserializing constructor.  Alternatively, we could provide a surrogate and restore the values in the surrogate's SetObjectData() method.

In the example above, lastPropertyChanged represents the last property that was changed.  Even though PropertyDescriptor is not serializable, I can save a memento of the value in ISerializable.GetObjectData(), and then restore the value from the memento in the deserialization constructor:

// Called during serialization (because support for ISerializable is declared).

void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)

{

    info.AddValue("Address", this.address);

    info.AddValue("City", this.city);

    info.AddValue("Company", this.company);

    ...

 

    if (lastPropertyChanged != null)

        info.AddValue("LastPropertyChanged", lastPropertyChanged.Name);

}

 

// Called during deserialization (because support for ISerializable is declared).

public CustomerSerializableFieldsNonSerialized(SerializationInfo info, StreamingContext context)

{

    this.address = info.GetString("Address");

    this.city = info.GetString("City");

    this.company = info.GetString("Company");

    ...

 

    try

    {

        string propertyName = info.GetString("LastPropertyChanged");

        if (propertyName != null)

            lastPropertyChanged = TypeDescriptor.GetProperties(this)[propertyName];

    }

    catch (SerializationException)

    {

        // LastPropertyChanged was not added when the object was serialized (see GetObjectData).

    }

}

The key here is that even though lastPropertyChanged is not serializable, the memento that I save instead (lastPropertyChanged.Name) is serializable.

IDeserializationCallback

Another, simpler way of restoring non-serialized data, is to implement IDeserializationCallback on the class being serialized:

// Called after deserialization (because support for IDeserializationCallback is declared).

void IDeserializationCallback.OnDeserialization(object sender)

{

    properties = TypeDescriptor.GetProperties(this);

}

There's just one method to be provided, as shown.  It is called after deserialization is complete.  In this example, the properties field is a collection of PropertyDescriptors for the type being serialized.  This is the kind of data that is a perfect candidate for IDeserializationCallback - something that can be restored without specific knowledge of the object, or that has a reasonable default value.

Whew.

Serialization can be a little complicated.  It's easier if you just remember a few things:

  1. The Serializable class attribute must always be applied OR a serialization surrogate provided.
  2. Unserializable bases or fields can be serialized with an ISerializable implementation OR by a serialization surrogate.
  3. Fields and events can be omitted from serialization with the NonSerialized attribute.
  4. Non-serialized data can be restored in the ISerializable deserialization constructor OR in the SetObject() method of a serialization surrogate OR in IDeserializationCallback.OnDeserialization().

And that's all I have to say about that.  Download the sample code if you'd like to explore more.

kick it on DotNetKicks.com

Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList
Tags:  
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (1) | Comment RSSRSS comment feed