im in ur web, enriching ur code

 
 

Build Providers: Strongly typed page urls in ASP.NET

I'm a fan of strong typing in .NET as you gain compile time validation of code, Visual Studio Intellisense support, not to mention less mucking around with type conversion or dealing with (usually inconsistently) entering strings for page names and configuration setting keys inline. Visual Studio provides intellisense and some degree of validation for pages (i.e. urls) within a project when using Design Mode, but using those urls in code, e.g..

Response.Redirect( "~/MyDir/MyPage.aspx" );

you're out of luck as there isn't any validation unless you write it yourself.

Perhaps a better syntax is to access urls within your project from a property somewhere, like so:

Response.Redirect( MyDir_MyPage );

This way, we reference a static variable or constant to retrieve the url we want to redirect the browser to. If the variable is spelt wrong, the project won't compile and we can fix the error. Being a member within the project means we're provided with intellisense support as well.

However, maintaining variables or constants for your pages can be tedious. If you change the name of a page from "MyPage.aspx" to "MyMovedPage.aspx" the actual value of the variable  "MyDir_MyPage" would also need to be updated. Once again, unless you have some automated tests to validate the page exists, the project will compile, but the if you forgot to update the reference, you'll get the wrong url.

Using a Build Provider to deal with changing file names

To get around these limitations, and to help lessen the maintenance within your project, we can use a build provider to generate a static class containing all the page's in our project. Once that's done, we get:

  • Intellisense - no more typing page address strings in code.
  • Compile time support - if a page is renamed, the build provider updates the generated class, and changes the name of the variable. Since the variable you referenced in your project no longer exists, you'll get compile errors wherever the variable is used. It's a simple hop, skip and a jump to go through and correct those errors.

So now we'll be able to use the following code, and know that if we change something, we'll get notified when when we build if something is amis.

Response.Redirect( Href.MyDir.MyPage );

The code for the build provider generates the following psudo code:

  • create a static root class called "Href"
  • all pages within the root of the project will be exposed as string constants
  • recurse subfolders, adding nested/inner classes and members to represent the hierarchy of the project

We'll look at the code for the build provider a little later, first lets look at what it will generate.

It's all Constants and Nested Classes

Using the previous example, the code the build provider generates looks something along the lines of...

public static class Href

{

  // ... repeat, one constant for each

  // file in the project root folder

  public const string Default = "~/";

 

  // ... repeat, one "nested class" for

  // each folder in the project root folder

  public static class MyDir

  {

    // ... repeat, one constant for each

    // file in the folder: /MyDir

    public const string MyPage = "~/mydir/mypage.aspx";

  }

}

Next: Setting up the build provider and configuration options.

Build providers must be compiled and referenced in the /bin folder of your project. You can't simply add the code for the build provider to the App_Code folder, as it will give a "Could not load type ''MyBuildProvider" error.

Once we've added the assembly reference to our project, we can add the relevant configuration information to the web.config.

<system.web>

  <compilation>

    <buildProviders>

      <add extension=".hrefs" type="CSharpVitamins.Compilation.HrefBuildProvider, CSharpVitamins"/>

    </buildProviders>

  </compilation>

</system.web>

Here I've chosen to associate files with the ".hrefs" extension with my HrefBuildProvider. You could easily choose another extension, as it's simply a mapping for a file extension found within the App_Code folder and the build provider. 

Now we can create a file with that extension in the App_Code folder (let's use "Site.hrefs" for now, but you could use any name. You could even have multiple .hrefs files with different class names, one for aspx pages, another for your images, javascript and css files :), and add the following configuration - then tailor to suit your preferences.

<?xml version="1.0" encoding="utf-8" ?>

<settings

  namespace=""

  className="Href"

  maxDepth="100"

  lowercaseUrls="true"

  includeExtension="false"

  makePartial="false">

 

  <files include="\.(as[pcmh]x|html?)$" exclude="" />

  <folders include="" exclude="App_|Bin" />

</settings>


Setting Description
namespace The namespace the generated class will be placed within. Leave blank to add to the global namespace. 
className The actual class name that forms the stub of all urls. Defaults to "Href".
maxDepth The maximum depth of recursion for processing. Enter a value of 1 for root files only, or higher to include subfolders. Defaults to 100.
lowercaseUrls When true, forces the values of urls to lowercase. Handy of you want to have to pages named in PascalCase but want urls all lowercase. Property names follow the same casing as the file name. Defaults to false.
includeExtension When true, the extension is appended to the property name separated by an underscore. Defaults to false.
makePartial When true, the root class uses the partial modifier, enabling you to add additional members through a non-auto-generated class file.
files Contains two regular expression patterns, once each for files to include and exclude. Include defaults to "\.aspx$", Exclude defaults to empty (none are explicitly excluded).
folders Same as files, but applied to folders. Include default to empty (all are included), Exclude defaults to "App_|Bin".

Apart from the juicy code, which is coming next, that's it. Now you'll have intellisense for your file locations and compile time checking. It's worth noting that the generated class from the build provider will regenerate when the .hrefs file is modified, or you rebuild. If you want to see the generated class (it's stored in the "Temporary ASP.NET Files" folder), I'd suggest simply to right click on "Href" from your code, and choose, "Go to definition" - it will bring up the temporary file for you to peruse.

