Philippe Truche’s Blog

12 August 2008

.NET Assembly Versioning Lifecycle

While it may seem that versioning assemblies in .NET can be a black art, it really isn’t.  The difficulty lies in the lack of guidance in this area.  In fact, I had to read many blogs to put together an approach on a project I was working on a few years (see Suzanne Cook’s excellent blog post “When to Change File/Assembly Versions“). 

I recently moved to a new project and found that this guidance is still helpful.  As a result, I decided to share it because it is still something that is not discussed much, yet it plays an important part of the configuration management of a .NET application.

Version Attributes
The .NET framework makes available three (3) separate version attributes:

  • AssemblyVersion.  As discussed in Assembly Versioning, the .NET runtime uses the assembly version number for binding purposes.  It is used for .NET internal purposes only and should not be correlated to the product version.
  • AssemblyFileVersion.  Instructs a compiler to use a specific version number for the Win32 file version resource. The Win32 file version is not required to be the same as the assembly’s version number.
  • AssemblyInformationalVersion.  “The attribute defined by this class attaches additional version information to an assembly” for documentation purposes only.  It is text-based informational version and typically corresponds to the product’s marketing literature, packaging, or product name.  This data is never used at runtime.

Example:
[assembly: AssemblyVersion("3.0.0.0")]
[assembly: AssemblyFileVersionAttribute("3.0.2134.6636")]
[assembly: AssemblyInformationalVersion("3.0.1")]

Version Formats
Version formats are suggested for the assembly version (AssemblyVersionAttribute), the file version (AssemblyFileVersionAttribute), and the “product number” (AssemblyInformationalVersionAttribute).

Assembly Version Format
Version information for an assembly (set by using the AssemblyVersion attribute) consists of the following four values, as described in the Version Class in the .NET framework documentation.

  • Major Version. “Assemblies with the same name but different major versions are not interchangeable. This would be appropriate, for example, for a major rewrite of a product where backward compatibility cannot be assumed.”
  • Minor Version. “If the name and major number on two assemblies are the same, but the minor number is different, this indicates significant enhancement with the intention of backward compatibility. This would be appropriate, for example, on a point release of a product or a fully backward compatible new version of a product.”
  • Build Number.  “A difference in build number represents a recompilation of the same source. This would be appropriate because of processor, platform, or compiler changes.”
  • Revision.  “Assemblies with the same name, major, and minor version numbers but different revisions are intended to be fully interchangeable. This would be appropriate to fix a security hole in a previously released assembly.”

The assembly version number is used internally by .NET and should not matter to anyone outside of the development team.

File Version Format
This is the actual file version (set by using the AssemblyFileVersionAttribute attribute) and it satisfies the following goals:

  • It correlates the product binaries to the source files from which they were compiled (as long as labeling is performed in the source code control database)
  • It allows for the re-creation of older builds.
  • It clearly identifies upgrade and bug fix releases.
  • It clearly identifies which version of the source code is in production.

The File Version could follow this format:

  • Major Version. This is the internal version of the product and is assigned by the application team.  It should not change during the development cycle of a product release.
  • Minor Version. This is normally used when an incremental release of the product is planned rather than a full feature upgrade.  It is assigned by the application team, and it should not be changed during the development cycle of a product release.
  • Build Number. The build process usually generates this number.  Keep in mind that the numbers cannot be more than 5 digits.  One scheme for generating this number is what I call the ‘YearDaysCounter.’  The first 1-3 digits pertain to the day of the year on which the build is being performed.  It has the advantage of being sequential, going from 001 to 365 as time progresses through the year. The last 2 digits pertain to the iteration of the build for a particular day and thus allow a range of 01 through 99.
  • Revision. This could be assigned by the build team and could contain the reference number of the migration to the production environment.  When it is not known yet, a 0 is used until the number is issued.

Product Number Format
This is the product number that is communicated to stakeholders outside the development and build teams (e.g. Application XYZ is on release “3.0.1”).  It follows the simpler 3-part format <major>.<minor>.<maintenance>.  The following guidelines are useful in changing the product version number:

  • Major Release.  Increment this number when major functionality is being released.
  • Minor Release.  Increment this number when alterations or enhancements to existing functionality is made and changes the end user experience.
  • Maintenance Release: Increment this number when bug fixes are released or performance enhancements are made that exclude functionality changes or material ‘look and feel’ changes to the user interface.

