+ All Categories
Home > Documents > Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

Date post: 12-Feb-2017
Category:
Upload: donhi
View: 221 times
Download: 0 times
Share this document with a friend
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: [email protected]
Transcript
Page 1: Configure Your .Net Application - The Other INI

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: [email protected]

Page 2: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

2

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.

Page 3: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

3

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

Page 4: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

4

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:

Page 5: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

5

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.

Page 6: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

6

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.

Page 7: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

7

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-->

Application exe Configuration:

acad.exe.config [Require Custom Merging]

Machine Configuration: Machine.config

[MyCADApp].dll.config

Roaming-User Configuration:

User.config

Local-User Configuration:

User.config

External Configuration: [AppSettings].config

MER

GIN

G

Page 8: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

8

<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.

Page 9: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

9

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.

Page 10: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

10

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>

Page 11: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

11

<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:

Page 12: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

12

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.

Page 13: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

13

<?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...

Page 14: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

14

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>

Page 15: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

15

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.

Page 16: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

16

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

Page 17: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

17

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.

Page 18: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

18

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:

Page 19: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

19

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.

Page 20: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

20

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; } }

Page 21: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

21

/*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 

Page 22: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

22

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

Page 23: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

23

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; }         } 

Page 24: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

24

         /// <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> 

Page 25: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

25

        /// <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 

Page 26: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

26

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

Page 27: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

27

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 } }

Page 28: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

28

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

Page 29: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

29

<?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> 

Page 30: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

30

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()                 

Page 31: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

31

            {                    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; }         } 

Page 32: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

32

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:

Page 33: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

33

• 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.

Page 34: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

34

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

Page 35: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

35

#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 } }

 

Page 36: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

36

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.

Page 37: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

37

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-->

Page 38: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

38

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;

Page 39: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

39

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 }

Page 40: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

40

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  }  

Page 41: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

41

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 } 

Page 42: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

42

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> 

Page 43: Configure Your .Net Application - The Other INI

Configure Your .Net Application - The Other INI

43

    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 } 


Recommended