I use ASP.NET MVC with jQuery and have a lot of Ajax requests to my controllers.
Use Partial Views (usercontrols) to build the intial view when a page is loaded. The
You can do this way as well, Put this Inside your controller.
protected string RenderPartialViewToString(string viewName, object model)
{
if (string.IsNullOrEmpty(viewName))
viewName = ControllerContext.RouteData.GetRequiredString("action");
ViewData.Model = model;
using (StringWriter sw = new StringWriter()) {
ViewEngineResult viewResult = ViewEngines.Engines.FindPartialView(ControllerContext, viewName);
ViewContext viewContext = new ViewContext(ControllerContext, viewResult.View, ViewData, TempData, sw);
viewResult.View.Render(viewContext, sw);
return sw.GetStringBuilder().ToString();
}
}
Based on this stackoverflow anwser I have just set out to do the same thing.
First create an extension method for the controller class.
public static string RenderViewToString(this Controller controller, string viewName, object model)
{
using (var writer = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(controller.ControllerContext, viewName);
controller.ViewData.Model = model;
var viewCxt = new ViewContext(controller.ControllerContext, viewResult.View, controller.ViewData, controller.TempData, writer);
viewCxt.View.Render(viewCxt, writer);
return writer.ToString();
}
}
Then return the json in the controllers action method.
return Json(new {
Html = this.RenderViewToString("MyView", model),
SomeExtraData = data
});
Your ajax requests will now receive json with the html contained in it. Still experimenting with this approach over returning plain Html fragments.
Hope that helps.
EDIT Updated to work with razor
Here is a way to take advantage of the Content-Type
returned by each respective result. We are using this to send back error information, and there's already JS in place to display messages, so we get either the partial view we want, or control information for error reporting etc.
For reference, a partial view returns text/html
and a JSON response should return application/json
.
As usual, the fun part is on the javascript side, and the JQuery ajax()
does not disappoint here!
In your controller, just return either PartialView()
or Json(model,)
as appropriate; we are using it in try/catch
format.
public ActionResult Edit(int id) {
try {
var model = getYourModel();
return PartialView("Edit", model);
}
catch (Exception ex) {
var mi = new MessageInfo(MessageType.Error, String.Format("Edit failed: {0}", ex.Message), true);
return Json(mi, "application/json", JsonRequestBehavior.AllowGet);
}
}
On the JS side, we are using the following function. Note that you need to re-establish any JQuery events you hooked in $(document).ready()
from the initial page-level GET, so we have a callback parameter.
function getPartialView(action, controller, model, divId, callback) {
var url = "/" + controller + "/" + action + "/";
$.ajax({
type: "GET",
url: url,
data: model,
success: function (data, textStatus, jqXHR) {
var ct = jqXHR.getResponseHeader("Content-Type");
var mx = ct.match("text\/html");
if (mx != null) {
$(divId).html(data);
if (callback) {
callback($(divId));
}
}
else {
addMessage(data.type, data.title, data.text, data.sticky);
}
},
error: function (jqXHR, textStatus, errorThrown) {
addMessage(3, "\"" + url + "\": Failed", textStatus + ": " + errorThrown, false);
}
});
}
The only tricky bit is checking the Content-Type
header in the response, and behaving accordingly. Please note we are "cheating" by assuming JSON if it wasn't HTML. We are calling our pre-existing addMessage()
function, do whatever you need!
And finally, here is a sample Anchor element with onclick
targeting getPartialView()
above.
<a href="#" onclick="getPartialView('Action', 'Controller', model, '#partialviewdivid', function(dvx) { connectJqueryEvents(dvx); })">Cancel</a>
Works Great...
Except for form submits via Ajax.BeginForm()
where the JSON payload is treated mistakenly as HTML due to insufficient validation of Content-Type
. The result is your div
gets some JSON added to it, which basically does not render as HTML. The AjaxOptions.OnSuccess
callback does execute, but it's too late for your DOM at that point!
There is a simple solution, but unfortunately it requires a small repair to jquery-unobtrusive-ajax.js
because the asyncOnSuccess()
function was short-sighted as written.
function asyncOnSuccess(element, data, contentType) {
var mode;
if (contentType.indexOf("application/x-javascript") !== -1) {
return;
}
if (contentType.indexOf("application/json") !== -1) {
return;
}
...snip...
}
In the OOTB version, the second if
statement is missing; adding it is the fix necessary to keep it from slamming your JSON payload into the DOM.
With this fix in place, the JSON payload passes into your AjaxOptions.OnSuccess
Javascript, and you can proceed as necessary.
Yes You Can Get Both
Hopefully you know since you are sending Json, you could send back any kind of model, and let the Javascript sort it out; hasOwnProperty()
comes in handy there. So obviously you can send back some view HTML via already-mentioned RenderViewToString()
.
I believe you could return the rendered html as a string - this could alternately be an html string containing an error message to display?