2009-09-18

Difference between Eval and Bind databinding expressions

Hi. I just faced a problem where I was using an ASP.NET GridView control which was bound to a SqlDataSource. One of the bound columns was “User” and, of course, brought the user name. What I wanted to do was to add a HyperLink to that text, so that instead of just saying “Mark”, with would show up as a link to the UserDetalis page, passing “Mark” as a QueryString variable. Something like this:

  1: <a href='UserDetails.aspx?user=Mark>Mark</a>

So, because I had a databound GridView, I just had to make the “User” column a template column and replace the standard label (Label1 in this code)

  1: <asp:GridView ID="myGridView" runat="server" 
  2:     DataSourceID="myDataSource">
  3:     <Columns>
  4:         <asp:TemplateField HeaderText="User" SortExpression="User">
  5:             <ItemTemplate>
  6:                 <asp:Label ID="Label1" runat="server" Text='<%# Bind("User") %>' />
  7:             </ItemTemplate>
  8:         </asp:TemplateField>
  9:     </Columns>
 10: </asp:GridView>

by a HyperLink (HyperLink1 in this code), and set its NavigateUrl property to the new value, which held the whole hyperlink HTML text. So the first thing that came to my mind was to do something like this:

  1: <asp:HyperLink ID="HyperLink1" runat="server" Text='<%# Bind("User") %>'
  2:     NavigateUrl='<%# String.Format("UserDetails.aspx?user={0}", Bind("User")) %>' />

But it turned out I got a runtime error that said: The name 'Bind' does not exist in the current context. If I used Eval instead of Bind:

  1: <asp:HyperLink ID="HyperLink1" runat="server" Text='<%# Eval("User") %>'
  2:     NavigateUrl='<%# String.Format("UserDetails.aspx?user={0}", Eval("User")) %>' />

It worked like a charm.

OK, I got my problem solved, but now I needed to know the difference between both, and specifically why it worked with one and not the other. Well, here it is:

Eval is one way, for read-only purposes, as Bind is two way, for read-write operations. Eval is actually a static protected method defined on the TemplateControl class, from which the Page class is derived, and it returns a string. Because it’s a real method, you could combine it with other statements, like the string.Format we just used. Bind, on the other hand, is not an actual method, it’s a new ASP.NET 2.0 databinding keyword, so of course, it can’t be used with other method calls or stuff. Bottom line is, if you need to only read the text, and perhaps change it a little, use Eval. If you need to write back to the db or something like that, use Bind.

Hope you got it clear. This article has some more deep definitions, which you might find useful as well.

2009-09-15

Creating design-time compatible properties and events, part 2

Hi all. In part 1 of this article, we learned how to make our simple properties show up in the properties window at design-time, so that they looked just like the built in ones that .NET controls have. Here, in part 2, we’ll discuss how to make your events show up, as well as some more advanced stuff, like adding custom lists.

First, events, opposite to Properties, don’t show by default in the properties Windows (that is, they’re not Browsable by default), so in order to show them all we need to do is to set that property to true:

  1: [Browsable(true)]
  2: public event EventHandler MyEvent;

It’s all it takes.

Now, the fun part. Let’s add some custom lists. Suppose we want to add a property to our NiceTextBox called SomeList, which can hold any of these four values: One, Two, Three and Four. There are several ways to achieve this, but I’m gonna take the simple way and use a string. So, first, we’ll have to create a “converter” class. Converter classes are used to convert specific types to and from other different types. Since we’re using a string, we’ll need to create a class that inherits System.ComponentModel.StringConverter. We’'ll use this to convert my list to and from a string.

  1: public class SomeListConverter : StringConverter
  2: {
  3:     //Values list. You could bring this from a database or so
  4:     private readonly string[] values = new[] { "One", "Two", "Three", "Four" };
  5: 
  6:     public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
  7:     {
  8:         //When true, only a value from the list can be selected
  9:         return true;
 10:     }
 11: 
 12:     public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
 13:     {
 14:         //Return the list as a collection of standard values.
 15:         //This is the method the designer will use to pupulate the list
 16:         return new StandardValuesCollection(values);
 17:     }
 18: 
 19:     public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
 20:     {
 21:         //When false, the list will be editable, meaning that the user could type 
 22:         //a value as well. When true, the list will be read-only
 23:         return true;
 24:     }
 25: }

Now, let’s create the property. We’ll have to use the TypeConverter attribute to tell the property what class it should use to convert our List (of type StandardValuesCollection) to and from a string:

  1: [TypeConverter(typeof(SomeListConverter))]
  2: [Category("Custom properties"), DefaultValue(""), Description("Some custom list used")]
  3: public string SomeList { get; set; }