Source code for the HrefBuildProvider

Rather than walking through the step by step of creating a build provider, I'll just point you to a couple of other articles on the subject if you wish to learn more, then I can just display the fully commented source code for the class below :)

You can download the source code for the HrefBuildProvider or look at the class definition below.

using System;

using System.CodeDom;

using System.Collections.Generic;

using System.IO;

using System.Text.RegularExpressions;

using System.Web.Compilation;

using System.Web.Hosting;

using System.Xml;

 

namespace CSharpVitamins.Compilation

{

/// <summary>

/// Generates a static class for urls within an ASP.NET project

/// </summary>

public class HrefBuildProvider : BuildProvider

{

  #region Fields

 

  /// <summary>

  /// Path to the web root for determining urls within the site

  /// </summary>

  string _basePath;

  /// <summary>

  /// Whether to convert urls to lowercase - based on your preference

  /// </summary>

  bool _useLowerCaseUrls;

  /// <summary>

  /// Whether to include the extension of the file in the name

  /// of the member

  /// </summary>

  bool _includeExtension;

  /// <summary>

  /// Whether to create the root class as 'partial', allowing

  /// additional members to be added to the resultant class.

  /// </summary>

  bool _makePartial;

  /// <summary>

  /// Maximum recurring depth for the project - 1 for top level,

  /// 2 for first level of subfolders etc...

  /// </summary>

  int _maxDepth = 100;

  /// <summary>

  /// Namespace the generated class will be placed within.

  /// Leave empty to add to the global namespace e.g. global::Href

  /// </summary>

  string _namespace;

  /// <summary>

  /// The name of the main class e.g. Href

  /// </summary>

  string _className;

 

  /// <summary>

  /// Current depth of recursion

  /// </summary>

  int _depth = 0;

  /// <summary>

  /// Predicate to determine if a file should be included

  /// </summary>

  Predicate<FileSystemInfo> isValidFile;

  /// <summary>

  /// Predicate to determine if a folder should be included

  /// </summary>

  Predicate<FileSystemInfo> isValidFolder;

 

  #endregion

 

  #region GenerateCode

 

  /// <summary>

  /// Initialises settings from the BuildProvider file, and

  /// generates the appropriate code.

  /// </summary>

  /// <param name="assemblyBuilder"></param>

  public override void GenerateCode(AssemblyBuilder assemblyBuilder)

  {

    // init settings from xml source file

    init();

 

    // create root class e.g. global::Href

    CodeTypeDeclaration root = createStaticClass(_className, _makePartial);

    addSummary(root, "Provides access to urls within the project.");

    build(new DirectoryInfo(_basePath), root);

 

    CodeNamespace ns = new CodeNamespace();

    ns.Types.Add(root);

 

    // leave blank for global::namespace

    if (!string.IsNullOrEmpty(_namespace))

      ns.Name = _namespace;

 

    CodeCompileUnit unit = new CodeCompileUnit();

    unit.Namespaces.Add(ns);

    assemblyBuilder.AddCodeCompileUnit(this, unit);

  }

 

  #endregion

 

  #region build

 

  /// <summary>

  /// Iterates over the given directory, adding the files as members to

  /// the parent type, then recurse down the folder tree until the max

  /// depth is reached.

  /// </summary>

  /// <param name="dir">The directory to process</param>

  /// <param name="parent">Parent class to add members too.</param>

  void build(DirectoryInfo dir, CodeTypeDeclaration parent)

