Blog

posts by Fons Sonnemans

Assembly Comparison Tool (WinChurn)

1 Comments
By Fons Sonnemans, 11-jul-2003

I have found a great tool on the GotDotNet user samples website

WinChurn allows you to compare two versions of the same managed assembly. This is extremely helpful when tracking changes in your assembly. The tool comes with a help system, as well as some examples to get you started. Optionally, the tool allows you to generate a policy file, however caution is urged when using this feature. While the tool is extremely robust and accurate at detecting public API changes, it cannot detect internal changes. The tool uses Reflection to generate the results.

Assembly Comparison Tool (WinChurn)

READ MORE

C# Minesweeper Game

8 Comments
By Fons Sonnemans, 29-jun-2003

Download Minesweeper.zip

Introduction

I have always wondered how hard it would be to write a game in C#. I picked Minesweeper for my first try. Made an OO design and then went programming.

See the result in the downloadable zip file. The whole program is less then 400 lines of code!


Screenshot

I have reduced the complexity a bit by eliminating the flag-icon and all menu options.


Design

The application is build using 3 objects: a Form, a Game which holds an array of Square objects.


UML Class Diagram (Visio)

MinewsweeperForm class
This class creates a Game object when the Start button is clicked. It also updates the Timer and Mines display. It contains a Panel control in which the Game object generates the Squares.

Game class
This class contains a multidimensional array of Square objects which is initialized in the constructor. The mines are randomly placed in the squares. It has a Timer which is used to calculate the elapsed gametime. The events DismantledMinesChanged and Tick are used to notify the MinesweeperForm that is has to update the GUI.

Square class
This class contains a normal WinForm Button object which Click and MouseDown events are captured. These events trigger the real game logic. The number of surrounding mines is calculated (cascading). Mines are dismantled (marked) or explode which is reported to the Game object using events.

Conclusion

Writing the Minesweeper game was a lot of fun. It demonstrates how object orientation really makes it easy. Examine it by stepping trough the code using the VS.NET debugger. I hope you enjoyed this article and sample.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

Tags: CSharp, Apps

READ MORE

I Love CodeSmith

0 Comments
By Fons Sonnemans, 09-jun-2003

I have tried CodeSmith for the first time and I love it. CodeSmith allows you to create templates that will generate code for any ASCII based language. It is simple and very powerful. You can easily adjust it to fit your needs.

