Click here to Skip to main content
15,893,487 members
Articles / Web Development / ASP.NET

Implementing Model-View-Presenter in ASP.NET

Rate me:
Please Sign up or sign in to vote.
4.80/5 (27 votes)
17 Nov 2007CPOL12 min read 129.7K   2.7K   120  
Three implementations of Model-View-Presenter in ASP.NET 2.0.
///////////////////////////////////////////////////////
// Code author: Martin Lapierre, http://devinstinct.com
///////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security;
using System.Web.Configuration;

namespace SubSonic
{
    /// <summary>
    /// Resolves access to configuration files for ASP.NET, EXEs and DLLs.
    /// </summary>
    public class ConfigurationResolver
    {
        #region Fields

        /// <summary>
        /// The assembly configuration file name.
        /// </summary>
        private string _assemblyConfigFile = null;

        /// <summary>
        /// The configuration file overrides.
        /// </summary>
        private string[] _configFileOverrides = null;

        /// <summary>
        /// The excluded files from mapped lookup.
        /// </summary>
        /// <remarks>
        /// Some files may be unavailable due to security settings (trust, permissions).
        /// The resolver ignores these files and falls back to the next accessible file.
        /// </remarks>
        private Dictionary<string, Exception> _excludedFiles = new Dictionary<string, Exception>();

        #endregion Fields


        #region Constructors

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <remarks>
        /// This constructor is not intended to be used directly: 
        /// use the ConfigurationProvider class instead.
        /// Note that an assembly configuration file is not supported for Web application projects; 
        /// it is supported however for DLLs referenced by web applications.
        /// </remarks>
        internal ConfigurationResolver(Assembly assembly)
        {
            _assemblyConfigFile = GetAssemblyConfigFileName(assembly);
        }

        #endregion Constructors


        #region Properties

        /// <summary>
        /// Returns the assembly configuration file that may be used by this instance.
        /// </summary>
        public string AssemblyConfigurationFile
        {
            get { return _assemblyConfigFile; }
        }

        /// <summary>
        /// Returns the default configuration file that may be used by this instance.
        /// </summary>
        public string DefaultConfigurationFile
        {
            get { return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; }
        }

        /// <summary>
        /// Gets or sets overrides for configuration files.
        /// </summary>
        /// <remarks>
        /// Can be set at runtime to provide configuration files to 
        /// be used instead than the assembly and default config files.
        /// </remarks>
        public string[] ConfigFileOverrides
        {
            get { return _configFileOverrides; }
            set
            {
                if (value == null)
                    _configFileOverrides = null;
                else
                    InitConfigFileOverrides(value);
            }
        }

        /// <summary>
        /// Returns true if the current instance has configuration file overrides, false otherwise.
        /// </summary>
        public bool HasOverrides
        {
            get { return _configFileOverrides != null; }
        }

        /// <summary>
        /// Returns true if the currently running application is a Web application, false otherwise.
        /// </summary>
        /// <remarks>
        /// An application is set as a Web application if its configuration file is Web.config.
        /// </remarks>
        public bool IsRunningWebApp
        {
            get
            {
                return Path.GetFileName(DefaultConfigurationFile).ToLower().CompareTo("web.config") == 0;
            }
        }

        /// <summary>
        /// Gets the ConnectionStringsSection data for the current application's default configuration.
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        public ConnectionStringSettingsCollection ConnectionStrings
        {
            get
            {
                ConnectionStringSettingsCollection connectionStrings = null;

                try
                {
                    Configuration cfg;

                    if (_configFileOverrides != null) // Use configuration overrides.
                        foreach (string fileName in _configFileOverrides)
                        {
                            cfg = OpenMappedConfiguration(fileName);
                            if (cfg != null)
                            {
                                connectionStrings = cfg.ConnectionStrings.ConnectionStrings;
                                if (cfg.ConnectionStrings.ElementInformation.IsPresent)
                                    break;
                            }
                        }
                    else // Use runtime configuration files.
                    {
                        if (_assemblyConfigFile != null)
                        {
                            cfg = OpenMappedConfiguration(_assemblyConfigFile);
                            if (cfg != null)
                            {
                                if (cfg.ConnectionStrings.ElementInformation.IsPresent)
                                    connectionStrings = cfg.ConnectionStrings.ConnectionStrings;
                            }
                        }

                        if (connectionStrings == null) // Fall back to default config file. 
                        {
                            if (IsRunningWebApp)
                                connectionStrings = WebConfigurationManager.ConnectionStrings; // Web.config
                            else
                                connectionStrings = ConfigurationManager.ConnectionStrings; // App.config (Assembly.exe.config)
                        }
                    }
                }
                catch (Exception exception)
                {
                    throw new ConfigurationErrorsException("Configuration error.", exception);
                }

                if (connectionStrings == null)
                    throw new ConfigurationErrorsException("Connection strings not found.");

                return connectionStrings;
            }
        }

