Configure Your .Net Application - The Other INI

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 43

Configure Your .

Net Application - The Other INI

Jason Huffine – Tennessee Valley Authority

CP5462 This course is intended for anyone developing .Net applications for AutoCAD that would like to
learn how to make their applications more dynamic with configuration files. There’s a lot of value in storing
information that can be read at startup or saved during use without the requirement of a database! Using
configuration files, developers can save user-specific preferences such as window size, last opened directory, or
application settings saved by the user. Application parameters can be updated without recoding and recompiling.
Historically, INI files have been a popular tool for the accomplished programmer. The .Net framework offers a
more integrated solution using its configuration classes in conjunction with the xml-based configuration files. In
this class we will cover the basics of the .Net configuration classes, how to integrate their use into your .Net
application for AutoCAD, and how to customize configuration elements specifically suited for your application.

Learning Objectives
At the end of this class, you will be able to:
• Understand the basic mechanics and fundamental elements of the Microsoft configuration system.
• Understand how to create/modify your application’s configuration file.
• Understand how to use the .Net configuration classes to interface with the configuration file.
• Understand how to use the .Net framework and its configuration classes to create and handle custom
configuration elements.
• Understand how to persist custom object data in a configuration file.

About the Speaker


Jason is an electrical engineer for the Tennessee Valley Authority. He currently manages the
AutoCAD customization and automation development for the Substation Projects division. This
division consists of various levels of physical and electrical design as well as field engineering and
construction. Much of his support and developed automation is centered on enhancing design
processes, automating incorporation of intelligence into the drawings, as well as processing
information contained within design drawings in lieu of support of construction processes. His toolset
includes using scripts, VBA, LISP, and VB.NET. He has a BS in electrical engineering from Tennessee
Technological University, an MBA from the University of Tennessee at Chattanooga, Black Belt
certification in Lean/Six-Sigma, and his PE license. On occasion he has presented corporate-level
proposals and facilitation of process improvement events as well as departmental training in support of
his developed automation.

Email: bjhuffine@tva.gov
Configure Your .Net Application - The Other INI

INTRODUCTION
Integrating the power of the AutoCAD .Net API with the flexibility and power behind the Microsoft .Net
Framework can yield some amazing results. Just as many of us have found the Autodesk products and
interfaces to be complex, deep, and well thought out, the .Net Framework provides a literal framework
of classes and objects at a mind-numbing depth and complexity all intended to provide developers with
a controlled toolset beneficial to accomplishing specified tasks. It should be no surprise that Microsoft
has provided objects for use in creating and interacting with configuration files. This document outlines
these objects, identifies how to use them and progresses toward total customization of them.

So what’s in it for me? Easy... configuration files can be used to store integral application settings
information. The hierarchical structure of the configuration system allows developers to control and
store settings data via both manual and programmatic updates without requiring additional code! Need
a few reasons? Here’s a few that could give some idea as to when it may be beneficial to use
configuration files:

• In lieu of an INI file. INI files require P/Invoke or custom parsing code to which the rules may
not always be the same from one to the next.
• When a small data store is needed. Most applications require storage of some data. If the
amount of data needed to be stored is too small to involve all the overhead of maintaining a
database and a more streamlined and less clunky approach that streamreaders/writers with
custom parsing code is desired, then a configuration file may be an ideal solution.
• When configuration data needs to be shared between applications.
• When user preferences need to be stored somewhere for a custom look and feel to the
application.
• In lieu of hard coding settings or program constants. Being able to update a configuration file’s
settings information via any text or xml editor means being able to update program constants,
calculation parameters, etc, without recoding, recompiling, and redeploying!
• When strongly-typed data needs to be read, modified, and saved in a simple, easy-to-use
system.
• When using a predefined .Net configuration element can simplify application development and
maintenance or add control. For example, when connecting with an external database, defining
supporting CLR versions, needing reference versioning control, specifying trace and debug
settings data, specifying how the .Net Framework is to connect to the Internet and handle web
addresses, specifying cryptography settings, ... okay, okay, okay... I think you get the point now.
The point is... there are many predefined configuration file elements that can provide a quick
and efficient means of application control and storage of critical, but variable, settings data such
as the <ConnectionStrings> element collection for storing database connection string data or
the <dependentAssembly> element that can be used to identify a specific reference for
versioning control or the <cryptographySettings> element to store encryption settings data.

2
Configure Your .Net Application - The Other INI

This topic is incredibly simple for those wanting a simple solution, but can be incredibly deep for those
wanting to perform complex operations using the technology. In fact, there are industry experts who
have been considering writing books on the topic to more clearly document the technology. With that in
mind, consider that this document provides a lot of information from beginning to advanced levels in a
short amount of space. So unfortunately, there may be specific details, predefined elements, or other
more specific, related topics not covered in this document. No worries though, added are some
references at the end to assist, and if all else fails, feel free to contact me and maybe between the two
of us we can figure it out.

Assumptions/Givens

Obviously, discussion of such a topic without writing volumes of information requires some assumptions
and givens. Hopefully these will assist the reader more clearly identify the scope of this document.

• Configuration hierarchies and classes can be applied in both an application (exe) and web
context. This document will only consider the application context.
• This document focuses on the Microsoft .Net Framework configuration file technology and how
to leverage it with AutoCAD applications developed using the AutoCAD .Net API. Therefore it is
assumed that the reader already understands the concepts of setting up a basic project for
AutoCAD automation, which API references to use, how to interact with the drawing database,
etc.
• The reference code is based on projects developed with the 4.0 Framework, however, they
were tested with the 3.5 Framework as well.
• Though there are quite a few pages to this document, a large portion of it is illustrated code.
This makes including examples in both C# and VB difficult to do without making a book out of it.
So where there are the greatest differences, both will be shown. Otherwise, C# examples will
be used. There are some fantastic free translator tools online that can assist if you are a
diehard VB’er (if that’s even a word) or again, feel free to contact me and samples in VB can be
provided.
• To keep this document concise and to-the-point, there are some assumptions with some of the
technology, such as knowing how to work with an INI or the components of an xml file. For
those who are familiar with this, no problems, for those who are not, I’ve included appendices of
supplemental information. This way hopefully there will be no one left behind.

THE FUNDAMENTALS
Background

Application “initialization” files, aka the ever popular INI files, have been in use for MANY, MANY years.
In fact, Initialization File Mapping was introduced with Windows NT and Windows 95 to aid developer
migration of data stored in classic INI files to (at that time) the new Windows Registry. Even so, many
continue to use them today. INI files have the *.ini extension and, as illustrated below, its structure can
be typified as a simple, plain-text file with section tags surrounded by square brackets, property

3
Configure Your .Net Application - The Other INI

elements in each section with assigned values, and semicolons prefixing lines for commenting. INI files
can be read with Notepad or any other plain-text editor.

For the most part, reading INI files is accomplished with one of two approaches:

1.] P/Invoke of unmanaged COM references or


2.] Writing custom text file parsing methods.

Writing custom parsing methods introduces several inconsistencies:

• Some allow whitespace above/below, some do not.


• What character should be used to identify quoted values, (‘) or (“)?
• Some use the pound (#) character versus the semicolon character (;) for comments.
• Should the property name/value delimiter be the colon (:) or equal sign (=)?
• Typically, there is no hierarchy in an INI file, so if the user wished to create the illusion of one,
how?
• How can a developer persist a custom object (class) into an INI without having to write out and
read in every detail?
• Writing custom parsing code creates another layer processing that can be slower than if
read/written directly by predefined system methods.

Hopefully, the information to follow will address all these and then some by using the predefined
Microsoft .Net Configuration classes.

Configuration File Basics

Configuration files are xml-based files whose naming convention appends .config to the name of its
corresponding executable. For example, if an AutoCAD .Net application executable is titled
MyCADApp.dll, the configuration file would be aptly named MyCADApp.dll.config. Because these files
are xml-based, they can be viewed in a variety of ways such as Notepad, Visual Studios, Internet
Explorer (great way to view as read-only and allow expand/collapse of sections without accidently
modifying the file), and other third-party xml editors. There are five types of configuration files in the
application context. The following identifies and briefly explains each:

4
Configure Your .Net Application - The Other INI

1.] Machine Configuration File: Machine.config

The machine-level configuration, Machine.config, can be found in the directories


“C:\Windows\Microsoft.NET\Framework\v2.0.50727\Config” for the 2.0 and 3.5 Framework
and “C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\Config” for the 4.0 Framework.
This configuration file uses the <appSettings> element to store configuration information that
affects all applications running within the .Net CLR. Feel free to open this and navigate
around its content, but due to the serious implications of an accidental modification, I would
greatly suggest that anyone uncomfortable should use Internet Explorer so that sections can
be collapsed or expanded for easy navigation but still keep the view at a read-only
perspective.