You can download it from CodeSmith (it's FREEWARE)

READ MORE

C# InputBox

1 Comments
By Fons Sonnemans, 30-apr-2003

Download InputBoxSample.zip

Introduction

Visual Basic 6.0 has an InputBox() function, Visual Basic.NET has one but in C# you don't. You can solve this easily by adding a reference to 'Microsoft.VisualBasic.dll' and use the static method Microsoft.VisualBasic.Interaction.InputBox().

The VB implementation has some shortcomings which I solved in my improved InputBox class. You never know whether the user entered a empty text or clicked the Cancel button. It is also impossible to have validation on the text.


Example: InputBox

InputBox class

The InputBox class is a public class with a private constructor. You use it by calling the static Show method. This method instantiates a new instance of the class, sets it's properties and returns the result. The result is not a string but a InputBoxResult object. This object has two properties: OK and Text. OK is a boolean indicating that the user clicked the OK button and not the Cancel button. The Text contains the string the user entered.

public static InputBoxResult Show(string prompt,stringtitle, stringdefaultResponse,
                                 InputBoxValidatingHandler validator,intxpos,intypos){
    using (InputBox form=new InputBox()) {
        form.labelPrompt.Text =prompt;
        form.Text = title;
        form.textBoxText.Text =defaultResponse;
        if(xpos >=0 &&ypos >=0){
            form.StartPosition = FormStartPosition.Manual;
            form.Left = xpos;
            form.Top = ypos;
        }
        form.Validator = validator;

        DialogResult result=form.ShowDialog();

        InputBoxResult retval=new InputBoxResult();
        if(result == DialogResult.OK){
            retval.Text = form.textBoxText.Text;
            retval.OK = true;
        }
        returnretval;
    }
}

publicstatic InputBoxResult Show(string prompt,stringtitle, stringdefaultText,
                                 InputBoxValidatingHandler validator){
    return Show(prompt, title,defaultText,validator,-1,-1);
}

Usage

You activate the InputBox by calling the static Show() method. It has 4 required and 2 optional arguments (using overloading).

private void buttonTest_Click ( object sender , System.EventArgs e) {
    InputBoxResult result= InputBox.Show("Test prompt:","Some title","Default text", null);
    if(result.OK){
        textBox1.Text = result.Text;
    }
}


Validation

You can add validation logic using the validator argument. The validator is a InputBoxValidatingHandler delegate which you can use to validate the Text. The following sample checks whether the Text is not empty. If so it sets Cancel to true and the Message to 'Required'.

private void buttonTest_Click ( object sender , System.EventArgs e) {
    InputBoxResult result= InputBox.Show("Test prompt:","Some title","Default text",
                                             new InputBoxValidatingHandler(inputBox_Validating));
    if(result.OK){
        textBox1.Text = result.Text;
    }
}

privatevoid inputBox_Validating(objectsender, InputBoxValidatingArgs e){
    if(e.Text.Trim().Length ==0){
        e.Cancel =true;
        e.Message ="Required";
    }
}


Example: Required text

Conclusion

The ImputBox class is just a simple class which you can use in your Windows Forms application to prompt for a text. It can also be used from Visual Basic.NET when you compile it in a Library and reference this Library from you VB project.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

READ MORE

Free VS.NET Add-In: Spelly.NET

0 Comments
By Fons Sonnemans, 01-apr-2003

Spelly is a Visual Studio addin that allows you to easily spell-check source code and/or comments. Spelly is smart enough to understand identifiers with under_scores and MixedCase.

Spelly is available for both Visual Studio .NET and Visual C++ 5/6.

READ MORE

SQL-strings considered harmful

0 Comments
By Fons Sonnemans, 10-mrt-2003

Download SqlInsert.zip

Introduction

Did you know that any malicious user can corrupt your database by injecting harmful SQL strings? To prevent SQL injection, you can use the parameters collections when building SQL strings. However, I developed a more sophisticated method to construct SQL statements from user's input and execute them safely. In this article I describe how to write and execute SQL statements by using objects instead of SQL strings. These objects also address specific SQL statement syntax issues on different RDBMS: they enable you to write generic and RDBMS independent code.

SQL Inject

An SQL statement that is to be executed by SQL Server is in most applications constructed using an SQL string (or StringBuilder) and executed by means of a command object. An SQL string can contain two or more SQL statements (each statement separated by a semi colon) that all will be executed by SQL Server.

If you build SQL strings using unfiltered input, your application may be subject to malicious user input (remember, never trust user input). The risk is that when you insert user input into a string that becomes an executable statement, a malicious user can append SQL commands to your intended SQL statements by using escape characters.

Example: consider a form in which a record has to be added to a table called Titles. The user can type field values like title (name), publisher-ID, price etc. in textboxes on the form. On OK click the InsertTitle() method is called using these input values as arguments.


private void buttonOK_Click ( object sender , System.EventArgs e) {
    InsertTitle(textBoxId.Text, textBoxTitle.Text,comboType.Text,
                numberBoxPubId.Value,numberBoxPrice.Value,
                numberBoxAdvance.Value,numberBoxYTDSales.Value,
                textBoxNotes.Text,datePickerPubDate.Value);
}

privatevoid InsertTitle(string id,stringtitle, stringtype,
    intpubId,double price,doubleadvance, intytd_sales,
    string notes, DateTime pubdate){

    StringBuilder sql =new StringBuilder("insert into titles ");
    sql.Append("(title_id, title, type, pub_id, price, advance, ");
    sql.Append("ytd_sales, notes, pubdate)");
    sql.Append(" values (");
    sql.Append("'").Append(id).Append("' ,");
    sql.Append("'").Append(title).Append("' ,");
    sql.Append("'").Append(type).Append("' ,");
    sql.Append(pubId).Append(" ,");
    sql.Append(price).Append(" ,");
    sql.Append(advance).Append(" ,");
    sql.Append("'").Append(notes).Append("' ,");
    sql.Append(string.Format("{0:MM/dd/yyyy})",pubdate));

    SqlCommand c = new SqlCommand(sql.ToString(), MyConnection);

    c.ExecuteNonQuery();
}

In the example, the SQL statement is constructed from a simple string concatenation. The assumption is that some fields of the form are alphanumeric, and thus must be surrounded by single quotes. Since SQL sees the single quotes as a string delimiter, all a hacker needs to do is insert an extra quote, followed by any SQL code into the textBoxNotes. For example, say the value:

', null); delete from titles --

was entered. The InsertTitle() method would build the SQL statement to look something like this:

insert into titles ( title_id , title , type , pub_id , price , advance ,
[ ytd_sales ] , [ notes ] , [ pubdate ] )
values ( 'FS1234' , 'dummy' , 'business' , 1 , 1 , 1 , 1 , '' , null ) ; delete from titles - - , 22 /04/2000 )

This would cause SQL to execute the INSERT statement, possibly returning an error because the pubdate field was NULL (if the database were so programmed). The server would then execute the DELETE statement, deleting all records from the table. The harm is done!

The Solution
To prevent SQL injection, you can use the parameters collection when building SQL strings. No matter what a malicious user includes as input, the input is treated as a literal.

private void InsertTitle(string id,stringtitle, stringtype,
    intpubId,double price,doubleadvance, intytd_sales,
    string notes, DateTime pubdate){

    StringBuilder sql =new StringBuilder("insert into titles ");
    sql.Append("(title_id, title, type, pub_id, price, advance, ");
    sql.Append("ytd_sales, notes, pubdate)");
    sql.Append(" values (@par0, @par1, @par2, @par3, @par4, @par5, ");
    sql.Append("@par6, @par7, @par8)");

    SqlCommand c = new SqlCommand(sql.ToString(), MyConnection);
    c.Parameters.Add(new SqlParameter("par0", id));
    c.Parameters.Add(new SqlParameter("par1", title));
    c.Parameters.Add(new SqlParameter("par2", type));
    c.Parameters.Add(new SqlParameter("par3", pubId));
    c.Parameters.Add(new SqlParameter("par4", price));
    c.Parameters.Add(new SqlParameter("par5", advance));
    c.Parameters.Add(new SqlParameter("par6", ytd_sales));
    c.Parameters.Add(new SqlParameter("par7", notes));
    c.Parameters.Add(new SqlParameter("par8", pubdate));

    c.ExecuteNonQuery();
}