Like the assembly version number, the product version number should be changed after going to production.  It is set by using the AssemblyInformationalVersion attribute.

14 July 2008

Using the Enterprise Library Validation Block with WCF Asynchronous Operations

Filed under: .NET, WCF — Tags: , , , , — Philippe Truche @ 3:58

I was recently surprised to find out first hand that the Enterprise Library Validation Block throws a somewhat obscure exception: ArgumentNullException(“operation.SyncMethod”) when the WCF AsyncPattern is employed.

By debugging into the Enterprise Library source code (EntLib 3.1), I came to understand that the Validation Block was coded to handle synchronous service operations only (using the FaultContract<ValidationFault> attribute on the service operation signature .  In our case, some of our operations also implement the asynchronous pattern and we have Begin and End method pairs for each.

While I was aware that one-way operations do not allow faults to be returned, I was’t sure about asynchronous service operations (implemented as asynchronous on the service side).  I checked the .NET documentation and was able to confirm that FaultContract<T> is supported on service operations implemented asynchronously using the AsyncPattern property.   A difficult to find comment exists in the documentation of the FaultException(TDetail) Generic Class: “The FaultContractAttribute can be used to specify SOAP faults for both two-way service methods and for asynchronous method pairs.”  See http://msdn.microsoft.com/en-us/library/ms576199.aspx.

With this information in hand, I reasonned that I should be able to modify the validation block.  I made the code changes, ran the NUnit tests included in the validation block, and employed the modified assembly on our asynchronous service operations.  I made modifications to ValidationParameterInspector.cs and ValidationBehavior.cs.  The scope of the modifications included the following methods:

  • ValidationParameterInspector.ValidationParameterInspector
  • ValidationParameterInspector.BeforeCall
  • ValidationBehavior.HasValidationAssertions

Note that the modifications apply to both EntLib 3.1 and EntLib 4.0.  The modifications are included below:

Changes to ValidationParameterInspector.cs

/// <summary>

/// Constructor to initialize <see cref=”InputValidators”/>.

/// </summary>

/// <remarks>

/// When the operation is a <see cref=”OperationDescription.BeginMethod”/> because it implements the

/// the <see cref=”OperationContractAttribute.AsyncPattern”/>,

/// it will cause the <see cref=”InputValidators”/> to have empty validators for the

/// <see cref=”AsyncCallback”>callback</see> and <see cref=”Object”>state</see> method

/// arguments.

/// </remarks>

/// <param name=”operation”>The <see cref=”OperationDescription”/> of the operation.</param>

/// <param name=”ruleSet”>A <see cref=”String”/> naming the ruleset to be used.</param>

public ValidationParameterInspector(OperationDescription operation, string ruleSet)

{

// Check the synchronous method of the service first. If the operation implements the

// OperationContractAttribute.AsyncPattern property, set the method to the BeginMethod.

MethodInfo method = operation.SyncMethod;

if (method == null) method = operation.BeginMethod;

 

foreach (ParameterInfo param in method.GetParameters())

{

switch (param.Attributes)

{

case ParameterAttributes.Out:

case ParameterAttributes.Retval:

break;

default:

inputValidators.Add(CreateInputParameterValidator(param, ruleSet));

inputValidatorParameterNames.Add(param.Name);

break;

   }

}

}

 

/// <summary>

/// Performs the validations.

/// </summary>

/// <param name=”operationName”>A <see cref=”String”/> containing the name of the operation.</param>

/// <param name=”inputs”>

/// An <see cref=”object”/> array containing the input parameters of the method and expected as described on

/// the operation, and thus excluding input parameters resulting from the service’s implementation of the

/// <see cref=”OperationContractAttribute.AsyncPattern”/> property.

/// </param>

/// <returns>null</returns>

public object BeforeCall(string operationName, object[] inputs)

{

ValidationFault fault = new ValidationFault();

// Because there could be fewer inputs than inputValidators, iterate

// through the inputs to run the corresponding validations. This accounts

// for asynchronous methods where the callback and state are defined in the

// method but are not exposed outside the boundary of the service.

for (int i = 0; i < inputs.Length; ++i)

{

ValidationResults results = inputValidators[i].Validate(inputs[i]);

AddFaultDetails(fault, inputValidatorParameterNames[i], results);

}

 

if (!fault.IsValid)

{

   throw new FaultException<ValidationFault>(fault);

}

 

return null;

}

 

Changes to ValidationBehavior.cs

/// <summary>

/// Returns a <see cref=”Boolean”/> to indicate whether or not an operation has validation assertions.

/// </summary>

/// <remarks>

/// If the method implements the <see cref=”OperationContractAttribute.AsyncPattern”/> property,

/// the BeginMethod is used instead of the SyncMethod because the SyncMethod is null.

/// </remarks>

/// <param name=”operation”></param>

/// <returns></returns>

private bool HasValidationAssertions(OperationDescription operation)

{

if (operation == null) throw new ArgumentNullException(“operation”);

 

// Check the synchronous method of the service first. If the operation implements the

// OperationContractAttribute.AsyncPattern property, set the method to the BeginMethod.

MethodInfo methodInfo = operation.SyncMethod;

if (methodInfo == null) methodInfo = operation.BeginMethod;

 

return methodInfo.GetCustomAttributes(typeof(ValidatorAttribute), false).Length > 0 ||

   HasParametersWithValidationAssertions(methodInfo.GetParameters());

} 

 

I created a copy of this post on CodePlex.  See http://www.codeplex.com/entlib/Thread/View.aspx?ThreadId=31493.

2 June 2008

WCF, Conditional Compilation, and language differences

Filed under: .NET, WCF, Web Services — Tags: — Philippe Truche @ 11:46

Perhaps you might have defined WCF message contracts lately, and you want to be explicit about the encryption level you require for your message parts.  I am taking the example of a message contract, but really my point is applicable to any element to which you wish to apply the EncryptionLevel enumeration.

In C#:

[MessageHeader(Name = "Environment", MustUnderstand = true
#if !DEBUG
                     , ProtectionLevel = ProtectionLevel.Sign
#endif
)] 
    public String Environment
    {
        get { return _environment; }
        set { _environment = value; }
    }

In VB.NET:

#If CONFIG = “Debug”
    <MessageHeader(Name:=”Environment”, MustUnderstand:=True)> _
    Public Property Environment() As String
#Else
    <MessageHeader(Name:=”Environment”, MustUnderstand:=True, ProtectionLevel:=ProtectionLevel.Sign)> _
    Public Property Environment() As String
#End If
         Get
             Return _environment
         End Get
         Set(ByVal Value As String)
            _environment = Value
         End Set
    End Property 

As you can see, there are some key differences in how attributes are specified using conditional compilation:

  • In C#, I can toggle parts of the same attribute on and off.  In VB.NET, I must repeat the entire attibute (no parts allowed by the compiler).
  • Because of line continuation constraints in VB.NET, the line following the line being continued must be included inside the conditional compilation block.
  • Using VB.NET in Visual Studio (2005 or 2008), it is not clear which part of the conditional compilation is “active” based on the selected configuration.  In C#, the inactive block is grayed out.

Keep in mind these “consequences” as you proceed with WCF attributes in the language you write with.

25 April 2007

Using Visual Studio Team System to load test web services

Filed under: .NET, Testing, VSTS, Web Services — Philippe Truche @ 1:55

I recently had to load test web services for the smart client application we have developed.  I wanted to be able to go through quick cycles (test, evaluate, adjust) without having to wait for a few days for a load test slot in our LoadRunner lab, so I offered to use Visual Studio team system to perform the tests.  I already had experience with Microsoft’s previous load test tool – Application Center Test.  I was pleased with VSTS’ version of a load test tool.  Not quite as powerful as LoadRunner, but much more accessible in ease of use.

Anyway, I used VSTS successfully to load test web services and satisfy the following requirements:

  • Randomly pick data from a set for a given test.
  • Invoke the web services using HTTP POSTs (i.e. without having to create a client)
  • Use a configuration file to store the SOAP envelopes needed to make the web service calls.

Picking data randomly 

When using VSTS tests, the test methods are decorated with the [TestMethod] attribute.    The [DataSource] attribute can also be used on the test method to indicate that the test is data-driven.  Caution: the test is executed as many times as there are rows in the data source.  That may be OK when performing unit tests, but it is not OK for a load test.  So how do you get around this?  Well, I got my data source to return only one row picked randomly.  Using SQL Server 2005 Express, I defined views that would select one row of data using the following construct: “SELECT TOP(1) columnName1 AS alias1, columnName2 AS alias2, …. , columnNameN as aliasN FROM tableName ORDER BY NEWID();”

You might wonder why I am using an aliases on the column names.  The reason why I did this was to decouple the column names in the tables from the element names in the SOAP envelopes.  When the data source provides the row of data, I have a utility running through the column names of the DataRow object and substituting the data in the SOAP envelope based on the name of the columns found in the DataRow provided through the [DataSource] attribute.  This simple convention makes it very easy to parameterize the SOAP sent to the service.  Because the load test client should be as efficient as possible, I use a simple string parsing algorithm to perform the substitutions.

Invoke the web services using HTTP POSTs

I created a static class that uses an HttpWebRequest object to create the POST.   The result is returned into an HttpWebResponse object by simply doing this:

using ( HttpWebResponse response = (HttpWebResponse)request.GetResponse())

{

return response.StatusCode.ToString();

}

If the web server responds with error response codes (e.g. HTTP 401, HTTP 500), VSTS detects that and reports on the errors.  This is much better than Application Center Test where I was left to parse through the responses to make sure I did not get an error back.

Use a configuration file to store the SOAP envelopes needed to make the web service calls.

For this, my team member created an XML file that is loaded into an XmlDocument from which XmlNodes are selected and stored into an object that holds the SOAPAction to perform, the SOAP envelope to send the service, and the Url to use.  Yes, I know, this is very ASMX-centric, and will likely need to change with WCF, but we were not developing a framework either.  We needed a point solution to get going quickly.

Leave a comment if you have any questions or comments.

24 March 2007

Encrypting Enterprise Library Sections in the *.exe.config file

Filed under: .NET — Philippe Truche @ 4:49

I got a question on encrypting Enterprise Library sections in the application configuration file.  I must admit this was somewhat tricky, so here is some additional information on this topic.  To encrypt an Enterprise Library (EL) section, you must copy the EL section handler assemblies to the system directory. To do this, go to the File System Editor in the Setup project and click on the System Folder; then, include EL section handlers. For example, to encrypt the section, you must include Microsoft.Practices.EnterpriseLibrary.Common.dll and Microsoft.Practices.EnterpriseLibrary.Security.dll in the System Folder of the Setup project. The reason for this is the msiexec.exe is installed in the system folder (usually C:\Windows\System32) and it looks for dependent assemblies in there.  Here is a screenshot of what this looks like in the setup project: 

File System Editor - System Folder

The config file, once encrypted, will look like this:

EL section encrypted in config file

Hopefully this post addresses the areas that might have been ambiguous in my previous posts.

17 March 2007

Smart Client and Web Application Development

Filed under: .NET — Philippe Truche @ 12:59

In the past 6 months, I have had the opportunity to develop a smart client application, using the CAB, the Smart Client Software Factory, the Web Service Software Factory, and the Enterprise Library 2.0.  I have worked on web application development since 2000, and client/server before that.

I was asked recently how smart client is different from a web application, whether it’s easier or more difficult to develop.  I think to answer this properly, one really needs to think about the architectural differences.  Let’s start with a web application.  In my experience, the layers of the application reside on the same physical tier.  I am pretty sure this is true in the majority of cases, and there are good reasons for it.  It is much easier to pass business entity objects between the UI, business, and data layers when those layers reside on a web server (typically in a single application domain), and it certainly does not prevent scaling out since you can replicate this setup on a number of servers grouped together in a cluster (a.k.a web farm).  As outlined by the patterns and practices group in “Application Architecture for .NET: Designing Applications and Services“, the Web Farm With Local Business Logic is a common deployment architecture and has the advantage of providing the best performance.

 Now on to the smart client.  The deployment pattern we have implemented is named “Rich Client with Web Service Access” in the same Application Architecture for .NET: Designing Applications and Services document.  This setup, along with the alternate “Rich Client with Remote Components” naturally involves multiple physical tiers.  The mere fact that a client is deployed to end user computers means that there are multiple physical tiers.

This leads me to this conclusion.  If you are going to develop a smart client application, you cannot escape the realities of distributed computing.  Comparing a web application with local business logic to a smart client application is not a fair comparison because the web application deployed in such a way does not involve distributed computing – objects are passed between the layers without worry about network latency or serialization.  A fairer comparison is a web application with remote business logic on the one side and the smart client application on the other because both deal with multiple physical tiers.

What do you think?  Please post your comments as I am interested in hearing your opinions.

4 March 2007

Installer code for deploying a Windows Forms client

Filed under: .NET — Philippe Truche @ 8:53

On my previous post, I discussed the requirement for an installer for an application created using the Smart Client Software Factory. I also discussed the peculiarities of the dynamic web references in library projects and how they are misleading since the entries in the app.config of the library projects need to be copied into the app.config of the Windows executable projects.

Some people have commented to me this is additional complexity brought on by working with Windows forms. I’d like to highlight this behavior
is a Visual Studio 2005 behavior. The same issue exists with ASP.NET applications. If you create a web reference in a library project instead of the web project, you will also need to copy the entries from the app.config of the library project into the web.config file.

This being said, let’s get into the code of the installer.

First, let’s use a helper class to deal with the various environments:

internal static class EnvironmentCode

{

public static string Development = “DEV”;

public static string UserAcceptanceTesting = “UAT”;

public static string SystemAcceptanceTesting = “SAT”;

public static string Production = “PROD”;

}

Let’s also create an application configuration class to handle configuration items related to the application. Notice the constructor needs to be passed an environment code and the assembly path. The assembly path is obtained from Context.Parameters["assemblypath"] of the installer class that inherits from System.Configuration.Install.Installer. This will contain the path and file name of the installer Dll, not the application being
installed. That’s why the constructor takes this parameter and derives the target installation path of the application being installed. I removed the code comments for brevity here, but code comments are included in the code download.

internal class ApplicationConfiguration

{

private string _installationDirectory = String.Empty;

private string _exeConfigPath = String.Empty;

private string _sourceConfigPath = String.Empty;

public ApplicationConfiguration(string environmentCode, string
assemblyPath)

{

if (environmentCode == null)
throw new ArgumentNullException(“environmentCode”);

if (assemblyPath == null)
throw new ArgumentNullException(“assemblyPath”);

// (1) Set the installation directory

_installationDirectory = assemblyPath.Substring(0,
assemblyPath.LastIndexOf(@”\”));

// (2) Set the path to the executable file that
is being installed on the target

// computer.
We are expecting to find only one file that finishes in
“.exe”.

string[] applicationExecutablePath = Directory.GetFiles(_installationDirectory, “*.exe”);

if (applicationExecutablePath.Length != 1)

throw new InstallException(“Application
executable not found in installation directory.”
);

// Set the name of the config file for the exe
(i.e. the application)

_exeConfigPath = applicationExecutablePath[0].ToString() + “.config”;

// (3) Set the path to the source configuration
file. Important assumption:

// The file must follow the naming convention
[EnvironmentCode].*.app.config

// and there must be only one file with the
environment code prefix.

if (environmentCode.ToUpperInvariant() != EnvironmentCode.Development)

{

string[] configFiles = Directory.GetFiles(_installationDirectory,
environmentCode.ToUpperInvariant() + “*.app.config”);

if (configFiles.Length == 1)

{

_sourceConfigPath = configFiles[0];

}

else

{

throw new
InstallException(environmentCode + “.*.app.config not found in “

+ _installationDirectory);

}

}

}

public string InstallationDirectory

{

get { return _installationDirectory; }

}

public string ExeConfigPath

{

get { return _exeConfigPath; }

}

public string SourceConfigPath

{

get { return _sourceConfigPath; }

}

}

Finally, the ApplicationInstaller class derives from the System.Configuration.Install.Installer class and overrides the Install method. Here is a diagram of the class:

ApplicationInstaller Class

The install method is pretty lean and calls discrete private methods like ProcessCustomActionData(). All of these methods are appropriate behaviors of the ApplicationInstaller class. The end result follows:

[RunInstaller(true)]

public partial class ApplicationInstaller :
System.Configuration.Install.Installer

{

private ApplicationConfiguration _applicationConfiguration;

private List<string> _configSectionsToEncrypt = new List<string>();

private String _encryptionProviderName = String.Empty;

private Boolean _encryptApplicationSettings = true;

public ApplicationInstaller()

{

InitializeComponent();

}

public override void Install(System.Collections.IDictionary stateSaver)

{

base.Install(stateSaver);

#if DEBUG

System.Windows.Forms.MessageBox.Show(“You may now attach the debugger to the managed msiexec instance. “ + “Click the OK button after you have
attached the debugger.”
, “Installer Message”, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);

#endif

ProcessCustomActionData();

CopyEnvironmentConfigToExeConfig();

EncryptExeConfigFile();

SecureEnvironmentConfigFiles();

return;

}

private void ProcessCustomActionData()

{

// Translate the environment code

string environmentCode = this.Context.Parameters["ENVT"].ToUpperInvariant();

switch (environmentCode)

{

case “DEV”:

environmentCode = EnvironmentCode.Development;

break;

case “UAT”:

environmentCode = EnvironmentCode.UserAcceptanceTesting;

break;

case “SAT”:

environmentCode = EnvironmentCode.SystemAcceptanceTesting;

break;

case “PROD”:

environmentCode = EnvironmentCode.Production;

break;

default:

this.Context.LogMessage(“Unrecognized environment variable. The value passed was “ + environmentCode + “.”);

this.Context.LogMessage(“Defaulting to an installation for the development environment.”);

environmentCode = EnvironmentCode.Development;

break;

}

_applicationConfiguration = new ApplicationConfiguration(environmentCode,
this.Context.Parameters["assemblypath"]);

// get Configuration section names from custom action parameter

// section names must be seperated by commas

String[] separator = { “,” };

String[] sectionNames = this.Context.Parameters["sectionNames"].Split(separator, StringSplitOptions.None);

for (int i = 0; i < sectionNames.Length; i++)

{

_configSectionsToEncrypt.Add(sectionNames[i]);

}

//get Protected Configuration Provider name from custom action parameter

_encryptionProviderName = this.Context.Parameters["provName"];

//get applicationSettings flag (decides whether or not the
//entire applicationSettings
section group will be encrypted.

// If not provided, defaults to true.

String encryptApplicationSettings = this.Context.Parameter["encryptApplicationSettings"];

if (encryptApplicationSettings == Boolean.FalseString)
_encryptApplicationSettings = false;

return;

}

private void CopyEnvironmentConfigToExeConfig()

{

if (_applicationConfiguration == null) throw new InstallException(“_applicationConfiguration cannot be null.”);

if (!String.IsNullOrEmpty(_applicationConfiguration.SourceConfigPath))

{

File.Copy(_applicationConfiguration.SourceConfigPath,
_applicationConfiguration.ExeConfigPath, true);

this.Context.LogMessage(“Succesfully copied “ +
_applicationConfiguration.SourceConfigPath +

” to “ +
_applicationConfiguration.ExeConfigPath);

}

return;

}

private void EncryptExeConfigFile()

{

if (_applicationConfiguration == null) throw new InstallException(“_applicationConfiguration cannot be null.”);

if (String.IsNullOrEmpty(_encryptionProviderName)) throw new InstallException(“CustomActionData must contain a provName.”);

// Map to application exe.config file.

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();

fileMap.ExeConfigFilename = _applicationConfiguration.ExeConfigPath;

Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap,

ConfigurationUserLevel.None);

if (_encryptApplicationSettings)

// Encrypt all sections in the applicationSettings section
group

{

ApplicationSettingsGroup
applicationSettings =
(ApplicationSettingsGroup)configuration.GetSectionGroup(“applicationSettings”);

foreach (ConfigurationSection applicationSettingsSection in
applicationSettings.Sections)

{

if (applicationSettingsSection != null)

{

if (!applicationSettingsSection.SectionInformation.IsProtected)

{

applicationSettingsSection.SectionInformation.ProtectSection(_encryptionProviderName);

applicationSettingsSection.SectionInformation.ForceSave
= true;

configuration.Save(ConfigurationSaveMode.Full);

}

}

}

}

foreach (string sectionName in _configSectionsToEncrypt)

{

ConfigurationSection configurationSection;

try

{

configurationSection = configuration.GetSection(sectionName);

}

catch (Exception ex)

{

throw new InstallException(“The setup program must copy custom section handlers to the system directory.”, ex);

}

if (configurationSection != null)

{

if (!configurationSection.SectionInformation.IsProtected)

{

configurationSection.SectionInformation.ProtectSection(_encryptionProviderName);

configurationSection.SectionInformation.ForceSave
= true;

configuration.Save(ConfigurationSaveMode.Full);

}

}

}

return;

}

private void SecureEnvironmentConfigFiles()

{

if (_applicationConfiguration == null) throw new InstallException(“_applicationConfiguration cannot be null.”);

String[] environmentConfigFiles = Directory.GetFiles(_applicationConfiguration.InstallationDirectory, “*.app.config”);

for (int i = 0; i < environmentConfigFiles.Length; i++)

{

if (File.Exists(environmentConfigFiles[i]))

{

using (FileStream fs = new FileStream(environmentConfigFiles[i],
FileMode.Create, FileAccess.Write, FileShare.None))

{

this.Context.LogMessage(“Blanked out “ +
environmentConfigFiles[i]);

}

File.SetAttributes(environmentConfigFiles[i],
FileAttributes.Hidden);

}

}

return;

}

}