The result will be pretty neat:

OK, so that’s it for simple lists. But what if one of our custom properties needs to be set through a more complex class? Let’s take a look at the following example, we’ll create a property called Version, which will have all four parts of a regular assembly version: Major, Minor, Build and Private. We’ll do it so that the user can set the version value directly (something like 2.1.0.17), or he can set the value for each part separately. That kind of behavior, like the one we see in common properties as Location and Size, is achieved through something called expandable objects, specifically by using the ExpandableObjectConverter class.

First, we’ll create an ApplicationVersion class:

  1: public class ApplicationVersion
  2: {
  3:     public short Major { get; set; }
  4: 
  5:     public short Minor { get; set; }
  6: 
  7:     public short Build { get; set; }
  8: 
  9:     public short Private { get; set; }
 10: }

This class we’ll represent the type of our Version property (which we’ll construct later). Now, we’ll have to create the converter. Like said before, because this property will be an expandable object, we’ll have to inherit the ExpandableObjectConverter class:

  1: internal class ApplicationVersionConverter : ExpandableObjectConverter
  2: {
  3:     public override bool CanConvertTo(ITypeDescriptorContext context, 
  4:         Type destinationType)
  5:     {
  6:         //This will indicate if the Converter can convert the object TO the 
  7:         //specified type. In this case, ApplicationVersion to string
  8:         return destinationType == typeof(ApplicationVersion) || 
  9:             base.CanConvertTo(context, destinationType);
 10:     }
 11: 
 12:     public override bool CanConvertFrom(ITypeDescriptorContext context, 
 13:         Type sourceType)
 14:     {
 15:         //This will indicate if the Converter can convert the object FROM the 
 16:         //specified type. In this case, string to ApplicationVersion
 17:         return sourceType == typeof(String) || 
 18:             base.CanConvertFrom(context, sourceType);
 19:     }
 20: 
 21:     public override object ConvertFrom(ITypeDescriptorContext context, 
 22:         System.Globalization.CultureInfo culture, object value)
 23:     {
 24:         //This will implement the logic to convert from string to object. 
 25:         /It will be used when the user directly enters the version value 
 26:         //(2.0.4.17) instead of setting each part individually
 27:         if (value is string)
 28:             try
 29:             {
 30:                 string s = value.ToString();
 31:                 string[] versionParts = s.Split('.');
 32:                 if (versionParts.Length > 0)
 33:                 {
 34:                     var version = new ApplicationVersion();
 35:                     if (versionParts[0] != null)
 36:                         version.Major = Convert.ToInt16(versionParts[0]);
 37:                     if (versionParts[1] != null)
 38:                         version.Minor = Convert.ToInt16(versionParts[1]);
 39:                     if (versionParts[2] != null)
 40:                         version.Build = Convert.ToInt16(versionParts[2]);
 41:                     if (versionParts[3] != null)
 42:                         version.Private = Convert.ToInt16(versionParts[3]);
 43:                     return version;
 44:                 }
 45:             }
 46:             catch (Exception)
 47:             {
 48:                 throw new ArgumentException(
 49:                     string.Format(
 50:                         "The value {0} isn't a valid Version number", value));
 51:             }
 52:         return base.ConvertFrom(context, culture, value);
 53:     }
 54: 
 55:     public override object ConvertTo(ITypeDescriptorContext context, 
 56:         System.Globalization.CultureInfo culture, 
 57:         object value, Type destinationType)
 58:     {
 59:         //This will implement the logic to convert from object to string. 
 60:         //It serves the exact opposite purpose of the above method
 61:         if (destinationType == typeof(string) && value is ApplicationVersion)
 62:         {
 63:             var version = (ApplicationVersion)value;
 64:             return string.Format("{0}.{1}.{2}.{3}", version.Major, 
 65:                 version.Minor, version.Build, version.Private);
 66:         }
 67:         return base.ConvertTo(context, culture, value, destinationType);
 68:     }
 69: }

Finally, we’ll just create our Version property:

  1: [TypeConverter(typeof(ApplicationVersionConverter))]
  2: [Category("Custom properties"), DefaultValue("0.0.0.0"), Description("App version")]
  3: public ApplicationVersion Version { get; set; }

Here’s what we’ll get:

So, it’s possible to create properties and events that are fully design-time compatible. It might take a little work, but it’s definitely worth it, specially for people that create custom controls and plan to share or sell them. Hope you enjoyed the articles.