To mark a name as a parameter and not as a string of literal characters, you place a prefix in front of the parameter name (and, as an option, a suffix after it). For example, parameter marker characters might be "@" (SqlServer), ":" (Oracle), or "%". Typically, you use the parameter marker character recognized for your database. Use can used unnamed parameters when your database doesn't support named parameters. An unnamed parameter is a question mark (?) that you put anywhere in the query that you want to prompt for or substitute a literal value.

InsertStatement classes

To make parameter usage easier you should use Objects and not SQL strings to build your SQL statements. The InsertStatement classes are used to build an IDbCommand. They create the CommandText and the Parameters collection. I have currently four implementations for SqlServer, MS Access, MySql and Oracle. It enabled me to support also RDBMS specific syntax for: keywords, parameters, table-names & column-names, (outer)joins and aliasses.

private void InsertTitle(string id,stringtitle, stringtype,
    intpubId,double price,doubleadvance, intytd_sales,
    string notes, DateTime pubdate){

    SqlInsertStatement i =new SqlInsertStatement();

    i.TableName ="titles";
    i.Values.Add("title_id",new Constant(id));
    i.Values.Add("title",new Constant(title));
    i.Values.Add("type",new Constant(type));
    i.Values.Add("pub_id",new Constant(pubId));
    i.Values.Add("price",new Constant(price));
    i.Values.Add("advance",new Constant(advance));
    i.Values.Add("ytd_sales",new Constant(ytd_sales));
    i.Values.Add("notes",new Constant(notes));
    i.Values.Add("pubdate",new Constant(pubdate));

    SqlCommand c = (SqlCommand)i.CreateCommand();
    c.Connection = MyConnection;

    c.ExecuteNonQuery();
}

The SqlInsertStatement will create the following CommandText for a System.Data.SqlClient.SqlCommand:

insert into [ titles ] ( [ title_id ] , [ title ] , [ type ] , [ pub_id ] , [ price ] , [ advance ] ,
[ ytd_sales ] , [ notes ] , [ pubdate ] )
values ( @par0 , @par1 , @par2 , @par3 , @par4 , @par5 , @par6 , @par7 , @par8 )

The AccesInsertStatement would create the following CommandText for a System.Data.OleDb.OleDbCommand:

insert into [ titles ] ( [ title_id ] , [ title ] , [ type ] , [ pub_id ] , [ price ] , [ advance ] ,
[ ytd_sales ] , [ notes ] , [ pubdate ] )
values ( ? , ? , ? , ? , ? , ? , ? , ? , ? )

The MySqlInsertStatement would create the following CommandText for a Microsoft.Data.Odbc.OdbcCommand:

insert into `titles` (`title_id`, `title`, `type`, `pub_id`, `price`, `advance`,
`ytd_sales`, `notes`, `pubdate`)
values(?,?,?, ?,?,?,?, ?,?)

The OracleInsertStatement will create the following CommandText for a System.Data.OracleClient.OracleCommand:

INSERT INTO titles(title_id, title,type,pub_id,price, advance,
ytd_sales,notes, pubdate)
VALUES (:par0, :par1,:par2,:par3,:par4,:par5,:par6,:par7,:par8)

The InsertStatement and ValuesClause use arrays of tokens to store all information. The CreateCommand() method uses a CommandBuilder to create the CommandText and Parameters. These CommandBuilders will iterate through the arrays and translate each token (keyword, literal, constant, expression, etc) to the RDBMS specific implementation.

Select, Update and Delete classes

You can use the same solution for the Select, Update and Delete statements. They are a lot more difficult but it is really worth the effort, especially when you want to create generic, RDBMS independent components. I ended up with the following model:

The SelectStatementBase class has for each clause an association with a Clause object.

Example with an outerjoin and a like in the where clause:

SelectStatementBase s =new SqlSelectStatement();
s.Select.AddAstrix();

s.From.Add("sales","s");
s.From.Add(new Join("stores", "s.stor_id","stores.stor_id", JoinTypes.Left));

s.Where.And(new ConditionLike("title", new Constant("%book%")));

s.OrderBy.Add("ord_date",true);
s.OrderBy.Add("qty");

Update and Delete model:


Additional Information

Conclusion

Writing SQL statements using objects instead of strings made my life easier. I hope you will use my classes which use the Parameters so SQL injection is prevented.

The download contains the C# source code of the Insert classes and a sample project.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

READ MORE

Reliably and quickly improve your C# code with Xtreme Simplicity's C# Refactory

0 Comments
By Fons Sonnemans, 04-feb-2003

I have found a promising C# tool which fully integrates with Visual Studio.NET. I'm going to test it soon (I hope).

Have a look yourself at http://www.xtreme-simplicity.net

READ MORE

Save your UserSettings in an Isolated Store

0 Comments
By Fons Sonnemans, 01-feb-2003

Download UserSettings.zip

Introduction

I like applications who remember my settings the next time I use it. This is an easy feature which is often forgotten. Most of the time because it is quite some work. With this article I want to help you with this and saving you a lot off work.

Then there is always the question 'where do we store the settings?'. I see a lot of applications using the Registry, ini-files or xml-files. All these solutions are causing a security risk. Especially when you want your application to be downloaded from the web using the 'no touch' deployment features of .NET.

