Skip to content

Home

TechEd 2007

Yesterday I arrived in Barcelona for TechEd 2007. I followed already some interesting sessions about LINQ, it brings you closer how you can access data in a more intuitive and more object-oriented way (data = objects). I really like LINQ to SQL, one of the drawbacks, is that it can only be used against a SQL Server database. If you need to target another database and program against a conceptual model (not a 1-1 mapping with your datatable structure and objects) you can have a look at LINQ to Entities.

Reflection and Generics

On my current project I had the need to iterate through the properties of an object with reflection and to check if one of the properties is a generic List type, e.g. IList, IList, etc. To check through reflection on a generic type, you need to use the GetGenericTypeDefinition method.

foreach (PropertyInfo propertyInfo in entity.GetType().GetProperties())
{
   if (propertyInfo.PropertyType.IsGenericType &&
       typeof(List<>).IsAssignableFrom
              (propertyInfo.PropertyType.GetGenericTypeDefinition()))
   {
      IEnumerable enumerable = propertyInfo.GetValue(entity, null) as IEnumerable;
      IEnumerator enumerator = enumerable.GetEnumerator();

      while (enumerator.MoveNext())
      {
         // do something
      }
   }
}

Linksys WRT54GL

Last week, I purchased a Linksys WRT54GL at RouterShop.nl. The service at Routershop.nl was really fast: at around 1:00pm I created the order and it was delivered the very next day!

The big advantage of WRT54GL is that you can upgrade the firmware of the device. Some popular firmware's are DD-WRT, OpenWRT and Tomato. I am using Tomato, of which today a new version has been released, i.e. Tomato 1.09. Tomato has a very nice interface, many interesting features and great-looking graphs. Below you will find a nice graph concerning the bandwidth:

linksysbandwidthmonitor

Note that you need to install the Adobe SVG Viewer in order to view the graphs.

CeBIT 2007

Last weekend I visited CeBIT 2007 in Hannover. We rented a house for the weekend in Steinhude which is not far from CeBIT and can easily be reached by car and train. If you are looking to stay overnight in Steinhude I would recommend this house, all comfort is available (TV, shower, kitchen, etc.).

Like every year, there were lots of brands and new products to discover. One of the cool things at CeBIT, is that you can gather a lot of gadgets :). An eyecatcher was this modding project for the World Cyber Games 2007 which is a 200 hours project! And I found that server rack of IBM also impressive.

CeBit2007

One of the things that really touched me - a trip down memory lane -, was a working Commodore 64 of the good old days. There was a room that showed some computer history.

CeBit2007_02

If I remember correctly, I have had the following computers with which I grew up, and I have to admit, it was mainly for playing games :-)

And when I came of age, I switched to the traditional personal computer. But I must say that there is really nothing compared to those old skool arcade games, such as

Integrating Validation Application Block of Enterprise Library with CSLA.NET

CSLA.NET framework from Lhotka contains a lot of mechanisms for adding validations and business rules. Through CSLA.NET you can easily provide your own custom rules. Enterprise Library v3.0 now also contains a validation application block (VAB) that can be used through attributes and even from a configuration file.

The two validation mechanisms of validation are complementary. This can be done by adding a custom rule that uses the ValidationFactory of the VAB. This means we have something like:

public class VABRules
{
    public class VABRuleArgs : RuleArgs
    {
        private string _ruleset;

        public string Ruleset
        {
            get { return _ruleset; }
        }


        public VABRuleArgs(string propertyName) : this(propertyName, null)
        {
        }

        public VABRuleArgs(string propertyName, string ruleset) : base(propertyName)
        {
            _ruleset = ruleset;
        }
    }

    public static bool VABValid<T>(object target, RuleArgs e)
    {
        Validator<T> validator = ValidationFactory.CreateValidator<T>(((VABRuleArgs)e).Ruleset);

        if (validator == null)
            return true;

        ValidationResults results = validator.Validate(target);

        if (results == null)
            return true;

        foreach (ValidationResult result in results)
        {
            if (result.Key == e.PropertyName)
            {
                e.Description = result.Message;
                return false;
            }
        }

        return true;
    }
}

Having the VAB rule we simply need to decorate our properties with the validation attributes of VAB and an override of the AddBusinessRules method is needed to take into account the VAB rules. For example we can define a customer business object as follow:

[Serializable()]
public class Customer : Csla.BusinessBase<Customer>
{
    private int _id = 0;
    private string _firstName = string.Empty;
    private string _email = string.Empty;
    private int _rewardPoints;
    private string _countryCode = string.Empty;

