views:

328

answers:

3

I want to have a menu that when I click replaces the content of a "main" div with content from a mvc view. This works just fine if I use a .aspx page, but any master.page content is then doubled (like the and any css/js). If I do the same but uses a .ascx user control the content is loaded without the extras, but if any browser loads the menu item directly (ie search bot's or someone with JS disabled), the page is displayed without the master.page content.

The best solution I've found so far is to create the content as a .ascx page, then have a .aspx page load this if it's called directly from the menu link, while the ajax javascript would modify the link to use only the .ascx. This leads to a lot duplication though, as every user control needs it's own .aspx page.

I was wondering if there is any better way of doing this? Could for example the master.page hide everything that's not from the .aspx page if it was called with parameter ?ajax=true?

A: 

What about making an ActionMethod that changes what it renders depending on the type of http request it gets? So if it is an ajax request it would render the ascx but if it is not, then it can render the whole view (or redirect to another action that renders the whole view)?

something like

public ActionResult Section1()
{
    if (Request.IsAjaxRequest())
    {
        return PartialView("section1.ascx");
    }

    return View("section.aspx");
}

and i guess section.aspx coud have inside a RenderPartial(section1.ascx) (so you dont do the page twice).

Francisco Noriega
This is exactly the solution I was describing above, as I said I find it less than perfect because I have to have an .aspx page for each .ascx page.
devzero
A: 

Here is an example of the method I use with great success:

In the View:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<IEnumerable<Namespace.Stuff>>" %>

<asp:Content ID="Content3" ContentPlaceHolderID="head" runat="server">
    <script type="text/javascript">
     $(document).ready(function(){
        $("#optionsForm").submit(function() {
            $("#loading").dialog('open');
            $.ajax({
                type: $("#optionsForm").attr("method"),
                url: $("#optionsForm").attr("action"),
                data: $("#optionsForm").serialize(),
                success: function(data, textStatus, XMLHttpRequest) {
                    $("#reports").html(data); //replace the reports html.
                    $("#loading").dialog('close'); //hide loading dialog.
                },
                error: function(XMLHttpRequest, textStatus, errorThrown) {
                    $("#loading").dialog('close'); //hide loading dialog.
                    alert("Yikers! The AJAX form post didn't quite go as planned...");
                }
            });
            return false; //prevent default form action
        });
    });
    </script>
</asp:Content>

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">

    <div id="someContent">
        <% using (Html.BeginForm("Index", "Reports", FormMethod.Post, new{ id = "optionsForm" }))
          { %>

          <fieldset class="fieldSet">
            <legend>Date Range</legend>
            From: <input type="text" id="startDate" name="startDate" value="<%=ViewData["StartDate"] %>" />
            To: <input type="text" id="endDate" name="endDate" value="<%=ViewData["EndDate"] %>" />
            <input type="submit" value="submit" />
          </fieldset>

        <%} %>
    </div>

    <div id="reports">
        <%Html.RenderPartial("ajaxStuff", ViewData.Model); %>
    </div>

    <div id="loading" title="Loading..." ></div>
</asp:Content>

In the Controller:

public ActionResult Index(string startDate, string endDate)
{
    var returnData = DoSomeStuff();

    if (Request.IsAjaxRequest()) return View("ajaxStuff", returnData);
    return View(returnData);
}
Bradley Mountford
bangoker allready answered in the exact same way, using a lot less code, and you still have the problem that you have to have a separate .aspx page for each view.
devzero
The solution I gave you has one aspx with one ascx which is rendered on initial load and which receives the data directly on postback. It can easily be extended to one aspx with multiple ascx controls that are dynamically loaded if Request.IsAjaxRequest by making the name of the ascx a form field that is sent in the ajax request. We use this method with great success. Sorry about the amount of code...just wanted to give you an end-to-end example.
Bradley Mountford
+1  A: 

We've solved this by using a baseController class that all controllers inherit from, and using an override for OnActionExecuted:

    /// <summary>
    /// Changes the masterpage to a slim version in AjaxRequest
    /// </summary>
    /// <param name="filterContext"></param>
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var action = filterContext.Result as ViewResult;
        if (action != null && Request.IsAjaxRequest())
        {
            action.MasterName = "Ajax";
        }
        base.OnActionExecuted(filterContext);
    }

The "Ajax" master page is a then a simple masterpage with only 1 contentPlaceHolder. This works fine as long as all aspx pages that can be called with ajax only uses this placeholder.

devzero