Microsoft has solved this problem for me by introducing an isolated stores. With these stores, you can read and write data that less trusted code cannot access and prevent the exposure of sensitive information that can be saved elsewhere on the file system. Data is stored in compartments that are isolated by the current user and by the assembly in which the code exists.

The downloadable zipfile contains a UserSettingLibrary which can be used to store user settings into an isolated store.

IsolatedUserSettings class

The IsolatedUserSettings class can be used to store and retrieve user settings in and from an IsolatedStorageFile. The RegisterForm() method can be used to register a form. The location, size and windowstate of the form will be saved and restored. The SaveSetting() method saves an serializable object which can be restored using the GetSetting() method.


using System;
using System.IO;
using System.IO.IsolatedStorage;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace ReflectionIT.UserSettings
{
    /// <summary>
    /// The IsolatedUserSettings class can be used to store and retrieve user settings
    /// in and from an IsolatedStorageFile.
    /// </summary>
    public class IsolatedUserSettings : IDisposable
    {
        private Hashtable _settings;
        privatebool_closed =false;
        privatestring_fileName;

        /// <summary>
        /// Constructor
        /// </summary>
        public IsolatedUserSettings(stringfileName)
        {
            _fileName=fileName;
            IsolatedStorageFile isoFile= GetStore();
            string[] files=isoFile.GetFileNames(fileName);
            if(files.Length == 1){
                // file exists -> Deserialize
                using(IsolatedStorageFileStream stream= new IsolatedStorageFileStream(
                         fileName, FileMode.Open, isoFile)){
                    BinaryFormatter formatter=new BinaryFormatter();
                    try{
                        _settings= (Hashtable)formatter.Deserialize(stream);
                    }
                    catch(SerializationException){
                        _settings= new Hashtable();
                    }
                }
            }else{
                // start fresh
                _settings= new Hashtable();
            }
        }

        /// <summary>
        /// ND Destructor
        /// </summary>
        ~IsolatedUserSettings() {
            Dispose(false);
        }

        /// <summary>
        /// Close the IsolatedUserSettings
        /// </summary>
        publicvoid Dispose(){
            Dispose(true);
            System.GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Close the IsolatedUserSettings
        /// </summary>
        /// <param key="disposing"></param>
        protectedvirtualvoid Dispose(bool disposing){
            this.Close();
        }

        /// <summary>
        /// Write the UserSettings to the IsolatedStorage
        /// </summary>
        publicvirtualvoid Close() {
            if(!_closed){
                IsolatedStorageFile isoFile= GetStore();

                using(IsolatedStorageFileStream stream= new IsolatedStorageFileStream(
                         FileName, FileMode.Create,isoFile)){
                    BinaryFormatter formatter=new BinaryFormatter();
                    formatter.Serialize(stream,_settings);
                }
                _closed= true;
            }
        }

        /// <summary>
        /// Get the fileName
        /// </summary>
        publicstring FileName {
            get{return _fileName;}
        }

        /// <summary>
        /// Return the Store
        /// </summary>
        /// <returns></returns>
        private IsolatedStorageFile GetStore(){
            return IsolatedStorageFile.GetStore(IsolatedStorageScope.Assembly |
                IsolatedStorageScope.User | IsolatedStorageScope.Domain |
                IsolatedStorageScope.Roaming, null,null);
        }

        /// <summary>
        /// Register a Form
        /// </summary>
        /// <param key="key"></param>
        /// <param key="form"></param>
        publicvirtualvoid RegisterForm(string key, Form form){
            FormSetting f;
            if(_settings.ContainsKey(key)) {
                f =(FormSetting)_settings[key];
                f.Restore(form);
            }else{
                f =new FormSetting(key);
                f.Backup(form);
                _settings.Add(key, f);
            }
            form.SizeChanged +=new EventHandler(f.FormMovedOrResized);
            form.LocationChanged +=new EventHandler(f.FormMovedOrResized);
        }

        /// <summary>
        /// UnRegister a Form
        /// </summary>
        /// <param key="key">Name of the Form. A forms with an existing key will
        /// automatically get the stored settings.</param>
        /// <param key="form"></param>
        publicvirtualvoid UnRegisterForm(string key, Form form){
            if(_settings.ContainsKey(key)) {
                FormSetting f =(FormSetting)_settings[key];
                f.Backup(form);
                form.SizeChanged -= new EventHandler(f.FormMovedOrResized);
                form.LocationChanged -= new EventHandler(f.FormMovedOrResized);
            }
        }

        /// <summary>
        /// Save a setting with a given key
        /// </summary>
        /// <param key="key">Name of the setting by which it is saved an can be
        /// restored using GetSetting().</param>
        /// <param key="setting"></param>
        publicvoid SaveSetting(stringkey,objectsetting){

            // Check Serializable attribute
            TypeAttributes attributes=setting.GetType().Attributes;
            if((attributes& TypeAttributes.Serializable)==0) {
                thrownew ArgumentException("Setting argument must be marked Serializable.");
            }

            // Add or Overwrite setting
            if(_settings.ContainsKey(key)) {
                _settings[key]= setting;
            }else{
                _settings.Add(key, setting);
            }
        }

        /// <summary>
        /// Return the setting with the given key
        /// </summary>
        /// <param key="key"></param>
        /// <returns>
        /// The setting object or null when the the given key does not exists
        /// </returns>
        publicobject GetSetting(stringkey){
            return GetSetting(key,null);
        }

        /// <summary>
        /// Return the setting with the given key
        /// </summary>
        /// <param key="key"></param>
        /// <returns></returns>
        publicobject GetSetting(stringkey,objectdefaultValue){
            if(_settings.ContainsKey(key)) {
                return_settings[key];
            }
            returndefaultValue;
        }
    }
}

The FormSetting class is used to store a form's location, size and windowstate in a serializable object. The next time you register a form with the same key the location, size and windowstate is restored to the saved values. So if you resize your form, close it and open it again the size will be the same as it was when you last closed it.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace ReflectionIT.UserSettings
{
    /// <summary>
    /// Summary description for FormSetting.
    /// </summary>
    [Serializable]
    publicclass FormSetting
    {
        private Point _location;
        privatestring_name;
        private Size _size;
        private FormWindowState _windowState;

        public FormSetting(stringname)
        {
            this._name =name;
        }

        /// <summary>
        /// Get the name used for this FormSetting
        /// </summary>
        publicstring Name {
            get{return (this._name);}
        }

        /// <summary>
        /// Backup the Size, Location and WindowState (only when not Minimized)
        /// </summary>
        /// <param name="form"></param>
        publicvirtualvoid Backup(Form form){
            if(form.WindowState != FormWindowState.Minimized) {
                // Only when not Maximized
                if(form.WindowState != FormWindowState.Maximized){
                    this._location=form.Location;
                    this._size=form.Size;
                }
                this._windowState=form.WindowState;
            }
        }

        /// <summary>
        /// Eventhandler
        /// </summary>
        /// <param name="sender">Form which is Moved or Resized</param>
        /// <param name="e"></param>
        publicvirtualvoid FormMovedOrResized(object sender, EventArgs e){
            Backup((Form)sender);
        }

        /// <summary>
        /// Restore the Size, Location and WindowState for the given form
        /// </summary>
        /// <param name="form"></param>
        publicvirtualvoid Restore(Form form){
            form.Location = this._location;
            form.Size = this._size;
            form.WindowState =this._windowState;
        }
    }
}

Sample

The included sample uses the IsolatedUserSettings class to store and restore the user settings. In this case the Form (Location, Size and WindowState), the Checked property of a CheckBox and a MostRecentlyUsedList object. The settings are restored in the constructor and saved in the Closing event handler, the Form properties are saved automatically by the internally used FormSetting class. You must set the StartPosition property of the form to 'Manual', otherwise the location will not be restored.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using ReflectionIT.UserSettings;

namespace Test
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1: System.Windows.Forms.Form
    {
        private System.Windows.Forms.GroupBox groupBox1;
        private System.Windows.Forms.Button buttonMRUAdd;
        private System.Windows.Forms.TextBox textBoxMRU;
        private System.Windows.Forms.ListBox listBoxMRU;

        private MostRecentlyUsedList _mru;
        private IsolatedUserSettings _settings =new IsolatedUserSettings("settings.dat");
        private System.Windows.Forms.CheckBox checkBox1;

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components=null;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            // Get UserSettings
            _settings.RegisterForm(this.Name,this);
            _mru=(MostRecentlyUsedList)_settings.GetSetting("mru",new MostRecentlyUsedList(5));
            checkBox1.Checked = (bool)_settings.GetSetting("checked",true);
            listBoxMRU.DataSource = _mru;
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protectedoverridevoid Dispose(bool disposing)
        {
            if(disposing )
            {
                if(components!= null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing);
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        staticvoid Main()
        {
            Application.Run(new Form1());
        }

        /// <summary>
        /// Add the Text to the MRU and bind it to the datasource
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        privatevoidbuttonMRUAdd_Click(object sender, System.EventArgs e){
            _mru.Add(textBoxMRU.Text);
            textBoxMRU.Clear();
            textBoxMRU.Focus();

            listBoxMRU.DataSource = null;// force update
            listBoxMRU.DataSource =_mru;
        }

        /// <summary>
        /// Save the MRU and checkBox1.Checked to the UserSettings
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        privatevoid Form1_Closing(object sender, System.ComponentModel.CancelEventArgs e){
            _settings.SaveSetting("mru",_mru);
            _settings.SaveSetting("checked",checkBox1.Checked);
            _settings.Close();
        }
    }
}

The MostRecentlyUsedList class has a Serializable attribute which makes it possible to store it in an IsolatedUserSetting.

 

using System;
using System.Collections;

namespace Test
{
   &nbsp/// <summary>
   &nbsp/// Summary description for MostRecentlyUsedList.
   &nbsp/// </summary>
   &nbsp[Serializable]
   &nbsppublicclass MostRecentlyUsedList : IList
   &nbsp{
   &nbsp   &nbspprivateint_capacity;
   &nbsp   &nbspprivate ArrayList _list;

   &nbsp   &nbsp/// <summary>
   &nbsp   &nbsp/// Initializes a new instance of the MostRecentlyUsedList class
   &nbsp   &nbsp/// that is empty and has the specified capacity
   &nbsp   &nbsp/// </summary>
   &nbsp   &nbsp/// <param name="capacity">The number of elements that the new
   &nbsp   &nbsp/// MostRecentlyUsedList is capable of storing. </param>
   &nbsp   &nbsppublic MostRecentlyUsedList(intcapacity)
   &nbsp   &nbsp{
   &nbsp   &nbsp   &nbsp_capacity=capacity;
   &nbsp   &nbsp   &nbsp_list=new ArrayList(capacity);
   &nbsp   &nbsp}

   &nbsp   &nbsp/// <summary>
   &nbsp   &nbsp/// Add a object to the beginning of the list. If the value already
   &nbsp   &nbsp/// exists it is removed first. The length of the list will never
   &nbsp   &nbsp/// exceed the capacity.
   &nbsp   &nbsp/// </summary>
   &nbsp   &nbsp/// <param name="value">The Object to be added to the beginning
   &nbsp   &nbsp/// of the MostRecentlyUsedList</param>
   &nbsp   &nbsppublicvoid Add(objectvalue)
   &nbsp   &nbsp{
   &nbsp   &nbsp   &nbspif(_list.Contains(value)){
   &nbsp   &nbsp   &nbsp   &nbsp_list.Remove(value);
   &nbsp   &nbsp   &nbsp}else{
   &nbsp   &nbsp   &nbsp   &nbspif(this.Count ==this.Capacity){
   &nbsp   &nbsp   &nbsp   &nbsp   &nbsp_list.RemoveAt(this.Count -1);
   &nbsp   &nbsp   &nbsp   &nbsp}
   &nbsp   &nbsp   &nbsp}
   &nbsp   &nbsp   &nbsp_list.Insert(0,value);
   &nbsp   &nbsp}

   &nbsp   &nbsp/// <summary>
   &nbsp   &nbsp/// Get or sets the number of elements that the MostRecentlyUsedList
   &nbsp   &nbsp/// is capable of storing.
   &nbsp   &nbsp/// </summary>
   &nbsp   &nbsppublicint Capacity {
   &nbsp   &nbsp   &nbspget{return (this._capacity);   &nbsp}
   &nbsp   &nbsp   &nbspset{
   &nbsp   &nbsp   &nbsp   &nbspthis._capacity=value;
   &nbsp   &nbsp   &nbsp   &nbspwhile(this.Count >=this.Capacity){
   &nbsp   &nbsp   &nbsp   &nbsp   &nbsp_list.RemoveAt(this.Count -1);
   &nbsp   &nbsp   &nbsp   &nbsp}
   &nbsp   &nbsp   &nbsp}
   &nbsp   &nbsp}
   &nbsp   &nbsp....

Conclusion

The IsolatedUserSettings is an simple class which uses an isolated store to serialize and deserialize objects. With it you can enhance your applications without much effort.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

READ MORE

NEW C# LANGUAGE FEATURES

0 Comments
By Fons Sonnemans, 10-jan-2003

On November 7th, at the OOPSLA Conference in Seattle, WA, C# creator Anders Hejlsberg unveiled several potential language features for the next major release of Visual C# .NET. The four primary features Anders spoke about were:

  • Generics, a form of C++ templates that makes reusing existing code easier
  • Iterators, a construct that makes traversing collections of data significantly faster and easier
  • Anonymous methods, an easier way to perform simple tasks using delegates
  • Partial types, a means for programmers to split code across multiple files
Read more

READ MORE

ASP.NET DataIslandGrid Control

0 Comments
By Fons Sonnemans, 01-jan-2003

Download DataIslandGrid.zip

Introduction

The standard ASP.NET DataGrid control is a great control. You can use it for many things, it even supports Paging and Column Sorting. Those last two option although work using postbacks to the server.

Internet Explorer 5.0 (and higher) support XML Client-side Data-Binding. This is a powerful DHTML feature which is not used in the .NET Framework. It allows.

The DataIslandGrid control is an ASP.NET grid which is bound to a DataTable in a DataSet. The DataSet is serialized and rendered to an Xml DataIsland. The Grid uses Tabular Data-Binding to the Xml DataIsland. This makes it possible to support client-side Column Sorting and Paging. The Column Sorting is implemented using a JavaScript and a StyleSheet in a second Xml DataIsland.


Example

The following grid is a DataIslandGrid which is bound to the Authors table of the SQLServer pubs database.

IDFirstnameLastnameAddressCity
<< < > >>
172-32-1176
Johnson
White
abc
Stein
213-46-8915
Marjorie
Green
abc
Oakland
238-95-7766
Rony
Carson
abc
Berkeley
267-41-2394
Michael
O'Leary
abc
San Jose
274-80-9391
Dean
Straight
abc
Oakland
341-22-1782
Meander
Smith
abc
Lawrence
409-56-7008
Abrahamfdfs
Bennett
abc
Berkeley
427-17-2319
Ann
Dull
abc
Palo Alto
472-27-2349
Burt
Gringlesby
abc
Covelo
486-29-1786
Charlene
Locksley
abc
San Francisco

The Html of this page must have redirective to the DataIslandGridProject. Then you can add the DataIslandGrid control to the Page. The best way to do this is by adding the assembly to your Toolbox. You then can drag&drop the DataIslandGrid from your Toolbox onto your Page.

<%@ Register TagPrefix="rit" Namespace="ReflectionIT.Web.UI.WebControls"  Assembly="DataIslandGridProject"%>
.
.
<rit:DataIslandGrid id="DataIslandGrid1"runat="server" CellSpacing="0" BorderWidth="1px"
AllowSorting="true" CssClass="clsTest"width="80%" BorderColor="WhiteSmoke">
   <rit:BoundColumn CssClass="clsColumnID" Width="120px" DataField="au_id" HeaderText="ID"/>
   <rit:BoundColumn DataField="au_fname" HeaderText="Firstname"></rit:BoundColumn>
   <rit:BoundColumn DataField="au_lname" HeaderText="Lastname"></rit:BoundColumn>
   <rit:BoundColumn DataField="address" HeaderText="Address"></rit:BoundColumn>
   <rit:BoundColumn DataField="city" HeaderText="City"></rit:BoundColumn>
</rit:DataIslandGrid>
.
.

You add Columns to the datagrid using the BoundColumn Collection Editor.

The following code binds the DataGrid to the Authors table. I have converted the SQLServer table to an MS Access database to make testing easier.

public class WebForm1: System.Web.UI.Page
{
    protected ReflectionIT.Web.UI.WebControls.DataIslandGrid DataIslandGrid1;

    privatevoid Page_Load(objectsender, System.EventArgs e)
    {
        stringconnectString=string.Format(
         @"Provider=Microsoft.Jet.OLEDB.4.0;Password=;User ID=Admin;Data Source={0};",
         Server.MapPath("pubs.mdb"));

        using(OleDbConnection con=new OleDbConnection(connectString)){
            con.Open();
            using(OleDbCommand cmd=new OleDbCommand("select * from Authors",con)){
                OleDbDataAdapter da= new OleDbDataAdapter(cmd);
                DataSet ds= new DataSet();
                da.Fill(ds);

                // Bind the DataGrid
                DataIslandGrid1.DataSet =ds;
            }
        }
    }
}

DataIslandGrid Control

The DataIslandGrid class is a WebControl with a lot of attributes. The ParseChilderenAttribute is set to the Columns property. It enables a control to specify how the page parser interprets nested elements within the control's tags when the control is used declaratively on an ASP.NET page. Columns is a property of type BoundColumnsCollection and holds items of the BoundColumn type

[ToolboxData("<{0}:DataIslandGrid runat=server></{0}:DataIslandGrid>")]
[ParseChildren(true,"Columns")]
[PersistChildren(false)]
[Designer(typeof(ReflectionIT.Web.UI.WebControls.Design.DataIslandGridDesigner))]
[DefaultProperty("Columns")]
publicclass DataIslandGrid : System.Web.UI.WebControls.WebControl

The class has a set of public properties which allows you to configure it. The real magic is done in the Render() and OnPreRender() methods:

  • JavaScript code for client-side sorting is added to the Page.
  • The DataTable from the DataSet is rendered to an Xml DataIsland using the WriteXml() method of the DataSet. 
  • An Xml StyleSheet is rendered in a second Xml DataIsland. This StyleSheet is used by the JavaScript to do the sorting. It uses a xsl:for-each loop with a order-by attribute. The value of this attribute is set in the JavaScript.
  • A <table> tag is rendered with a datasrc attribute which is set to the ID of first XmlDataIsland
  • For each Column a table cell (<td>) is rendered within a <thead> section. In this Cell a hyperlink is rendered with a NavigateUrl property to the JavaScript which does the client-side sorting. The Text of the hyperlink is set to the HeaderText property.
  • For each Column a table cell (<td>) is rendered within a <tbody> section. In this Cell a <div> tag is rendered with a datafld attribute which is set to the DataField property.
  • A <tfoot> section is rendered with four hyperlinks for paging: MoveFirst, MovePrevious, MoveNext and MoveLast.
  • All tags are closed.
/// <summary>
/// Add a JavaScript to the Page to sort a column
/// </summary>
/// <param name="e"></param>
override protected void OnPreRender(EventArgs e){

     if(this.Page.Request.Browser.JavaScript == true){
         // Build JavaScript        
         System.Text.StringBuilder s =new System.Text.StringBuilder();
         s.Append("\n<script type='text/javascript' language='JavaScript'>\n");
         s.Append("<!--\n");
         s.Append("function sortColumn(xmldoc, xsldoc, sortcol) {\n");
         s.Append(" xsldoc.selectSingleNode(\"//xsl:for-each\").setAttribute(\"order-by\", sortcol);\n");
         s.Append(" xmldoc.documentElement.transformNodeToObject(xsldoc.documentElement,xmldoc);\n");
         s.Append("}\n");
         s.Append("// -->\n");
         s.Append("</script>\n");

         // Add the Script to the Page
         this.Page.RegisterClientScriptBlock("SortDataIslandGrid", s.ToString());
     }
}

/// <summary>
/// Sends server control content to a provided HtmlTextWriter object, which writes
/// the content to be rendered on the client.
/// </summary>
/// <param name="writer">The HtmlTextWriter object that receives the server control content.</param>
protectedoverridevoid Render(HtmlTextWriter writer) {

     if(_dataSet!=null) {


         // XML
         stringoldNS= DataSet.Namespace;
         DataSet.Namespace ="";

         writer.AddAttribute("id","xml" +this.ClientID);
         writer.RenderBeginTag("XML");

         DataSet.WriteXml(writer, XmlWriteMode.IgnoreSchema);
         writer.RenderEndTag();//XML

         DataSet.Namespace =oldNS ;

         // XSL (sorting)
         if(this.AllowSorting){
             writer.AddAttribute("id","xsl"+this.ClientID);
             writer.RenderBeginTag("XML");

             writer.RenderBeginTag(DataSet.DataSetName);
             writer.AddAttribute("order-by", "?");
             writer.AddAttribute("select", DataTable.TableName);
             writer.AddAttribute("xmlns:xsl", "http://www.w3.org/TR/WD-xsl");
             writer.RenderBeginTag("xsl:for-each");
             writer.WriteBeginTag(DataTable.TableName);
             writer.Write(HtmlTextWriter.TagRightChar);

             foreach(DataColumn colin DataTable.Columns){
                 writer.WriteBeginTag(col.ColumnName);
                 writer.Write(HtmlTextWriter.TagRightChar);
                 writer.WriteBeginTag("xsl:value-of");
                 writer.WriteAttribute("select",col.ColumnName);
                 writer.Write(HtmlTextWriter.SlashChar);
                 writer.Write(HtmlTextWriter.TagRightChar);
                 writer.WriteEndTag(col.ColumnName);
             }

             writer.WriteEndTag(DataTable.TableName);
             writer.RenderEndTag(); //xsl:for-each"
             writer.RenderEndTag();

             writer.RenderEndTag(); //XML
         }
     }

     bool design=(this.Site !=null&&this.Site.DesignMode);
     if(_dataSet!=null| design){
         // Table
         writer.AddAttribute("datasrc","#xml" +this.ClientID);
         writer.AddAttribute("id",this.ClientID);
         writer.AddAttribute("CellPadding",this.CellPadding.ToString());
         writer.AddAttribute("CellSpacing",this.CellSpacing.ToString());
         if(this.AllowPaging){
             writer.AddAttribute("dataPageSize", PageSize.ToString());
         }
         if(!this.BorderWidth.IsEmpty){
             writer.AddAttribute("border", BorderWidth.ToString());
         }
         if(this.CssClass != string.Empty){
             writer.AddAttribute("class", CssClass);
         }

         if(this.ControlStyleCreated &&this.ControlStyle !=null){
             ControlStyle.AddAttributesToRender(writer);
         }
         writer.RenderBeginTag("table");

         // Header
         writer.RenderBeginTag("thead");
         writer.RenderBeginTag("tr");

         foreach(ReflectionIT.Web.UI.WebControls.BoundColumn bcin_columns){
             if(!bc.Width.IsEmpty){
                 writer.AddAttribute("width",bc.Width.ToString());
             }
             writer.RenderBeginTag("th");

             if(this.AllowSorting &bc.Sortable){
                 this.WriteSort(writer,bc.DataField,bc.HeaderText);
             }else{
                 writer.Write(bc.HeaderText);
             }

             writer.RenderEndTag();
         }

         writer.RenderEndTag();// tr
         writer.RenderEndTag();// thead

         // body
         writer.RenderBeginTag("tbody");

         int t =design?(AllowPaging ? PageSize :4) :1;
         for(int x =0; x < t; x++){
             writer.RenderBeginTag("tr");

             foreach(ReflectionIT.Web.UI.WebControls.BoundColumn bcin_columns){
                 bc.Render(writer);
             }
             writer.RenderEndTag(); //tr
         }
         writer.RenderEndTag();// tbody

         // Footer
         if(AllowPaging){
             writer.RenderBeginTag("tfoot");
             writer.RenderBeginTag("tr");
             writer.AddAttribute("colspan", _columns.Count.ToString());
             writer.RenderBeginTag("th");

             this.WritePaging(writer, ".firstPage();","<<");
             this.WritePaging(writer, ".previousPage();","<");
             this.WritePaging(writer, ".nextPage();",">");
             this.WritePaging(writer, ".lastPage();",">>");

             writer.RenderEndTag(); //th
             writer.RenderEndTag();//tr
             writer.RenderEndTag();//tfoot
         }

         writer.RenderEndTag();// table
     }
}

protectedvirtualvoid WritePaging(HtmlTextWriter writer, stringfunction,stringtext){
     writer.AddAttribute("href","javascript:"+this.ClientID +function);
     writer.RenderBeginTag("a");
     writer.Write(HttpUtility.HtmlEncode(text));
     writer.RenderEndTag();//a
     writer.Write(HtmlTextWriter.SpaceChar);
}

protectedvirtualvoid WriteSort(HtmlTextWriter writer, stringdataField,stringheaderText){
     writer.AddAttribute("href","javascript:sortColumn(xml"+this.ClientID +
         ".XMLDocument, xsl"+this.ClientID +".XMLDocument, '+"+dataField+"');");
     writer.RenderBeginTag("a");
     writer.Write(HttpUtility.HtmlEncode(headerText));
     writer.RenderEndTag();//a
     writer.Write(HtmlTextWriter.SpaceChar);
}

You can configure the appearance of the DataGrid easily by assigning a stylesheet classname to the CssClass property of the DataGrid and its BoundColumns. I have also included a DataIslandGridDesigner class which renders the design-time html.

Links:

I have used the following articles to create this control and to write this article:

Conclusion

The Internet Explorer features for Data-Binding to Xml DataIslands are very powerful. The DataIslandGrid control makes it easy to use them in a ASP.NET application. It demonstrates the power of .NET controls.

Any suggestions and feedback for improving this article is most welcome. Send your suggestions and feedback to Fons.Sonnemans@reflectionit.nl

Tags: Web, ASP.NET

READ MORE

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.