Thursday, December 10, 2009

Web Site Project vs. Web Application Project

My organization has decided to migrate an existing Web Site Application (WSP) to a Web Application Project (WAP).  This is a quick post to share the decision-making process highlights and to consolidate some of the references we consulted.

I found an MSDN article Introduction to Web Application Projects that gave a nice WAP overview with a simple decision matrix for choosing between WSP and WAP.

I found these blog posts to be particularly helpful on how to make a decision, especially Vishal Joshi’s thorough and knowledgeable comparison:

My broad impression after reading these various resources is that the WSP is best suited for demo or small web sites, or if there is a strong case for granular deployment (e.g., deploy an individual web page).  WAP is the clear favorite for enterprise production web sites.

Our decision to switch to WAP came down to these four key factors that favor WAP:

  • Better support for unit testing
  • Better support for continuous integration
  • Upcoming Web Deployment features (VS 2010) only support WAP
  • Better control over application development (e.g., Namespace control)

Conversion assistance from WSP to WAP is available as an online tutorial and as an MSDN article.

Wednesday, December 2, 2009

Continuous Integration Server Setup – Part 2

This is the second part of my process for setting up a prototype Continuous Integration server using CruiseControl.NET.  I describe the server setup in Part 1.  This post describes how I configured my projects and unit tests using the ccnet.config file.

I decided to use a Visual Studio 2008 console application solution as the test for my prototype CI server.  There were some steps required to set up that solution to build and test properly in a CI environment.  I detailed the solution configuration steps in a separate blog post.

I then edited the ccnet.config file to build and run my projects and unit tests.  I extensively followed the advice of Geoff Lane from his DRY your CruiseControl.NET Configuration post.

  • I defined reusable variables and blocks
  • I split my ccnet.config file into multiple parts.
    • The ccnet.config file itself contains the variable and block definitions that are shared by all projects.  It imports the user information and all project information from external files.  The ccnet.config file is managed in source control.
    • Individual project files config contain variable and block definitions specific to the projects.  All project files are managed in source control.
    • The user information is stored in a separate config file and contains the plain text UID and PWD for connecting to the SubVersion server.  The user config file is not managed in source control as a security precaution.

Download sample CruiseControl.NET ccnet.config and other config files

Here are some other Key Points:

  • The <artifactDirectory> and <workingDirectory> values should exactly mimic the folder structure in the Visual Studio solution so that all references are valid when the projects are checked out to the CI server.
  • Set the <webURL> value to use the actual host name (rather than localhost) so that CI email notifications contain a valid clickable link.
  • I configured my NUnit tests to run using the <nunit> task block, but I could have configured the unit tests to run as part of the MSBuild process in the .csproj file.
  • I configured the email publisher so that details are not included (<email includeDetails=”false” … />).  Including the details is a handy feature, but I discovered that anytime there was an exception when trying to connect to the SubVersion server, the cruisecontrol UID and password were included in the email details.
  • I manage the ccnet.config file itself in source control and added a CCNET project to the top of the ccnet.config file to check out the ccnet.config file.  In other words, set up CCNET so that when any developer makes a change to the ccnet.config file, the updated file is immediately checked out to the CI server by the previous version of the ccnet.config file.

Next steps to take the prototype to prime time on a production CI server:

  1. Resolve errors I am still seeing in the build logs – there are four occasional error messages:  “Source control operation failed: svn: OPTIONS of 'project name': Could not read status line: An existing connection was forcibly closed by the remote host” or “Source control operation failed: svn: Server sent unexpected return value (401 Authorization Required) in response to PROPFIND request for 'project name'” or “Source control operation failed: svn: OPTIONS of 'project name': SSL negotiation failed: An existing connection was forcibly closed by the remote host” or “Source control operation failed: svn: Server sent unexpected return value (401 Authorization Required) in response to REPORT request for 'project name'.”  I can think of several possible causes for these errors:
    1. These errors might go away when the CI server is set up as a true production server instead of as a VM virtual server.
    2. The CI server has a newer version of svn.exe than the version of SVN on the source control server.
    3. The self-signed certificate used by the source control server might have to be replaced with a commercial certificate.
  2. Consider changing my strategy so that NUnit tests are run after the build by MSBuild (rather than as a task in the ccnet.config file).  Using MSBuild is the recommended strategy suggested by the CCNET documentation and by Craig Berntson’s Continuous Integration for .NET Development white paper.
  3. Most of my projects are developed against the ESRI ArcGIS Server framework, which loads all of its assemblies to the GAC.  Despite best efforts to use local Lib copies (with Browse references) of the ESRI assemblies, it looks like the CI server will need to install ArcGIS Server to avoid build or test errors.