  {

    if (_depth >= _maxDepth)

      return;

 

    ++_depth;

 

    /// 'members' keeps a record of the number of times a

    /// member name is repeated within the parent type/class.

    /// Pass this to 'ensureUniqueMemberName' to get the name

    /// with an index number appended to the end e.g. the second

    /// occurrence of "MyProperty" becomes "MyProperty1"

    Dictionary<string, int> members = new Dictionary<string, int>();

 

    ///

    /// process files:

    /// iterate over files and add the member

    /// public const string MyfileName = "~/myfileName.aspx";

    ///

    FileInfo[] files = dir.GetFiles();

    foreach (FileInfo file in files)

    {

      if (isValidFile(file))

      {

        CodeMemberField field = new CodeMemberField();

        field.Name = getName(file);

        field.Type = new CodeTypeReference(typeof(string));

        field.Attributes = MemberAttributes.Public | MemberAttributes.Const;

        field.InitExpression = getInitExpression(getUrl(file));

        addSummary(field, getUrl(file, false));

        ensureUniqueMemberName(members, field);

 

        parent.Members.Add(field);

      }

    }

 

    ///

    /// process subfolders:

    /// iterate over folders and add a nested class

    ///

    DirectoryInfo[] subfolders = dir.GetDirectories();

    foreach (DirectoryInfo folder in subfolders)

    {

      if (isValidFolder(folder))

      {

        CodeTypeDeclaration nested = createStaticClass(getName(folder), false);

        addSummary(nested, "Provides access to urls under: {0}", getUrl(folder, false).TrimStart('~'));

        ensureUniqueMemberName(members, nested);

        build(folder, nested);

 

        /// .ctor will have already been added to members

        /// so only add to parent if there are additional

        /// members present

        if (nested.Members.Count > 1)

          parent.Members.Add(nested);

      }

    }

 

    --_depth;

  }

 

  #endregion

 

  #region init

 

  /// <summary>

  /// Initialises settings from the config file.

  /// </summary>

  /// <example>

  ///

  /// Sample configuration - all nodes and elements are optional, but the file

  /// needs a root node to load as an xml document.

  /// <?xml version="1.0" encoding="utf-8" ?>

  /// <settings

  ///    namespace=""

  ///    className="Href"

  ///    maxDepth="100"

  ///    lowercaseUrls="true"

  ///    includeExtension="false">

  ///   

  ///    <files include="\.(aspx|html)$" exclude="\.htm$" />

  ///    <folders include="" exclude="App_|Bin|Templates" />

  ///  </settings>

  ///

  /// </example>

  void init()

  {

    _basePath = HostingEnvironment.ApplicationPhysicalPath;

 

    XmlDocument xml = new XmlDocument();

    using (Stream stream = VirtualPathProvider.OpenFile(base.VirtualPath))

      xml.Load(stream);

 

    /// code below uses syntax,

    ///      xml["elementName"] ?? xml.CreateElement( "elementName" );

    /// as a lazy initialisation technique, rather than checking for

    /// the node and performing alternate initialisation - this keeps

    /// it within the same reading context.

 

    ///

    /// general settings

    ///

    XmlElement settings = xml["settings"] ?? xml.CreateElement("settings");

    _maxDepth = int.Parse(getAttributeValue(settings, "maxDepth", "100"));

    _useLowerCaseUrls = bool.Parse(getAttributeValue(settings, "lowercaseUrls", "false"));

    _includeExtension = bool.Parse(getAttributeValue(settings, "includeExtension", "false"));

    _makePartial = bool.Parse(getAttributeValue(settings, "makePartial", "false"));

    _namespace = getAttributeValue(settings, "namespace");

    _className = getAttributeValue(settings, "className", "Href");

 

    ///

    /// files settings

    ///

    XmlElement files = settings["files"] ?? xml.CreateElement("files");

    isValidFile = createFilter(

      getAttributeValue(files, "include", @"\.aspx$"),

      getAttributeValue(files, "exclude")

      );

 

    ///

    /// folders settings

    ///

    XmlElement folders = settings["folders"] ?? xml.CreateElement("folders");

    isValidFolder = createFilter(

      getAttributeValue(folders, "include"),

      getAttributeValue(folders, "exclude", "App_|Bin")

      );

  }

 

  #endregion

 

  #region getAttributeValue

 

  /// <summary>

  ///

  /// </summary>

  /// <param name="parent"></param>

  /// <param name="name"></param>

  /// <param name="default"></param>

  /// <returns></returns>

  static string getAttributeValue(XmlElement parent, string name, string @default)

