views:

254

answers:

5

In my C#/ASP.NET project I have an object which has certain categories of behavior. Each behavior category is physically dependent on a root object but to make the code read better i want to clearly differentiate the categories. I think it would be interesting to see how my implementation compares to what others might write to solve the same problem.

In the example below i have a class which generates URLs to different places on the website. The website has a storefront which has it's own set of links which should be accessed differently from the urls such as the homepage.

public class WebsiteURLs
{
   // One category of urls on the website is the store.
   public class StoreURLs
   {
     private WebsiteURLs _website;
     public class StoreURLs(WebsiteURLs website)
     {
       _website = website;
     }
     public string BestSellers
     { 
        get { return _website.ResolveURL("~/store/bestSellers.html"); }
     }
   }
   private StoreURLs _store;
   public StoreURLs Store // property for generating store urls
   {
     get
     {
        if (_store == null ) {
          _store = new StoreURLs(this);
        }
        return _store;
     }
   }

   public string Homepage
   {
       get { return ResolveURL("~/default.aspx"); }
   }

   // .. Other Categories Here
   protected string ResolveURL(string url)
   {
     return HttpContext.Current.Response.ApplyAppPathModifier(url);
   }
}

Using this code would look similar to the following

WebsiteURLs website;
Console.WriteLine(website.Store.BestSellers);

Robert:

The example itself is only one scenario where I have found myself trying to more explicitly organize the functionality. Have you ever found yourself using a prefix around related methods. String.TrimStart(), String.TrimEnd(), String.Trim() is an example that comes to mind from the C# framework.

My attempt to orgnaize the code above (from a readability standpoint) suffers from the inabilty of nested classes to access outer classes members. There is extra work involved in constructing the inner class because i must pass a reference of the WebsiteURLs instance to the constructor of the StoreURLs class, it is almost a violation of C# coding practices because this closure behavior is not the behavior of nested classes within the language. I'm curious what C# code others would use in situations where there exists lots of related functionality (think tens or hundreds of methods). (Note: The code above is much more fluid to write in Java where nested classes do have access to their outer classes members).

A: 

Use C# Regions. Regions will categories your code properly.

public class WebsiteURLs
{
   #region StoreURLs sub-class
   // One category of urls on the website is the store.
   public class StoreURLs
   {
     private WebsiteURLs _website;
     public class StoreURLs(WebsiteURLs website)
     {
       _website = website;
     }
     public string BestSellers
     { 
        get { return _website.ResolveURL("~/store/bestSellers.html"); }
     }
   }
   #endregion

   #region StoreURLs prop. accessor
   private StoreURLs _store;
   public StoreURLs Store // property for generating store urls
   {
     get
     {
        if (_store == null ) {
          _store = new StoreURLs(this);
        }
        return _store;
     }
   }
   #endregion

   #region Homepage
   public string Homepage
   {
       get { return ResolveURL("~/default.aspx"); }
   }
   #endregion

   #region Methods
   // .. Other Categories Here
   protected string ResolveURL(string url)
   {
     return HttpContext.Current.Response.ApplyAppPathModifier(url);
   }
   #endregion
}
this. __curious_geek
Regions are a terrible way to identify or separate functionality because they simply hide text in a class rather than actually reduce complexity. We should look to a more SOLID solution. In jest, I have heard regions are like putting the toilet seat down without flushing. A good analogy because all the carp code is still there, it's just that you can choose not to see it if you like. I strongly advise against this approach.
Bernhard Hofmann
A: 

I'm not sure that I understand the goal. But...

I believe I would just create an interface or base class that does most of the generation work and just derive from it for each category.

Or, create a factory class that responds to a category enumeration.

Ryan
+2  A: 

My immediate response when I see your example is to think that you're binding something relatively static - a set of types - to something relatively dynamic - a bunch of URLs belonging to a web site. That smells wrong to me. Doing this tightly couples your code to the structure and function of the web site. Any change to any URL anywhere in the web site that you're binding to forces you to rebuild and redeploy your software.

So if you're going to do that, you have to have a pretty good reason. What are you getting out of this? The ability to use type-checking (and IntelliSense, probably) on a hierarchy of web pages. You're paying a really significant cost to get this.

Is it worth it? Is this:

url = website.Store.BestSellers;

really so much better than this:

url = website.GetUrl("Store.BestSellers");

that it's worth the amount of work you're going to have to put in just to achieve it?

There are certainly circumstances under which the answer to that question could be "yes." But I wouldn't spend another minute on this design without being sure I knew it.

Robert Rossney
See my edits to the top post
SmokingRope
A: 

I would configure the path in web.config, a resource file or in a database, doing so, you gain lot of advantage, example you could use different config for testing / production, you can switch to another page just editing your configuration without change in the source and recompiling ...

kentaromiura
A: 

I agree with Robert. Another thing you could do is to create a resource file (Key/Value) of your site urls and refer to it like this:

string url = Resources.Navigation.Home;

It gives you automatic checking of names at compiletime rather than runtime (since the string wont be evaluated until then)

CodeSpeaker