This question has come up many times in the forums:
I have an application that needs functionality X, but I need it everywhere - including my Master Page. Where should I put this stuff?
This kind of issue is pretty common using MVC, where logic is piped along some clearly defined paths. Often, however, you find that you need to have some special logic - some "horizontal" logic - that extends across your "vertical" controller pipes. A typical "horizontal" bit of logic might be statistics, security, logging, or email - common application functionality that transcends your application's MVC implementation.
My thoughts below are one of a few ways that you can address this. The goal is to reduce duplication (DRY) and keep it as centralized as you can.
One of the ways I've been recommending people deal with these issues is to create a "Service" class, and put it in a folder called "Services". These classes contain very specific logic, and are made up by (usually) a set of static methods.
With the MVC forums that I've been creating, I have the following Service classes in my Services folder:
- Gravatar
- Akismet (anti-spam)
- Search (post/thread search)
- TextSterilizer (cleans out bad words and blocked expressions)
- BBCodeFormatter (encodes using BB Code replacement)
- CSharpFormatter (Manoli CSharpFormat)
- UserStats (posts, last post, last login, points, etc)
You can see how each of these can be used all over a forums application - thus their "horizontal" service nature. In fact if my application had a service tray - each of these might have an icon in there.
But What About UI?
If your service has a UI component, you can expose it using a UserControl, and then render it out using the MVC Toolkit's RenderUserControl() method:
<%=Html.RenderUserControl("~/Gravatar/GravatarImage.ascx")%>
Helper Classes
Another post came in where the user asked (paraphrased):
On my model I have a property which I am exposing using an enum. I want the user to be able to read this information properly so what should I do - add a property to my model, or format it using CodeBehind? I'm being told not to use CodeBehind - what should I do?
It's not fair to say "don't use CodeBehind" - it does come in handy for View-specific logic that you don't want to "clutter" the page. But mostly, if you have to doctor some data for your view, you can expand on it some and probably reuse it somewhere in your application.
This is where "Helper" classes come in - they contain UI code that know nothing of a Controller or Model information - they simply do the "heavy lifting" of diplaying data to your view. You can watch a Rails-cast on this (Rails uses Helpers extensively) that shows some great UI refactoring.
In this case the user wanted to output the enum value as a string, which you can do this way:
<%=Enum.GetName(typeof(Product.SomeEnum),ViewData.Product.MyEnum)%>
But this is a tad messy in the UI. You can indeed add a property to your model, but this ties UI logic to your model, which isn't always the best option (but it does work).
The thing to do here, in my mind, is to create a Helper class for your Controller, and have it handle the formatting.
If we're using Northwind, we can create a ProductHelper class that outputs the enum's value:
public static class ProductHelper{ public static string EnumToString(object en){ return Enum.GetName(typeof(en),en); } }
This works, but in looking at this you can see that there's more we can do with it. Specifically - it's highly reusable code, and the entire application (not just the Product-related views) can use it.
Given this, let's take this "up-scope" and create a site-wide UI Helper class, with a little more love:
public static class AppHelper{ public static string EnumToReadable(object en){ string result=string.Empty; result= Enum.GetName(typeof(en),en); //make it human readable return SubSonic.Sugar.Strings.ParseCamelToProper(result); } }
You can also create this as an extension method and put it in your UI library - like we did with SubSonic!
