locked
ASP.NET MVC 5 EditorTemplate Not Displayed After Postback RRS feed

  • Question

  • User-2095166486 posted

    I have a database that contains the following tables: DIM_Invoice, DIM_Carton, DIM_InvoiceItem. It's a hierarchical structure – An Invoice contains one or more Cartons. A Carton contains one or more InvoiceItems.

    Eventually I need to create a hierarchical view, which will display the Invoice information, the Cartons related to the Invoice, and the InvoiceItems related to each Carton. There are a couple fields at each level that should be editable.

    For now, however, I’m just trying to get the first two levels to work: An Invoice has multiple Cartons associated with it.

    The following are my models which were generated by Entity Framework:

    namespace Invoices2.Models
    {
        using System;
        using System.Collections.Generic;
    
        public partial class DIM_Invoice
        {
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
            public DIM_Invoice()
            {
                this.DIM_Carton = new HashSet<DIM_Carton>();
            }
    
            public int LPK_Invoice { get; set; }
            public string InvoiceNumber { get; set; }
            public string PONumber { get; set; }
            public Nullable<System.DateTime> InvoiceDate { get; set; }
            public Nullable<System.DateTime> OrderDate { get; set; }
            public Nullable<System.DateTime> ShipDate { get; set; }
            public string Distributor { get; set; }
            public Nullable<bool> AllReceived { get; set; }
            public Nullable<int> RowVersion { get; set; }
            public string Comments { get; set; }
    
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
            public virtual ICollection<DIM_Carton> DIM_Carton { get; set; }
        }
    }
    
    namespace Invoices2.Models
    {
        using System;
        using System.Collections.Generic;
    
        public partial class DIM_Carton
        {
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
            public DIM_Carton()
            {
                this.DIM_InvoiceItem = new HashSet<DIM_InvoiceItem>();
            }
    
            public int LPK_Carton { get; set; }
            public string CartonNumber { get; set; }
            public Nullable<int> LPK_Invoice { get; set; }
            public string InvoiceNumber { get; set; }
            public Nullable<int> RowVersion { get; set; }
            public string Comments { get; set; }
    
            public virtual DIM_Invoice DIM_Invoice { get; set; }
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
            public virtual ICollection<DIM_InvoiceItem> DIM_InvoiceItem { get; set; }
        }
    }

    The following are the Edit sections for DIM_InvoiceController. Again they were auto-generated by Visual Studio, except that I've edited the Post section so that it does not redirect to the Index view.

    // GET: DIM_Invoice/Edit/5
    public ActionResult Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        DIM_Invoice dIM_Invoice = db.DIM_Invoice.Find(id);
        if (dIM_Invoice == null)
        {
            return HttpNotFound();
        }
        return View(dIM_Invoice);
    }
    
    // POST: DIM_Invoice/Edit/5
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "LPK_Invoice,InvoiceNumber,PONumber,InvoiceDate,OrderDate,ShipDate,Distributor,AllReceived,RowVersion,Comments")] DIM_Invoice dIM_Invoice)
    {
        if (ModelState.IsValid)
        {
            db.Entry(dIM_Invoice).State = EntityState.Modified;
            db.SaveChanges();
            return View(dIM_Invoice);
            //return RedirectToAction("Index");
        }
        return View(dIM_Invoice);
    }

    I've moved a copy of the automatically generated Edit view for DIM_Carton into the Shared/EditorTemplates folder so that it can act as an EditorTemplate, and then simplified it a bit and removed some of the fields because I only need to display the CartonNumber field:

    @model Invoices2.Models.DIM_Carton
    
        <div class="form-horizontal">
            <h4>DIM_Carton</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            @Html.HiddenFor(model => model.LPK_Carton)
    
            <div class="form-group">
                @Html.LabelFor(model => model.CartonNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.CartonNumber, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.CartonNumber, "", new { @class = "text-danger" })
                </div>
            </div>
    
        </div>

    Finally, here is the Edit view for DIM_Invoice, which is trying to utilize the DIM_Carton EditorTemplate by calling @Html.EditorFor(model => model.DIM_Carton)

    @model Invoices2.Models.DIM_Invoice
    
    @{
        ViewBag.Title = "Edit";
    }
    
    <h2>Edit</h2>
    
    
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()
    
        <div class="form-horizontal">
            <h4>DIM_Invoice</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            @Html.HiddenFor(model => model.LPK_Invoice)
    
            <div class="form-group">
                @Html.LabelFor(model => model.InvoiceNumber, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.InvoiceNumber, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.InvoiceNumber, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.PONumber, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.PONumber, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.PONumber, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.InvoiceDate, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.InvoiceDate, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.InvoiceDate, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.OrderDate, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.OrderDate, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.OrderDate, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.ShipDate, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.ShipDate, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.ShipDate, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.Distributor, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Distributor, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Distributor, "", new { @class = "text-danger" })
                </div>
            </div>
    
            <div class="form-group">
                @Html.LabelFor(model => model.AllReceived, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    <div class="checkbox">
                        @Html.EditorFor(model => model.AllReceived)
                        @Html.ValidationMessageFor(model => model.AllReceived, "", new { @class = "text-danger" })
                    </div>
                </div>
            </div>
    
    // ***Here's the call to the EditorTemplate*** 
            <div class="form-group">
                <div class="col-md-10">
                    @Html.EditorFor(model => model.DIM_Carton)
                </div>
            </div>
    
            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Save" class="btn btn-default" />
                </div>
            </div>
        </div>
    }
    
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }

    OK, I'm new to MVC so bear with me please... here is the specific problem I'm trying to solve:

    At first I tried to do with utilizing a partial view where the DIM_Invoice Edit view would look at the DIM_Carton Edit view as a partial view. However, whenever a edit was done to DIM_Invoice in the browser and posted back, the information displayed in the partial view would not be retained (i.e. it just "disappeared", for lack of a better description, from the page).

    After googling I found a post here on stackoverflow which said I should use EditorTemplates. The post gave a resource for EditorTemplates and I also found another resource on simple-talk which was helpful.

    However, after trying to use this method, I still have the same problem... the data in the EditorTemplate is not retained after post-back from the main view.

    After googling some more, I didn't find any further information that would help.

    I did notice that the simple-talk example uses a div class called "editor-field", but IntelliSense is not giving me "editor-field" as an option... so maybe there's a library I'm missing or something?

    So... how do I get the EditorTemplate to persist after a post-back from the main view?

    Thanks very much in advance to anyone who can help.

    Monday, January 4, 2016 4:43 PM