Key features to look for in the code:

  • The use of #if DEBUG means that when running the installation program with a DEBUG build, a popup window gices you a change to attach to the msiexec process from Visual Studio. There are several instances running; you will look for the one instance that is managed and has the title “Installer Message” as set in the code.

  • The CopyEnvironmentConfigToExeConfig method simply copies the source config file and overwrites the *.exe.config file. For example, if you’re deploying to SAT, SAT.app.config overwrites YourApplication.exe.config.

  • The environment specific config files can’t simply be deleted. If you do that, when you click on the shortcut to the applicationm the installer will attempt to repair the application. Instead, I wrote the SecureEnvironmentConfigFiles method to blank out the files and set them as hidden.

  • If custom section handlers are used (e.g. Enterprise Library), then the
    section handlers must be copied to the system directory because the msiexec process executes from that directory and the CLR will be probing for the assemblies there. In the setup project, use the file system editor and include the requisite assemblies in the “System
    Folder” folder. For example, to encrypt the securityConfiguration section (Enterprise Library), Microsoft.Practices.EnterpriseLibrary.Common.dll and Microsoft.Practices.EnterpriseLibrary.Security.dll are included in the System Folder.

  • The Custom Actions editor needs to include a custom action in the Install folder. The CustomActionData property for this custom action can read the environment value from the user interface and pass the values to the install program as follows:

/ENVT=[BUTTON3]

/sectionNames=”nameOfSection1,nameOfSection2,etc…”

/provName=”configProtectedDataProviderNameInConfigFile”

/encryptApplicationSettings=”True|False”

For example:

/ENVT=[BUTTON3] /sectionNames=”securityConfiguration “
/provName=”DPAPIProtection”
/encryptApplicationSettings=”True”

  • Don’t forget to include the installer project output to the setup project (in the File System Editor, like other project outputs).

  • The app.config includes the DPAPIProtection as follows:

<configProtectedData defaultProvider=DPAPIProtection>

<providers>

<add useMachineProtection=true

name=DPAPIProtection

type=System.Configuration.DpapiProtectedConfigurationProvider,
System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,
processorArchitecture=MSIL
/>

</providers>

</configProtectedData>

There are some nice references out there. I’d like to recommend these two. They were a great help to me and I definitely wanted to acknowledge them:

2 March 2007

Installing a Smart Client Software Factory client application

Filed under: .NET — Philippe Truche @ 7:28

I’ve been working on a Smart Client Software Factory development effort, and as we were nearing the end of the development, I spent some time looking at how I would deploy the client application to end users.  Though I was initially attracted to ClickOnce deployment because of the automatic update feature, I shied away from this and ended up crafting an MSI installer.  Having tinkered with both technologies, I now understand better when ClickOnce and MSIs are appropriate.  In my opinion, ClickOnce should be used only if MSIs cannot be used.  I find MSIs provide much more control over the installation process, and from my experience, are somewhat easier to put together when your application is architected around the Composite UI Application Block. 

 This being said, the installer has to be able to handle the following requirements:

  •  The same MSI should be used for development, testing, and production.  This helps to ensure that the same code base is used throughout all environments.
  • The installer should be able to encrypt app.config sections during the installation.
  • You should be able to debug into the installer during development.

Now, before I move into the meat of the installer program and the setup project, let’s talk about app.config files and the web references in Visual Studio 2005.  When you add a web reference, Visual Studio 2005 performs a number of steps to make it easy to change the web service Url without re-compiling code.   One of those steps is to create entries in the app.config file of the project.  If the project does not contain an app.config, Visual Studio will create one for you.  So what happens to the app.config when the library project get compiled?  Well, you end up with a [AssemblyName].dll.config.  So your application directory may contain one exe, one exe.config, and nothing else.  You might be tempted to copy the dll.config files into the application directory, but that will not achieve the desired result: you want the application to obtain its web service Urls from configuration so you can make changes between environments easily.  So here is the trick:

Open the app.config file(s) from the library project(s) and copy the <section name=YourProjectName.Properties.Settings child element of <sectionGroup name=applicationSettings“…> into the same place in the app.config file of the Windows Forms project.  Then, copy the entire content of the <applicationSettings> section from the library project’s app.config into the Windows Forms app.config file, taking care not to replace its contents but add to it.  As a result, the app.config of the Windows Forms project will look like this:

<configuration>

<configSections>

<sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >     

<section name="YourExeProject.Properties.Settings"… />

<section name="YourLibraryProject1.Infrastructure.Library.Properties.Settings"…/>

<section name=" YourLibraryProject2.Properties.Settings"…/>

</sectionGroup>

</configSections>

<applicationSettings>

<YourExeProject.Properties.Settings>

<setting name="Timeout" serializeAs="String">

<value>30</value>

</setting>

</YourExeProject.Properties.Settings>

<YourLibraryProject1.Properties.Settings>

<setting name="Service1" serializeAs="String">

<value>http://localhost/VirtualDir/Service1.asmx</value>

</setting>

</YourLibraryProject1.Properties.Settings>

<YourLibraryProject2.Properties.Settings>

<setting name="Service2" serializeAs="String">

<value>http://localhost/VirtualDir/Service2.asmx</value>

</setting>

</YourLibraryProject2.Settings>

</applicationSettings>

</configuration>

 

I’ll get into
the code that satisfies the installer requirements on my next post.

21 February 2007

Visual Studio 2005 Service Pack 1 Installation Issue with EFS

Filed under: .NET — Philippe Truche @ 6:06

Argh… what a struggle it was to install Visual Studio 2005 Service Pack 1 on my laptop.  I googled the issue and did not turn up any useful information.   Since I figured out what the issue was, I figured this blog might come in handy for others.

Here was the error I was getting: “Error 1321: The Installer has insufficient privileges to modify this file: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ItemTemplates\VisualBasic\1033\AboutBox.zip.”

It turns out this issue is caused by the Encryption File System (EFS).  To get around the issue, I performed the following steps:

  1. Ran the installer file (VS80sp1-KB926601-X86-ENU.exe) up to the point I have to click OK.  This step alone takes a few minutes.
  2. Before clicking the OK button to have the installer proceed with the installation, I snoozed the Encryption File System by clicking on Snooze on the popup menu of the Encryption File System tray icon.
  3. In the command line, I ran the following command to decrypt the files in the “C:\Program Files\Microsoft Visual Studio 8″ path: C:\>cipher /A /D /S:”C:\Program Files\Microsoft Visual Studio 8″
  4. I clicked OK on the installer to let it proceed with the installation of service pack 1.
  5. The installer ran for a while, and eventually completed the installation successfully.

Voila.  That was it.  Let this entry be useful to someone else.

16 February 2007

Web Service Software Factory – Oracle

Filed under: .NET — Philippe Truche @ 1:50

The web service software factory (December 2006 release) actually is made up of 3 guidance packages:

  • Web Service Software Factory (ASMX)
  • Web Service Software Factory (WCF)
  • Web Service Software Factory (Data Access)

Each of these guidance packages is independent from each other, and the out-of-the-box data access guidance package works with SQL Server.

I have collaborated with yogz on the development of an Oracle compatible data access guidance package.  Far from being complete, the package does provide the following functionality:

  • Ability to generate data repository classes from business entities, with selection factories compatible with Oracle.
  • Ability to handle code constructs for retrieving a single object (Get One operation), a single object with sub-objects (Get One Complex), and a generic list of objects that optionally may have sub-objects (Get Many).  Unfortunately, some manual work is still required because the guidance package UI does not show sub-objects and Oracle ref cursors are not executed for retrieval of schema information.
  • Many refinements were made on the processing of IN/OUT parameters in the ”Get” operations.  These refinements were not ported to the “Update” and “Insert” operations yet.

The Oracle data access guidance package is available here: http://www.codeplex.com/servicefactory/Wiki/View.aspx?title=OracleSchemaDiscovery&referringTitle=Home.

Many improvements remain to be made, but going from the 80/20 to the 100 is going to take significant effort.  We’ve used this on our project and found that the 80 was good enough…

Blog at WordPress.com.