    [Browsable(false), System.ComponentModel.DataObjectField(true, true)]
    public int Id
    {
        get
        {
            CanReadProperty("Id", true);
            return _id;
        }
    }

    [NotNullValidator(MessageTemplate="First Name may not be empty")]
    [StringLengthValidator(1, 60, MessageTemplate = "First Name must be between 1 and 60 characters long")]
    public string FirstName
    {
        get
        {
            CanReadProperty("FirstName", true);
            return _firstName;
        }
        set
        {
            CanWriteProperty("FirstName", true);
            if (!_firstName.Equals(value))
            {
                _firstName = value;
                PropertyHasChanged("FirstName");
            }
        }
    }

    [RegexValidator(@"w+([-+.']w+)*@w+([-.]w+)*.w+([-.]w+)*")]
    public string Email
    {
        get
        {
            CanReadProperty("Email", true);
            return _email;
        }
        set
        {
            CanWriteProperty("Email", true);
            if (!_email.Equals(value))
            {
                _email = value;
                PropertyHasChanged("Email");
            }
        }
    }

    [Int32RangeValidator(0, 1000000, MessageTemplate = "Rewards points cannot exceed 1,000,000")]
    public int RewardPoints
    {
        get
        {
            CanReadProperty("RewardPoints", true);
            return _rewardPoints;
        }
        set
        {
            CanWriteProperty("RewardPoints", true);
            if (!_rewardPoints.Equals(value))
            {
                _rewardPoints = value;
                PropertyHasChanged("RewardPoints");
            }
        }
    }

    [NotNullValidator(MessageTemplate = "Country may not be empty")]
    public string CountryCode
    {
        get
        {
            CanReadProperty("CountryCode", true);
            return _countryCode;
        }
        set
        {
            CanWriteProperty("CountryCode", true);
            if (!_countryCode.Equals(value))
            {
                _countryCode = value;
                PropertyHasChanged("CountryCode");
            }
        }
    }

    protected override object GetIdValue()
    {
        return _id;
    }

    protected override void AddBusinessRules()
    {
        ValidationRules.AddRule(VABRules.VABValid<Customer>, new VABRules.VABRuleArgs("FirstName"));
        ValidationRules.AddRule(VABRules.VABValid<Customer>, new VABRules.VABRuleArgs("Email"));
        ValidationRules.AddRule(VABRules.VABValid<Customer>, new VABRules.VABRuleArgs("RewardPoints"));
        ValidationRules.AddRule(VABRules.VABValid<Customer>, new VABRules.VABRuleArgs("CountryCode"));
    }
}

AcceptButton and CancelButton on a UserControl

In many enterprise applications there is the need that, regardless on which control you have the focus, that you can hit the Enter and/or Esc key to perform a default action. This behaviour is also common to web applications. On the Form control you find properties like AcceptButton and CancelButton, whereas the UserControl doesn't have these properties. The code below contains an AcceptButton and CancelButton that allows you to define a default action when the Enter or Esc key is pressed respectively.

public class UserControlEx : System.Windows.Forms.UserControl
{    
    private Button _acceptButton;
    private Button _cancelButton;

    public event EventHandler<EventArgs> AcceptEvent;
    public event EventHandler<EventArgs> CancelEvent;

    [Browsable(true)]
    public Button AcceptButton
    {
        get { return _acceptButton; }
        set { _acceptButton = value; }
    }

    [Browsable(true)]
    public Button CancelButton
    {
        get { return _cancelButton; }
        set { _cancelButton = value; }
    }

    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (msg.WParam.ToInt32() == (int)Keys.Enter)
        {
            OnAcceptEvent(EventArgs.Empty);

            if (_acceptButton != null)
                _acceptButton.PerformClick();
        }

        if (msg.WParam.ToInt32() == (int)Keys.Escape)
        {
            OnCancelEvent(EventArgs.Empty);

            if (_cancelButton != null)
                _cancelButton.PerformClick();
        }

        return base.ProcessCmdKey(ref msg, keyData);
    }

    protected virtual void OnAcceptEvent(EventArgs args)
    {
        if (AcceptEvent != null)
            AcceptEvent(this, args);
    }

    protected virtual void OnCancelEvent(EventArgs args)
    {
        if (CancelEvent != null)
            CancelEvent(this, args);
    }
}

Geographical data and ASP.NET AJAX

In some enterprise applications you need to show geographical data such as countries and postcodes. Most of the time you need it for a registration page, where the user need to fill in the country and postcode/area.

Geonames is a free geographical database that contains over 8 million geographical names and it can be accessed through a number of webservices. For example the url http://ws.geonames.org/countryInfo? gives an xml with all countries, whereas the following request http://ws.geonames.org/postalCodeSearch?placename=be gives us all postcodes for a particular country (e.g. Belgium).

Most likely you need two dropdown lists, one for countries and one for postcodes, where the postcode dropdown is dependent from the country dropdown list. This is a very good example to introduce AJAX by using the CascadingDropdown that is included in ASP.NET AJAX.

To implement this functionality we need to implement two methods on a webservice, namely GetCountries and GetPostalCodesByCountry. The GetCountries simply returns all countries sorted by name and looks like this:

[WebMethod]
public CascadingDropDownNameValue[] GetCountries()
{
    List<CascadingDropDownNameValue> list = new List<CascadingDropDownNameValue>();

    CountryItemCollection countries = IStaySharp.Geonames.GeonamesService.GetAllCountries();

    for (int i = 0; i < countries.Countries.Length; i++)
    {
        list.Add(new CascadingDropDownNameValue(
            countries.Countries[i].CountryName,
            countries.Countries[i].CountryCode));
    }

    list.Sort(CompareCascadingDropDownNameValueByName);
    return list.ToArray();
}

Note that the list need to be converted to an array of CascadingDropDownNameValue objects. Note that we also sort the list by implementing a delegate named CompareCascadingDropDownNameValueByName.

private static int CompareCascadingDropDownNameValueByName(CascadingDropDownNameValue x, CascadingDropDownNameValue y)
{
    if (x == null && y == null)
        return 0;
    else if (x == null && y != null)
        return -1;
    else if (x != null && y == null)
        return 1;
    else
        return x.name.CompareTo(y.name);
}

The other webservice method, called GetPostalCodesByCountry, need to retrieve all postcodes for a particular country. The signature of the method is very strict. The parameter names must be named knownCategoryValues and category, otherwise it will fail!

[WebMethod]
public CascadingDropDownNameValue[] GetPostalCodesByCountry(string knownCategoryValues, string category)
{
    List<CascadingDropDownNameValue> list = new List<CascadingDropDownNameValue>();

    StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);

    if (kv.ContainsKey("Country"))
    {
        string countryName = kv["Country"];

        PostalCodeItemCollection postalCodes = IStaySharp.Geonames.GeonamesService.GetPostalCodes(countryName);

        for (int i = 0; i < postalCodes.PostalCodes.Length; i++)
        {
            list.Add(new CascadingDropDownNameValue(
                string.Format("{0} ({1})", postalCodes.PostalCodes[i].PostalCode, postalCodes.PostalCodes[i].Name),
                postalCodes.PostalCodes[i].PostalCode));
        }
    }

    list.Sort(CompareCascadingDropDownNameValueByName);
    return list.ToArray();
}

In order to complete the webservice, the attribute ScriptService (line 3) need to be included so that a client javascript proxy can be generated. You can test this by calling your webservice like this http://localhost:9999/GeonamesService.asmx/js.

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService()]
public class GeonamesService : System.Web.Services.WebService
{
   ...
}

Finally we only need to add two CascadingDropDown controls on our aspx page with the following settings:

<asp:ScriptManager ID="scriptManager" runat="server" />

<asp:DropDownList ID="countriesDropDown" runat="server"/>
<ajaxToolkit:CascadingDropDown
    ID="countriesCascadingDropDown"        
    TargetControlID="countriesDropDown"
    Category="Country" 
    PromptText="Please select a country" 
    LoadingText="[Loading countries...]" 
    ServicePath="/GeonamesService.asmx"
    ServiceMethod="GetCountries"
    runat="server"/> 

<br/><br/>

<asp:DropDownList ID="postalCodesDropDown" runat="server"/>
<ajaxToolkit:CascadingDropDown
    ID="postalCodesCascadingDropDown"
    TargetControlID="postalCodesDropDown"
    Category="PostalCode" 
    PromptText="Please select postalcode" 
    LoadingText="[Loading postalcodes...]" 
    ServicePath="GeonamesService.asmx"
    ServiceMethod="GetPostalCodesByCountry"
    ParentControlID="countriesDropDown"
    runat="server"/>

The source code can be downloaded here: IStaySharp.AJAXSample

Virtual PC 2007 RC1

Virtual PC 2007 RC1 has been released, and can be downloaded on the Microsoft Connect site, if you participated in the beta tests. The main new features are:

  • Support for Windows Vista™ as a host operating system
  • Support for Windows Vista™  as a guest operating system
  • Support for 64-bit host operating systems
  • Support for hardware-assisted virtualization
  • Built-in support for network installations

More details about the release notes can also be downloaded on the Microsoft Connect site.