Answers

  • User-2095166486 posted

    I've come up with at least a temporary solution to this problem, so I'll tell here what I have done for now in order to get it to work.  However, I think this is kind of a hack and I should be doing it better somehow.  So, if anyone has any information I'm still listening.

    So basically I created sort of a loop that will force the page to refresh after the postback.  I changed my controller for the Edit action method so that when the HttpPost is successful it redirects to another controller action method called RefreshInvoiceData:

            // POST: DIM_Invoice/Edit/5
            // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
            // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
            [HttpPost]
            [ValidateAntiForgeryToken]
            public ActionResult Edit([Bind(Include = "LPK_Invoice,InvoiceNumber,PONumber,InvoiceDate,OrderDate,ShipDate,Distributor,AllReceived,RowVersion,Comments")] DIM_Invoice dIM_Invoice)
            {
                if (ModelState.IsValid)
                {
                    db.Entry(dIM_Invoice).State = EntityState.Modified;
                    db.SaveChanges();
                    //return View(dIM_Invoice);
                    //return RedirectToAction("Index");
                    return RedirectToAction("RefreshInvoiceData", new { invoiceId = dIM_Invoice.LPK_Invoice.ToString() });
                }
                return View(dIM_Invoice);
            }

    The RefreshInvoiceData action method then redirects back to the Edit action method and passes it the ID for the current Invoice.  This kinda tricks the entire view into refreshing.

    // GET: DIM_Invoice
            public ActionResult RefreshInvoiceData(int? invoiceId)
            {
                if (invoiceId == null)
                {
                    return Content("the invoiceId is null");
                    //return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }
                return RedirectToAction("Edit", new { id = invoiceId });
            }

    If anyone has a better answer I'd love to hear it.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 5, 2016 8:19 PM

All replies

  • User-986267747 posted

    Hi groovepriest,

    An Invoice has multiple Cartons associated with it.

    <div class="form-group"> <div class="col-md-10"> @Html.EditorFor(model => model.DIM_Carton) </div> </div>

    public virtual ICollection<DIM_Carton> DIM_Carton { get; set; }

    @model Invoices2.Models.DIM_Carton

    According to your description, the DIM_Invoice class has multiple Cartons associated with it, but in your view and your code,  the DIM_Invoice class only has one Carton associated with it.

    In my experience, For the list of DIM_Carton, the MVC framework doesn't render complex properties unless we tell it how to render those. We have to create display templates for DisplayFor and editor template for EditorFor helpers.  So you should modify your editor template and define the model is a list of DIM_Carton,  like this:

    @model ICollection<Invoices2.Models.DIM_Carton>

    Besides, if you'd like to render your editor template in your view, you should use the following code.

        // ***Here's the call to the EditorTemplate***
            <div class="form-group">
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Cus,"YourEditTemplate")
                </div>
            </div>

    I hope it's helpful to you.

    Best Regards,

    Klein zhang

    Tuesday, January 5, 2016 3:15 AM
  • User-2095166486 posted

    Hi Klein Zhang--

    I have this question posted on 3 websites and yours is the only attempt at a response that I've received, so I appreciate your help very much.

    However, I don't think this is the issue.  To clarify... the EditorTemplate works fine when the page first loads, and it automatically iterates and shows all of the cartons for a given invoice.  The problem occurs when a change to the Invoice is posted back... the carton data is no longer displayed at that point.  I need to find a way to refresh the EditorTemplate or make it retain the previous data or something like that.

    Tuesday, January 5, 2016 4:11 PM
  • User-2095166486 posted

    I've come up with at least a temporary solution to this problem, so I'll tell here what I have done for now in order to get it to work.  However, I think this is kind of a hack and I should be doing it better somehow.  So, if anyone has any information I'm still listening.

    So basically I created sort of a loop that will force the page to refresh after the postback.  I changed my controller for the Edit action method so that when the HttpPost is successful it redirects to another controller action method called RefreshInvoiceData:

            // POST: DIM_Invoice/Edit/5
            // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
            // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
            [HttpPost]
            [ValidateAntiForgeryToken]
            public ActionResult Edit([Bind(Include = "LPK_Invoice,InvoiceNumber,PONumber,InvoiceDate,OrderDate,ShipDate,Distributor,AllReceived,RowVersion,Comments")] DIM_Invoice dIM_Invoice)
            {
                if (ModelState.IsValid)
                {
                    db.Entry(dIM_Invoice).State = EntityState.Modified;
                    db.SaveChanges();
                    //return View(dIM_Invoice);
                    //return RedirectToAction("Index");
                    return RedirectToAction("RefreshInvoiceData", new { invoiceId = dIM_Invoice.LPK_Invoice.ToString() });
                }
                return View(dIM_Invoice);
            }

    The RefreshInvoiceData action method then redirects back to the Edit action method and passes it the ID for the current Invoice.  This kinda tricks the entire view into refreshing.

    // GET: DIM_Invoice
            public ActionResult RefreshInvoiceData(int? invoiceId)
            {
                if (invoiceId == null)
                {
                    return Content("the invoiceId is null");
                    //return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
                }
                return RedirectToAction("Edit", new { id = invoiceId });
            }

    If anyone has a better answer I'd love to hear it.

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, January 5, 2016 8:19 PM