Art Of Programming

musings by Dmytrii Nagirniak

Reusing Controller Without Inheritance

I created couple of controllers that provide some predefined functionality. All I needed is just to inherit from them. Then I decided to move them in a separate library so I would be able to reuse it in other projects. But then I faced a problem. Controllers of my project are forced to be inherited from those ones.
Thou I don’t have a problem with it, I additionally want every controller to have more functionality in it. You know, we always want to have something common in all our controllers.
If .NET would support multiple inheritance it would not be a problem at all.
So what I really need in my project is:
  1. Be able to inherit from any controller in.
  2. Have additional functionality in any controller.
  3. Have NO code duplicates.

Again. If we inherit - we cannot have common ADDITIONAL functionality (except the inherited one) and we’ll have to duplicate all the code needed in each controller (it seems so at first glance).
But it is not the case if the base controller supports extensibility. I remembered about IDynamicActions but it’s not fully what I need. I just need to add common functionality to all controllers without inheritance. DynamicActions reuse actions (which I might need soon but not for now).
Then I remembered Umbrella Project Review and it’s extension points. The idea was good enough for me and I decided to go with it, but in a simplified way.
No more words. The end result is below (sample controllers):
// Notice CommonControllerExtension and this.Extension
public class HomeController : AbstractBaseController<CommonControllerExtension> {
    public ActionResult Index() {
        ViewData["Username"] = Extension.User.Username;
        return View();
    }
}
and other controller
public class TopicController : CrudController<Topic, CommonControllerExtension> {
    protected override void PrepareUpdateInfo(UpdateModelInfo updateInfo) {
        updateInfo.Include("Name", "Content");
    }

    protected override IEnumerable<Topic> GetObjectsToList() {
        // Note Extension.WorkSpace!
        return Extension.WorkSpace.Query().Ocl<Topic>("Topic.allInstances->orderBy(CreatedDate)");
    }
}

The point is that we have two completely different controllers: HomeController is inherited from the abstract one; the TopicController is inherited from the CrudController (which is in turn inherited from abstract one). What they have in common is:
  1. They explicitly or implicitly inherit from AbstractBaseController.
  2. They have the same generic parameter: CommonControllerExtension.
The second point enables the controller to share common functionality via extension class. It can be single in the whole project or couple of them - your choice.
Here the main thing AbstractBaseController
public abstract class AbstractBaseController<TExtendPoint> : Controller where TExtendPoint : class, IControllerExtensionPoint<Controller>, new() {
    
    private TExtendPoint extension;


    #region Overriden

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {
        base.OnActionExecuting(filterContext);
        Extension.OnInit(filterContext);
        OnInit(filterContext);
    }
    protected override void OnActionExecuted(ActionExecutedContext filterContext) {
        base.OnActionExecuted(filterContext);
        Extension.OnBeforeRender(filterContext);
        OnBeforeRender(filterContext);
    }
    protected override void OnResultExecuted(ResultExecutedContext filterContext) {
        OnEnd(filterContext);
        Extension.OnEnd(filterContext);
        base.OnResultExecuted(filterContext);
    }

    #endregion


    
    public TExtendPoint Extension {
        get {
            if (extension != null)
                return extension;

            // Create an instance with default constructor.
            extension = new TExtendPoint();
            extension.Controller = this;
            return extension;
        }
    }


    /// <summary>
    /// Is called when controller is ready to be initialised with additional info.
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnInit(ActionExecutingContext context) {
    }

    /// <summary>
    /// Is called when controller done it's job and action has been executed, but before the view is rendered.
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnBeforeRender(ActionExecutedContext context) {
    }

    /// <summary>
    /// Is called when the request has been processed and the controller is ready to clean-up the resourse before it gets disposed.
    /// </summary>
    /// <param name="context">The context.</param>
    public virtual void OnEnd(ResultExecutedContext context) {
    }


    /// <summary>
    /// Gets or sets the head title which is ViewData["HeadTitle"]
    /// </summary>
    /// <value>The head title.</value>
    public string HeadTitle {
        get {
            return ViewData["HeadTitle"].PropGet(o => o.ToString());
        }
        set {
            ViewData["HeadTitle"] = value;
        }
    }

}

And here is the IControllerExtensionPoint interface:
/// <summary>
/// The interface that allows to provide additional functionality to controller without inheritance.
/// The implementor must have the default constructor.
/// </summary>
/// <typeparam name="TController">The type of the controller.</typeparam>
public interface IControllerExtensionPoint<TController> where TController: Controller {

    /// <summary>
    /// Gets or sets the extending controller.
    /// The setter is automatically invoked just after the instance is created by the controller.
    /// </summary>
    /// <value>The controller.</value>
    TController Controller { get; set; }


    /// <summary>
    /// Is called when controller is ready to be initialised with additional info.
    /// </summary>
    /// <param name="context">The context.</param>
    void OnInit(ActionExecutingContext context);

    /// <summary>
    /// Is called when controller done it's job and action has been executed, but before the view is rendered.
    /// </summary>
    /// <param name="context">The context.</param>
    void OnBeforeRender(ActionExecutedContext context);

    /// <summary>
    /// Is called when the request has been processed and the controller is ready to clean-up the resourse before it gets disposed.
    /// </summary>
    /// <param name="context">The context.</param>
    void OnEnd(ResultExecutedContext context);

}
Here’s the sample implementation sample:
public class CommonControllerExtension : IControllerExtensionPoint<Controller>, IEcoServiceProvider {

    #region IControllerExtensionPoint<Controller> Members

    public Controller Controller { get; set; }

    public void OnInit(ActionExecutingContext context) {
        User = GetTheUserFromSomewhere();
    }

    public void OnBeforeRender(ActionExecutedContext context) { 
    }

    public void OnEnd(ResultExecutedContext context) {
        if (ProviderOfWorkSpace != null)
            ProviderOfWorkSpace.ReleaseWorkSpace();
    }

    #endregion


    #region IEcoServiceProvider Members

    public T GetEcoService<T>() where T : class {
        return WorkSpace.GetEcoService<T>();
    }

    public object GetEcoService(Type serviceType) {
        return WorkSpace.GetEcoService(serviceType);
    }

    #endregion



    public IWorkSpaceProvider ProviderOfWorkSpace {
        get;
        set;
    }

    public IWorkSpace WorkSpace {
        get {
            if (ProviderOfWorkSpace == null)
                ProviderOfWorkSpace = new DefaultWebAppWorkSpaceProvider(() =>
                    new XmlWorkSpace());
            return ProviderOfWorkSpace.GetWorkSpace();
        }
    }

    public TObject GetObjectFromRequest<TObject>(string name) where TObject : class, ILoopBack {
        var vpr = Controller.ValueProvider.GetValue(name);
        if (vpr == null)
            return null;
        return WorkSpace.GetObject<TObject>(vpr.AttemptedValue);
    }

    public User User { get; set; }
}

What we really have achieved here is reusing functionality with avoiding need of no multiple inheritance. It also supports notification via OnInit, OnBeforeRender and OnEnd methods.
The drawback is that each controller must be inherited (implicitly or explicitly) from AbstractBaseController. The price I’m ready to pay because of I inherit from Controller anyway.
Enjoy!

Comments