2.] Application Configuration File: [AppName].exe.config, [AppName].dll.config

The application-level configuration, [AppName].exe.config or [AppName].dll.config, is where


most of this document will spend its time. This configuration file will contain all application-
level settings and can even be used to define the use of and default values for settings
associated with the Roaming-User and Local-User configuration files. These files are
typically stored in the same directory as the application executable, but can be placed
elsewhere if necessary. There will be much more discussion on this to come.

3.] Roaming-User Configuration File: User.config

This is a higher-level, user configuration file that is typically used to identify the user-specific
settings that can be accessed on different machines connected to the same network when
Windows roaming profiles are enabled. Though it fits within the configuration hierarchical
architecture, using the roaming profile will be a bit beyond the scope of this document. The
location of these roaming configuration files are %UserProfile%\Application Data\[Company
Name]\[Application Name]_[Evidence Type]_[Evidence Hash]\[Version]\user.config where
the Company Name, Application Name, and Version values are as identified by the
application assembly.

4.] Local-User Configuration File: User.config

This is probably the most specific configuration file in the configuration hierarchy of the
application context. This is where user-specific settings information can both be stored and
written to programmatically. Its default settings values are defined in the application
configuration file while the actual values are stored in the user.config file located in
%UserProfile%\Local Settings\Application Data\[Company Name]\[Application Name]_
[Evidence Type]_[Evidence Hash]\[Version]\user.config where the Company Name,
Application Name, and Version values are as identified by the application assembly. These
values can be modified from the Application tab in the Visual Studio project properties
designer window. These values are automatically applied in the AssemblyInfo.cs or
AssemblyInfo.vb project files as assembly attributes.

5
Configure Your .Net Application - The Other INI

5.] Separate, External Configuration Files: AppSettings.config

Sometimes it may make sense to separate settings information across multilple


configuration files. If so, there are ways of merging these auxiliary files with the application-
level configuration file so that it provides seamless integration.

The Merging Concept

Not only can a hierarchical network of elements be utilized within a configuration file, but the entire .Net
configuration architecture is hierarchical. Consider the following illustration of this concept regarding
the previously outlined configuration files. The hierarchy is machine → application → roaming-user →
local-user, with local-user being the most specific. This merging concept is also documented in several
other online articles and in MSDN. A very thorough and detailed outline of this concept and the
configuration architecture can be found in a series of online articles titled Unraveling the Mysteries of
.Net 2.0 Configuration. See the References section of this document for more about this article.

6
Configure Your .Net Application - The Other INI

Machine Configuration: Machine.config

Application exe Configuration:

acad.exe.config [Require Custom Merging] External Configuration:


[AppSettings].config
[MyCADApp].dll.config
MERGING

Roaming-User
Configuration:
User.config

Local-User
Configuration:
User.config

When an application loads its configuration data, the .Net CLR will automatically combine all these
configuration settings into a single merged view. If a setting is duplicated, the more specific setting will
override the least specific. In other words, if the setting in the local-user configuration file is duplicated
in the roaming-user, the local-user will override and replace the roaming-user setting.

Configuration Elements, Sections, and Groups

Before diving straight into creating and modifying a configuration file, let’s get a better handle on its
structure. Configuration elements can be nested, creating a hierarchy of section groups, sections,
basic data elements, element collections, etc. Element tags are used for each. These are illustrated
below using a series of custom elements.

<!--Configuration Group-->
<example.group>

<!--Section with attributes-->


<example.section
dateModified="7/7/2011">

<!--Basic Element Nested in Section-->


7
Configure Your .Net Application - The Other INI

<BasicElement
dateTimeValue="7/7/2011"
intValue="100"
/>

<!--Element Collections-->
<myElementCollection>
<add type="cake" flavor="carrot"/>
<add type="pie" flavor="coconut"/>
<add type="cheesecake" flavor="chocolate"/>
</myElementCollection>

</example.section>

<!--Add as many Sections as necessary-->

</example.group>

The configuration file schema includes a slew of predefined elements that are included in the .Net
Framework’s configuration schema. These predefined elements can be leveraged to specify trace
listeners that collect, store, and route trace/debug messages, determine how the CLR handles garbage
collection for the specified application, determine the version of an assembly to use, specify
cryptography mapping schema, define database connection strings, and much more. As previously
mentioned, the network of configuration classes is incredibly deep, so with exception of a few helpful
predefined elements, the rest will be considered out of scope.

Developers can also create custom elements to store settings information as well. This can be
extremely beneficial when attempting to more clearly organize the structure of a large configuration file.
In either case, whether using standard, predefined, or custom elements, most (if not all) will be nested
within the root <configuration> element.

BASIC CONFIGURATION FILES


This document will begin with the most basic approach and then increase the complexity and benefits.
This section describes how to build and interact with a configuration file using the most basic approach.

Creating a Basic Configuration File

Without further ado, let’s begin building our first configuration file. There are multiple ways we can
accomplish this, but let’s start with the most basic approach and keep things simple.

Step-1. Using Visual Studios, start and setup a class library project for an AutoCAD, .Net
application including the acdbmgd.dll and acmgd.dll references.

Step-2. From the Project menu, select the project properties menu item. Then select the
Settings tab. If using the C# language, Visual Studios will provide a link in the center
of the Settings tab requesting the necessary approval to create a Settings class
within the project. Click this link.

8
Configure Your .Net Application - The Other INI

Step-3. Type in the setting Name, select the Type, and enter a default Value. Try creating
multiple settings using a variety of types.

Browse... is a Type selection option that can be very handy. As seen below, any
referenced types (in our cases the acdbmgd.dll and acmgd.dll types) can be seen for
selection. This is great as it allows us to use the custom types in the settings derived
in AutoCAD’s .Net API. Also, after adding the references, you may need to ensure
the start external program has the acad.exe supplied on the Debug settings tab in
the project properties and a quick build is done. Evidently Visual Studios will not see
the AutoCAD API reference objects without first having an internal reference to its
parent exe.

A little word of caution... this feature is finicky. It seems that Visual Studios caches
reference information for performance reasons and there may be times when this
cache doesn’t update correctly and the references can’t be seen. This can be
frustrating, but there are some back door workarounds through manual modification
of the Settings.settings and Settings.Designer.cs files if absolutely necessary. If the
issue is not seeing the AutoCAD API reference types, try resetting start external
program setting, save, and rebuild.

9
Configure Your .Net Application - The Other INI

Step-4. Now select a Scope for each. You’ll notice that there are two: Application and User.

User-scoped settings are those that will be persisted in the local-user, user.config
file. These can be modified and saved during runtime. The default values and
section declaration will remain in the application-level configuration file though.

Application-scoped settings are those that will be persisted in the application-level


configuration (*.dll.config). These cannot be modified and saved during runtime.
Instead, these are read-only settings where the intention is solely for administrator
modification and redeployment of the application-level configuration file.

Step-5. Select Save, view the VS Solution Explorer, and notice that a new file called
app.config was added.

app.config is a template configuration file associated to the particular application.


This file is not the official build version configuration file though. When building the
project, the compiler will copy this file into the appropriate bin directory along with the
executable using the required naming convention, MyCADApp.dll.config. So...

WARNING!!! As tempting as it may be for many to want to rename this file, it is


critical that you DO NOT change its name. If changed, the compiler will not find it
and therefore will not create the official build version.

Taking a look at the results of what Visual Studios has created for us in the app.config file, we see the
following:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<!--the <configSections> element declares the section groups userSettings and
applicationSettings along with the sections applicable to each-->
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup, System,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section name="DocumentConfigSample.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser"
requirePermission="false" />
</sectionGroup>

10
Configure Your .Net Application - The Other INI

<sectionGroup name="applicationSettings"
type="System.Configuration.ApplicationSettingsGroup, System,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" >
<section name="DocumentConfigSample.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System,
Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
</sectionGroup>
</configSections>

<!--The userSettings settings as defined in the Project Properties/Settings Tab


Note: These settings can be changed during runtime-->
<userSettings>
<DocumentConfigSample.Properties.Settings>
<setting name="myColor" serializeAs="String">
<value>255, 255, 192</value>
</setting>
<setting name="mySize" serializeAs="String">
<value>100, 200</value>
</setting>
</DocumentConfigSample.Properties.Settings>
</userSettings>

<!--The applicationSettings as defined in the Project Properties/Settings Tab


