tags:

views:

235

answers:

2

In the method CreateView() (check my View Engine below) or in my custom action filter (also below) I have to somehow check if the View we are requesting is a ViewUserControl. Because otherwise I get an error saying

"A master name cannot be specified when the view is a ViewUserControl."

when I have "modal=true" in QueryString and the View request is ViewUsercontrol because you cannot set master page on ViewUserControls (obviously).

This is my custom view engine code right now:

    public class PendingViewEngine : VirtualPathProviderViewEngine
    {
        public PendingViewEngine()
        {
            // This is where we tell MVC where to look for our files. 
            /* {0} = view name or master page name       
             * {1} = controller name      */
            MasterLocationFormats = new[] {"~/Views/Shared/{0}.master", "~/Views/{0}.master"};
            ViewLocationFormats = new[]
                                    {
                                        "~/Views/{1}/{0}.aspx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx",
                                        "~/Views/{1}/{0}.ascx"
                                    };
            PartialViewLocationFormats = new[] {"~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.ascx"};
        }

        protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
        {
            return new WebFormView(partialPath, "");
        }

        protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
        {
            return new WebFormView(viewPath, masterPath);
        }
}

My action filter:

public class CanReturnModalView : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // setup the request, view and data
        HttpRequestBase request = filterContext.RequestContext.HttpContext.Request;

        bool modal = false;

        if (request.QueryString["modal"] != null)
            modal = bool.Parse(request.QueryString["modal"]);

        if (filterContext.Result is ViewResult)
        {
            ViewResult view = (ViewResult) (filterContext.Result);

            // replace the view Master page file with Modal Masterpage
            if (modal)
                view.MasterName = "AdministrationModal";

            filterContext.Result = view;
        }
        else if (filterContext.Result is RedirectToRouteResult)
        {
            RedirectToRouteResult redirect = (RedirectToRouteResult) filterContext.Result;
            // append modal route value to the redirect result if modal was requested
            if (modal)
                redirect.RouteValues.Add("modal", true);

            filterContext.Result = redirect;
        }
    }
}

The above ViewEngine fails on calls like this:

<% Html.RenderAction("Display", "MyController", new { zoneslug = "some-zone-slug" });%>

The action I am rendering here is this:

        public ActionResult Display(string zoneslug)
        {
            WidgetZone zone;

            if (!_repository.IsUniqueSlug(zoneslug))
                zone = (WidgetZone) _repository.GetInstance(zoneslug);
            else
            {
// do something here
            }

// WidgetZone used here is WidgetZone.ascx, so a partial
            return View("WidgetZone", zone);
        }

I cannot use RenderPartial because you cannot send route values to RenderPartial the way you can to RenderAction. To my knowledge there is no way to provide RouteValueDictionary to RenderPartial() like the way you can to RenderAction().

A: 

Try this ( untested ). FindView returns an ViewEngineResults which has a property called IView View which could be a PartialViewResult. Just check and return and use an empty master if nothing exists.

public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
    if( base.FindView(controllerContext, viewName, "", useCache).View is PartialViewResult )
        return base.FindView(controllerContext, viewName, "", useCache);

    if (controllerContext.HttpContext.Request.IsAjaxRequest())
        return base.FindView(controllerContext, viewName, "Modal", useCache);

    return base.FindView(controllerContext, viewName, "Site", useCache);
} 

}

jfar
Hey jfar, this doesn't help. Maybe I should note that the error occurs when I render my partial views (i.e ViewUserControls) not thru RenderPartial but through RenderAction like this: <% Html.RenderAction("Display", "WidgetZoneV2", new { zoneslug = "left-default-zone" }); %> The "Display" is the Action on WidgetZoneV2 controller which returns WidgetZoneV2 ViewUserControl.The if sentence for PartialViewResult is never "true" in my case, so it always jumps over it.
mare
Why are you using RenderAction? Something is bizarre here. Can you explain more about what your doing in your question?
jfar
I am using RenderAction because with that I can get the data for the View in the action that I am calling, let's say "on-demand" as opposed to calling RenderPartial for which I would have to prepare the data in advance (when preparing the view that would host the RenderPartial call).
mare
I'd have to see the code. I don't understand what your doing and think you may be attacking your problem from the wrong angle. RenderAction should rarely be used outside of master template/page components or narrow caching issues.
jfar
For example, you have strongly typed ASCX. It expects some data to be provided as a model, which I'm sure you understand. With RenderPartial there is no way to do something like the above (check the revised question).
mare
The code I posted or something similar to it should work. You need to find out how to fire that if.
jfar
+2  A: 

You can achieve the described behaviour by calling PartialView instead of view:

Create your view like you would now:

  <% Html.RenderAction("PartialIndex", new { test = "input" }); %>

As you can see there is nothing special to it.

Then in your controller action simply call PartialView instead of View:

public ActionResult PartialIndex(string test)
{
    var viewResult = PartialView("PartialIndex", (object)test);
    return viewResult;
}

And also override the View method to overrule the mastername when the modal parameter is passed into the query string.

public bool IsModal
{
    get
    {
        var modalParameter = Request.QueryString["modal"];

        if (string.IsNullOrEmpty(modalParameter))
            return false;

        bool modalValue;
        var success = bool.TryParse(modalParameter, out modalValue);

        return success && modalValue;
    }
}

protected override ViewResult View(string viewName, string masterName, object model)
{
    if (IsModal)
        masterName = "Alternate";
    return base.View(viewName, masterName, model);
}

The View method override is only hit when you render an actual view (and not a partial one), so it is safe to assume that a masterName can be set.

Thomas
How could I miss that PartialView result?? :) I was always, since my beginings with ASP.NET MVC, used only the View result. If you return PartialView, then the "if" check in the above action filter skips it and, as such, does not try to apply master page. I only used the "return PartialView()" part of your response and had to change nothing else in my code. So it works great and I'm confirming your answer.
mare