sharing about .NET and technology RSS 2.0
# Thursday, January 25, 2007

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.rar (332,56 KB)

Thursday, January 25, 2007 2:13:28 AM (Romance Standard Time, UTC+01:00) -  # -  Comments [3] -
.NET | ASP.NET
# Saturday, December 30, 2006

The Validation Application Block (VAB) of the upcoming Enterprise Library v3, uses attributes to describe validations. This gives us for example the opportunity to generate ASP.NET validators based on the attributes decorated on the properties.

Take for example the NotNullValidator of VAB, this can be translated to a RequiredFieldValidator, whereas the RegexValidator can be translated to RegularExpressionValidator. You can go further with the NotNullValidator and mark required fields with a different backcolor and adding an asterix (*) to the end of the control.

I am big fan of the DetailsView control, you can simply bind a DataSource control to it, and it will automatically provide you with a caption to each control and two-way binding. Below you find an example how you can extend the BoundField control, that investigates the NotNullValidator attribute of VAB. Note that I am currently extending it for the other set of validators and in a more OO way. More info will follow later.

public class BoundFieldEx : System.Web.UI.WebControls.BoundField
{      
    public override void InitializeCell(
        DataControlFieldCell cell, DataControlCellType cellType, 
        DataControlRowState rowState, int rowIndex)
    {
        base.InitializeCell(cell, cellType, rowState, rowIndex);

        if ((((rowState & DataControlRowState.Edit) != DataControlRowState.Normal) && !this.ReadOnly) || 
             ((rowState & DataControlRowState.Insert) != DataControlRowState.Normal))
        {
            TextBox textBox = null;

            if (cell != null && cell.Controls.Count > 0)
                textBox = cell.Controls[0] as TextBox;

            if (textBox != null)
            {
                Type dataItemType = null;
                
                if (DataBinder.GetDataItem(base.Control) != null)
                    dataItemType = DataBinder.GetDataItem(base.Control).GetType();

                if (dataItemType != null)
                {                        
                    ValidatorAttribute attribute = IsRequired(dataItemType, base.DataField);

                    if (attribute != null)                        
                    {
                        string textBoxID = this.DataField;
                        textBox.ID = textBoxID;

                        RequiredFieldValidator validator = new RequiredFieldValidator();
                        validator.ControlToValidate = textBoxID;
                        validator.ID = string.Concat("RequiredValidatorOf", textBoxID);
                        validator.Display = ValidatorDisplay.Dynamic;                            
                        validator.ErrorMessage = attribute.MessageTemplate;
                        cell.Controls.Add(validator);
                    }
                }
            }
        }                                      
    }

    private ValidatorAttribute IsRequired(Type dataType, string property)
    {
        PropertyInfo propertyInfo = dataType.GetProperty(property);

        if (propertyInfo != null)
        {
            foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
            {
                if (attribute is NotNullValidatorAttribute)
                    return attribute as ValidatorAttribute;
            }
        }

        return null;
    }
}

Now you create a custom business object, called Customer, and bind it to the DetailsView through an ObjectDataSource.

public class Customer
{        
    private string _firstName;
    private string _lastName;
    
    [NotNullValidator(MessageTemplate="Firstname cannot be empty")]        
    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    [NotNullValidator(MessageTemplate="Lastname cannot be empty")]        
    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }
    
    public Customer Fill()
    {    
        Customer customer = new Customer();
        customer.FirstName = "Christoph";
        customer.LastName = "De Baene";        
        return customer;
    }
}

Inside your aspx page you have something like

<asp:ObjectDataSource id="customerDataSource"
    TypeName="IStaySharp.Business.Customer, IStaySharp.Business"
    DataObjectTypeName="IStaySharp.Business.Customer, IStaySharp.Business"
    SelectMethod="Fill"     
    runat="server">
 </asp:ObjectDataSource>
         
 
 <asp:ValidationSummary runat="server"/>
        
<asp:DetailsView DataSourceID="customerDataSource" DefaultMode="Edit" AutoGenerateRows="false" runat="server">
    <Fields>
        <rfx:BoundField HeaderText="Firstname"  DataField="FirstName"/>
        <rfx:BoundField HeaderText="Lastname"   DataField="LastName"/>                                               
    </Fields>
</asp:DetailsView>

Here is the result if you leave the properties empty:

Saturday, December 30, 2006 12:31:05 AM (Romance Standard Time, UTC+01:00) -  # -  Comments [1] -
.NET | ASP.NET | EntLib
# Wednesday, September 27, 2006

Cassini is a ASP.NET sample application that shows how to write a web server using .NET. This means that you can host ASP.NET using the ASP.NET hosting APIs (System.Web.Hosting). This is really an alternative to IIS. There is also a second project called UltiDev Cassini Web Server which is based on the Cassini source with the following additional features:

  • Comes ready for distribution with Visual Studio ASP.NET applications
  • Runs as a windows service
  • Hosts and runs multiple ASP.NET applications
  • Provides management UI and simple API for configuring web applications
  • Comes in two flavors: 2.0 version for ASP.NET 2.0 applications, and 1.1 for applications compiled for ASP.NET 1.1

