Blog

posts tagged with visual studio

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

WinForms AcceptButton Extender

0 Comments
By Fons Sonnemans, 01-dec-2002

Download AcceptButton.zip

Introduction

The System.Windows.Forms.Form class has an AcceptButton property which can be used to set the button on the form that is clicked when the user presses the ENTER key. The accept (default) button should be the button that represents the action that the user is most likely to perform if that action isn't potentially dangerous. This button has a dark border to let the user know that it is the accept button.

This feature works great only when you have one accept button. Have a look at the following diaglog.

The OK button is in this dialog the accept button. This is the correct behavior when the textbox 'Name' and datepicker 'Date' have the focus. There should not be an accept button when the (multiline) textbox 'Description' has the focus. When listbox 'Avialiable' has the focus the '> Add >' button must be the accept button. And when listbox 'Assigned' has the focus the '> Add >' button must be the accept button.

This can be accomplished by implementing the following Enter and Leave event handlers.


private void textBoxDescription_Enter ( object sender , System.EventArgs e) {
    this.AcceptButton =null;
}

privatevoid textBoxDescription_Leave(objectsender, System.EventArgs e) {
    this.AcceptButton =buttonOK;
}

privatevoid listBoxAvailable_Enter(objectsender, System.EventArgs e) {
    this.AcceptButton =buttonAdd;
}

privatevoid listBoxAvailable_Leave(objectsender, System.EventArgs e) {
    this.AcceptButton =buttonOK;
}

privatevoid listBoxAssigned_Enter(objectsender, System.EventArgs e) {
    this.AcceptButton =buttonRemove;
}

privatevoid listBoxAssigned_Leave(objectsender, System.EventArgs e) {
    this.AcceptButton =buttonOK;
}

But why write code when the same can be accomplished by setting some properties in the designer. The AcceptButton extender class can be used to eliminate this code.

AcceptButton Extender Provider

An extender provider is a component that provides properties to other components. The AcceptButton class implements the IExtenderProvider interface making it an Extender Provider. When you add an AcceptButton control to a Form, all other controls (accept buttons) on the form have the UseDefaultAcceptButton and the AcceptButton property added to their list of properties.

You now only have to set the two properties for each control:

Control UseDefaultAcceptButton property AcceptButton property
textBoxName True (none)
textBoxDescription False (none)
dateTimePickerDate True (none)
listBoxAvailable False buttonAdd
listBoxAssigned False buttonRemove

Conclusion

The AcceptButton class is good example for which you can use Extender Providers. Extender Providers are somewhat strange to write but very powerful. I use them a lot.

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

READ MORE

Janus System has released some great .NET controls

0 Comments
By Fons Sonnemans, 22-okt-2002

I have used the Janus GridEX control in many of my VB6 projects. It worked perfectly and it is very fast.

I have tested the .NET Trial version, it is even better. This is a component that everybody who is building WinForms application should buy.

Have a look at Janus GridEX Control for .NET and try it yourself.

READ MORE

C# SortCode Macro for Visual Studio.NET 2003

0 Comments
By Fons Sonnemans, 01-okt-2002

Download ReflectionITMacro.zip

Introduction

You should write your code in a consistent style. This is easier said then done. The SortCode macro helps you with this by sorting your C# code.

The SortCode macro sorts all members of a type (class or struct) on alphabetical order grouped by their type.

The default order of the types is:

  • Events
  • Fields
  • Enums
  • Constructor
  • Destructor
  • Properties and Functions
  • Properties and Functions of implemented interfaces

This order can easily be changed by modifying the private class MemberComparer. This class implements the IComparer interface.

Usage

Unzip the Zipfile and open the 'ReflectionIT.vsmacros' file. The ReflectionIT macro project will be loaded into the Macro Explorer.

Open a C# sourcefile in the VS.NET editor.

Run the SortCode macro from the CSharp module using the Macro Explorer. You will see that your code is sorted.


Warning

Only use the SortCode macro on projects which compile. It uses the Automation and Extensibility Reference (EnvDTE) of Visual Studio.NET. The EnvDTE can only work with CodeElements which are declared correctly.
You can Undo the SortCode macro using the 'Undo' menu option.

Other Macros

The ReflectionIT macro project also contains some other useful macros, for C# and VB.NET. I can imagine that more people have developed useful macros. Please mail them to me so I can extend my macro project.

Conclusion

The SortCode macro can help you organize your code. It demonstrates the power of Visual Studio.NET and the .NET Framework.

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

READ MORE

Regular Expressions

0 Comments
By Fons Sonnemans, 06-sep-2002

I find Regular Expression powerful but difficult. Eric Gunnerson wrote the Regular Expression Workbench. This makes them easy.

It's a tool to create, test, and study regular expressions with this workbench. With the "Examine-o-matic" feature, hover over the regex to decode what it means

READ MORE

Free .NET tools

0 Comments
By Fons Sonnemans, 25-aug-2002

READ MORE

.NET Framework Service Pack 2

0 Comments
By Fons Sonnemans, 09-aug-2002

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.