3

I am creating an MVC mini app within an Umbraco web page. I've asked questions in the Umbraco user forums but so far have not gotten a solution.

My Umbraco 6.1.6 site has a webForms MasterPage(s). On my content page is a rich text editor in which I place a macro. That macro loads a Macro Partial View which looks like this:

<p><span style="color: red;">@TempData["CustomMessage"]</span></p>

@Html.Partial("~/Views/MacroPartials/Admin/Ethics/_Opinions.cshtml")
<br />
<input type="button" value="Add a New Opinion" class="btn btn-default" onclick="OpenCreatePopup()" />

<div id="opinionEditForm"></div>

The @Html.Partial loads a jQuery grid of records. Each has an edit button that triggers the following javascript function:

function editOpinion(id) {
    var formDiv = $("#opinionEditForm");
    formDiv.html();
    formDiv.load("/umbraco/surface/OpinionsSurface/editOpinion/" + id, function () {
        $("form").removeData("validator");
        $("form").removeData("unobtrusiveValidation");
        $.validator.unobtrusive.parse("form");
        bootstrapValidatorFix();
        formDiv.dialog({
            modal: true,
            width: 830,
            height: 800,
            title: "Edit Ethics Opinion",
            resizable: false
        });
    });
}

That function hits a SurfaceController:

    // GET: OpinionsSusrface/editOpinion/5
    [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
    public ActionResult editOpinion(int id)
    {
        Table<Opinion> opinions = db.GetTable<Opinion>();

        var opinion = (from o in opinions
                      where o.opinionID == id
                      select new { opinionID = o.opinionID, opinionNumber = o.opinionNumber, title = o.title, opinionDate = o.opinionDate, summary = o.summary, bodyText = o.bodyText }).FirstOrDefault();

        OpinionViewModel ovm = new OpinionViewModel();
        ovm.opinionID = opinion.opinionID;
        ovm.opinionNumber = opinion.opinionNumber;
        ovm.title = opinion.title;
        ovm.opinionDate = opinion.opinionDate.ToString("MM/yyyy");
        ovm.summary = opinion.summary;
        ovm.bodyText = opinion.bodyText;

        ViewBag.Action = "Edit";
        return PartialView("/Views/MacroPartials/Admin/Ethics/_OpinionEdit.cshtml", ovm);
    }

which creates an edit form and the previous javascript injects that form into the afore mentioned div and displays that div as a modal dialog. I can make changes to the form, hit save, the form posts back to the following controller:

    // POST: OpinionsSurface/Edit/5
    [HttpPost]
    [NotChildAction]
    [ValidateAntiForgeryToken]
    [OutputCache(NoStore = true, Duration = 0, VaryByParam = "*")]
    public ActionResult editOpinion(OpinionViewModel model)
    {
        if (!ModelState.IsValid)
        {
            return CurrentUmbracoPage();
        }

        Table<Opinion> opinions = db.GetTable<Opinion>();

        Opinion opinion = opinions.Where(u => u.opinionID == model.opinionID).SingleOrDefault();

            opinion.opinionNumber = model.opinionNumber;
            opinion.title = model.title;
            opinion.summary = HttpUtility.HtmlDecode(model.summary);
            opinion.bodyText = HttpUtility.HtmlDecode(model.bodyText);
            opinion.opinionDate = DateTime.Parse(model.opinionDate);
            opinion.link = model.opinionNumber + ".pdf";

        try
        {
        db.SubmitChanges();
        }
        catch (Exception ex)
        {
        }
        TempData["CustomMessage"] = "Your form was successfully submitted at " + DateTime.Now;
        return RedirectToUmbracoPage(2097);
    }

and the record is updated, the page refreshes and my jQuery grid shows the updated values.

What never happens is the success message, passed into TempData is never displayed on the first partial view.

What appears to be an odd side effect, if I put

<p><span style="color: red;">@TempData["CustomMessage"]</span></p>

on the partial view the form is in, I get the TempData displayed the next time I click to edit a record! This proves to me that TempData still has my value, waiting to be used. So why will it only display on the partialview that is a child of my main partialview? Why won't it display in any location outside of the lowest nested partialview?

Connie DeCinko
  • 191
  • 1
  • 13

1 Answers1

1

I had a similar issue recently with a partial view macro that I was using to insert a form into a rich text editor. On submitting the form I found that I couldn't access TempData in my partial view and server side validation errors weren't working properly. However, I could access this information in the Index action method for the page and in the parent view so it seems that the macro partial is within a different controller context.

To get around the issue I added a pair of action filter attributes, one to store the ModelState and TempData in the session:

public class ExportModelStateToSessionAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        if (!filterContext.Controller.ViewData.ModelState.IsValid)
        {
            HttpContext.Current.Session["ModelState"] = filterContext.Controller.ViewData.ModelState;
        }

        HttpContext.Current.Session["TempData"] = filterContext.Controller.TempData;
        base.OnActionExecuted(filterContext);
    }
}

and another to retrieve the data for use in the macro partial:

public class ImportModelStateFromSessionAttribute : ActionFilterAttribute
{
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var modelState = HttpContext.Current.Session["ModelState"] as ModelStateDictionary;
        var tempData = HttpContext.Current.Session["TempData"] as TempDataDictionary;

        HttpContext.Current.Session.Remove("ModelState");
        HttpContext.Current.Session.Remove("TempData");
        if (modelState != null)
        {
            filterContext.Controller.ViewData.ModelState.Merge(modelState);
        }

        if (tempData != null)
        {
            foreach (var item in tempData)
            {
                filterContext.Controller.TempData[item.Key] = item.Value;
            }
        }

        base.OnActionExecuted(filterContext);
    }
}

In your case you'd then need to add the [ImportModelStateFromSession] attribute to your GET method and the [ExportModelStateToSession] attribute to your POST method.

Ideally you wouldn't have to store this information in the session but this is the only working solution I've come across so far.

Rob Purcell
  • 1,285
  • 1
  • 9
  • 19