Continuous Integration Server Setup – Part 1

I just finished setting up a prototype Continuous Integration server with CruiseControl.NET with a lot of help from Jeff McWherter and by following many of the suggestions in a white paper by Craig Berntson.  Here are the steps and lessons learned from this experience.

I built the prototype server in VMWare using Windows Server 2003 with a C: and E: drives, and created an administrator account.  Here are the next steps:

  1. Installed CruiseControl.NET 1.4.4 SP1 accepting all defaults
  2. Created an E:\Tools folder to contain all of the necessary utility applications (e.g., E:\Tools\nUnit\bin\net-2.0\nunit-console.exe and E:\Tools\svn\svn.exe)
  3. Created a cruisecontrol user in my organization’s ActiveDirectory and then added a local Windows account for this user.  This user has read-only privileges to the SubVersion repository.
  4. Configured the CruiseControl.NET service to start automatically and to Log On using the cruisecontrol account: Selected the service, opened the Properties, Log On tab, This account radio button, browsed to the cruisecontrol account, entered the password, applied, then restarted the service. 

I then did a little fine-tuning of the CC.NET configuration files:

  • I did not change ccnet.exe.config
  • I edited ccservice.exe.config
    • In <appSettings>, I inserted the key <add key=”ccnet.config” value=”config\ccnet.config” />
    • I did this so that my config files are cleanly grouped into the C:\Program Files\CruiseControl.NET\server\config folder.
  • I edited dashboard.config
    • I commented out all unwanted <xslFileNames> entries from the <buildReportBuildPlugin> block
    • I commented out all unwanted <xslReportBuildPlugin> entries from the <buildPlugins> block
    • I did this to remove unused tests and builds from my dashboard.

The final step was to resolve a problem due to our SubVersion server using a self-signed certificate, which would cause an error when CruiseControl.NET tried to check out any of the projects (error message = “Server certificate verification failed: issuer is not trusted”).

  1. I tried various ways to accept the SubVersion server certificate without success by following the suggestions in the CruiseControl.NET documentation.
  2. I finally resolved the problem by importing the certificate using IE:  browsed to the SubVersion server, continued past the certificate error warning, entered my credentials, clicked the IE Certificate Error button (to the right of the URL), clicked the View Certificates link, clicked the Install Certificate button, and accepted all defaults.

That’s it for the overall setup steps on the CI prototype server.  In Part 2 I will describe how I configured my ccnet.config file to build and test my projects automatically.

Sunday, November 8, 2009

Linq to SQL Partial Classes

While experimenting with Linq to SQL, I realized right away that I needed to extend the functionality of the entity classes generated by the Linq to SQL designer.  I wanted to be able to sort the entity classes by having them implement IComparable.

The solution turned out to be simple:  extend each entity class by defining a partial class, and implement IComparable in the partial class definition.  For example, I had an entity class Newspaper, which I extended with a partial class:

public partial class Newspaper : IComparable<Newspaper>
{
#region IComparable<Newspaper> Members
public int CompareTo(Newspaper other)
{
if (other == null)
throw new ArgumentNullException("other");
return Newspaper_Name.CompareTo(other.Newspaper_Name);
}
#endregion
}



Linq to SQL First Cut

I experimented a little bit with Linq to SQL and learned some of the basic syntax.  Here is a screenshot of the DBML designer file:

Capture