Note: These settings cannot be changed during runtime and are intended to
represent those settings that require administrator modifications and redeployment
for control.-->
<applicationSettings>
<DocumentConfigSample.Properties.Settings>
<setting name="myString" serializeAs="String">
<value>Test string</value>
</setting>
<setting name="myBoolean" serializeAs="String">
<value>True</value>
</setting>
</DocumentConfigSample.Properties.Settings>
</applicationSettings>
</configuration>

If developing in VB, there will be an additional group element called <system.diagnostics>. Simply
collapse or delete this. This section is a predefined element that is intended to allow the developer to
incorporate trace capabilities via the system Event Log. If chosen to ignore or delete this, there is no
harm as the code generator for VB is simply trying to add some helpful code when initializing a
configuration file.

Interaction with the Basic Configuration File

Creating the configuration file was extremely simple! And believe it or not, so is interacting with it.
Reading and writing settings values is accomplished slightly different with C# than with VB. Accessing
the values with VB means using the Settings property in the My namespace, while with C#, using the
Settings.Default property of the application’s Properties namespace:

11
Configure Your .Net Application - The Other INI

VB:
'Read the stored Settings (both Application- and User-scoped)
Dim acadColor As Autodesk.AutoCAD.Colors.Color = My.Settings.myColor

'Write to User-scoped Settings only


My.Settings.myColor = Autodesk.AutoCAD.Colors.Color.FromRgb(0, 0, 0)
My.Settings.Save()

'Obtain the Default User-scoped Settings Value


Dim strMyColor As String = My.Settings.Properties("myColor").DefaultValue

C#:
//Read the stored Settings (both Application- and User-scoped)
Autodesk.AutoCAD.Colors.Color acadColor =
MyCADApp.Properties.Settings.Default.myColor;

//Write to User-scoped Settings only


MyCADApp.Properties.Settings.Default.myColor =
Autodesk.AutoCAD.Colors.Color.FromRgb(0, 0, 0);
MyCADApp.Properties.Settings.Default.Save();

//Obtain the Default User-scoped Settings Value


string strMyColor = (string)
MyCADApp.Properties.Settings.Default.Properties["myColor"].DefaultValue;

Reading and writing settings values seems pretty straightforward. However, what happens after the
updated values is saved? Where will we find these? How can we verify that the application is doing
what we asked it to? Since application-scoped settings are not intended to be programmatically
updated during runtime, these are persisted in the application’s configuration file, i.e. the
MyCADApp.dll.config file. So this is really no big deal.

As for the user-scoped settings, the application configuration file (i.e. MyCADApp.dll.config) only shows
the default settings’ values and not the persisted ones. Not to worry though, they’re not out in the great
beyond where we’ll never find them again. You may remember that we stated the local-user
configurations are stored in the %UserProfile%\Local Settings\Application Data\[Company
Name]\[Application Name]_[Evidence Type]_[Evidence Hash]\[Version]\user.config directory. The
problem here is that if you look in that directory associated with your specific application’s [Company
Name] you will not find anything. What gives? It’s there, just not where you think. Since any AutoCAD
.Net automation are developed as class libraries, *.dll, they do not run within their own process space.
Instead, they are loaded within the ACAD.exe process space, making AutoCAD the host application.
Now let’s take another look but with Autodesk, Inc as the [Company Name], and aula... there lies a
user.config file. Open the file and you’ll find all the persisted user-scoped settings values for your
application.

12
Configure Your .Net Application - The Other INI

<?xml version="1.0" encoding="utf‐8"?>


<configuration>
<configSections>
<sectionGroup name="userSettings"
type="System.Configuration.UserSettingsGroup, System,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
<section name="adskBasicConfig_CS.Properties.Settings"
type="System.Configuration.ClientSettingsSection, System,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser"
requirePermission="false" />
</sectionGroup>
</configSections>
<userSettings>
<adskBasicConfig_CS.Properties.Settings>
<setting name="mySize" serializeAs="String">
<value>400, 800</value>
</setting>
<setting name="myColor" serializeAs="String">
<value>0,0,0</value>
</setting>
</adskBasicConfig_CS.Properties.Settings>
</userSettings>
</configuration>

EXTERNAL CONFIGURATION FILES


There may be times when it makes sense to separate settings data from the main application
configuration file. Externalizing an auxiliary configuration file is a very simple process with use of a .Net
configuration element that’s been around since before the 2.0 version framework, the <AppSettings>
element.

Creating the External Configuration File

Consider that a class library executable has been developed for use in AutoCAD and has its own
configuration file associated with itself. Now consider that the developer wishes to extend this
configuration with a file independent from the application and accessible to other applications. Follow
these steps to create an auxiliary configuration file:

Step-1. Using Visual Studios, open and create the desired project intended to maintain the
auxiliary configuration file.

Step-2. In Solution Explorer, right-click the project title and select Add/New Item...
13
Configure Your .Net Application - The Other INI

Step-3. From the language category selected, select Application Configuration File. Since
this configuration file is intended to be maintained outside of the application context,
there’s no harm renaming this one. For the sample code to follow, this one has been
named “Auxiliary.config”... Okay, okay... I’m an engineer and creativity is not my best
suit. Now select Add.

The configuration file created should look something like this:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
</configuration>

Step-4. This is a start, but we need some data to work with. The <appSettings> element is
one that is intended to work with a key/value collection of settings elements. So let’s
type: <appSettings></appSettings> in between the <configuration> element to
define the configuration section boundary to which these settings will be placed.
Then type <add key="SomeUniqueKey" value="SettingValue"/> for each
setting in between the <appSettings> element. Here’s an example:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
<appSettings>
<add key="fontFilePath"
value="C:/Program Files/AutoCAD 2012/Fonts"/>
<add key="bmpFilePath"
value="C:/Program Files/AutoCAD 2012/Icons"/>
<add key="plotstylesFilePath"
value="C:/Program Files/AutoCAD 2012/Plot Styles"/>
<add key="myInteger" value="1050"/>
<add key="myColor" value="Red"/>
<add key="myLine"
value="Line{Point1:(0,0,0),Point2:(5,5,5)}"/>
</appSettings>
</configuration>

14
Configure Your .Net Application - The Other INI

Now if you’ve wondered where it is we’re supposed to define the data type, then
you’re asking a good question. You don’t. Unfortunately, the <appSettings> element
is not strongly-typed. To ensure a strongly-typed environment, you’ll have to
incorporate that into your handling of the values. More on this later.

Interaction with an External Configuration File

So far, creating an external configuration file has been a breeze. Believe it or not, so is interacting with
one. The only trick here is that working with one requires an understanding of how to open and
encapsulate an external configuration file. The reason for this not simply being a “Step 1, 2, 3”
approach is because of context. Earlier we showed that it is easy to use the Settings tab from the
Project Designer in Visual Studios to both create and interact with configuration file settings. These
settings operate in the context of our MyCADApp.dll executable and the CLR assumes the two are in
the same directory. This time, the settings are in a separate and independent file, unassociated with
the executable. To better explain this process, let’s begin with opening an external configuration file.

MSDN and many other resources will illustrate opening an external configuration file by associating it to
the application’s configuration. The problem with this is that the application configuration (i.e. that
regarding the official application context) is not your MyCADApp.dll, it’s the parent host application,
acad.exe. So be aware that these resources may create some really confusing results. No worries
though, there are some methods available for opening an external configuration file.

First, let’s discuss opening the Auxiliary.config file we created earlier. Since this is an independent and
unassociated file, we can use the OpenMappedExeConfiguration() method of the
ConfigurationManager class which returns a Configuration class reference associated to the specified
configuration file. Now that’s a mouthful! The ConfigurationManager class in the System.Configuration
namespace is a sealed, static class that provides access to configuration files. The Configuration class
object returned represents the merged view of the configuration settings from all the configuration files
that apply to a specific entity (computer, application, or web site). The ConfigurationManager actually
provides several Open...Configuration() methods for opening various levels of configuration files, but for
this example the OpenMappedExeConfiguration() applies best for opening an external one. Below is
an example of how this method can be used to open an external file.

//Get the auxiliary configuration file


ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
fileMap.ExeConfigFilename = @”C:\...\Auxiliary.config";
Configuration auxConfig = ConfigurationManager.
OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);

Now let’s jazz things up a bit and open the acad.exe.config file that should be located in the same
directory as acad.exe. Ahhhhh yes... I did go there! There is a whole host of reasons for using the
acad.exe.config file. For example, one could use it to store AutoCAD system variable settings’ values
intended to initialize the drawing environment, store settings associated with automation developed
around specific AutoCAD features such as publishing or plotting, or to modify configuration properties
that affect how AutoCAD loads and operates our custom .Net applications.

