1

I've been using a method from a popular SO post found here: How to render an ASP.NET MVC view as a string?

Instead of using the method as an extension method, I placed it on a BaseController, and then all of my controllers inherit the base. Here is my implementation:

public string RenderRazorViewToString(string viewName, object model = null)
{
    ViewData.Model = model;
    using (var sw = new StringWriter())
    {
        var viewResult = ViewEngines.Engines.FindPartialView(ControllerContext,
                         viewName);
        var viewContext = new ViewContext(ControllerContext, viewResult.View,
                          ViewData, TempData, sw);
        viewResult.View.Render(viewContext, sw);
        viewResult.ViewEngine.ReleaseView(ControllerContext, viewResult.View);
        return sw.GetStringBuilder().ToString();
    }
}

And it has worked perfectly (I think/hope) until Yesterday. I have a javascript function that makes an AJAX call to a controller, and provides the parameter "firstName". The controller accepts it, creates a new viewmodel, sets the FirstName property, and then renders the modal (bootstrap3) partial view. The modal uses @Html.EditorFor to render the FirstName property of the ViewModel. However, instead of the text displayed being what is set on the controller - the value is the parameter passed by Javascript. In essence, when the parameter of the AJAX GET request matches the property name in a ViewModel (case in-sensitive), the parameter supplied takes precedence. Here's the code I've been testing with:

function testModelBindingTempData() {
    var $modalContainer = $('#modalContainer');
    $.ajax({
        url: "@Url.Action("GetTestModal")",
        method: "GET",
        data: {
            firstName: "Michael is the bees knees."
        },
        success: function(data) {
            $modalContainer.html(data).find('.modal').modal('show');
        },
        error: function(xhr, status, err) {
            parseJsonHeaders(xhr, { notificationLocation: '.notification-section' });
        }
     });
}
public JsonResult GetTestModal(string firstName)
{
    TestModalViewModel vm = new TestModalViewModel();
    vm.FirstName = "Michael is okay";
    //just render and hope the best
    return Json(RenderRazorViewToString("~/Views/Ajax/_TestModal.cshtml", vm));
}
@model Sample.Web.ViewModels.Ajax.TestModalViewModel

<div class="modal modal-primary" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header"><span>Primary Simple Modal</span></div>
            <div class="modal-body">
                Used to inform the user of important stuff!
                @Html.EditorFor(m => m.FirstName)
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>

The result: modal displays value supplied from the AJAX call instead of value set in the ViewModel

I discovered the problem value is in the ViewData.ModelState key-value pair, but I have no idea why THAT value is being set instead of the value set in the controller and the expression in the the HTML helper.

My expectation is that when you set a value in the controller, it should take precedence over a model state value, or any other data available.

Does anyone know why this is happening? Any advice is appreciated, thanks!

/edit: After additional testing, this appears to happen with regular ActionResults/Views too. :/

Seth
  • 31
  • 7
  • It appears to be the default behavior from here: https://stackoverflow.com/questions/26654862/textboxfor-displaying-initial-value-not-the-value-updated-from-code/26664111#26664111 And that makes sense if the same ViewModel/View were being returned due to validation, but with unobtrusive validation - where the method doesn't even attempt to execute - I would argue the opposite behavior is what should be default. – Seth Oct 15 '20 at 15:37

1 Answers1

0

It is expected since you are invoking JSONResult in your:

public JsonResult GetTestModal(string firstName)

What does this mean? ASP.NET MVC will return a response using JSON format. If you want to pass your ViewData data, include it on your JSONResult as property of your anonymous object so that you can populate it on your AJAX success handler:

Json(new { MyViewData = firstName } , JsonRequestBehavior.AllowGet);

Then on your AJAX success handler, you can pickup the property MyViewData to populate on your HTML Tags:

success: function(data) {
        $modalContainer.html(data.MyViewData).find('.modal').modal('show');
    },

If you want to use ViewBag, don't use JSONResult, use ViewResult instead:

return View("ViewName", ModelName);
Willy David Jr
  • 8,604
  • 6
  • 46
  • 57
  • I don't want the view data object in the javascript, I want the view to render using the values from the ViewModel instead of from ModelState – Seth Oct 15 '20 at 15:42
  • As I've said, you cannot do that if you use JSONResult. – Willy David Jr Oct 15 '20 at 15:43
  • So the only way to do it is manual updating each field with javascript? – Seth Oct 15 '20 at 15:45
  • That is correct. That is, if you are using JSONResult. From the "data" variable that you are using on your AJAX success handler. You can populate and add additional property, on my example, I am only using one property. – Willy David Jr Oct 15 '20 at 15:46