2
votes

I have a action method in view @Html.Action("office_holders_Test") and in controller class i have method below,

I am trying to loop through the items in foreach loop each office holder and add to the office holder view, but in my code after looping first item this function return me only first officeholder in officeholder partial view

public ActionResult  office_holders_Test()
{
    int Application_id = Convert.ToInt32(TempData["Application_id"]);
    if (Application_id != 0)
    {
        var office_holders = Applicant_Service.GetOfficeHolderBy_Application_Id(Application_id);
        DisplayDropdownList();
        foreach (var OfficeHolder in office_holders)
        {
            return PartialView("../Applicant/OfficeHolderEntryEditor", OfficeHolder);
        }
        else
        {
            return PartialView("../Applicant/OfficeHolderEntryEditor", new tr_office_holders());
        }
        return PartialView("../Applicant/OfficeHolderEntryEditor")           
    }
}
3
There is no point returning in an loop right? - Shiham
You exit the function as soon as you use return PartialView(...); You method can only return 1 view - you need to pass the collection to the view and use a foreach loop in the view to generate the html for each item - user3559349
I'd suggest to create another partial view, which should accept the whole model (i.e., collection of "OfficeHolder" type). Within that partial view, loop through each item of the collection, and call your "OfficeHolderEntryEditor" partial view. The controller action should invoke the newly created partial view and should simply pass "office_holders" collection. - Nirman

3 Answers

0
votes

The best option that I can suggest use is to use EditorTempate.

You should create folder in your View\Shared and name it EditorTemplates.

In this folder create View with the same name as your model class have (ex. tr_office_holders.cshtml).

This View should contain html for one item and starns with:

@model YourApplicationModelNamespace.tr_office_holders

There is one problem - you can't call EditorTemplate for collection out of Contoller that's why I suggest you create extension method:

public static class ContollerExtensions
{
    /// <summary>
    /// Render partial method (Allows to render collections that implement IEnumerable interface)
    /// </summary>
    /// <param name="controller"></param>
    /// <param name="viewName"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    public static string RenderPartial(this Controller controller, string viewName, object model)
    {
        var modelItems = model as IEnumerable<object>;
        if (modelItems == null)
            return controller.RenderPartialViewToString(viewName, model);
        else
        {
            var result = String.Empty;
            foreach (var modelItem in modelItems)
            {
                result += controller.RenderPartialViewToString(viewName, modelItem);
            }
            return result;
        }
    }

    /// <summary>
    /// Internal method that renders view and returning string
    /// </summary>
    /// <param name="controller"></param>
    /// <param name="viewName"></param>
    /// <param name="model"></param>
    /// <returns></returns>
    private static string RenderPartialViewToString(this Controller controller, string viewName, object model)
    {
        if (string.IsNullOrEmpty(viewName))
        {
            viewName = controller.ControllerContext.RouteData.GetRequiredString("action");
        }

        controller.ViewData.Model = model;

        using (var sw = new StringWriter())
        {
            var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
            var viewContext = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, sw);
            viewResult.View.Render(viewContext, sw);

            return sw.GetStringBuilder().ToString();
        }
    }
}

And you can call this in your controller like this:

public ActionResult  office_holders_Test()
{
    int Application_id = Convert.ToInt32(TempData["Application_id"]);
    if (Application_id != 0)
    {
        var office_holders = Applicant_Service.GetOfficeHolderBy_Application_Id(Application_id);
        DisplayDropdownList();
        this.RenderPartial("EditorTemplates/tr_office_holders", office_holders);      
    }
    else
    {
        return this.RenderPartial("EditorTemplates/tr_office_holders", new tr_office_holders());
    }
}

Note that there is no difference now render one element or collection.

0
votes

So it sounds like you want to display the items from a collection within your view, by looping through them? You'd be better off using editor templates rather than partial views. MVC has some nice features within display and editor templates for handling collections, which you can take advantage of. I've written an article on using Display Templates, which you might find useful.

I can't tell whether you have any form inputs within your partials, so can't advise whether to use editor or display templates. The rule of thumb is that if you're displaying read-only markup, use Display templates. If you're providing form controls for user interaction, use Editor Templates. You've called yours an Editor, so we'll go with that.

I would separate the business of displaying the markup from the business of collating the data. So your controller only concerns itself with creating the model and not which template to render. We'll leave that consideration to the view. Add a ViewModel for the page, which you'll add your office holders to. Let's call it OfficeHoldersTestViewModel:

public class OfficeHoldersTestViewModel
{
    public IEnumerable<OfficeHolder> OfficeHolders { get; set; }
}

Your Controller action now only needs to create one of these and populate it with the office holders:

public ActionResult office_holders_Test()
{
    int Application_id = Convert.ToInt32(TempData["Application_id"]);
    var model = new OfficeHoldersTestViewModel { OfficeHolders = new List<OfficeHolder>() };
    if (Application_id != 0)
    {
        var office_holders = Applicant_Service.GetOfficeHolderBy_Application_Id(Application_id);
        model.OfficeHolders = office_holders;
    }

    return View(model);
}

Next, create an Editor Template in Views/Shared/EditorTemplates called OfficeHolder.cshtml and set the model property to your OfficeHolder class:

@model OfficeHolder
<!-- markup for an Office Holder goes here -->

Finally, within your main view, set the model to the OfficeHoldersTestViewModel and use the EditorFor extension method where you want your office holders to appear:

@model OfficeHoldersTestViewModel
<div class="row">
    <div class="col-xs-12">
        @Html.EditorFor(m => m.OfficeHolders)
    </div>
</div>

The nice thing about this extension method is that it works for collections as well as individual objects. In this case, it will use the template you've created for your single Office Holder and apply it to each item in the collection, rendering them one below the other.

Further reading on using Display Templates, which you might find useful.

0
votes

The reason you see only the first partial view is that your code has a return inside the foreach loop which stops the code execution.

You have to return only one view, you can create another partial view and pass the list of office_holders and inside the view make the for loop:

public ActionResult  office_holders_Test()
{
    int Application_id = Convert.ToInt32(TempData["Application_id"]);
    if (Application_id != 0)
    {
        var office_holders = Applicant_Service.GetOfficeHolderBy_Application_Id(Application_id);
        DisplayDropdownList();

            return PartialView("OfficeHolderEntryList", office_holders)        
     }

     return PartialView("OfficeHolderEntryList")
}

The view: OfficeHolderEntryList.cshtml

    @foreach (var officeHolder in Model)
    {
        Html.RenderPartial("OfficeHolderEntryEditor",officeHolder);
    }