15
Configure Your .Net Application - The Other INI

Since the application configuration is that associated with acad.exe (i.e. the host application), opening it
is even simpler than for an unassociated and independent external file:

//Get the acad.exe configuration file


Configuration acadConfig = ConfigurationManager.
OpenExeConfiguration(ConfigurationUserLevel.None);

The ConfigurationUserLevel enumeration parameter in OpenExeConfiguration() has three options:

1.] None: Gets the Configuration that applies to the application context. For AutoCAD .Net
development, this would mean the acad.exe.config since acad.exe is the host application.
2.] PerUserRoaming: Gets the Configuration associated with the Roaming-User, user.config, as
associated with the host application.
3.] PerUserRoamingAndLocal: Gets the Configuration associated with the Local-User, user.config,
as associated with the host application.

Below are a couple of examples that illustrate reading the settings information from the <appSettings>
element. Don’t forget to add a reference to System.Configuration! This will apply to all examples in this
document.

//The basic and straight forward approach


string key = auxConfig.AppSettings.Settings["myColor"].Key;
string value = auxConfig.AppSettings.Settings["myColor"].Value;

//Shorten retrieval of data by creating references to the appSettings section


AppSettingsSection appSettings = config.AppSettings;
value = appSettings.Settings["myColor"].Value;

//The iterative approach


foreach (string key in auxConfig.AppSettings.Settings.AllKeys)
{
if (auxConfig.AppSettings.Settings[key] != null)
{
string value = auxConfig.AppSettings.Settings[key].Value;
}
}

Notice that the value returned is a string value. Again, this is because the <appSettings> element is not
based on a strongly-typed system. This must be accomplished in the handling of the value once
retrieved. Don’t let this discourage you from using it though, because most system data types that are
derived from System.Object will have predefined methods. If you wish to store and persist custom
AutoCAD object data, try creating a conversion class of your own. To keep things clean and simple in
this document, I illustrate this in Appendix C: Type Conversion Examples for those interested.

Writing values is really not that different from reading them. The biggest difference will be inclusion of
the Configuration.Save() and ConfigurationManager.RefreshSection() methods. The Save() method is
fairly obvious, but the RefreshSection() method is not. The RefreshSection() method refreshes the

16
Configure Your .Net Application - The Other INI

named section so that it will be re-read from disk the next time it is retrieved. In this example I add a
new <appSettings> element item in the list, remove another, and modify a third.

//Add a new setting


auxConfig.AppSettings.Settings.Add("myKey", "myValue");
//Remove another setting
auxConfig.AppSettings.Settings.Remove("anotherKey");
//Modify a final setting
auxConfig.AppSettings.Settings["finalKey"].Value = "somevalue";

//Save and refresh when finished...


auxConfig.Save(ConfigurationSaveMode.Modified);
ConfigurationManager.RefreshSection("appSettings");

CUSTOMIZED CONFIGURATION FILES


Creating and using configuration files using the VS Project Designer or via an external file is great for
storing basic listed, scoped, and strongly-typed settings data. Unfortunately, it lacks organization for
those more complicated settings storage needs. It is therefore my pleasure to introduce the concept of
creating and handling custom configuration elements. Using these not only improves organization of
the data stored in the configuration file, but also puts a lot of additional control in the hands of the
developer. Also, as mentioned in the former section regarding external configuration files, all examples
in this one also require that a .Net reference to System.Configuration be added to the Visual Studios
solution.

Creating the Application Configuration File

There are two ways to create the MyCADApp.dll.config file we need. The first is to use the VS Project
Designer and create as many Application- and User-scoped settings as necessary, which has already
been outlined in Basic Configuration Files section of this document. The App.config development
template file will then be created automatically. Or if there are no settings of this kind to include and we
plan to go straight to using custom configuration elements, follow these steps:

Step-1. Using Visual Studios, start and setup a class library project for an AutoCAD, .Net
application including the acdbmgd.dll and acmgd.dll references.

Step-2. In Solution Explorer, right-click the project title and select Add/New Item...

Or the menu item Project/Add New Item...


Or the [Add New Item] button on the Standard toolbar.

Step-3. From the language category selected, select Application Configuration File template.
DO NOT change the name. Now select Add.

17
Configure Your .Net Application - The Other INI

The App.config file is created and should look something like this:

<?xml version="1.0" encoding="utf-8" ?>


<configuration>
</configuration>

App.config is a template configuration file for your particular application. This file is
not the official build version configuration file. When building, the compiler will copy
this file into the appropriate bin directory along with the executable using the required
naming convention, MyCADApp.dll.config. So...

WARNING!!! As tempting as it may be for us to specify the name of a file, it is


critical that you DO NOT change the name of this file. If you do, the compiler will not
find it and therefore will not create the official build version.

Debug/Build/Run Setup

Now that we have the App.config created, we can build the application. Upon doing so we will find the
application’s dll and its built configuration file, *.dll.config, in the solution’s Debug/Release directory.
Due mostly to the nature of how the configuration classes operate in the .Net Framework, extension of
the application context, and the overall AutoCAD .Net application loading process it is necessary for us
to consider a few additional factors.

Building/Debugging the Configured AutoCAD .Net Application

With custom configuration elements, the configuration handler code is located in the .Net application
developed, while the framework configuration classes associate with the acad.exe host application.
Also, since the debug version is being processed from a directory other than the AutoCAD install folder,
AutoCAD must be made aware of this. The fix is a two-stage process:

Stage-1.] Enter two post-build event commands from the Build Events tab in the Visual
Studios Project Designer. Each command should copy the latest build version of
the executable and application configuration file to the AutoCAD install directory.
For example:
18
Configure Your .Net Application - The Other INI

Copy “C:\...\Debug\MyCADApp.dll” “C:\Program Files\Autodesk\AutoCAD 2012


- English\MyCADApp.dll”
Copy “C:\...\Debug\MyCADApp.dll.config” “C:\Program Files\ Autodesk\
AutoCAD 2012 - English\MyCADApp.dll.config”

Stage-2.] Edit the acad.exe.config file so that it can associate to the application’s debug
version assembly. To do this, add the following within the <runtime> element of the
acad.exe.config file.

<runtime>
...
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="MyCADApp" />
<codeBase version="1.0.0.0"
href="FILE://C:/.../Debug/MyCADApp.dll" />
</dependentAssembly>
</assemblyBinding>
</runtime>

Note that neither of these steps is necessary for “in-production” deployment of the application if it is
copied to the AutoCAD install directory. These steps are really only necessary for debugging.

“In­Production” Deployment of the Configured AutoCAD .Net Application

Deploying applications where custom elements are of concern is simple. There’s really only one rule,
copy the compiled release version *.dll and *.dll.config files to the AutoCAD install directory together.
They should not be separated in the directory. ADN Support actually recommends that developers
consider deploying their .Net applications to the install directory anyways, especially when using the
CUI or Ribbon API, exposing .Net classes to COM, etc.

19
Configure Your .Net Application - The Other INI

Developing Custom Configuration Handlers

Custom configuration elements can add organization and a lot of additional control. However we must
keep in mind that the custom elements, sections, and groups we use are not recognized by the system,
so we must develop the handler code for each one used. This section of the document will outline the
configuration file declaration and usage as well as any associated handler code necessary to support
custom elements, sections, and groups.

Configuration Elements

It is the configuration elements and their specified attributes that will be used to store settings
information in our configuration files. Below is an example:.

<!--Custom Element... by Declarative approach-->


<myDeclElement dateTimeValue="01/01/2011"/>

<!--Custom Element... by Programmatic approach-->


<myProgElement myColor="red"/>

Creating the custom configuration element handler is actually very easy. To do so, add a class that
inherits from ConfigurationElement, then add properties to represent each of the element’s attributes
intended to store settings data. Before examples are provided, let’s first discuss the approach. There
are two models for doing this, programmatic and declarative. The programmatic approach allows for
finer control but requires a little more code. The declarative model is a lot simpler in that the element
attributes (class properties) are decorated with .Net attributes that instruct the configuration system
about the property types via reflection. Though this model is simpler to create, it may not provide as
much control as a developer may wish for in some situations. Below are examples of both, but keep in
mind that these models can be used for other configuration handlers as well. So in future examples,
you’ll see one or the other to keep things interesting.

Programmatic Model:
using System.Configuration;

public sealed class ConfigElement_P : ConfigurationElement


{

#region Properties

//Define an element attribute called myColor


private static ConfigurationProperty _myColor =
new ConfigurationProperty("myColor", typeof(Color),
Color.Red, ConfigurationPropertyOptions.IsRequired);

public Color ColorProperty


{
get { return (Color)base[_myColor]; }
set { base[_myColor] = value; }
}

20
Configure Your .Net Application - The Other INI

/*Or the property can be defined using the this keyword


* as indexed to the string name of the ConfigurationProperty
* defined above...
*
public Color ColorProperty
{
get { return (Color)this["myColor"]; }
set { this["myColor"] = value; }
}

*/

//To help improve performance and efficiency, override the element's


//property collection property to be populated in the constructor
private static ConfigurationPropertyCollection _Properties =
new ConfigurationPropertyCollection();
protected override ConfigurationPropertyCollection Properties
{
get { return _Properties; }
}

#endregion

#region Constructors

public ConfigElement_P()
{
//Add the element attribute properties to the
//element properties collection
_Properties.Add(_myColor);

#endregion
}

Declarative Model:

using System.Drawing;
using System.Configuration;

public sealed class ConfigElement_D : ConfigurationElement


{
#region Properties
[ConfigurationProperty("dateTimeValue", IsRequired = true)]
public DateTime DateProperty
{
get { return (DateTime)this["dateTimeValue"]; }
set { this["dateTimeValue"] = value; }
}
#endregion
}

Configuration Element Collection


21
Configure Your .Net Application - The Other INI

Sometimes there will be situations where a collection of elements are useful. For example, a list of
block names and file paths could be beneficial in a program where specific blocks containing specific
properties need to be used in an application.

<!‐‐Custom Element Collection AddRemoveClear(ARC) Map‐‐>


<myElementCollectionARCMap>
<add name="ARCMapItem#1" path="C:\Test\ARCMap\Item1"/>
<add name="ARCMapItem#2" path="C:\Test\ARCMap\Item2"/>
<add name="ARCMapItem#3" path="C:\Test\ARCMap\Item3"/>
<add name="ARCMapItem#4" path="C:\Test\ARCMap\Item4"/>
</myElementCollectionARCMap>
<!‐‐Note: With an ARC Map, the <add/>, <remove/>, and <clear/>
elements can be renamed if desired.‐‐>

<!‐‐Custom Element Collection Basic Map‐‐>


<myElementCollectionBasicMap>
<myCollectedElement name="BasicMapItem#1" path="C:\Test\BasicMap\Item1"/>
<myCollectedElement name="BasicMapItem#2" path="C:\Test\BasicMap\Item2"/>
<myCollectedElement name="BasicMapItem#3" path="C:\Test\BasicMap\Item3"/>
</myElementCollectionBasicMap>

Before defining how to create the configuration element collection handlers, there are two basic types
of collections we need to understand:

1.] AddRemoveClear (ARC) Map

This is the default element collection type. Collections of this type can be merged in the
hierarchy of configuration files. With these collection types the basic <add/>, <remove/>, and
<clear/> element commands can be used across multiple levels of configuration files to control
the collection. These commands can also be renamed in the configuration element collection
handler as well if desired.

2.] Basic Map

The Basic Map collection type is different from the ARC Map in that it is more specific, in fact
additive in nature (once added cannot be removed by a more specific configuration file).
Collections of this type apply to the configuration level to which they are specified. Also, unlike
the ARC Map, the collection cannot be cleared with the <clear/> element.

To develop the configuration element collection handler:

Step-1. Develop the configuration element handler that represents each element in the
collection as described in the section of this document titled Customized
Configuration Files / Developing Custom Configuration Handlers / Configuration
Elements.

Step-2. For the configuration element collection handler, create a class that inherits
ConfigurationElementCollection from the System.Configuration namespace. Also
apply the ConfigurationCollection() attribute to the class declaration where the

22
Configure Your .Net Application - The Other INI

collection type and item element is defined. Depending upon the collection type, this
attribute can provide an opportunity to rename the <add/>, <remove/>, and <clear/>
elements.

using System.Configuration;

namespace adskCustomConfiguration
{
[ConfigurationCollection(typeof(ConfigCollectedElement),
/*This attribute can also be used to redefine the add/remove/clear
* element names as indicated below. Otherwise, the basic
* <add />, <remove />, and <clear/> elements are used in the config.
* Redefining element names here does NOT apply to the Basic Map
* collection type.*/
AddItemName="ElementNameForAdding",
ClearItemsName="ElementNameForClearing",
RemoveItemName="ElementNameForRemoving",
CollectionType = ConfigurationElementCollectionType.AddRemoveClearMap)]
public class ConfigElementCollection_ARCMap:ConfigurationElementCollection
{}
}

Step-3. Override the following ConfigurationElementCollection properties:

1.] Properties - Used to apply any element attributes for the collection element
as well as improve performance.

2.] CollectionType - Override the default value and return the appropriate
collection type.

3.] ElementName - This property is really only necessary for the Basic Map
collection type when desiring to rename the <add/> element.

#region Properties

private static ConfigurationPropertyCollection _Properties =


new ConfigurationPropertyCollection();

/// <summary>
/// Override configuration property collection to apply any necessary attributes
/// and improve performance
/// </summary>
protected override ConfigurationPropertyCollection Properties
{
get { return _Properties; }
}

/// <summary>
/// Define the collection type as being an ARC Map by overriding
/// </summary>
public override ConfigurationElementCollectionType CollectionType
{
get { return ConfigurationElementCollectionType.BasicMap; }
}
23
Configure Your .Net Application - The Other INI

/// <summary>
/// Added particularly for the Basic Map approach
/// Gets the name used to identify this collection of elements
/// in the configuration file when overridden.
/// </summary>
protected override string ElementName
{
get { return "myCollectedElement"; }
}

#endregion

Step-4. Add collection indexers.

#region Element Collection Indexers

public ConfigCollectedElement this[int index]


{
get { return (ConfigCollectedElement)base.BaseGet(index); }
set
{
if (base.BaseGet(index) != null)
{
base.BaseRemoveAt(index);
}
base.BaseAdd(index, value);
}
}

public ConfigCollectedElement this[string name]


{
get { return (ConfigCollectedElement)base.BaseGet(name); }
}

#endregion

Step-5. Override the ConfigurationElementCollection methods normally used to create a new


element and obtain the collected elements’ key attribute.

#region Overridden Support Methods

/// <summary>
/// Creates new custom element for collection
/// </summary>
protected override ConfigurationElement CreateNewElement()
{
return new ConfigCollectedElement();
}

/// <summary>
/// Identifies the key attribute for the collection
/// </summary>

24
Configure Your .Net Application - The Other INI

/// <returns>Key</returns>
protected override object GetElementKey(ConfigurationElement element)
{
return (element as ConfigCollectedElement).Name;
}

#endregion

Step-6. Finally, add some useful methods for programmatic interaction with the collection.

#region Helpful methods for handling element collection

public void Add(ConfigCollectedElement element)


{
base.BaseAdd(element);
}

public void Remove(string name)


{
base.BaseRemove(name);
}

public void Remove(ConfigCollectedElement element)


{
base.BaseRemove(GetElementKey(element));
}

public void Clear()


{
base.BaseClear();
}

public void RemoveAt(int index)


{
base.BaseRemoveAt(index);
}

public string GetKey(int index)


{
return (string)base.BaseGetKey(index);
}

#endregion

25
Configure Your .Net Application - The Other INI

Sections/SectionGroups

Sections and SectionGroups are elements typically used to better organize the configuration file and to
contain the custom elements. At a minimum, Sections are required for custom configuration, however
SectionGroups are not. If SectionGroups are used, the Sections will be nested within them. Below is a
very generic illustration of this concept:

<customSectionGroup>
<customSection>
<customElement name="myName" path="C:\...\..." description="A generic description"/>
</customSection>
</customSectionGroup>

Every Section and SectionGroup must be defined within the configuration file’s <configSections>
element. This is necessary so that the system can identify the appropriate custom handlers. The
example below defines a custom Section and SectionGroup:

<configSections>
<sectionGroup
name="MyApplicationGroup"
type="adskCustomConfiguration.ConfigGroup,adskCustomConfiguration">
<section
name="MyApplicationSection"
type="adskCustomConfiguration.ConfigSection,adskCustomConfiguration"/>
</sectionGroup>
</configSections>

Each Section used in a specified SectionGroup is defined within the appropriate <sectionGroup>
declaration element. The name attribute defines the name of the Section or SectionGroup. The type
attribute defines the class and namespace of the Section or SectionGroup handler. Typically the type
attribute format will be:

type="Fully qualified class name, assembly file name, version, culture,


public key token"