Here are two LINQ queries I developed with their ANSI SQL equivalents:

SELECT Newspapers.*
FROM Newspapers
INNER JOIN Newspapers_Advertisements
ON Newspapers.Newspaper_Id = Newspapers_Advertisements.Newspaper_Id
WHERE Newspapers_Advertisements.Advertisement_Id = '@guid'

List<Newspaper> allPapers = new List<Newspaper>();
allPapers.AddRange(
    from paper in db.Newspapers
    join paper_ad in db.Newspapers_Advertisements
        on paper.Newspaper_Id equals paper_ad.Newspaper_Id
     where paper_ad.Advertisement_Id == adId
     select paper
);

SELECT DISTINCT Newspapers.*
FROM Newspapers
WHERE Newspapers.Newspaper_ID NOT IN
(SELECT Newspapers_Advertisements.Newspaper_Id
FROM Newspapers_Advertisements
WHERE Newspapers_Advertisements.Advertisements_Id = '@guid')

List<Newspaper> allPapers = new List<Newspaper>();
allPapers.AddRange(
    (from paper in db.Newspapers
     where !db.Newspapers_Advertisements.Any(
         na => (na.Newspaper_Id == paper.Newspaper_Id
         & na.Advertisement_Id == adId))
     select paper).Distinct()
);

Thursday, November 5, 2009

.NET Solution Configuration Notes

I’ve been working recently on setting up a Continuous Integration server with CruiseControl.NET as my CI server, SubVersion for version control, and NUnit for unit testing.  One of the lessons I learned in the process is the importance of proper solution configuration so that the automated build and testing process works.  Many thanks to Jeff McWherter for his outstanding assistance in this endeavor.

Herein I’ll describe the final Visual Studio Solution configuration I used.  I am sure that this configuration could still use some fine-tuning for optimal CC.NET integration, but this is a working setup.  I will discuss how I configured CruiseControl.NET in a separate posting.

My .NET solution is for a console application.  I divided my solution into one project for the console application itself and then 5 other class library projects (each with a corresponding unit testing project).  I created a shared Lib folder to contain any 3rd party assemblies, including a local copy of any assembly referenced from the GAC (which I obtained by creating the reference, setting the Copy Local property to true, building the project, then moving the DLL to my Lib folder and recreating the reference to point to the DLL).  I could have put a separate Lib folder in each project folder, but decided that was overkill since the individual class libraries will never be shared with other solutions.

SolutionFolder

Jeff recommended that I also cross-reference my projects using Browse references to the built DLL files, rather than by using Project references.  This can be accomplished by setting the Build Output path to be bin\ for Debug and Release build configurations.  I decided against this approach after experimentation.  I found that my projects built without error on my CI server when using Project references.  Also, I found that my solution did not always build properly if I used Browse references, and I lost the nice functionality of being able to Go To Definition for any class outside of the active project.

Setting up the project references properly proved to be the most critical step in having successful CI builds.  Here are a few more conventions that I implemented when configuring this solution:

  • Set up my SVN ignore property (see below) following the guidelines I found in the Introducing Source Control MSDN article in the section describing which files are included and excluded in Visual Studio source control.
  • Created a SharedAssembly folder at the root level of my solution to contain AssemblyInfo.cs information shared by all of the projects in my solution.  The SharedAssembly folder contains two files:  AssemblyVersionInfo.cs and SharedAssemblyInfo.cs.  I configured, linked, and referenced these files following the instructions from Jeremy Jameson.
  • Included my .sln file in version control.  I found arguments both ways on this subject but decided that it made sense to include the .sln file for this solution.  The .sln file is built on the continuous integration server.

One possible improvement to be made is to run unit tests using MSBuild as part of the .csproj setup for each unit test project.  Craig Berntson makes this recommendation in his Continuous Integration for .NET Development white paper.  Instead, I set the unit tests to run using the CruiseControl.NET config file.

Here is my SubVersion ignore property list:

*.log
*.scc
*.suo
*.vbproj.user
*.csproj.user
*.vbproj.webinfo
*.csproj.webinfo
Bin
bin
Obj
obj