        /// <summary>
        /// Gets the AppSettingsSection data for the current application's default configuration. 
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        public NameValueCollection AppSettings
        {
            get
            {
                NameValueCollection appSettings = null;

                try
                {
                    Configuration cfg;

                    if (_configFileOverrides != null) // Use configuration overrides.
                        foreach (string fileName in _configFileOverrides)
                        {
                            cfg = OpenMappedConfiguration(fileName);
                            if (cfg != null)
                            {
                                // TODO: perf improvement; use caching until cfg changed.
                                appSettings = new NameValueCollection();
                                foreach (KeyValueConfigurationElement element in cfg.AppSettings.Settings)
                                    appSettings.Add(element.Key, element.Value);

                                if (cfg.AppSettings.ElementInformation.IsPresent)
                                    break;
                            }
                        }
                    else // Use runtime configuration files.
                    {
                        if (_assemblyConfigFile != null)
                        {
                            cfg = OpenMappedConfiguration(_assemblyConfigFile);
                            if (cfg != null)
                            {
                                if (cfg.AppSettings.ElementInformation.IsPresent)
                                {
                                    // TODO: perf improvement; use caching until cfg changed.
                                    appSettings = new NameValueCollection();
                                    foreach (KeyValueConfigurationElement element in cfg.AppSettings.Settings)
                                        appSettings.Add(element.Key, element.Value);
                                }
                            }
                        }

                        if (appSettings == null) // Fall back to default config file. 
                        {
                            if (IsRunningWebApp)
                                appSettings = WebConfigurationManager.AppSettings; // Web.config
                            else
                                appSettings = ConfigurationManager.AppSettings; // App.config (Assembly.exe.config)
                        }
                    }
                }
                catch (Exception exception)
                {
                    throw new ConfigurationErrorsException("Configuration error.", exception);
                }

                if (appSettings == null)
                    return new NameValueCollection();

                return appSettings;
            }
        }

        #endregion Properties


        #region Methods

        /// <summary>
        /// Retrieves a specified configuration section for the current application's default configuration. 
        /// </summary>
        /// <remarks>
        /// The method uses the configuration file overrides, if set.
        /// Otherwise, it tries to use the assembly configuration file and
        /// falls back to app.config or web.config if the configuration information is not found.
        /// </remarks>
        /// <typeparam name="TSection">The type of the section to retrieve.</typeparam>
        /// <param name="sectionName">The configuration section path and name.</param>
        /// <returns>
        /// The specified ConfigurationSection object. 
        /// Throws a ConfigurationErrorsException if the section does not exist.
        /// </returns>
        public TSection GetSection<TSection>(string sectionName) where TSection : ConfigurationSection
        {
            // Validate parameters.
            if (string.IsNullOrEmpty(sectionName))
                throw new ArgumentNullException("sectionName");

            TSection section = null;

            try
            {
                Configuration cfg;
                

                if (_configFileOverrides != null) // Use configuration overrides.
                    foreach (string fileName in _configFileOverrides)
                    {
                        cfg = OpenMappedConfiguration(fileName);
                        if (cfg != null)
                        {
                            section = (TSection)cfg.GetSection(sectionName); // Throws if invalid section type.
                            if (section != null && section.ElementInformation.IsPresent)
                                break;
                        }
                    }
                else // Use runtime configuration files.
                {
                    if (_assemblyConfigFile != null)
                    {
                        cfg = OpenMappedConfiguration(_assemblyConfigFile);
                        if (cfg != null)
                        {
                            TSection candidateSection = (TSection)cfg.GetSection(sectionName); // Throws if invalid section type.
                            if (candidateSection != null && candidateSection.ElementInformation.IsPresent)
                                section = candidateSection;
                        }
                    }

                    if (section == null) // Fall back to default config file. 
                    {
                        // Throws if invalid section type.
                        // Use (Web)ConfigurationManager.GetSection and not Configuration.GetSection 
                        // (more performant as the first ones use caching - see help).
                        if (IsRunningWebApp)
                            section = (TSection)WebConfigurationManager.GetSection(sectionName); // Web.config
                        else
                            section = (TSection)ConfigurationManager.GetSection(sectionName); // App.config (Assembly.exe.config)
                    }
                }
            }
            catch (Exception exception)
            {
                throw new ConfigurationErrorsException("Configuration error.", exception);
            }

            // TODO: maybe should return null; this is the default .NET behavior. But here it's safer.
            if (section == null)
                throw new ConfigurationErrorsException(string.Format("Section '{0}' not found.", sectionName));

            return section;
        }