  {

    XmlAttribute attribute = parent.Attributes[name];

    return null == attribute ? @default : attribute.Value.Trim();

  }

 

  /// <summary>

  ///

  /// </summary>

  /// <param name="parent"></param>

  /// <param name="name"></param>

  /// <returns></returns>

  static string getAttributeValue(XmlElement parent, string name)

  {

    return getAttributeValue(parent, name, null);

  }

 

  #endregion

 

  #region createFilter

 

  /// <summary>

  /// Creates a Predicate for filtering based on the given regex patterns

  /// </summary>

  /// <param name="includePattern"></param>

  /// <param name="excludePattern"></param>

  /// <returns></returns>

  static Predicate<FileSystemInfo> createFilter(string includePattern, string excludePattern)

  {

    Regex include = null, exclude = null;

 

    if (!string.IsNullOrEmpty(includePattern))

      include = new Regex(includePattern,

        RegexOptions.Compiled | RegexOptions.IgnoreCase);

 

    if (!string.IsNullOrEmpty(excludePattern))

      exclude = new Regex(excludePattern,

        RegexOptions.Compiled | RegexOptions.IgnoreCase);

 

    return createFilter(include, exclude);

  }

 

  /// <summary>

  /// Creates a Predicate for filtering based on the given regex

  /// </summary>

  /// <param name="include"></param>

  /// <param name="exclude"></param>

  /// <returns></returns>

  static Predicate<FileSystemInfo> createFilter(Regex include, Regex exclude)

  {

    if (null != include && null != exclude)

      return delegate(FileSystemInfo info) { return include.IsMatch(info.FullName) && !exclude.IsMatch(info.FullName); };

    else if (null != include)

      return delegate(FileSystemInfo info) { return include.IsMatch(info.FullName); };

    else if (null != exclude)

      return delegate(FileSystemInfo info) { return !exclude.IsMatch(info.FullName); };

    else

      return delegate(FileSystemInfo info) { return true; };

  }

 

  #endregion

 

  #region getInitExpression

 

  /// <summary>

  /// Returns the given value surrounded with quotes.

  /// </summary>

  /// <param name="value"></param>

  /// <returns></returns>

  static CodeSnippetExpression getInitExpression(string value)

  {

    return new CodeSnippetExpression(string.Concat("\"", value, "\""));

  }

 

  #endregion

 

  #region getName

 

  /// <summary>

  /// Gets the normalised/escaped member name for the given file

  /// </summary>

  /// <param name="file"></param>

  /// <returns></returns>

  string getName(FileSystemInfo file)

  {

    string name = Path.GetFileNameWithoutExtension(file.Name);

 

    if (_includeExtension)

      name += file.Extension.Replace('.', '_');

 

    name = Regex.Replace(name, @"[^a-z0-9_]*", string.Empty,

      RegexOptions.Compiled | RegexOptions.IgnoreCase);

 

    // Ensure pascal casing of the name - not really required

    //name = TextHelper.PascalCase( name );

 

    // Escape names not starting with a letter

    if (name.Length > 0 && !char.IsLetter(name[0]))

      name = "_" + name;

 

    // Escape C# keywords

    // included in separate library, omitted here for clarity

    //name = CodeHelper.CSharp.EscapeWord(name);

 

    return name;

  }

 

  #endregion

 

  #region getUrl

 

  /// <summary>

  /// Gets the url of the file, relative to the app root

  /// </summary>

  /// <param name="file"></param>

  /// <returns></returns>

  string getUrl(FileSystemInfo file)

  {

    return getUrl(file, true);

  }

 

  /// <summary>

  /// Gets the url of the file, relative to the app root

  /// </summary>

  /// <param name="file"></param>

  /// <param name="allowNormalisation">When true, allows lowercasing of the

  /// url and stripping of default.aspx</param>

  /// <returns></returns>

  string getUrl(FileSystemInfo file, bool allowNormalisation)

  {

    string url = file.FullName.Substring(_basePath.Length);

 

    if (allowNormalisation)

    {

      if (_useLowerCaseUrls)

        url = url.ToLower();

 

      if (url.EndsWith("default.aspx", StringComparison.OrdinalIgnoreCase))

        url = url.Substring(0, url.Length - 12);

    }

 

    return string.Concat("~/", url.Replace("\\", "/"));

  }

 

  #endregion

 

  #region addSummary

 

  /// <summary>

  /// Adds a summary doc comment to the type's comment collection