Tuesday, October 27, 2009

Advanced Unit Testing

This is a training I provided to other developers on my team on some more advanced unit testing topics, including mocking and dealing with various unit test “gotcha” scenarios.  This is Part 3 of 3 in a series of talks geared towards improving code quality at my organization. 

Part I – Introduction to Design Patterns as a Best Practice

Part II – Introduction to Unit Testing

Download Presentation and Source Code:

  • PDF version of my presentation
  • Visual Studio 2008 solution containing sample code and unit tests

Wednesday, September 23, 2009

Introduction to Unit Testing

This is a training I provided to other developers on my team on how to get started with unit testing and test-driven development.  This is Part 2 of 3 in a series of talks geared towards improving code quality at my organization. 

Part I – Introduction to Design Patterns as a Best Practice

Part III – Coming soon, and will cover more advanced unit-testing topics including mocking (Rhino Mocks), refactoring, and more sophisticated unit test strategies.

Download Presentation and Source Code:

  • PDF version of my presentation
  • Visual Studio 2008 solution containing sample code and unit tests.

Friday, September 4, 2009

Enabling HTTP Compression for ASP.NET and IIS 6

I just went through the process of enabling HTTP Compression for 2 ASP.NET applications running under Windows Server 2003 and IIS 6, so I thought I’d share a summary of the process.

Huge thanks to Robert Boedigheimer for his assistance with this process.  I attended his excellent Things Every ASP.NET Developer Should Know session at devLINK last month and then he helped me work through the particulars.

Robert also explained to me how to determine if my site (internal) is using HTTP Compression.

  1. Start Microsoft’s Network Monitor.
  2. Open a “New Capture Tab”
  3. On the toolbar click “Start” to start a trace.
  4. Using any browser, browse to a web page for the site in question.
  5. In Network Monitor, click “Stop” in the toolbar.
  6. In the “Display Filter” panel, type “http” (no quotes) and Apply.
  7. Find the Response frame in the middle panel.  Select that frame, then examine the Hex Details frame.
  8. To the far right in the Hex Details frame, you should see that Content-Encoding is gzip.

Other good references:

I created a batch file that will perform the entire HTTP Configuration process.  Here are the overall steps:

  1. Stop IIS.
  2. Backup the MetaBase
  3. Create the temporary folder to contain the compressed files, and set the max folder size
  4. Update the MetaBase with the new settings
  5. Restart IIS.
  1: @ECHO OFF
  2: iisreset /stop /noforce
  3: MKDIR "E:\IIS Temporary Compressed Files"
  4: cscript.exe iisback.vbs /backup /b BeforeHttpCompressionChange
  5: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcDoStaticCompression TRUE
  6: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcDoDynamicCompression TRUE
  7: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcDoOnDemandCompression TRUE
  8: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcNoCompressionForHttp10 TRUE
  9: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcNoCompressionForProxies FALSE
 10: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcNoCompressionForRange FALSE
 11: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcCompressionDirectory "E:\IIS Temporary Compressed Files"
 12: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcMaxDiskSpaceUsage "52428800"
 13: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/Parameters/HcDoDiskSpaceLimiting TRUE
 14: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcDoStaticCompression TRUE
 15: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcDoOnDemandCompression TRUE
 16: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcDoDynamicCompression TRUE
 17: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcFileExtensions "css" "doc" "htm" "html" "js" "txt" "xml"
 18: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcScriptFileExtensions "asp" "ashx" "asmx" "aspx" "axd" "dll" "exe" "svc"
 19: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcOnDemandCompLevel 9
 20: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/deflate/HcDynamicCompressionLevel 9
 21: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcDoStaticCompression TRUE
 22: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcDoOnDemandCompression TRUE
 23: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcDoDynamicCompression TRUE
 24: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcFileExtensions "css" "doc" "htm" "html" "js" "txt" "xml"
 25: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcScriptFileExtensions "asp" "ashx" "asmx" "aspx" "axd" "dll" "exe" "svc"
 26: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcOnDemandCompLevel 9
 27: cscript.exe adsutil.vbs Set W3SVC/Filters/Compression/gzip/HcDynamicCompressionLevel 9
 28: iisreset /start
 29: PAUSE

Monday, August 10, 2009

Introduction to Design Patterns as a Best Practice

This is a training I provided to other developers on my team on how to improve code quality and testability by implementing “Gang of Four” design patterns.  I plan to give another talk soon on basic TDD techniques using these same design patterns in conjunction with NUnit and Rhino Mocks.

Download Presentation and Source Code:

  • PDF version of my presentation
  • Visual Studio 2008 solution containing sample code and unit tests.

Saturday, August 1, 2009

Dynamic MapResourceManager Part 2: Initialization Using Code

This is part 2 of a 2-part post on how I set up a dynamically-configurable ESRI Web ADF MapResourceManager control. Part 1 is how I used a custom web.config section to store the ResourceItem properties for the MapResourceManager.  Part 2 shows how to dynamically initialize a MapResourceManager using the ResourceItem properties I set up in Part 1.  This approach allows for dynamic “on the fly” switching of layers displayed in an ESRI Web ADF Map control.  This methodology also provides robust layer error handling so that if an individual layer cannot be displayed (e.g., its map server is down), the remaining Map layers are displayed without crashing the entire Web ADF application.

ESRI classes generally can’t be mocked using Rhino Mocks, so the first step was to create adapters for the MapResourceItem and MapResourceManager classes, adapting only those methods needed for the initialization process, shown here:

  1:     public interface IMapResourceItemAdapter
  2:     {
  3:         bool FailedToInitialize { get; }
  4:         void InitializeResource();
  5:         string Name { get; }
  6:     }
  7:     
  8:     public class MapResourceItemAdapter : IMapResourceItemAdapter
  9:     {
 10:         private readonly MapResourceItem _mri;
 11:         public MapResourceItemAdapter(MapResourceItem mri)
 12:         {
 13:             if (mri == null)
 14:                 throw new ArgumentNullException("mri");
 15:             _mri = mri;
 16:         }
 17:         public MapResourceItem GetMapResourceItem()
 18:         {
 19:             return _mri;
 20:         }
 21:         public string Name
 22:         {
 23:             get { return _mri.Name; }
 24:         }
 25:         public void InitializeResource()
 26:         {
 27:             try
 28:             {
 29:                 _mri.InitializeResource();
 30:             }
 31:             catch { throw; }
 32:         }
 33:        public bool FailedToInitialize
 34:         {
 35:             get 
 36:             {
 37:                 try
 38:                 {
 39:                     return CalculateFailedToInitialize(_mri.FailedToInitialize, _mri.InitializationFailure, _mri.LayerDefinitions);
 40:                 }
 41:                 catch { return false; }
 42:             }
 43:         }
 44: 	// use separate method for this test because Rhino Mocks can't mock an ESRI MapResourceItem
 45:         internal static bool CalculateFailedToInitialize(bool failedToInitialize,
 46:             Exception initializationFailure, LayerDefinitionCollection layerDefinitions)
 47:         {
 48:             return (failedToInitialize || initializationFailure != null || layerDefinitions == null);
 49:         }
 50:     }
 51:     
 52:     public interface IMapResourceManagerAdapter
 53:     {
 54:         int Add(IMapResourceItemAdapter adapter);
 55:         int IndexOf(string name);
 56:         bool InitializeMapResourceManager();
 57:         void Insert(int index, IMapResourceItemAdapter adapter);
 58:         void Remove(IMapResourceItemAdapter adapter);
 59:         void RemoveAt(int index);
 60:     }
 61: 
 62:     public class MapResourceManagerAdapter : IMapResourceManagerAdapter
 63:     {
 64:         private MapResourceManager _mrm;
 65:         public MapResourceManagerAdapter(MapResourceManager mrm)
 66:         {
 67:             if (mrm == null)
 68:                 throw new ArgumentNullException("mrm");
 69:             _mrm = mrm;
 70:         }
 71:         public int Add(IMapResourceItemAdapter adapter)
 72:         {
 73:             try
 74:             {
 75:                 MapResourceItem item = ((MapResourceItemAdapter)adapter).GetMapResourceItem();
 76:                 return _mrm.ResourceItems.Add(item);
 77:             }
 78:             catch { throw; }
 79:         }
 80:         public void Remove(IMapResourceItemAdapter adapter)
 81:         {
 82:             try
 83:             {
 84:                 MapResourceItem item = ((MapResourceItemAdapter)adapter).GetMapResourceItem();
 85:                 _mrm.ResourceItems.Remove(item);
 86:             }
 87:             catch { throw; }
 88:         }
 89:         public void Insert(int index, IMapResourceItemAdapter adapter)
 90:         {
 91:             try
 92:             {
 93:                 MapResourceItem item = ((MapResourceItemAdapter)adapter).GetMapResourceItem();
 94:                 _mrm.ResourceItems.Insert(index, item);
 95:             }
 96:             catch { throw; }
 97:         }
 98:         public void RemoveAt(int index)
 99:         {
100:             try
101:             {
102:                 _mrm.ResourceItems.RemoveAt(index);
103:             }
104:             catch { throw; }
105:         }
106:         public int IndexOf(string name)
107:         {
108:             try
109:             {
110:                 return _mrm.ResourceItems.IndexOf(_mrm.ResourceItems.Find(name));
111:             }
112:             catch { throw; }
113:         }
114:         public bool InitializeMapResourceManager()
115:         {
116:             bool initialized = true;
117:             try
118:             {
119:                 List<MapResourceItem> items = new List<MapResourceItem>();
120:                 foreach (MapResourceItem mri in _mrm.ResourceItems)
121:                     items.Add(mri);
122:                 foreach (MapResourceItem mri in items)
123:                     _mrm.ResourceItems.Remove(mri);
124:             }
125:             catch { initialized = false; }
126:             return initialized;
127:         }
128:     }


