BACKGROUND
We've got some links on our site that have formats that look like:
http://oursite.com/books/c_sharp_in_depth_12345.
To handle this, we use a simple property called Url
:
public class Book
{
public string Url { get; set;}
}
Basically, the URL comes from our website's domain, a section of the site (i.e. books), the name of the page and a unique id to identify the resource. The full URL is being stored in the database.
We don't like the fact that our database is storing the section name. That's a property of web layer property, not a property of the book. The database should not be dependent upon the web layer.
So we remove the section from the URL and get the following:
public class Book
{
public string UrlWithoutSection { get; set;}
}
OK, that works for this URL. But then our company's SEO czar says that our URL is wrong and that Google, our-one-true-love, will only love us if we re-write our urls like this:
http://oursite.com/programming-books/c-sharp-in-depth-12345
Uh, oh, I thought that we had removed the database dependency to the web layer, but we hadn't. Turns out, we had removed the dependency to the section, but the format of the URL still exists in the database. We fix this by abstracting the URL into an object:
public class OurUrl
{
public string title { get; set; }
public string id { get; set; }
}
Cool, now the dependency to the web layer is gone. Uh oh, here this time our CEO comes to us. We just bought a new company and now we're selling magazines. Uh, great? The magazine URLs are going to look like this:
http://oursite.com/magazines/computers/stack-overflow-the-magazine/2012/01/01/12345
OK, no problem, just create another object.
public class OurMagazineUrl : OurUrl
{
public DateTime PublishedDate { get; set; }
// Magazine enum will have to be created.
public MagazineType Type { get; set; }
}
It works, except then I begin to realize that we have plans for a big site. Lots of URLs. Lots of differently formatted URLs. Creating a new class every time seems like a major headache.
THE PROBLEM IN A NUTSHELL
How do you handle URLs so that the web layer is properly decoupled from the business layer and the data layer? I've come up with several thoughts about solutions:
MORE ABOUT THE PROBLEM
I'm hoping that this helps clarify some confusion.
We are using ASP.Net MVC. We use routes. We use helpers. We pass flattened DTOs to our web layer, not business objects. This problem concerns the service layer and an explosion of DTOs.
This is mainly a high traffic news site, not a line of business site. It can have many different urls and the urls can change at any time. They can be complex and arbitrarily determined by management.
URL Examples (not real, made up for example purposes).
1. http://oursite.com/news/wi/politics/supreme-court/recent-ruling-someid
2. http://oursite.com/news/wi/politics/election-2012/candidate-x-takes-stand-on-issue-y-someid
3. http://oursite/com/news/politics/mayor-says-things-are-a-ok-someid
4. http://oursite.com/news/milwaukee/local-bar-named-to-HOF-someid
5. http://oursite.com/news/wi/politics/supreme-court-someid
6. http://oursite.com/news/whatever-cat-our-CEO-wants/subcat1/subcat2/etc/2011/10/31/some-story-someid
All of the above are "articles" and we have an Article class. An article has a number of navigation properties, such as AuthorObject, RelatedLinksCollection, etc. Business objects are far too heavy to pass to a client, so we pass DTOs that flatten information (e.g. AuthorName). The above links, however can require different information, even though they are all "articles".
- needs Category, Subcategory, Title and Id
- needs Category,Subcategory, PoliticsCategory, Title and Id
- needs Category, Title and Id
- needs Category, Title and Id
- needs Category, Subcategory, Title and Id
- needs CeoCategory, CeoSubcategory, PublishedDate,Title and Id
In static programming languages, such as c#, the normal way would be to handle this would be to create separate DTO classes. You could add inheritance to reduce some of the code, but you still end up with multiple "article" dto classes.
public class IArticleDto {
public string title { get; set; }
public string body { get; set; }
public string Category { get; set; }}
public class StateArticleDto: IArticleDto {
public string title { get; set; }
public string body { get; set; }
public string Category { get; set; }}
public string StateCode { get; set; }
public string Subcategory { get; set; }
}
public class SupremeCourtArticleDto: IArticleDto {
public string title { get; set; }
public string body { get; set; }
public string Category { get; set; }}
public string Subcategory { get; set; }
}
public class ArbitraryCeoArticleDto: IArticleDto {
//who knows
}
etc.
The ability to write custom urls in any way possible in not negotiable. If an article relates to something (state, Category, etc.), it can become part of the url.
Solutions?
Continue to add
Url
objects as needed. How many? At least a dozen, but naming them will be troublesome. Doing one per business object solves the name issue, but that means dozens or hundreds of new objects. Yuck.IOC - Pass in the route pattern to the Data Access layer via configuration. The data access layer can then create a full url. The url pattern name is still a problem.
Use a
Dictionary<TKey, TValue>
,KeyValuePair<TKey, TValue>
, etc. to pull in.Use an
Expando
orDynamicObject
for the url details. So url will contain a couple basic properties (name
andid
), but other properties could be added when necessary.
I'm thinking about using 4), because it seems like something that dynamic programming does better than static languages. However, it may just be that I'm looking at it most, because I like new toys to play with (I haven't used expando before).
It's bettern than 1), because of the object explosion. I'm not sure that 2) will work for complex scenarios. You could pass in simple route name + route information to the data layer using DI, but it seems harder to accomplish with no additional gain. And it probably wouldn't work for complex routes -- for that, I think that you need to have a rules engine on the UI side.
As compared to 3), I think that 4) is slightly better. Someone correct me if I'm mistaken, but dynamic types seem to be no more than syntatic sugar on top of a dictionary, but with the advantage of cleaner code. Just a thought.
Here is my first go at #4. It works and the dynamic object type works elegantly.
Then I created a custom class to handle the URI details. I added a constructor with name + id, because I always want a resource name + id to be passed with no exceptions.
Then, in my DataAccess code
One note: this solves the problem of the service layer, but the web layer still needs to know how to build the URL. That will be some type of helper class / rules engine that will determine what's used based on the property names & types in UriDetails.
Let me know what you think.