  /// </summary>

  /// <param name="type"></param>

  /// <param name="format"></param>

  /// <param name="args"></param>

  void addSummary(CodeTypeMember type, string format, params object[] args)

  {

    addSummary(type.Comments, format, args);

  }

 

  /// <summary>

  /// Adds a summary doc comment to the collection

  /// </summary>

  /// <param name="comments"></param>

  /// <param name="format"></param>

  /// <param name="args"></param>

  void addSummary(CodeCommentStatementCollection comments, string format, params object[] args)

  {

    comments.Add(new CodeCommentStatement("<summary>", true));

    comments.Add(new CodeCommentStatement(string.Format(format, args), true));

    comments.Add(new CodeCommentStatement("</summary>", true));

  }

 

  #endregion

 

  #region createStaticClass

 

  /// <summary>

  /// Creates a static class type with the given name

  /// </summary>

  /// <param name="name"></param>

  /// <param name="partial"></param>

  /// <returns></returns>

  static CodeTypeDeclaration createStaticClass(string name, bool partial)

  {

    CodeTypeDeclaration type = new CodeTypeDeclaration(name);

    type.TypeAttributes |= System.Reflection.TypeAttributes.Sealed;

    type.Attributes = MemberAttributes.Public | MemberAttributes.Static;

    type.IsClass = true;

    type.IsPartial = partial;

 

    CodeConstructor ctor = new CodeConstructor();

    ctor.Attributes = MemberAttributes.Private;

    type.Members.Add(ctor);

 

    return type;

  }

 

  #endregion

 

  #region ensureUniqueMemberName

 

  /// <summary>

  /// Ensures a unique name within the dictionary to get the name

  /// with an index number appended to the end e.g. the second

  /// occurrence of "MyProperty" becomes "MyProperty1"

  /// </summary>

  /// <remarks>A caveat of this method is where you may have two members

  /// like MyPage.aspx, MyPage.html as well as MyPage1.aspx. A conflict

  /// will occur giving duplicate members i.e. in the same order MyPage,

  /// MyPage1 and MyPage1 a second time. The likelihood of such conflicts

  /// is possible, with one possible solution to recurse using

  /// ensureUniqueMemberName with the newly generated name, or enumerate

  /// the parent type's Members to see whether there will be a conflict.

  /// </remarks>

  /// <param name="members">Dictionary containing the name of the members

  /// in the current namespace as the key + the number of appearances as

  /// the value.</param>

  /// <param name="type">The next member to add to the current namespace.</param>

  static void ensureUniqueMemberName(Dictionary<string, int> members, CodeTypeMember type)

  {

    string key = type.Name;

    int count;

    if (members.TryGetValue(key, out count))

    {

      type.Name = string.Concat(key, ++count);

      members[key] = count;

    }

    else

    {

      members[key] = 0;

    }

  }

 

  #endregion

}

}

 

kick it on DotNetKicks.com