I also needed a factory class to create new MapResourceItemAdapter instances.  This approach simplifies unit testing because the factory class can be mocked easily.  Here is the factory code:



  1:     public interface IMapResourceItemFactory
  2:     {
  3:         IMapResourceItemAdapter CreateMapResourceItem(TNC.GIS.WebControlsLibrary.CustomConfig.LayerProperties layerProp);
  4:     }
  5: 
  6:     public class MapResourceItemFactory : IMapResourceItemFactory
  7:     {
  8:         public IMapResourceItemAdapter CreateMapResourceItem(LayerProperties layerProp)
  9:         {
 10:             MapResourceItemAdapter adapter = null;
 11:             try
 12:             {
 13:                 MapResourceItem item = new MapResourceItem();
 14:                 item.Name = layerProp.Name;
 15:                 item.DisplaySettings.Visible = layerProp.Visible;
 16:                 item.DisplaySettings.Transparency = layerProp.Transparency;
 17:                 item.DisplaySettings.ImageDescriptor.ImageFormat = layerProp.ImageFormat;
 18:                 item.DisplaySettings.ImageDescriptor.Height = layerProp.Height;
 19:                 item.DisplaySettings.ImageDescriptor.Width = layerProp.Width;
 20:                 item.DisplaySettings.ImageDescriptor.Dpi = layerProp.Dpi;
 21:                 item.DisplaySettings.ImageDescriptor.ReturnMimeData = layerProp.ReturnMimeData;
 22:                 item.DisplaySettings.ImageDescriptor.TransparentBackground = layerProp.TransparentBackground;
 23:                 item.DisplaySettings.ImageDescriptor.TransparentColor = layerProp.TransparentColor;
 24:                 item.DisplaySettings.DisplayInTableOfContents = layerProp.DisplayInTableOfContents;
 25:                 GISResourceItemDefinition defn = new GISResourceItemDefinition();
 26:                 defn.DataSourceType = layerProp.DataSourceType;
 27:                 defn.DataSourceShared = layerProp.DataSourceShared;
 28:                 defn.DataSourceDefinition = layerProp.DataSourceDefinition;
 29:                 defn.ResourceDefinition = layerProp.ResourceDefinition;
 30:                 item.Definition = defn;
 31:                 adapter = new MapResourceItemAdapter(item);
 32:             }
 33:             catch (ArgumentNullException ex)
 34:             {
 35:                 throw new ExplanationException("The MapResourceItem could not be instantiated.", ex);
 36:             }
 37:             catch (NullReferenceException ex)
 38:             {
 39:                 throw new ArgumentNullException("The provided LayerProperties instance (layerProp) is null.", ex);
 40:             }
 41:             catch (Exception ex)
 42:             {
 43:                 throw new ArgumentException("The provided LayerProperties instance is invalid.", "layerProp", ex);
 44:             }
 45:             return adapter;
 46:         }
 47:     }