If you have the need for an offline version of your ASP.NET application, where easy deployment is required (without IIS), then this is a very good solution.

Wednesday, September 27, 2006 1:16:18 AM (Romance Daylight Time, UTC+02:00) -  # -  Comments [0] -
.NET | ASP.NET
# Sunday, February 12, 2006

Here I describe how you can add syntax highlighting (C#, SQL, Javascript, etc.) in MediaWiki. In MediaWiki you can add syntax highlighting through GeSHiHighlight but I found it interesting for combining the world of PHP and .NET. For syntax highlighting in .NET I used the CodeHighlighter ASP.NET Control from Actipro Software.

The intersection between PHP and .NET for communication are web services. Therefore I created a .NET webservice that provides the method Parse with the parameters code and languagekey (= C#, SQL, XMl, etc.)  which returns a HTML string.

using System;
using System.Web;
using System.Web.Services;
using System.Text;
using System.Web.Services.Protocols;
using System.Configuration;
using ActiproSoftware.CodeHighlighter;
using ActiproSoftware.SyntaxEditor;

[WebService(Namespace = "http://tempuri.org/SyntaxHighlighting")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class SyntaxHighlighting : System.Web.Services.WebService
{ 
[WebMethod]
public string Parse(string code, string languageKey)
{
   if (string.IsNullOrEmpty(code))
      return string.Empty;

   SyntaxLanguage language = GetSyntaxLanguage(languageKey);
   if (language == null)
      throw new ApplicationException("LANGKEYNOTEXIST");

   CodeHighlighterEngine engine = new CodeHighlighterEngine();
   engine.OutliningEnabled = false;
   return engine.GenerateHtmlInline(string.Empty, code, language);
}

CodeHighlighterConfiguration GetCodeHighlighterConfig()
{
   CodeHighlighterConfiguration config = (CodeHighlighterConfiguration)HttpContext.Current.Cache["CodeHighlighterConfig"];
   if (config == null)
   {
      config = (CodeHighlighterConfiguration)ConfigurationManager.GetSection("codeHighlighter");
      HttpContext.Current.Cache.Insert("CodeHighlighterConfig", config);
   }

   return config;
}

SyntaxLanguage GetSyntaxLanguage(string languageKey)
{
   if (string.IsNullOrEmpty(languageKey))
      return null;

   CodeHighlighterConfiguration config = GetCodeHighlighterConfig();
   foreach (string key in config.LanguageConfigs.Keys)
      if (key.ToLower() == languageKey.ToLower())
         return CodeHighlighter.GetLanguage(config, key);

   return null;
}
}

In MediaWiki you can extend WikiText so that for example your xml tag is recognized by the parser. This way you can extend the HTML output. In this example you would write in WikiText the following statements:

<code language="C#">
   public int x = 5; 
</code>

In the extensions folder of MediaWiki you add a file called CodeHighlighting.php with the following content:

<?php

$wgExtensionFunctions[] = "wfCodeHighlightingExtension";

function wfCodeHighlightingExtension() 
{
   global $wgParser;
   $wgParser->setHook('code', 'renderCode');
}

function renderCode( $input="", $argv=array() )
{
 $result = SyntaxHighlighting($input, $argv['language']);
 return '<pre>' . trim($result) . '</pre>';  
}

function SyntaxHighlighting($code, $languageKey)
{ 
 $location = 'http://localhost/SyntaxHighlightingWS/SyntaxHighlighting.asmx?wsdl';
 $result = $code;
 
 try
 { 
  $client = new SoapClient($location);
  $arr = array("code" => $code, "languageKey" => $languageKey);
  $result = $client->Parse($arr)->ParseResult; 
 }
 catch(SoapFault $exception)
 {  
  if (strpos($exception->faultstring, "LANGKEYNOTEXIST") === false)
  {
   throw $exception;
  }
 }   
 return $result; 
}
?>

Through the setHook method you can extend WikiText. Here the method renderCode is called when a XML element named 'code' is inside your WikiText. Calling a web service in PHP 5 is easy with the SoapClient object. If the language we pass does not exist, we simply return the original string.

One more thing must be done, is to add an include in the LocalSettings.php in your MediaWiki folder.

include("extensions/CodeHighlighting.php");

Finally:

Sunday, February 12, 2006 11:04:22 PM (Romance Standard Time, UTC+01:00) -  # -  Comments [0] -
.NET | ASP.NET | MediaWiki
Navigation
Archive
<September 2010>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789
About the author/Disclaimer

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

© Copyright 2010
Christoph De Baene
Sign In
Statistics
Total Posts: 176
This Year: 2
This Month: 0
This Week: 0
Comments: 283
All Content © 2010, Christoph De Baene
DasBlog theme 'Business' created by Christoph De Baene