문제

I understand how to use Partial Views, and I understand Ajax.ActionLink and Ajax.BeginForm when it comes to how to set those up in the view. I'm assuming each partial view has it's own controller. I'm thinking bounded context here, as in each partial view could talk to it's own bounded context via its own controller

I guess the piece I'm missing is:

  1. how to have partial views included in a "master view" (or holding view) and have each of these partial views independently post to a separate controller action, and then return to refresh the partial view WITHOUT loading the "master view" or holding view.
  2. the "master" view or holding view still needs to have its own controller, I want to keep the master controller from reloading its view, and let the view that is produced by an action method of the master controller hold a reference to these two partial views.

There are two approaches it seems I can take, one is to use the "Ajax." functionality of MVC3, the other is to use straight-up jQuery and handle all this interaction by hand from the client side.

Is what I'm trying to do possible both ways, or is one way "better suited" to this type of composite ui construction?

So far, the only things I have seen are trivial examples of composite ui construction like a link via an Ajax.ActionLink that refreshes a single on the page, or a form written as an Ajax.BeginForm that repopulates a div with some content from a partial view.

도움이 되었습니까?

해결책

Okay, so I finally have some working code that I think is the right way to do it. Here is what I went with. I have a two simple "entities"; Customer and BillingCustomer. They're really meant to be in separate "bounded contexts", and the classes are super-simple for demostration purposes.

public class Customer
{ 
    public Guid CustomerId { get; set; }
    public string Name { get; set; }
}

public class BillingCustomer
{
    public Guid CustomerId { get; set; }
    public bool IsOverdueForPayment { get; set; }
}

Note that both classes reference CustomerId, which for the sake of this demo, is a GUID.

I started with a simple HomeController that builds a ViewModel that will be utilized by the Index.cshtml file:

public ActionResult Index()
{
    var customer = new Customer {
        CustomerId = Guid.Empty, 
        Name = "Mike McCarthy" };

    var billingCustomer = new BillingCustomer { 
        CustomerId = Guid.Empty, 
        IsOverdueForPayment = true };

    var compositeViewModel = new CompositeViewModel {
        Customer = customer, 
        BillingCustomer = billingCustomer };

    return View(compositeViewModel);
}

The CompositeViewModel class is just a dumb DTO with a property for each domain entity, since the partial views I'll be calling into in my Index.cshtml file each need to pass their respective domain model into the partial view:

public class CompositeViewModel
{
    public BillingCustomer BillingCustomer { get; set; }
    public Customer Customer { get; set; }
}

Here is my resulting Index.cshtml file that uses the Index method on the HomeController

@model CompositeViews.ViewModels.CompositeViewModel

<h2>Index - @DateTime.Now.ToString()</h2>

<div id="customerDiv">
    @{Html.RenderPartial("_Customer", Model.Customer);}
</div>

<p></p>

<div id="billingCustomerDiv">
    @Html.Partial("_BillingCustomer", Model.BillingCustomer)
</div>

A couple things to note here:

  1. the View is using the CompositeViews.ViewModels.CompositeViewModel ViewModel
  2. Html.RenderPartial is used to render the partial view for each entity, and passes in the appropriate entity. Careful with the syntax here for the Html.Partial call!

So, here is the _Customer partial view:

@model CompositeViews.Models.Customer

@using (Ajax.BeginForm("Edit", "Customer", new AjaxOptions { 
    HttpMethod = "POST", 
    InsertionMode = InsertionMode.Replace, 
    UpdateTargetId = "customerDiv" }))
{
    <fieldset>
        <legend>Customer</legend>

        @Html.HiddenFor(model => model.CustomerId)

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <p>
            <input type="submit" value="Save" />
        </p>
    </fieldset>
}

the important part here is the Ajax.BeginForm call. Note that it's explicitly calling the Edit ActionMethod of the CustomerController. Also note that the UpdateTargetId is set to "customerDiv". This div is NOT in the partial view, but rather in the "parent" view, Index.cshtml.

Below is the _BillingCustomer view

@model CompositeViews.Models.BillingCustomer

@using (Ajax.BeginForm("Edit", "BillingCustomer", new AjaxOptions { 
    HttpMethod = "POST", 
    InsertionMode = InsertionMode.Replace, 
    UpdateTargetId = "billingCustomerDiv" }))
{
<fieldset>
    <legend>BillingCustomer</legend>

    @Html.HiddenFor(model => model.CustomerId)

    <div class="editor-label">
        @Html.LabelFor(model => model.IsOverdueForPayment)
    </div>
    <div class="editor-field">
        @Html.EditorFor(model => model.IsOverdueForPayment)
        @Html.ValidationMessageFor(model => model.IsOverdueForPayment)
    </div>

    <p>
        <input type="submit" value="Save" />
    </p>
</fieldset>
}

Again, note that UpdateTargetId is set to billingCustomerDiv. This div is located in the Index.cshtml file, not this partial view file.

So, the only thing we haven't looked at yet is the Edit ActionResult on the CustomerController and the BillingCustomerController. Here is the CustomerController

public class CustomerController : Controller
{
    [HttpGet]
    public PartialViewResult Edit(Guid customerId)
    {
        var model = new Customer {
            CustomerId = Guid.Empty, 
            Name = "Mike McCarthy"};

        return PartialView("_Customer", model);
    }

    [HttpPost]
    public ActionResult Edit(Customer customer)
    {
        return PartialView("_Customer", customer);
    }
}

There is nothing really "happening" in this controller, as the post deals directly with building a composite UI. Notice how we're returning via "PartialView" and specifying the name of the partial view to use, and the required model the view needs to render.

Here is BillingCustomerController

public class BillingCustomerController : Controller
{
    [HttpGet]
    public PartialViewResult Edit(Guid customerId)
    {
        var model = new BillingCustomer {
            CustomerId = Guid.Empty, 
            IsOverdueForPayment = true };

        return PartialView("_BillingCustomer", model);
    }

    [HttpPost]
    public PartialViewResult Edit(BillingCustomer billingCustomer)
    {
        return PartialView("_BillingCustomer", billingCustomer);
    }
}

Again, the same as CustomerController, except for the fact that it's this controller is dealing with the BillingCustomer entity.

Now when I load up my HomeController's Index ActionResult, I get a screen that looks like this:

CompositeUI

Each Save button will do an async postback to the controller the partial view needs to update and talk to in order to get data, all without causing a regular postback for the whole page. You can see the DateTime stamp does NOT change when hitting either save button.

So, that's how I went about building my first composite view using partial views. Since I'm still very new to MVC3, I could still be screwing something up, or doing something in a way that is harder than it needs to be, but this is how I got it working.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top