Question

I have feature on my website (some UI and associated functionality) that I want to be able to reuse on multiple pages. For the purposes of this question, let's say it's a "Comments" feature.

There is an area in my application for Components and within the area are a controller: /Controllers/CommentController, and two partial views: /Views/Comment/Index.ascx (for listing comments) and /Views/Comment/Create.ascx (for creating comments).

CommentController looks something like this:

public class CommentController : Controller
{      
  [ChildActionOnly]
  public ActionResult Index()
  {
      return PartialView(GetComments());
  }

  [HttpGet]
  [ChildActionOnly]
  public ActionResult Create()
  {
      return PartialView(); //this is wrong.
  }

  [HttpPost]
  [ChildActionOnly]
  public ActionResult Create(FormCollection formValues)
  {
      SaveComment(formValues);

      return RedirectToAction("Index"); //this is wrong too.
  }
}

Index Partial View:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>

<div>
  <% foreach (var item in Model) { %>    
      <div>
          <%: item.Comment %>
      </div>
  <% } %>

  <%: Html.ActionLink("Add a Comment", "Create", "Comment", new { area = "Components" }, null) %>
</div>

Create Partial View:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<dynamic>" %>
<div>
    <% using (Html.BeginForm())
       {%>
        Enter your comment:
        <div>
            <input type="text" name="comment" />
        </div>
        <p>
            <input type="submit" value="Create" />
            <% //also render a cancel button and redirect to "Index" view  %>
        </p>
    <% } %>
</div>

The Index partial view is included in a view with RenderAction, like so:

<% Html.RenderAction("Index", "Comment", new { area = "Components" }); %>

This code doesn't work because the forms within the partial views submit to actions on the CommentsController that are marked [ChildActionOnly] (this is by design, I don't want the "Components" to be requested independently of a hosting page).

How can I make this "component" approach work, i.e. have a partial view that submits a form to change the state of a component within a page without losing the hosting page itself?

EDIT: To clarify, the use of [ChildActionOnly] is not my problem here. If I remove the attribute from my action methods, my code only "works" in that it doesn't throw an exception. My "component" still breaks out of its hosting page when its form is submitted (because I'm telling the form to submit to the partial view's URL!).

Was it helpful?

Solution

You are making MVC fight itself by asking a form to target an action that is marked as ChildActionOnly.

My solution to this problem when I was designing a highly reusable wizard framework, was to NOT mark the actions as ChildActionOnly but instead to detect if the request was an ajax one or just a plain vanilla request.

The code for all this is packaged into a base controller class. In your derived controllers, you do something like:

[WizardStep(4, "Illness Details")]
public ActionResult IllnessDetails()
{            
    return Navigate();
}

Where the Navigate() method of the base controller has decided whether to return the full view or just the partial view, depending on whether it is, or isn't, an ajax request. That way, you can never return the partial view in isolation.

To ascertain if it is an Ajax request, I used a combination of Request.IsAjaxRequest() and TempData. The TempData is needed because my wizard framework implements the PRG pattern out of the box, so I need to persist the fact that the original post was an ajax one.

I guess this is just one solution and it took a bit of trial and error to get it right. But now I live happily ever after developing wizards like I was JK Rowling...

OTHER TIPS

Use Ajax to post the partial.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top