Monday, August 18, 2008

Book Review: Visual Studio Extensibility

Visual Studio ExtensibilityThis book goes into great detail about the various methods of extending the functionality of the Visual Studio 2008 IDE. It discusses Macros, Add-ins, Shell extensions and …This is not the kind of book that you sit down and read cover to cover but it is a very good reference with practical examples of the various extension methods.

The first section of the book contains a comprehensive review of Visual Studio and the various versions from version 6 through 2008. The book primarily focuses on the latest version of Visual Studio ( 2008 ) but a lot of the material applies to the 2005 version as well. The first four chapters cover Visual Studio history, the .NET Framework, an overview of extensibility options and a discussion of the automation model.

There are about 10 chapters which cover the various aspects of add-ins. All of these chapters focus on one small topic related to add-ins from the Add-in Wizard through Deployment and Testing of add-ins. There is even a discussion of localization which is an often over looked aspect of programming. This section is not something you would read through in one sitting but it is a valuable resource for understanding the various aspects of Visual Studio Add-ins.

The rest of the book discusses several other methods which can be used as extensions. This ranges from the complex such as Shell extensions to more common extensions such as Code Snippets and Macros. I have always liked Code Snippets and I have been impressed with the number which come built-in to Visual Studio. Adding your own custom snippets isn’t a difficult process but you have to know where to find the proper directories for the snippets. Having this book as a quick reference will be helpful.

There is also a chapter on MSBuild which is another somewhat overlooked tool. MSBuild has a ton of functionality and there are entire books dedicated to using it. The chapter in the book gives a good overview and a sample build file which can give you a good idea if this tool will be useful for your projects.

The bottom line is this book is quite useful as a reference with good examples which will get you started extending Visual Studio. It covers the advanced topics of add-ins and shell extensions as well as taking a close look at some of the out of the box extensibility tools. If you are writing extensions for Visual Studio or looking for a way to be more productive by using the included extensibility tools this book will help you out.

Friday, August 15, 2008

Security with the Data Dictionary

Advantage Data Dictionaries (i.e. databases) have many security options which can restrict access to data. This gives developers the ability to define permissions for any object within the database (i.e. Tables, Views, Stored Procedures, etc…). For example a specific set of views which summarize sales figures could be restricted so only the managers group can open them.

Advantage 9.0 added some additional options for configuring database security referred to as database roles. These include four new groups which are added to all data dictionaries. You can also restrict connections to Advantage allowing only data dictionary connections.

Database User Permissions

I want to quickly highlight some of the user permissions that are available within a Data Dictionary. A complete description of each of the permissions is available in the Advantage Help File.

Normal user permissions include: READ, UPDATE, INSERT, DELETE, EXECUTE, LINK_ACCESS and INHERIT. Most users will have the INHERIT permission meaning that they get their rights from their group membership. In general it is best to assign permissions, also referred to as rights, to groups and then assign users to the appropriate group. Most of these permissions are self-explanatory with the exception of LINK_ACCESS. This is allows users to open tables through a dictionary link.

Administrative permissions include: ALTER, CREATE, DROP and WITH GRANT. The ALTER permission provides some additional permissions for certain objects. The ALTER permission on a table allows for creating, deleting and modifying any Triggers, Indexes or Relations defined for the table. There is no explicit CREATE or DROP permission for these table specific objects.

Finally the WITH GRANT permission allows users to assign specific rights to other users or groups. Therefore if a user has ALTER permissions on a table and WITH GRANT permissions the user could grant the ALTER permission on the table to other users.

Database Groups

Four groups are automatically added to every version 9 and above data dictionary; DB:Admins, DB:Backup, DB:Debug and DB:Public. The first three groups have specific rights pre-defined and cannot be altered. Every user in the dictionary is automatically added to the DB:Public group and inherit all the rights assigned to the group. This makes management of new users easier since they will automatically have the same rights as all other dictionary users. You must configure the rights for the DB:Public group.

Users added to the DB:Admins have the same rights as the ADSSYS account. However, members of the DB:Admins group cannot change the ADSSYS password. You must login to the ADSSYS account to change its password, other passwords can be changed by members of the DB:Admins group.