Since most of the basic AutoCAD .Net applications will not be signed, versioning is not being
maintained in the AutoCAD install directory, and culture can be defaulted, the format can be simplified
to:

type="Fully qualified class name, assembly file name"

The configuration section handler is just as simple to create as the configuration element handler.
Though the examples below don’t illustrate this, a configuration Section or SectionGroup can also
contain attributes like any other element. To create a Section handler, derive a class that inherits from
ConfigurationSection in the System.Configuration namespace and then define each attribute just as
was done for the basic configuration element handler. Of course the point of a custom Section is to
contain custom elements. So add a property decorated with the ConfigurationProperty attribute
(assuming the declarative method) for each element represented in the section. Below is an example

26
Configure Your .Net Application - The Other INI

configuration section handler that represents a section that contains two custom elements and two
custom element collections.

using System.Configuration;

namespace adskCustomConfiguration
{
public sealed class ConfigSection:ConfigurationSection
{

#region Assigned Elements as Properties

[ConfigurationProperty("myProgElement")]
public ConfigElement_P ProgElement
{
get { return (ConfigElement_P)this["myProgElement"]; }
set { this["myProgElement"] = value; }
}

[ConfigurationProperty("myDeclElement")]
public ConfigElement_D DeclElement
{
get { return (ConfigElement_D)this["myDeclElement"]; }
set { this["myDeclElement"] = value; }
}

#endregion

#region Element Collections as Properties

//The AddRemoveClear version


[ConfigurationProperty("myElementCollectionARCMap")]
public ConfigElementCollection_ARCMap CustomElementCollectionARC
{
get { return (ConfigElementCollection_ARCMap)
this["myElementCollectionARCMap"]; }
}

//The BasicMap version


[ConfigurationProperty("myElementCollectionBasicMap")]
public ConfigElementCollection_BasicMap CustomElementCollectionBasic
{
get { return (ConfigElementCollection_BasicMap)
this["myElementCollectionBasicMap"]; }
}

#endregion

}
}

27
Configure Your .Net Application - The Other INI

Developing SectionGroup handlers is even easier. Simply identify the Sections nested in the
SectionGroups as properties similar to what has already been presented.

using System.Configuration;

namespace adskCustomConfiguration
{
public class ConfigGroup:ConfigurationSectionGroup
{

#region Configuration Sections as Properties

[ConfigurationProperty("MyApplicationSection")]
public ConfigSection MyConfigSection
{
get { return (ConfigSection)base.Sections["MyApplicationSection"]; }
}

#endregion

}
}

Interaction with Customized Configuration Files

The ConfigurationManager class and supporting methods have already been defined in this document
and will not be reiterated here. However, the basic concepts for opening, modifying, and saving custom
configuration settings will be illustrated here.

To open the customized configuration file, use the OpenExeConfiguration() method of the
ConfigurationManager class, specifying the string file path of the AutoCAD .Net application (e.g.
C:\...\MyCADApp.dll). This allows the OpenExeConfiguration() method to return a Configuration class
representation of the application configuration file as associated to the .Net application instead of the
host acad.exe. Since the method parameter supplied is the path to the application executable (*.dll)
and not the application configuration file (*.dll.config), the configuration file is required to be in the same
location as the executable.

Assuming SectionGroups were used, first create an instance of the SectionGroup handler with the
Configuration.SectionGroups.Get(“SectionGroup Element Name”) or Configuration.GetSectionGroup
(“SectionGroup Element Name”) methods. Then, from the SectionGroup reference object, use the
ConfigurationSectionGroup.Sections collection to retrieve the desired Section. Upon obtaining the
Section instance, each nested element can then be retrieved along with each of its attribute values.

If a SectionGroup was not used, the Section instance can be directly obtained with the
Configuration.Sections.Get(“Section Element Name”) or Configuration.GetSection (“Section Element
Name”). Below is a short example. Note that it uses reflection and the application’s codebase to
supply the executable file path.

Customized Configuration File


28
Configure Your .Net Application - The Other INI

<?xml version="1.0"?>
<configuration>

<!‐‐
Sections and SectionGroups have to be defined in the
<configSections> element so that the configuration classes
can find the configuration handlers
‐‐>
<configSections>
<sectionGroup name="MyApplicationGroup"
type="adskCustomConfiguration.ConfigGroup,adskCustomConfiguration">
<section name="MyApplicationSection"
type="adskCustomConfiguration.ConfigSection,adskCustomConfiguration"/>
</sectionGroup>
</configSections>

<MyApplicationGroup>

<MyApplicationSection>

<!‐‐Custom Element... by Declarative approach‐‐>


<myDeclElement dateTimeValue="01/01/2011"/>

<!‐‐Custom Element... by Programmatic approach‐‐>


<myProgElement myColor="red"/>

<!‐‐Custom Element Collection


AddRemoveClear(ARC) Map‐‐>
<myElementCollectionARCMap>
<add name="ARCMapItem#1" path="C:\Test\ARCMap\Item1"/>
<add name="ARCMapItem#2" path="C:\Test\ARCMap\Item2"/>
<add name="ARCMapItem#3" path="C:\Test\ARCMap\Item3"/>
<add name="ARCMapItem#4" path="C:\Test\ARCMap\Item4"/>
</myElementCollectionARCMap>

<!‐‐Custom Element Collection


Basic Map‐‐>
<myElementCollectionBasicMap>
<myCollectedElement name="BasicMapItem#1" path="C:\Test\BasicMap\Item1"/>
<myCollectedElement name="BasicMapItem#2" path="C:\Test\BasicMap\Item2"/>
<myCollectedElement name="BasicMapItem#3" path="C:\Test\BasicMap\Item3"/>
</myElementCollectionBasicMap>

</MyApplicationSection>

</MyApplicationGroup>

<startup><supportedRuntime version="v4.0"
sku=".NETFramework,Version=v4.0"/></startup></configuration>

29
Configure Your .Net Application - The Other INI

Reading Custom Configuration Settings

//Open application configuration


Uri uriCodeBase = new Uri(Assembly.GetExecutingAssembly().CodeBase);
FileInfo appFilePath = new FileInfo(uriCodeBase.LocalPath);

//Open the dll's application configuration


Configuration dllConfig = ConfigurationManager.OpenExeConfiguration(appFilePath.FullName);

//Get the custom SectionGroup


ConfigGroup custGroup = (ConfigGroup)dllConfig.SectionGroups.Get("MyApplicationGroup");

if (custGroup != null)
{
//Get custom section in group
ConfigSection custSection = (ConfigSection)custGroup.Sections["MyApplicationSection"];

if (custSection != null)
{
//Get color attribute value from one of the custom nested elements
Color myColor = (Color)custSection.ProgElement.ColorProperty;

//Add an additional folder to each file path


foreach (ConfigCollectedElement element in custSection.CustomElementCollectionARC)
{
string myCollectedElement = string.Format("Name: {0}, Path: {1}",
element.Name,
element.Path);
}
}
}

Since we used the set keyword when defining our attribute properties, modifying and saving settings
data is also very simple. Simply update the value and save.

Writing Custom Configuration Settings

//Update color from the initial Red to Black


Color myColor = (Color)custSection.ProgElement.ColorProperty;
if (myColor == Color.Red)
{
custSection.ProgElement.ColorProperty = Color.Black;
}

//Add an additional folder to each file path in the element collection


foreach (ConfigCollectedElement element in custSection.CustomElementCollectionARC)
{
element.Path += @"\ARC";
}

//Add a new Element to the collection


int NextItem = custSection.CustomElementCollectionARC.Count + 1 + TimesModified;
ConfigCollectedElement newElement = new ConfigCollectedElement()

30
Configure Your .Net Application - The Other INI

{
Name = "ARCMapItem#" + NextItem.ToString(),
Path = @"C:\Test\ARCMap\Item" + NextItem.ToString()
};
custSection.CustomElementCollectionARC.Add(newElement);

//Now remove the first element in the collection


custSection.CustomElementCollectionARC.RemoveAt(0);

//Save results
dllConfig.Save(ConfigurationSaveMode.Full);

Validation and Type Safety

Without spending a lot of time on the subject, let’s briefly consider validation and type safety. These
topics are not required when developing custom configuration files, but can be extremely handy in
certain situations. So it’s definitely worth a little of our time to discuss. The following will simply outline
the use of configuration validators and type converters. For custom development of these, see
Appendices D and E.

Validators

