Using the WCSF with an existing Web Application Project

We have what I would call a great offering in the Web Client Software Factory.  However, all of the guidance to date has been around using the WCSF in a green-field scenario, where you can start fresh.  I know that in the real world, this is not often the case.  This point was definitely driven home for me over the last three days.  On Monday and Tuesday, we had a WCSF Workshop (see Mariano's post for some info on how it went), In talking to customers during and after the workshop, there were numerous requests on how to incorporate the WCSF into existing web application projects, and then how to convert the site from the current architecture to the WCSF architecture.   To do this, we need to do a few things:

  • Enable the guidance package
  • Incorporate the Composite Web UI Application Block into the web application project
  • Move associated web pages into modules
  • Refactor existing business logic into foundational modules and expose them as services, which are consumed by the existing pages
  • Update the web pages to use the View Presenter pattern.

Today, I'm going to tackle the first two steps on a really simple web application project.  There will be follow-ups to this post explaining how to do the same to handle web site projects.  Further posts will talk about the re-architecture of an existing site (I just need to create a reasonably complex, not well structured, web site first). If nothing else, this article wil show you why we automate creating a new solution the way we do, as there are a quite a few things to worry about.

Before you begin

I am starting with a basic web application project, with one web page.  You could start with a copy of your existing web site, which you should back it up, just in case something here does not work. Either way, the instructions will be the same.

Enabling the Guidance Package

The first step to enabling the guidance package is to modify the SLN file. This requires opening the SLN in your favorite text editor and adding the following section inside the Global section:

GlobalSection(ExtensibilityGlobals) = postSolution RootNamespace = WebClientApplication1 IsWCSFSolutionWAP = True IsWCSFSolution = TrueEndGlobalSection

I placed this right above the line

EndGlobal

Save and close the SLN file.

Then, edit the .csproj file for your web application project and add the following bolded line to the node

 <ProjectExtensions>    <VisualStudio>      <FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">      <!-- SNIP there is a lot of info here removed for space considerations-->      </FlavorProperties>      <UserProperties IsWebProject="true" />    </VisualStudio>  </ProjectExtensions>

Now, you can open your solution in Visual Studio.  Go to Tools-> Guidance Package Manager, and enable the "Web Client Development (WAP)" Guidance Package.  (If you don't have that, you will need to download, compile, and register it from the WCSF CodePlex Community site.)  Once the guidance package is enabled, you should be able to right-click on the web application project and see the "Web Client Factory" menu with "Add View (with presenter)"

Adding the Composite Web UI Application Block to your Web Site

Now, we can enable CWAB on your site, so dependency injection will work, and the new modules and pages will work without throwing exceptions.

Copy Files

This is simplest if you use the GP and create a new "Web Client Solution (Web Application Project)" and can copy some files and folders.

When you create a new Web Client Application solution, a Library folder is created for you with all the Dlls you need.  You will need to copy the Library folder from an existing WCSF project to the folder that contains your SLN file.  This gets CWAB, all of the configuration, security, and exception shielding that you need.

Then, in the folder that your web site is in, copy over the Modules folder from your sample solution, so we have a Shell module to work with.

Your solution folder on the file system should look something like this:

  • MyLegacyWebSite
    • Library
      • Microsoft.Practices.CompositeWeb.dll
      • Microsoft.Practices.CompositeWeb.EnterpriseLibrary.dll
      • Microsoft.Practices.EnterpriseLibrary.Common.dll
      • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.dll
      • Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.dll
      • Microsoft.Practices.EnterpriseLibrary.Logging.dll
      • Microsoft.Practices.EnterpriseLibrary.Security.dll
      • Microsoft.Practices.ObjectBuilder.dll
      • Microsoft.Practices.ObjectBuilder.WCSFExtensions.dll
    • Modules
      • Shell
        • ...
    • MyLegacyWebSite
      • … all you existing files, etc
    • MyLegacyWebSite.sln

Edit the Web.Config

Next, open up the Web.Config for the web application.
At the top of the file, right under the opening <configuration> tag, paste the following huge snippet: 
<configSections>  <sectionGroup name="compositeWeb">    <section name="modules" type="Microsoft.Practices.CompositeWeb.Configuration.ModulesConfigurationSection, Microsoft.Practices.CompositeWeb"/>    <section name="authorization" type="Microsoft.Practices.CompositeWeb.Configuration.AuthorizationConfigurationSection, Microsoft.Practices.CompositeWeb"/>  </sectionGroup>  <section name="securityConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Security.Configuration.SecuritySettings, Microsoft.Practices.EnterpriseLibrary.Security, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>  <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>  <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/></configSections><compositeWeb>  <modules>    <module name="Shell" assemblyName="Shell" virtualPath="~/" />  </modules></compositeWeb><securityConfiguration defaultAuthorizationInstance="RuleProvider" defaultSecurityCacheInstance="">  <authorizationProviders>    <add type="Microsoft.Practices.EnterpriseLibrary.Security.AuthorizationRuleProvider, Microsoft.Practices.EnterpriseLibrary.Security, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="RuleProvider">      <rules>        <!-- Add your own rules here:        e.g.:              <add expression="R:Customer" name="AllowViewAccountsSummary"/>                    -->      </rules>    </add>  </authorizationProviders></securityConfiguration><loggingConfiguration name="Logging Application Block" tracingEnabled="true" defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">  <listeners>    <add source="Enterprise Library Logging" formatter="Text Formatter" log="Application" machineName="" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" traceOutputOptions="None" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="Formatted EventLog TraceListener"/>  </listeners>  <formatters>    <add template="Timestamp: {timestamp}&#xA;Message: {message}&#xA;Category: {category}&#xA;Priority: {priority}&#xA;EventId: {eventid}&#xA;Severity: {severity}&#xA;Title:{title}&#xA;Machine: {machine}&#xA;Application Domain: {appDomain}&#xA;Process Id: {processId}&#xA;Process Name: {processName}&#xA;Win32 Thread Id: {win32ThreadId}&#xA;Thread Name: {threadName}&#xA;Extended Properties: {dictionary({key} - {value}&#xA;)}" type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="Text Formatter"/>  </formatters>  <categorySources>    <add switchValue="All" name="General">      <listeners>        <add name="Formatted EventLog TraceListener"/>      </listeners>    </add>  </categorySources>  <specialSources>    <allEvents switchValue="All" name="All Events"/>    <notProcessed switchValue="All" name="Unprocessed Category"/>    <errors switchValue="All" name="Logging Errors &amp; Warnings">      <listeners>        <add name="Formatted EventLog TraceListener"/>      </listeners>    </errors>  </specialSources></loggingConfiguration><exceptionHandling>  <exceptionPolicies>    <add name="GlobalExceptionLogger">      <exceptionTypes>        <add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="None" name="Exception">          <exceptionHandlers>            <add logCategory="General" eventId="100" severity="Error" title="Enterprise Library Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" priority="0" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=2.0.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="Logging Handler"/>          </exceptionHandlers>        </add>      </exceptionTypes>    </add>  </exceptionPolicies></exceptionHandling>
…..

Then in the <system.web> node, add the following, if they are not already there:

<compilation debug="true"> <assemblies>  <add assembly="System.Management, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>   <add assembly="System.Configuration.Install, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> </assemblies></compilation><siteMap defaultProvider="DefaultSiteMapProvider" enabled="true"> <providers>  <add name="DefaultSiteMapProvider" type="Microsoft.Practices.CompositeWeb.Providers.ModuleSiteMapProvider, Microsoft.Practices.CompositeWeb" securityTrimmingEnabled="true"/> </providers></siteMap><httpModules> <add name="WebClientAuthorizationModule" type="Microsoft.Practices.CompositeWeb.Authorization.WebClientAuthorizationModule, Microsoft.Practices.CompositeWeb"/> <add name="ExceptionLoggerHttpModule" type="Microsoft.Practices.CompositeWeb.EnterpriseLibrary.ExceptionLogger, Microsoft.Practices.CompositeWeb.EnterpriseLibrary" /></httpModules>

That concludes the additions to the Web.Config file.  This may require some merging of sections, so be careful.

Change your solution

We will add the Shell Module to the solution structure. First, add a Solution Folder to the root of the solution called Modules.  In side this new solution folder, add an existing project, the shell module you copied earlier,

Next, we will add a few more references to your web application project.  You will want references to all of the dlls in the Library folder we copied earlier.  If you want to remove functionality later (like exception shielding) you can remove the references then.  Also, add a reference to the Shell project we added.

Finally, we need to make some changes to the Global.asax file.  If the project does not have one, add a "Global Application File".  If you have one, open it up.
There are a few changes to make:

  • Change the base class from System.Web.HttpApplication to Microsoft.Practices.CompositeWeb.WebClientApplication
  • If you already have an Application_Start method and you do not need the sender and event arguments within it, rename it to
     protected override void Start()
  • If you need the arguments, make sure the first line is
     base.Application_Start(sender, e); 

Moving on from here

Now, you should be able to compile, use the recipes as is this solution was created with the WCSF, and run your web site.  In the not too distant future, I will have another post on what to do next.