Users added to the DB:Backup group have the permission to run backup operations. This gives members EXECUTE rights for the sp_BackupDatabase and sp_BackupFreeTables system procedures. If you are using the AdsBackup utility which ships with Advantage you can specify a username using the –y switch. This option was added in the latest service release of Advantage 9 (version 9.0.0.7 at the time of this posting) and does not exist in prior versions. 

Users added to the DB:Debug group have the permission to debug SQL scripts executed on any connection to the database. Members of this group also have permission to modify any trigger, stored procedure or user defined function, however, they cannot create new objects.

Disabling Free Table Connections

For an additional level of security you can completely disable all connections to free tables. All users must connect to a dictionary when accessing data stored on the Advantage server. Any attempt to open a free table on the server will result in a 7083 "An Advantage Data Dictionary connection is required" error.

Advantage Configuration Utility This can be set using the Advantage Configuration Utility (pictured left). The setting is found on the Configuration Utility tab under Misc. Settings. This is a simple on or off feature. You can manually set this by setting the DISABLE_FREE_CONNECTIONS to a non-zero value.

For Windows servers this is stored under the following registry key HKEY_LOCAL_MACHINE\ SYSTEM\ CurrentControlSet\ Services\ Advantage\ Configuration. For Linux and NetWare servers this is specified in the server configuration file.

Wednesday, August 13, 2008

Securing Free Table Data

Security is a concern of every network administrator and software developer. The security of information and the servers on which it resides is critical. Advantage has many features which insure data security through encryption which ensures that only Advantage can read the data. However, there are several ways to restrict access to the data using the Operating System and other Advantage Features.

Using a Service Account

Service Account ConfigurationOne of the easiest and most effective configuration changes you can make is creating a specific account for the Advantage Service. By default the Advantage services uses the SYSTEM user account, a built-in account with access to all drives on the system. By creating a specific account you can limit access to specific file locations on the server.

For example the SYSTEM account has full access to the root drive. By using a specific user for Advantage the file system rights can be much more restrictive only allowing Advantage users to access specific folders on the server.

Once a service account has been created and assigned to the appropriate group(s) it can be used by Advantage. This is done by opening the properties of the Advantage Service and specifying the account on the Log On tab (pictured).

Network Operating System Rights

Advantage uses Network Operating System (NOS) rights for determining if users can access specific files by default. Therefore specific rights must be granted to users before they can access files on the server. This method requires some additional overhead in checking these rights for each user which connects. It also requires additional management since each user must be assigned to a specific group or granted rights to the data folders.

Advantage can also ignore the NOS rights and open the file for any Advantage user. This does not require an additional check to the OS before opening the file. It also allows for easier administration since the data folders do not need any specific rights, other than the Advantage service account, granted to users. This is a very useful technique if you need to provide direct access to the files for specific groups while allowing other groups to only access the data through Advantage.

Using Server-Side Aliases

Server-side aliases were introduced in version 8.0 of Advantage and provide a mechanism for hiding data from the users. Once the alias is defined in the server.ini file it can be used as a data path by an application. Instead of connecting to a data share you specify the alias name instead.

The path to the alias gets resolved by Advantage and therefore there is no need to share the folder where the data is stored. This ensures that the data cannot be opened through the network without going through Advantage. You can get more information about server-side aliases in this tech-tip.

Data Dictionaries

There additional options for security when using Data Dictionaries. Database users and groups can be created within the dictionary and assigned rights to individual objects within the dictionary. I will discuss the various options for data dictionaries in my next post.

Monday, August 11, 2008

Checking for the CapsLock Key

A couple of months ago I read "The Design of Everyday Things" and in my review I discussed a problem I had when logging into my Amazon account. As it turns out I had CapsLock on so I typed the correct letters for my password but in the wrong case. After realizing this I was able to successfully log into the site. This got me thinking about how this frustration could have been avoided if the developer had added code to check the status of CapsLock and letting the user know if it was on.

As it turns out it is pretty easy to check the status of CapsLock in a Win32 program using the GetKeyState() function. This function returns the state of a virtual key as a short value. The function is contained in the user32.dll so a reference to this external library must be made.

When using C# you must add a reference to the System.Runtime.InteropServices which contains the DllImport function used to define external functions. So to add a reference to the GetKeyState function you must add the following line to your code

   1: [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true,
   2: CallingConvention = CallingConvention.Winapi)]
   3: public static extern short GetKeyState(int keyCode);

With the reference to user32.dll and the GetKeyState function defined you can use the function within your application. In this case I check the state of the CapsLock key when a login form loads and when keys are pressed in the Username and Password boxes by calling the isCapsLock() function. This function displays a warning if the CapsLock is engaged.

   1: private void isCapslock()
   2: {
   3:     if ((((ushort)GetKeyState(0x14 /*VK_CAPITAL*/)) & 0xffff) != 0)
   4:     {
   5:         lblWarning.Text = "Caps Lock is on";
   6:         lblWarning.Visible = true;
   7:     }
   8:     else
   9:         lblWarning.Visible = false;
  10: }

When using Delphi the GetKeyState() function is already built-in so you do not need to add a reference to the user32.dll. The Delphi code is very similar to the C# code.

   1: procedure Form1.isCapsLock();
   2: begin
   3:   if 0 <> (GetKeyState(VK_CAPITAL) and $01) then
   4: begin
   5:     lblWarning.Caption := 'CapsLock is on';
   6:     lblWarning.Visible := true;
   7:   end
   8:   else
   9:     lblWarning.Visible := false;
  10: end; 

These examples are for Windows programs and cannot be used for a web based application. If you would like to provide similar functionality you will have to use some JavaScript to check the status of CapsLock. You can refer to Scott Mitchells article on 4 Guys from Rolla for an ASP.NET example.

Friday, August 8, 2008

Advantage ASP.NET Providers Configuration

This post is one of a series of posts about the Advantage Providers for ASP.NET other posts in this series are listed below.

In my previous posts I have discussed the implementation of various providers which use Advantage Database Server as the data store. This post will focus on using the providers which are in a single DLL (AdsProviders.dll) which can be downloaded here. The zip file also includes a data dictionary which is used to store the user data.

The providers were compiled using version 8.1 of the Advantage .NET Data Provider and therefore require version 8.1 or newer of the Advantage server.

Installation Steps

Once installed and configured the Advantage ASP.NET Provider will be used by the standard ASP.NET Login Controls. The provider consists of a single DLL (AdsProviders.dll) and a Data Dictionary (AdsAuth).

  1. Place the AdsAuth.add and AdsAuth.am files into a directory accessable to the WebServer. The required tables will automatically be generated when they are first accessed. The provided dictionary does not have an ADSSYS password assigned, it is highly recommended that you establish a strong password for the database prior to use in a production environment.
  2. Copy the ADSProviders.dll to the BIN directory of your WebSite.
  3. Add a reference to the Advantage .NET Data Provider to your WebSite.

Configuration

Each of the providers requires a connection string to be defined in the web.config file. This must point to the dictionary containing the required tables outlined in installation steps section above. An example of the connection string entry is below.

<connectionStrings>
  <add name="LoginServices" connectionString="data source=C:\Data\aspauth\aspauth.add; 
                        ServerType=REMOTE; User ID=adssys; Password=password;"/>
</connectionStrings>

To use the AdsMembershipProvider it must be defined in the web.config file. Details on the various options are available from Microsoft. It is important that the name provided for the connectionStringName property is one of the connection strings defined in the web.config file

<membership defaultProvider="AdsMembershipProvider" userIsOnlineTimeWindow="15">
  <providers>
    <add name="AdsMembershipProvider" type="AdsMembershipProvider" connectionStringName="LoginServices" 
        enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="true" 
        writeExceptionsToEventLog="true" passwordFormat="Encrypted"/> 
  </providers> 
</membership> 

To use the AdsRoleProvider it must be defined in the web.config file. Details on the various options are available from Microsoft. An example is provided below:

<roleManager defaultProvider="AdsRoleProvider" enabled="true" cacheRolesInCookie="true" 
    cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" 
    cookieSlidingExpiration="true" cookieProtection="All"> 
  <providers> 
    <add name="AdsRoleProvider" type="AdsRoleProvider" connectionStringName="LoginServices" 
        writeExceptionsToEventLog="True"/>
  </providers> 
</roleManager> 

In order to use an encrypted or hashed password a MachineKey must be defined. The machine key is used to encrypt passwords in the table. Many tools are available to generate machine keys on the Internet. Scott Forsyth developed a machine key generator and posted it on OrcsWeb. An example is posted below:

<machineKey ValidationKey='07A557873B9CC1E1D788724A4C1B1FB4D969986DAA4A1085E64 
    CE3720B626F07DB6F679A362CFA0078D03ACE3B8B5E083252B3E60454F887107CCF19CF63FE3B' 
    decryptionKey='37485C760ED3869F1479ABD76CC57AB7C0CD94F63CA07710' validation='SHA1'/> 

Wednesday, August 6, 2008

Advantage SiteMap Provider

This post is one of a series of posts about the Advantage Providers for ASP.NET other posts in this series are listed below.

A SiteMap Provider is another part of the ASP.NET Provider Model and is used to provide information about the structure of the web site. This provider is generally used with a SiteMapDataSoruce control which provides information to various visual controls such as the SiteMapPath which provides a breadcrumb path. The SiteMapDataSource can also be consumed by any other data aware control.

As with the other Advantage Providers the Advantage SiteMap provider uses an Advantage table to store site information. Unlike the other providers the SiteMap provider only reads the data from the table there are no methods for manipulating the data through the provider. You must use some other mechanism for adding pages to the site map.

The Advantage SiteMap provider builds a SiteMapNode by reading the information from the SiteMap table in the data dictionary. Once this SiteMapNode is populated it can be used by the SiteMapDataSource. The node is populated using the BuildSiteMap public method and some helper functions. The BuildSiteMap method reads the data from the SiteMap table into a DataReader which is then used to create the SiteMapNode.

   1: public override SiteMapNode BuildSiteMap()
   2: {
   3:     lock (this)
   4:     {
   5:         // Return immediately if this method has been called before
   6:         if (_root != null)
   7:             return _root;
   8:  
   9:         // Query the database for site map nodes
  10:         AdsConnection connection = new AdsConnection(_connect);
  11:  
  12:         try
  13:         {
  14:             connection.Open();
  15:             AdsCommand command = new AdsCommand("SELECT * FROM SiteMap", connection);
  16:             AdsDataReader reader = command.ExecuteReader();
  17:             _indexID = reader.GetOrdinal("ID");
  18:             _indexUrl = reader.GetOrdinal("Url");
  19:             _indexTitle = reader.GetOrdinal("Title");
  20:             _indexDesc = reader.GetOrdinal("Description");
  21:             _indexRoles = reader.GetOrdinal("Roles");
  22:             _indexParent = reader.GetOrdinal("Parent");
  23:  
  24:             if (reader.Read())
  25:             {
  26:                 // Create the root SiteMapNode and add it to the site map
  27:                 _root = CreateSiteMapNodeFromDataReader(reader);
  28:                 AddNode(_root, null);
  29:  
  30:                 // Build a tree of SiteMapNodes underneath the root node
  31:                 while (reader.Read())
  32:                 {
  33:                     // Create another site map node and add it to the site map
  34:                     SiteMapNode node = CreateSiteMapNodeFromDataReader(reader);
  35:                     AddNode(node, GetParentNodeFromDataReader(reader));
  36:                 }
  37:             }
  38:         }
  39:         finally
  40:         {
  41:             connection.Close();
  42:         }
  43:  
  44:         // Return the root SiteMapNode
  45:         return _root;
  46:     }
  47: }

