Philippe Truche’s Blog

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…

« Newer Posts

Blog at WordPress.com.