The last piece of the puzzle was to set up a MapResourceManagerInitializer class with a separate Helper class to simplify unit testing of the class logic:



  1:     public class MapResourceManagerInitializer
  2:     {
  3:         private readonly IMapResourceManagerAdapter _mrm;
  4:         private readonly IMapResourceItemFactory _mriFactory;
  5:         private readonly IErrorLogger _logger;
  6:         public MapResourceManagerInitializer(IMapResourceManagerAdapter mrm, IMapResourceItemFactory mriFactory, IErrorLogger logger)
  7:         {
  8:             if (mrm == null)
  9:                 throw new ArgumentNullException("mrm");
 10:             if (mriFactory == null)
 11:                 throw new ArgumentNullException("mriFactory");
 12:             if (logger == null)
 13:                 throw new ArgumentNullException("logger");
 14:             _mrm = mrm;
 15:             _mriFactory = mriFactory;
 16:             _logger = logger;
 17:         }
 18:         public bool InitializeAllResourceItems(Collection<LayerProperties> allLayers)
 19:         {
 20:             bool initialized = _mrm.InitializeMapResourceManager();
 21:             if (initialized)
 22:             {
 23:                 foreach (LayerProperties lp in allLayers)
 24:                 {
 25:                     IMapResourceItemAdapter item = _mriFactory.CreateMapResourceItem(lp);
 26:                     if (MapResourceManagerInitializerHelper.InitializeResourceItem(_mrm, item, _logger) == false) initialized = false;
 27:                 }
 28:             }
 29:             else 
 30:                 _logger.LogStringToFile("Unable to initialize the MapResourceManager control",
 31:                     TNC.ErrorHandlerAndLogging.Logging.LogErrorLevel.Error);
 32: 
 33:             return initialized;
 34:         }
 35:         public bool InitializeSelectedResourceItems(Collection<LayerProperties> selectedLayers)
 36:         {
 37:             bool initialized = true;
 38:             foreach (LayerProperties lp in selectedLayers)
 39:             {
 40:                 IMapResourceItemAdapter item = _mriFactory.CreateMapResourceItem(lp);
 41:                 if (MapResourceManagerInitializerHelper.InsertResourceItem(_mrm, item, _logger) == false) initialized = false;
 42:             }
 43:             return initialized;
 44:         }
 45:     }
 46: 
 47:     internal static class MapResourceManagerInitializerHelper
 48:     {
 49:         internal static bool InitializeResourceItem(IMapResourceManagerAdapter mrm,
 50:             IMapResourceItemAdapter item, IErrorLogger logger)
 51:         {
 52:             bool initialized = false;
 53:             try
 54:             {
 55:                 mrm.Add(item);
 56:                 item.InitializeResource();
 57:                 if (item.FailedToInitialize)
 58:                     mrm.Remove(item);
 59:                 else
 60:                     initialized = true;
 61:             }
 62:             catch (Exception ex)
 63:             {
 64:                 logger.LogException(ex);
 65:                 try
 66:                 {
 67:                     mrm.Remove(item);
 68:                 }
 69:                 catch { }   // do nothing
 70:             }
 71:             return initialized;
 72:         }
 73:         internal static bool InsertResourceItem(IMapResourceManagerAdapter mrm,
 74:             IMapResourceItemAdapter item, IErrorLogger logger)
 75:         {
 76:             int idx = GetItemIndex(mrm, item, logger);
 77:             if (idx == -999) return false;
 78:             bool removed = RemoveResourceItem(mrm, idx, logger);
 79:             if (removed == false) return false;
 80:             bool inserted = false;
 81:             try
 82:             {
 83:                 mrm.Insert(idx, item);
 84:                 item.InitializeResource();
 85:                 if (item.FailedToInitialize)
 86:                     RemoveResourceItem(mrm, idx, logger);
 87:                 else
 88:                     inserted = true;
 89:             }
 90:             catch (Exception ex)
 91:             {
 92:                 logger.LogException(ex);
 93:                 try
 94:                 {
 95:                     mrm.RemoveAt(idx);
 96:                 }
 97:                 catch { }   // do nothing
 98:             }
 99: 
100:             return inserted;
101:         }
102:         private static int GetItemIndex(IMapResourceManagerAdapter mrm,
103:             IMapResourceItemAdapter item, IErrorLogger logger)
104:         {
105:             try
106:             {
107:                 return mrm.IndexOf(item.Name);
108:             }
109:             catch (Exception ex)
110:             {
111:                 logger.LogException(ex);
112:                 return -999;
113:             }
114:         }
115:         private static bool RemoveResourceItem(IMapResourceManagerAdapter mrm,
116:             int idx, IErrorLogger logger)
117:         {
118:             try
119:             {
120:                 mrm.RemoveAt(idx);
121:                 return true;
122:             }
123:             catch (Exception ex)
124:             {
125:                 logger.LogException(ex);
126:                 return false;
127:             }
128:         }
129:     }


All of the pieces are assembled in the Page_Init event handler on the page containing the ESRI Web ADF MapResourceManager control.  This code combines the classes explained here in Part 2 with the code described in Part 1:



  1:     protected void Page_Init(object sender, EventArgs e)
  2:     {
  3:         if (!Page.IsCallback && !Page.IsPostBack)
  4:         {
  5:             try
  6:             {
  7:                 // instantiate a MapResourceManagerInitializer which will add the MapResourceItems to the MapResourceManager control
  8:                 MapResourceManagerInitializer initializer = new MapResourceManagerInitializer(
  9:                     new MapResourceManagerAdapter(this.MapResourceManager1), 
 10:                     new MapResourceItemFactory(),
 11:                     new ErrorLogger());
 12: 
 13:                 // instantiate a LayerPropertiesFactory which will provide the MapResourceItems settings 
 14:                 //  read from the MapResourceItemSettings custom config section
 15:                 LayerPropertiesFactory factory = new LayerPropertiesFactory(new LayerPropertiesCollection());
 16: 
 17:                 Collection<LayerProperties> layers;
 18:                 if (this.MapResourceManager1.ResourceItems.Count > 0)
 19:                 {
 20:                     // if the MapResourceManager already has been initialized, only refresh the Projects layer; get the correct layer based on user role
 21:                     if (Roles.IsUserInRole("role1"))
 22:                         layers = factory.LayersByRoleAndLayerOrder("role1", 0);
 23:                     else
 24:                         layers = factory.LayersByRoleAndLayerOrder("*", 0);
 25:                     initializer.InitializeSelectedResourceItems(layers);
 26:                 }
 27:                 else
 28:                 {
 29:                     // if the MapResourceManager has not been initialized, initialize all layers; get the correct Projects layer based on user role
 30:                     if (Roles.IsUserInRole("role1"))
 31:                         layers = factory.AllLayersByRole("role1");
 32:                     else
 33:                         layers = factory.AllLayersByRole("*");
 34:                     initializer.InitializeAllResourceItems(layers);
 35:                 }
 36:             }
 37:             catch (Exception ex)
 38:             {
 39:                 // do something with the exception
 40:             }
 41:         }
 42:     }