The System.Configuration namespace comes with several predefined validators and accompanying
validator attributes that can be used to ensure data correctness of element attribute values entered in
the configuration file. These validators add more control to the applicable scope of data values to be
accepted and will cause the system to throw an ArgumentException error when invalidated instead of a
generic one whose message may not be clear. Applying these are simple and depends upon whether
the element handler is defined using the programmatic or declarative approach. With the programmatic
approach, the validator is supplied as another parameter in the attributes’ field declaration. With the
declarative approach, the attribute property is decorated with a validator Attribute. To identify all the
available predefined Configuration validators and their accompanying attributes, see the MSDN article
on the System.Configuration namespace. Below is an example for each method:

Programmatic Approach:

//Define an element attribute called myColor


private static ConfigurationProperty _myColor =
new ConfigurationProperty("myColor", typeof(Color),
Color.Red, null, new ColorValidator(),
ConfigurationPropertyOptions.IsRequired);
...

Declarative Approach:

[ConfigurationProperty("dateTimeValue", DefaultValue="8/13/2010", IsRequired = true)]


[DateTimeValidator("1/1/2010","1/1/2012")]
public DateTime DateProperty
{
get { return (DateTime)this["dateTimeValue"]; }
set { this["dateTimeValue"] = value; }
}

31
Configure Your .Net Application - The Other INI

Converters

Type conversion is typically handled by the configuration system in the .Net Framework automatically
during the serialization/deserialization processes. However, there may be times when the developer
desires to persist custom data. This would be especially true when persisting objects from the
AutoCAD .Net API as many are non-serializable, sealed classes. System.Configuration contains many
predefined converter classes that are available for the more common uses and can be found in the
MSDN article on the namespace. As with validators, the method of applying the converter is dependent
upon the approach for defining the element attributes. The following illustrates application of a custom
converter called DBConverter for each approach.

Programmatic Approach:

private static ConfigurationProperty _BlockDB = new ConfigurationProperty(


"blockDB",
typeof(Database),
null,
new DBConverter(),
null,
ConfigurationPropertyOptions.None,
"AutoCAD Block DWG Database");
...

Declarative Approach:

[ConfigurationProperty("blockDB")]
[TypeConverter(typeof(DBConverter))]
public Database BlockDB
{
get { return (Database)this["blockDB"]; }
set { this["blockDB"] = value; }
}

CONCLUSION
Wow! We’ve seen how simple it is to create and interact with configuration files. We’ve also seen how
much more integrated, consistent, flexible, and robust this system is. And we’ve also covered usage of
this technology from a basic level to full customization. Hopefully you’re now envisioning many
opportunities for its use in your own applications. With use of this configuration technology, user
preferences, AutoCAD process settings data, custom AutoCAD .Net application settings data, and .Net
application support and directive data can be easily stored and leveraged to add a more interactive and
dynamic level to the application without requiring additional coding, recompiling, and all the overhead
that accompanies a database!

REFERENCES
Though mostly on the Internet, there are many wonderful resources out there. Many of which I couldn’t
have written this white paper without. So for those interested in learning more, here are a few of my
favorites:

32
Configure Your .Net Application - The Other INI

• MSDN: http://www.MSDN2.com
There is some great information all in the MSDN Library regarding the various base
classes. To see information regarding the various predefined elements try searching for
the “Configuration File Schema” based on the specified Framework version. For v4.0,
the MSDN contents path is MSDN Library/.NET Development/.NET Framework
4/General Reference for the .Net Framework/Configuration File Schema. From here the
higher level elements are illustrated and once any are clicked on can be drilled down
much further.

• Wikipedia: http://www.Wikipedia.org
Of course who can for such a great resource for clarifying our understanding based on
the experience and knowledge of others?

• The Code Project: http://www.codeproject.com


There are many developers who have written articles about various factors and
applications concerning the .Net configuration system. Just keep in mind that with
AutoCAD .Net application development, the application context is based on the host
application, acad.exe, and not the compiled *.dll executable loaded in the AutoCAD
process space. Therefore how we handle accessing the application configuration file will
be slightly different than what may be illustrated in these articles.

• Unraveling the Mysteries of .Net 2.0 Configuration:


http://www.codeproject.com/KB/dotnet/mysteriesofconfiguration.aspx
This is a Code Project article on custom configuration written by an outstanding
developer and contributor to the .Net community, Jon Rista. This has to be, in my
opinion, one of the most referenced sites online regarding this topic I’ve ever seen. Jon
covers the .Net configuration system and development of customized configuration files
in great detail. He also presents many configuration tips and tricks not covered in this
document that developers may find helpful. There’s no doubt that this article and MSDN
has supplied the most information and had the most impact for me in my research of this
topic. My hat’s off to you Jon! I hope you can eventually get your book out as I still
haven’t seen anything yet.

• PInvoke.net: http://www.pinvoke.net:
This is a great little tool for those interested in using P/Invoke. It is essentially a wiki of
P/Invoke signatures and data types.

Please note that this document is the result of personal practical application, bench top development,
compilation, and study of information that was derived from several sources. So there’s no one
statement intentionally pulled from any one source. So hopefully I’ve properly identified those
resources that have had the most impact for me in writing this and maybe they can be of great benefit
for others as well.

33
Configure Your .Net Application - The Other INI

APPENDIX A: EXAMPLE INI


INIFile Class: This class encapsulates the write/read methods using P/Invoke for use with INI files.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Runtime.InteropServices;

namespace INI_With_PInvoke
{
class INIFile
{

#region Properties

private string _FileName;


/// <summary>
/// INI Property returning the INI path
/// </summary>
public string FileName
{
get { return _FileName; }
}

#endregion

#region P/Invoke Method Declarations

/********************************************************************
* For more information on these and other COM methods to P/Invoke, *
* see www.PInvoke.net *
* ******************************************************************/

//Declare the Read and Write PInvoke methods based on the kernel32.dll
//COM library of the Win32 API

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]


[return: MarshalAs(UnmanagedType.Bool)]
static extern bool WritePrivateProfileString(string lpAppName,
string lpKeyName, string lpString, string lpFileName);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]


static extern uint GetPrivateProfileString(
string lpAppName,
string lpKeyName,
string lpDefault,
StringBuilder lpReturnedString,
uint nSize,
string lpFileName);

#endregion

34
Configure Your .Net Application - The Other INI

#region Constructor

/// <summary>
/// INIFile Constructor
/// </summary>
public INIFile(string FileName)
{
//initialize properties
_FileName = FileName;
}

#endregion

#region Support Methods

/// <summary>
/// Reads data from the initialized ini file.
/// </summary>
/// <returns>String representation of Key's assigned value.</returns>
public string Read(string Section, string KeyName)
{
StringBuilder sbReturn = new StringBuilder(255);

//Call the PInvoked method to read information into a StringBuilder


GetPrivateProfileString(Section, KeyName, "", sbReturn,
(uint) 255, this.FileName);

return sbReturn.ToString();
}

/// <summary>
/// Writes data to the initialized ini file.
/// If the file is not found, it will be created in C:\Windows
/// </summary>
public void Write(string Section, string KeyName, string Value)
{
//Call the PInvoked method to write information
WritePrivateProfileString(Section, KeyName, Value, this.FileName);
}

#endregion

}
}

35
Configure Your .Net Application - The Other INI

Class Usage: This console code example illustrates how the INIFile class can be used.
static void Main(string[] args)
{
//Let's use the MyApplication.ini that is copied to the same
//directory as this *.exe. Use reflection to obtain this *.exe's
//directory and include the ini file name in the full file path.
string MyAssemblyCodeBase = Assembly.GetExecutingAssembly().CodeBase;
Uri uriCodeBase = new Uri(MyAssemblyCodeBase);
FileInfo fiDirectory = new FileInfo(uriCodeBase.LocalPath);
string FileName = Path.Combine(fiDirectory.Directory.ToString(),
"MyApplication.ini");

//Initialize the INIFile class


INIFile MyAppINI = new INIFile(FileName);

//Just to ensure that the logo name is always changing...


//let's use the random function
Random random = new Random();
int incrementer = random.Next();

//Identify section/KeyName field


string Section = "Application";
string KeyName = "Logo";

//Read initial INI field value


string InitialValue = MyAppINI.Read(Section, KeyName);
Console.WriteLine("Initial from INI: [{0}] {1} = {2}", Section,
KeyName, InitialValue);

//Write new value to INI file. If the file cannot be found, it is


//automatically created in C:\Windows\
string Value = "logo" + incrementer + ".bmp";
MyAppINI.Write(Section, KeyName, Value);
Console.WriteLine("Wrote to INI: [{0}] {1} = {2}", Section,
KeyName, Value);

//Read from INI File


string UpdatedValue = MyAppINI.Read(Section, KeyName);
Console.WriteLine("Read back from INI: [{0}] {1} = {2}", Section,
KeyName, UpdatedValue);
Console.ReadLine();
}