Posted on Sunday, February 03, 2008 3:51 PM
Filed Under [ C#, Utilities, Tips, ASP.NET, Source Code ]

Comments

Gravatar
# Build Providers: Strongly typed page urls in ASP.NET
Posted by DotNetKicks.com
Posted on 2/4/2008 2:40 PM
You've been kicked (a good thing) - Trackback from DotNetKicks.com
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Dave Transom
Posted on 2/4/2008 3:14 PM
I just ran across a post by Kirill Chilingarashvili who is trying to achieve the exact same thing as I with this post. It's nicely written and is pretty clear to understand and has some screen shots too :)

It's worth a read.

Auto generate strong typed navigation class for all user controls in ASP.NET web application:
http://blog.devarchive.net/2008/01/auto-generate-strong-typed-navigation.html
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Zack
Posted on 2/8/2008 8:52 AM
This is pretty neat. I have a project that would be very complementary. Please take a look: http://blog.zacksfiasco.com/2007/11/make-programmatic-navigation-easier-in.html
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Harry
Posted on 2/9/2008 7:59 AM
I used PageMethods with my ASP.NET 2.0 project and it worked really well.

http://www.codeplex.com/PageMethods/

I think the ultimate answer is to have a domain specific schema for all the pages (ASP.NET 3.5 with MVC) like ...

http://mycompany/order/1234
http://mycompany/order/search/soda
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Dave Transom
Posted on 2/10/2008 10:25 PM
@Zack: I actually read your article when I was looking for a solution prior to writing my own.

@Harry: I've seen a reference to the PageMethods project before, but I dismissed it as PageMethods in ASP.NET AJAX (doh). The project looks like it tackles the 'dynamic' bits for parameters, where as my solution only deals with static files.

Thanks for the reminder.
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Neil Bostrom
Posted on 2/23/2008 1:03 AM
I looked a few months back at writing a similar BuildProvider. The big limitation I ran into was that BuildProviders are not supported in Web Applications, only Web Sites.

Shame really, was a neat solution!
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Dave Transom
Posted on 2/23/2008 11:19 AM
@Neil: Maybe have a look at Kirill's solution (posted in the first comment). That should see you right since you trigger the generation of the class by running a template and will work in both types of project (vs2008).
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by gedw99
Posted on 7/3/2008 10:52 AM
Does NOT work when you have files under Svn. The ".svn" when added as a excluded folder causes an index out of range error.

Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Dave Transom
Posted on 7/3/2008 12:23 PM
Hi gedw99,

How have you specified the "excluded" regular expression?

Mine worked fine out-of-the-box by using:
<folders include="" exclude="App_|Bin|\.svn" />

Saying that, there is an index out of range error which is thrown from the getName method when the Path.GetFileNameWithoutExtension(file.Name) returns an empty string i.e. when a folder called .svn is passed in - it thinks the ".svn" is an extension.

By _excluding_ the folders however, you shouldn't get the exception, but here's the updated code (if (name.Length > 0 ...)

string getName(FileSystemInfo file)
{
string name = Path.GetFileNameWithoutExtension(file.Name);

if (_includeExtension)
name += file.Extension.Replace('.', '_');

name = Regex.Replace(name, @"[^a-z0-9_]*", string.Empty,
RegexOptions.Compiled | RegexOptions.IgnoreCase);

// Escape names not starting with a letter
if (name.Length > 0 && !char.IsLetter(name[0]))
name = "_" + name;

return name;
}

Hope that helps.
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Chanva
Posted on 10/6/2008 1:28 AM
Maybe you could take a look into a new alternative. http://www.codeplex.com/UrlGenerator.
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Joshua Clark
Posted on 10/15/2009 7:55 AM
I like what you have here. It seems like a great solutions for the strong typing and the easy navigation of web pages in any ASP.NET app. However, it seems like a overloaded solution. It would seem that if what we are trying to achieve, I think, is to have strongly typed page names. Wouldn't it be easier just to write a class that maintained all the web page file names or a database that maintained that list of mappings?

I like the solution you came up with and other seem to have had success with it but I think this might be overkill for trying to get strongly-named page variable names.

http://joshua-clark.blogspot.com

~Josh
Gravatar
# re: Build Providers: Strongly typed page urls in ASP.NET
Posted by Dave Transom
Posted on 10/30/2009 10:13 AM
@Josh: I originally started maintaining a static class, but it soon became a chore to manually update - especially if pages moved or the number of pages increased in the project. Using a build provider solved that issue and keeps it fairy hands-off.

It was quite a big help when we were building a fairly complex workflow process (i.e. multiple pages grouped into categorical sub folders) for collecting data.

We saw immediate benefits at having the page structure at our intellisense fingertips and removed the ambiguity and the time it takes to write the equivalent string. It also meant that if we moved a file, we'd get a fairly obvious error at compile time and could fix it up pretty quick. It was great for this type of project.

I can see your point on how it might be overkill, I still use it for projects, but it depends on the type.

For example - an online shop might only have 5 - 10 pages (but 100's of urls for categories and products), you might do url rewriting and overall its easily managed without help (and quite possibly requires special construction of hyperlinks anyway) - where as a large-ish web forms admin might benefit from this more due to the slightly different nature in the way its used/coded/presented.

Probably a point of difference to note would be that this method handles pages (or file locations), rather than URLs - at least that's from seeing how I've used it that it fits best :)

Post Comment

Title *
Name *
Email
Url
Comment *  
Please add 5 and 1 and type the answer here:

Recently on C# Vitamins...

Powered By Subtext

 

About C# Vitamins

Dave has been working in the industry for around 14 years, and has a focus on Javascript, C#, ASP.NET and SQL Server web development; not to mention being a standards driven type of guy.

C# Vitamins is the result of his findings while working in the web industry and a desire to share with the community; and if it was traced back far enough, you might say it might not have existed if he hadn't taken such an interest in id Software's original Quake.

Related Links

Below is a list of related links of Dave's other sites.