A Lock is used making this method a critical section for thread safety. If the SiteMapNode has already been populated then it is returned to the calling method. Site maps do not generally change very often so reloading the node each time should be unnecessary. Once the table has been read the indexes of the various SiteMap items are determined and the root node is created using the CreateSiteMapNodeFromDataReader method.

   1: private SiteMapNode CreateSiteMapNodeFromDataReader(AdsDataReader reader)
   2: {
   3:     // Make sure the node ID is present
   4:     if (reader.IsDBNull(_indexID))
   5:         throw new ProviderException(_errmsg1);
   6:  
   7:     // Get the node ID from the DataReader
   8:     int id = reader.GetInt32(_indexID);
   9:  
  10:     // Make sure the node ID is unique
  11:     if (_nodes.ContainsKey(id))
  12:         throw new ProviderException(_errmsg2);
  13:  
  14:     // Get title, URL, description, and roles from the DataReader
  15:     string title = reader.IsDBNull(_indexTitle) ? null : reader.GetString(_indexTitle).Trim();
  16:     string url = reader.IsDBNull(_indexUrl) ? null : reader.GetString(_indexUrl).Trim();
  17:     string description = reader.IsDBNull(_indexDesc) ? null : reader.GetString(_indexDesc).Trim();
  18:     string roles = reader.IsDBNull(_indexRoles) ? null : reader.GetString(_indexRoles).Trim();
  19:  
  20:     // If roles were specified, turn the list into a string array
  21:     string[] rolelist = null;
  22:     if (!String.IsNullOrEmpty(roles))
  23:         rolelist = roles.Split(new char[] { ',', ';' }, 512);
  24:  
  25:     // Create a SiteMapNode
  26:     SiteMapNode node = new SiteMapNode(this, id.ToString(), url, title, description, 
  27:                                        rolelist, null, null, null);
  28:  
  29:     // Record the node in the _nodes dictionary
  30:     _nodes.Add(id, node);
  31:  
  32:     // Return the node        
  33:     return node;
  34: }

Notice that a SiteMap node has a roles property which can be used to restrict what the user sees. The SiteMap can be trimmed so only the pages the user can access are displayed. After the first node has been created subnodes are added based on their parent nodes. The parent is specified in the SiteMap table in the Parent field and must be specified for all but the first record in the table. The Parent field contains the ID of the node under which the subnode will fall.

You can download the provider from CodeCentral on the DevZone under the WebApps category. You can also use this direct link to the Advantage ASP.NET Providers project. I'll be discussing the implementation of these providers next.

Friday, August 1, 2008

FAQs – July 2008

Here is the latest installment of my monthly FAQs. I got some very interesting questions during July and here are a few of the questions I received.

Using the DBC Convert utility

With the release of Advantage 9.0 we added support for the Visual FoxPro 9 table format. However, we do not support opening of Database Containers (DBC), so we created a utility to convert a DBC into an Advantage Data Dictionary (ADD). The utility is a FoxPro program (.prg file) which ships with the Advantage ODBC Driver and OLEDB Provider.

To use the utility you need to connect to your DBC from within FoxPro 9 and then run the dbcconvert.prg file. This will read the properties of the DBC and create an ADD which can be used by Advantage. This is a one time operation and does not track changes made to the DBC. If you change the DBC you will need to run the convert utility again or manually apply the same changes to your ADD.

The utility adds references to all the DBF files, creates long file names, converts table and field validation rules, default field values and referential integrity rules. If you are interested in using Advantage with your FoxPro application visit our Getting Started with FoxPro page.

Accessing Tables >4GB on Novell Netware

Advantage Database Server has supported ADT Tables >4GB in size since version 6.X and DBF Tables >4GB since version 8. This functionality is available “out of the box” for the Windows and Linux versions of the server. However, if you need to access files >4GB in size on a Netware machine you will need to contact sales and get a special build of the server. 

Connection Pooling

If you use the Advantage .NET Data Provider you may have some familiarity with Connection Pooling. By default the provider uses connection pooling to provide efficient use of resources. Since ADO.NET works with data in a disconnected fashion connections may get opened and closed frequently. The connection pool reduces the overhead of re-creating the connections over and over.

You can control the connection pool using the following key words in your connection string. The default values are in parenthesis “()”

  • Pooling – True or False (true)
  • Min Pool Size – integer value (0)
  • Max Pool Size – integer value (100)
  • Connection Lifetime – integer (0)

One potential problem with the connection pool is the chance that some connections will remain open longer than you want them to. It can take some time for the .NET garbage collector to completely close all of the connections in the pool. If you need to quickly close connections in the pool you can use the FlushConnectionPool() method. This ensures that all connections in the connection pool are closed. You can also specify a specific connection string which will close all connections using that string.

AdsConnection vs DatabaseName

When using the Advantage TDataset Descendent the TAdsTable and TAdsQuery components have two properties which can be used to specify the connection to use. AdsConnection is the preferred choice since it uses a pointer to the connection object. DatabaseName is a string and a bit more flexible since you can specify an alias, the name of the connection component or a path.

Additionally the AdsConnection property is a better choice when using a mulit-threaded application since passing the pointer is more reliable then depending on the name resolution logic which is used when the DatabaseName property is specified.