        /// <summary>
        /// Finds a project configuration files from a specified path.
        /// </summary>
        /// <remarks>
        /// The method starts by searching the given path, then goes up the directory hierarchy until 
        /// it finds the configuration files. It stops looking if a project, solution or the root
        /// directory is reached.
        /// </remarks>
        /// <param name="path">
        /// A file path or a directory path. 
        /// A directory path must end with Path.DirectorySeparatorChar.
        /// </param>
        /// <returns>The configuration files found.</returns>
        public string[] FindProjectConfigFiles(string path)
        {
            List<string> configFiles = new List<string>();

            string targetFile = null;
            string[] filesFound = null;
            string currentDirectory = Path.GetDirectoryName(path);

            // Format directory correctly.
            if (currentDirectory != string.Empty && !currentDirectory.EndsWith(Path.DirectorySeparatorChar.ToString()))
                currentDirectory = string.Format("{0}{1}", currentDirectory, Path.DirectorySeparatorChar);

            do
            {
                // First look for the assembly's configuration file, 
                // which has priority over Web.config and App.config
                // (that is, must be the first in the list).
                if (_assemblyConfigFile != null)
                {
                    targetFile = string.Format("{0}{1}", currentDirectory, Path.GetFileName(_assemblyConfigFile));
                    if (File.Exists(targetFile))
                        configFiles.Add(targetFile);
                }

                // Then look for Web.config
                targetFile = string.Format("{0}{1}", currentDirectory, "Web.config");
                if (File.Exists(targetFile))
                    configFiles.Add(targetFile);

                // Then look for App.config
                targetFile = string.Format("{0}{1}", currentDirectory, "App.config");
                if (File.Exists(targetFile))
                    configFiles.Add(targetFile);

                // If at least one config file has been found, stop.
                if (configFiles.Count != 0)
                    break;

                // If we find a project file, stop.
                filesFound = Directory.GetFiles(currentDirectory, "*.*proj", SearchOption.TopDirectoryOnly);
                if (filesFound.Length != 0)
                    break;

                // If we find a solution file, stop.
                filesFound = Directory.GetFiles(currentDirectory, "*.sln", SearchOption.TopDirectoryOnly);
                if (filesFound.Length != 0)
                    break;

                // If we're at the root, stop.
                if (currentDirectory == string.Empty || currentDirectory == Path.GetPathRoot(path))
                    break;

                // Else we move one directory up.
                currentDirectory = currentDirectory.Remove(currentDirectory.Length - 1); // Remove last Path.DirectorySeparatorChar
                currentDirectory = currentDirectory.Substring(0, currentDirectory.LastIndexOf(Path.DirectorySeparatorChar) + 1);
            }
            while (true);

            if (configFiles.Count == 0)
                throw new Exception(string.Format("No project configuration found from '{0}'.", path));

            return configFiles.ToArray();
        }

        /// <summary>
        /// Opens the specified configuration file as a Configuration object. 
        /// </summary>
        /// <param name="fileName">The configuration file name.</param>
        /// <returns>
        /// Returns null if the file is not found or not accessible because of security.
        /// </returns>
        protected System.Configuration.Configuration OpenMappedConfiguration(string fileName)
        {
            // Validate parameters.
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            try
            {
                if (_excludedFiles.ContainsKey(fileName))
                    return null;

                ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = fileName; // Assumes already set to full path.

                System.Configuration.Configuration cfg = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);

                return cfg.HasFile ? cfg : null;
            }
            catch (SecurityException exception) 
            {
                _excludedFiles.Add(fileName, exception);
                Trace.WriteLine(string.Format("Lookup for file '{0}' failed. The process will try to locate another valid configuration file. The reason for the error was: '{1}'.", fileName, exception.Message), "SubSonic.ConfigurationResolver");
                return null;
            }
        }

        /// <summary>
        /// Gets the assembly configuration file name for the given assembly.
        /// </summary>
        /// <param name="assembly">The assembly to get the configuration file name for.</param>
        /// <returns>The assembly configuration file.</returns>
        protected string GetAssemblyConfigFileName(Assembly assembly)
        {
            // Validate parameters.
            if (assembly == null)
                throw new ArgumentNullException("assembly");

            string fileName = string.Format("{0}.config", Path.GetFileNameWithoutExtension(assembly.CodeBase));

            return GetFullPath(fileName);
        }

        /// <summary>
        /// Initializes the configuration file overrides.
        /// </summary>
        /// <param name="fileNames">The list of configuration file overrides.</param>
        protected void InitConfigFileOverrides(string[] fileNames)
        {
            // Validate parameters.
            if (fileNames == null)
                throw new ArgumentNullException("fileNames");
            if (fileNames.Length == 0)
                throw new ArgumentException("Array empty.", "fileNames");

            _configFileOverrides = new string[fileNames.Length];
            for (int iFileName = 0; iFileName < fileNames.Length; iFileName++)
            {
                if (string.IsNullOrEmpty(Path.GetFileName(fileNames[iFileName])))
                    throw new ArgumentOutOfRangeException("fileNames");
                _configFileOverrides[iFileName] = GetFullPath(fileNames[iFileName]);
            }
        }

        /// <summary>
        /// Gets the full path for a given file name.
        /// </summary>
        /// <param name="fileName">The file name to get the full path for.</param>
        /// <returns>The full path of the file.</returns>
        protected string GetFullPath(string fileName)
        {
            if (string.IsNullOrEmpty(Path.GetDirectoryName(fileName)))
            {
                string appBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                if (!appBase.EndsWith(Path.DirectorySeparatorChar.ToString()))
                    appBase += Path.DirectorySeparatorChar;
                return string.Format("{0}{1}", appBase, fileName);
            }
            else
                return fileName;
        }

        #endregion Methods
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions