In this installation of my blog series I’ll cover the first concrete item listed in the introduction. Implement a WinForm that modifies its own Config file.
Let’s start by creating a Main form for the UI that will be our work area for the Code Generator. We add a button “Modify Config” that executes the following code to launch the frmAppConfig form:
private void btnModifyConfig_Click(object sender, EventArgs e)
{
frmAppConfig dlgAppconfig = new frmAppConfig();
dlgAppconfig.ShowDialog();
}
The form itself looks like this at this point:
The frmAppConfig executes the following LoadConfigValues code called from its constructor:
private void LoadConfigValues()
{
try
{
NameValueCollection appSettings
= ConfigurationManager.AppSettings;
dgvAppConfigSettings.Rows.Clear();
dgvAppConfigSettings.Rows.Add(appSettings.Count);
int i = 0;
foreach (string key in appSettings)
{
string value = appSettings[key];
DataGridViewRow row = dgvAppConfigSettings.Rows[i++];
row.Cells["Key"].Value = key;
row.Cells["Value"].Value = value;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message,
"Setting Application Configuration Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
}
As you can see the method uses the System.Configuration.ConfigurationManager.AppSettings property to get a System.Collections.Specialized.NameValueCollection of the app settings in the app.config file. If we open the app.config file we’ll see the following:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<clear />
<add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
<add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
<add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
<add key="DeveloperName" value="John P. Frampton" />
<add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
<add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
<add key="TableDefinitionFilePath" value="C:\Syrinx\Syrinx.DB\Tables\" />
</appSettings>
<connectionStrings>
<add
name="FakeConnectionString"
connectionString="NotReally"/>
</connectionStrings>
</configuration>
Note that there is a connectionStrings section in the app.config with which we are not concerned and which will not be modified. If we run our application, not in debug mode, but with the actual compiled .exe, the frmAppConfig form will display the following:
As expected the DataGridView is populated with values from the appSettings section of the Config file. The only thing we have left to do is persist any changes back into the app.config file. That is accomplished with the following code:
private void PersistAppSettings()
{
Hashtable htAppConfigValuesInGrid = new Hashtable();
// Load the existing Grid values into a hashtable
htAppConfigValuesInGrid.Clear();
foreach (DataGridViewRow row in dgvAppConfigSettings.Rows)
{
if (row.Cells["Key"].Value != null)
{
htAppConfigValuesInGrid.Add(row.Cells["Key"].Value, row.Cells["Value"].Value);
}
}
// Open App.Config of executable and get AppSettingsSection
Configuration config
= ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
AppSettingsSection appSettings = (AppSettingsSection)config.GetSection("appSettings");
appSettings.Settings.Clear();
// Go through the hashtable and see if the value exists in the config file
// if so update it
// if not, add it
foreach (DictionaryEntry entry in htAppConfigValuesInGrid)
{
string key = entry.Key.ToString();
KeyValueConfigurationElement kvcElement = appSettings.Settings[key];
// exists in the grid but not in the appSettings, add the missing value
if (kvcElement == null)
{
appSettings.Settings.Add(key, (string)htAppConfigValuesInGrid[key]);
}
else
{
// only update the value if it has changed
if (kvcElement.Value != (string)htAppConfigValuesInGrid[key])
{
appSettings.Settings[key].Value = (string)htAppConfigValuesInGrid[key];
}
}
}
// Go through the settings and remove any that are not in the grid hashtable
foreach (KeyValueConfigurationElement element in config.AppSettings.Settings)
{
string keyVal = (string)htAppConfigValuesInGrid[element.Key];
if (keyVal == null)
{
appSettings.Settings.Remove(element.Key);
}
}
// Save the configuration file.
//config.Save(ConfigurationSaveMode.Modified, true);
config.Save();
// Force a reload of a changed section.
ConfigurationManager.RefreshSection("appSettings");
}
}
If we add a new setting and modify an existing one, the UI will show this:
And the executables app.config (SyrinxCodeGenerator.exe.config) will reflect the addition and update like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<clear />
<add key="DeveloperName" value="John P. Frampton" />
<add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
<add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
<add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
<add key="TableDefinitionFilePath _I_HAVE_CHANGED" value="C:\Syrinx\Syrinx.DB\Tables\" />
<add key="NEW_APP_SETTING" value="THIS_IS_NEW" />
<add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
<add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
</appSettings>
<connectionStrings>
<add
name="FakeConnectionString"
connectionString="NotReally"/>
</connectionStrings>
</configuration>
If we remove the new field from the UI we see that the app.config file reflects it like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<clear />
<add key="TableDefinitionFilePath _I_HAVE_CHANGED" value="C:\Syrinx\Syrinx.DB\Tables\" />
<add key="DataAccessFilePath" value="C:\Syrinx\Syrinx.Consumer\DataAccess\" />
<add key="ApplicationObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\ApplicationObject\" />
<add key="DeveloperName" value="John P. Frampton" />
<add key="InterfaceObjectFilePath" value="C:\Syrinx\Syrinx.Consumer\InterfaceObject\" />
<add key="CodeGeneratorDefinitionFilePath" value="C:\Syrinx\Syrinx.Architecture\DefinitionFiles\" />
<add key="StoredProcedureFilePath" value="C:\Syrinx\Syrinx.DB\StoredProcedures\" />
</appSettings>
<connectionStrings>
<add
name="FakeConnectionString"
connectionString="NotReally"/>
</connectionStrings>
</configuration>
That wraps it up for this installation, next we’ll look at saving and loading definition files to and from XML.
Over the past few weeks, I have been working with the Microsoft Dynamics CRM software which our client will use as the backbone for their new vehicle inventory management system. The Dynamics software offers a great deal of support right out-of-the-box for managing enterprise-level data, as well as offering a variety of configurations and customizations to make it fit the user needs. Some of these include role-based security for defining access privileges, custom workflows for modeling user interactions with key entities, such as accounts and contacts, reporting services built upon Microsoft SRS and client extensions by way of custom entities, forms and modules.
With all the features offered within Dynamics, it is necessary to point out one short-coming we have run into during development with this product. All entities, whether system-defined or custom, have a single form associated with them, which the user interacts with to perform basic CRUD operations. With this one-to-one relationship between entity and form, there is the limitation that a form cannot display data from second or subsequent entities; it can only show data for a main entity. In other words, a developer is not able to “easily” add the Account name to a Contact page, in a reliable fashion. There is a notion of referencing entities on a parent form to gather related data, but what if the user navigated directly to the Contact form, instead of navigating by way of the Account form?
The answer lies in the ability to call out to web services from within the Dynamics client itself. This is done by adding javascript code to the form’s OnLoad event. The code will be responsible for calling a web service and populating the desired fields in the client with the results returned from the service. This allows for the developer to create web services that will retrieve whatever data they need directly from the CRM database, and present the related data in a main entity’s form. This technique has been very useful in presenting a Dynamics form which represents data from the main entity and one or more related entities.