views:

29

answers:

2

The Background:

We are supplied with html files - 'wrappers' - from our client, into which we need to inject the content that we produce. They have different wrappers for different pages and we have to inject the corresponding content into a special tag that they supply in the wrapper.

The wrapper file name corresponds to the name of the action method. So for the example below, a custom action filter is executed which will determine the name of the wrapper and then call a method in the BaseController (which every controller implements) which will load the wrapper and inject our content into it.

    [WrapperAction]
    public ActionResult Home()
    {
        return View();
    }

The reason I put this in an ActionFilter is because I didn't want to be calling the BaseController's method to populate the wrapper in every action method that needed a wrapper. I thought it was much tidier to decorate the method with the ActionFilterAttribute

WrapperAction is defined as follows:

public class WrapperAction : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext aec)
    {
        var baseController = (BaseController)filterContext.Controller;

        string wrapper = aec.RequestContext.RouteData.Values["action"].ToString();

        baseController.PopulateWrapper(wrapper);
    }
}

and PopulateWrapper is defined as

    public void PopulateWrapper(string wrapperName)
    {
        // get wrapper file from disk
        Wrapper wrapper = _wrapperService.GetWrapper(Site.Id, wrapperName);

        // populate the file with our CSS (it already has its own pre-populated CSS)
        // our CSS file is determined by the id of a Site object.
        AppHelper.PopulateWrapperCss(wrapper, this.Site.Id);

        // make wrapper sections available to the Master page, 
        // split so that we can place our content around them
        ViewData["WrapperTop"] = wrapper.WrapperTop;
        ViewData["WrapperMiddle"] = wrapper.WrapperMiddle;
        ViewData["WrapperBottom"] = wrapper.WrapperBottom;
    }

The Dilema:

Now however, a new requirement has come in. There are some new tags in the wrapper that I need to populate, and I have to populate them with different data depending on the action method that calls the wrapper. That data is determined in the action methods, but the data needs to be used in the PopulateWrapper method that gets called by the WrapperAction.

I now need to have a method similar to

AppHelper.PopulateWrapperTags(wrapper, this.TagData);

and I need to have some way for BaseController's TagData property to be populated with data. I can asign the property in the action method like follows

    [WrapperAction]
    public ActionResult Home()
    {
        base.TagData = GetTagData();
        return View();
    }

but that kind of defeats the point of me having the WrapperAction in the first place because I don't want to have to be referring to the BaseController like that.

The Question:

Is there a way for me to supply WrapperAction with the data it needs to populate the wrapper with? Do I need to take the hit and start calling

        var tagData = GetTagData();
        string wrapperName = RouteData.Values["action"].ToString();
        base.PopulateWrapper(wrapperName, tagData);

in every controller? Is there a better way for me to do this?

+1  A: 

What you have already written is really very good and needs only slight adjustment to meet your new requirement.

The key is that your action filter is a type of OnActionExecuted which runs after your action code has completed, but before the view is executed. Therefore the filter method can build on whatever has taken place within the action method.

To achieve your requirement, create the tagData variable within your base controller, so that the controllers that inherit from it can populate it if necessary.

Then, within your action filter you simply need something like

if (tagData == null)
   baseController.PopulateWrapper(wrapper)
else
   baseController.PopulateWrapper(wrapper, tagData)

I'm sure you get the general idea.

Clicktricity
A: 

imho the following was just right, so I don't see how the action filter is tidier:

public ActionResult Home()
{
   PopulateWrapper();
   return View();
}

and in the controller base:

public void PopulateWrapper(string wrapperName)
{
   string wrapperName = RouteData.Values["action"].ToString();
   //... rest of populate wrapper

then you go:

public ActionResult Home()
{
    PopulateWrapper(GetTagData()); // after defining an overload in base
    return View();
}

Assuming you still go the action filter way, I don't see why you are forcing the dependency with controller base with it i.e. why the PopulateWrapper is in controller base.

Note that you can also pass the data in ViewData, but that feels worst in your scenario - specially given the dependency in controller base.

eglasius