Sample INI File: The MyApplication.ini file data used in this example.

36
Configure Your .Net Application - The Other INI

APPENDIX B: XML FORMATTING


For those not familiar with the xml format, it is essentially a nested, hierarchical network of elements
where each element represents a section of information. The elements are surrounded with pointed
brackets using one of two approaches:

1.] <MyElement/>
2.] <MyIntegerElement>100</MyIntegerElement>

For discussion’s sake I’ll refer to option 1.] as an empty-element tag method and 2.] as a start-end tag
method.

A rule of thumb that’s nice in helping to remember how to format the element tags is that the forward
slash character (/) indicates the closing character (i.e. / = “close”). Therefore, (<) open element, (/>)
close element. If using the empty-element tag approach, then the “close” character goes at the end as
one would assume. If the element formatting is enclosing (start-end tag method), the “close” character
goes at the beginning of the end tag to indicate that it is closing that element instance.

Enclosing elements usually surround some form of data. Since many of us are familiar with
programming terms and variables, consider this correlation:

<MyIntegerElement>100</MyIntegerElement>

... is equivalent to...

int MyIntegerElement = 100;

While elements describe the data, attributes are like properties of an element. In the following
example, the element CourseCertificate contains four attributes (properties): certType, ceu, size, and
units.

<CourseCertificate
certType="CourseCompletion"
ceu="1.5"
size="8.5x11"
units="inches">Configure Your .Net Applicaiton - The Other INI
</CourseCertificate>

Notice also that just as in any C-based language development, whitespaces do not matter. So this
could’ve just as easily been written as one long line.

Comments are indicated using the <!-- and --> wrappers. For example:

<!--Add more Custom Sections as necessary-->

37
Configure Your .Net Application - The Other INI

APPENDIX C: TYPE CONVERSION EXAMPLES


Previously, the point was made that usage of the <appSettings> element means using a non-strongly-
typed system. In fact, the values returned are string results.

//convert string values to specified data type


int myInteger;
int.TryParse(config.AppSettings.Settings["myInteger"].Value, out myInteger);

Color myColor = Color.FromName(config.AppSettings.Settings["myColor"].Value);

Line myLine;
acLineStringConverter.TryParse(config.AppSettings.Settings["myLine"].Value,
out myLine);
where...

public static class acLineStringConverter


{

#region Properties

public static Point3d Point1 { get; set; }

public static Point3d Point2 { get; set; }

#endregion

#region Conversion Methods

public static string ToString(Point3d point1, Point3d point2)


{
Point1 = point1;
Point2 = point2;

return string.Concat("Line{", "Point1:", Point1.ToString(),


",Point2:", Point2.ToString(), "}");
}

public static void TryParse(string s, out Line value)


{
if (s != string.Empty)
{
s = s.Replace("}", string.Empty);
string[] TypeData = s.Split('{', '(', ':', ')', ',');

if (TypeData[0] == "Line")
{
double X = 0, Y = 0, Z = 0;

double.TryParse(TypeData[3], out X);


double.TryParse(TypeData[4], out Y);
double.TryParse(TypeData[5], out Z);
Point1 = new Point3d(X, Y, Z);

X = 0; Y = 0; Z = 0;

38
Configure Your .Net Application - The Other INI

double.TryParse(TypeData[9], out X);


double.TryParse(TypeData[10], out Y);
double.TryParse(TypeData[11], out Z);
Point2 = new Point3d(X, Y, Z);

value = new Line(new Point3d(Point1.X, Point1.Y, Point1.Z),


new Point3d(Point2.X, Point2.Y, Point2.Z));
}
else
value = null;
}
else
value = null;
}
#endregion

39
Configure Your .Net Application - The Other INI

APPENDIX D: CUSTOM VALIDATOR EXAMPLE


The following example illustrates how to create a custom Validator and its accompanying attribute (if
determined necessary). To create the Validator, add a new class and set to derive from the
ConfigurationValidatorBase class. Then override the CanValidate() and Validate() methods.

public class DateTimeValidator : ConfigurationValidatorBase


{

#region Properties

public DateTime MaxDate { get; set; }

public DateTime MinDate { get; set; }

#endregion

#region Constructors

public DateTimeValidator(DateTime minDate, DateTime maxDate)


{
this.MaxDate = maxDate;
this.MinDate = minDate;
}

#endregion

#region Required Method Overrides and Abstracted Methods

public override bool CanValidate(Type type)


{
return (type == typeof(DateTime));
}

public override void Validate(object value)


{
DateTime objValue = (DateTime)value;

if (objValue<this.MinDate || objValue>this.MaxDate)
throw new ArgumentException("Value should be between " + this.MinDate +
" and " + this.MaxDate + "!");
}

#endregion

40
Configure Your .Net Application - The Other INI

Creating the accompanying Validator Attribute is just as simple. Add a new class and set to derive from
the ConfigurationValidatorAttribute class. Then override the ValidatorInstance() method.

public class DateTimeValidatorAttribute:ConfigurationValidatorAttribute


{

#region Properties

public DateTime MaxDate { get; set; }

public DateTime MinDate { get; set; }

#endregion

#region Constructors

public DateTimeValidatorAttribute(string minDate, string maxDate)


{

DateTime dttMinDate;
DateTime dttMaxDate;
DateTime.TryParse(maxDate, out dttMaxDate);
DateTime.TryParse(minDate, out dttMinDate);

this.MinDate = dttMinDate;
this.MaxDate = dttMaxDate;

#endregion

#region Required Overriding Methods

public override ConfigurationValidatorBase ValidatorInstance


{
get
{
return new DateTimeValidator(this.MinDate, this.MaxDate);
}
}

#endregion
}

41
Configure Your .Net Application - The Other INI

APPENDIX E: CUSTOM CONVERTER EXAMPLE


The following example illustrates how to create a custom Converter for use in a customized
configuration file. To create the Converter, add a new class and set to derive from the
ConfigurationConverterBase class. Then override the ConvertFrom() and ConvertTo() methods. The
following example illustrates how to create a converter for an AutoCAD Database object using the DXF
representation. Ahhhh yeah... I went there!

public class DBConverter:ConfigurationConverterBase


{
#region Properties

private static string _DxfPath = null;


public static string DxfPath
{
get
{
if (_DxfPath == null)
{
string assCodeBase = Assembly.GetExecutingAssembly().CodeBase;
Uri uriCodeBase = new Uri(assCodeBase);

FileInfo fiDirectory = new FileInfo(uriCodeBase.LocalPath);

_DxfPath = Path.Combine(fiDirectory.DirectoryName, "Temp.dxf");


}

return _DxfPath;
}
}

private static string _DxfInLogPath = null;


public static string DxfInLogPath
{
get
{
if (_DxfInLogPath == null)
{
string assCodeBase = Assembly.GetExecutingAssembly().CodeBase;
Uri uriCodeBase = new Uri(assCodeBase);

FileInfo fiDirectory = new FileInfo(uriCodeBase.LocalPath);

_DxfInLogPath = Path.Combine(fiDirectory.DirectoryName, "DxfIn.log");


}

return _DxfInLogPath;
}
}
#endregion

#region TypeConversion Methods

/// <summary>
/// Converts string representation of the Drawing DXF into a Database object
/// </summary>

42
Configure Your .Net Application - The Other INI

public override object ConvertFrom(ITypeDescriptorContext context,


System.Globalization.CultureInfo culture, object value)
{
//Cast value to string, exit method if value is empty
string strValue;
if (value is string)
strValue = value.ToString();
else
return null;

//Convert string representation to AutoCAD Database object


if (strValue != "" && strValue != null)
{
Encoding encDXF = Encoding.ASCII;
string DXFContents = strValue;

byte[] dxfBytes = encDXF.GetBytes(strValue);

File.WriteAllBytes(DxfPath, dxfBytes);

Database dbDXF = new Database(false, true);


dbDXF.DxfIn(DxfPath, DxfInLogPath);

return dbDXF;
}
else
return null;
}

/// <summary>
/// Converts AutoCAD Database object into a string representation of the Drawing
/// </summary>
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture, object value,
Type destinationType)
{

if (value != null && value is Database)


{
Database dbDwg = value as Database;

dbDwg.DxfOut(DxfPath, 16, false);

byte[] dxfBytes = File.ReadAllBytes(DxfPath);

Encoding encDXF = Encoding.ASCII;


string DXFContents = encDXF.GetString(dxfBytes);

return DXFContents;
}
else
return null;
}

#endregion
}

43

You might also like