Well you're in luck because I wrote a TON of code to do something similar. This also takes into account if you want to return the model as a JSON object or a View. It also wraps all the Ajax calls into a wrapper response element
Basically if you have a UI guy doing stuff, you NEVER need to know what he wants. Let him write the views, or make AJAX calls. This completely decouples the UI person from the C# developer (as long as he understands how to write MVC Views, he doesn't need to know at all how the controller works, just the model being passed).
The ControllerBase
class:
public abstract class MyControllerBase : Controller
{
// could be moved to web.config
private const _jsonDataType = "JsonDataType";
public bool IsAjaxRequest
{
get
{
return this.HttpContext.Request.IsAjaxRequest();
}
}
public bool IsAjaxHtmlRequest
{
get
{
return string.Equals(this.Request.Headers[MyControllerBase._jsonDataType], "html", StringComparison.CurrentCultureIgnoreCase);
}
}
private JsonResponse GetAjaxResponse()
{
JsonResponse result = new JsonResponse();
result.IsValid = true;
return result;
}
private JsonResponse<T> GetAjaxResponse<T>(T model)
{
JsonResponse<T> result = new JsonResponse<T>();
result.Data = model;
result.IsValid = true;
return result;
}
private JsonResponse<string> GetAjaxHtmlResponse()
{
JsonResponse<string> result = new JsonResponse<string>();
result.Data = this.PartialViewToString(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
result.IsValid = true;
return result;
}
private JsonResponse<string> GetAjaxHtmlResponse<T>(T model)
{
JsonResponse<string> result = new JsonResponse<string>();
result.Data = this.PartialViewToString(this.ControllerContext.RouteData.Values["Action"].ToString(), model);
result.IsValid = true;
return result;
}
private JsonResponse<string> GetAjaxHtmlResponse<T>(T model, string viewName)
{
JsonResponse<string> result = new JsonResponse<string>();
result.Data = this.PartialViewToString(viewName, model);
result.IsValid = true;
return result;
}
public ActionResult ViewOrAjax()
{
return this.ViewOrAjax(JsonRequestBehavior.DenyGet);
}
public ActionResult ViewOrAjax(JsonRequestBehavior jsonRequestBehavior)
{
if (this.ControllerContext.IsChildAction)
{
return this.PartialView(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
}
if (this.IsAjaxRequest)
{
if (this.IsAjaxHtmlRequest)
{
return this.Json(this.GetAjaxHtmlResponse(), jsonRequestBehavior);
}
return this.Json(this.GetAjaxResponse(), jsonRequestBehavior);
}
return this.View(this.ControllerContext.RouteData.Values["Action"].ToString(), null);
}
public ActionResult ViewOrAjax<T>(T model)
{
return this.ViewOrAjax<T>(model, JsonRequestBehavior.DenyGet);
}
public ActionResult ViewOrAjax<T>(T model, JsonRequestBehavior jsonRequestBehavior)
{
if (this.ControllerContext.IsChildAction)
{
return this.PartialView(model);
}
if (this.IsAjaxRequest)
{
if (this.IsAjaxHtmlRequest)
{
return this.Json(this.GetAjaxHtmlResponse(model), jsonRequestBehavior);
}
return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
}
return this.View(model);
}
public ActionResult ViewOrAjax<T>(IView view, T model, JsonRequestBehavior jsonRequestBehavior)
{
if (this.ControllerContext.IsChildAction)
{
return this.PartialView(model);
}
if (this.IsAjaxRequest)
{
if (this.IsAjaxHtmlRequest)
{
return this.Json(this.GetAjaxHtmlResponse(model), jsonRequestBehavior);
}
return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
}
return this.View(view, model);
}
public ActionResult ViewOrAjax<T>(string viewName, T model)
{
return this.ViewOrAjax<T>(viewName, model, JsonRequestBehavior.DenyGet);
}
public ActionResult ViewOrAjax<T>(string viewName, T model, JsonRequestBehavior jsonRequestBehavior)
{
if (this.ControllerContext.IsChildAction)
{
return this.PartialView(model);
}
if (this.IsAjaxRequest)
{
if (this.IsAjaxHtmlRequest)
{
return this.Json(this.GetAjaxHtmlResponse(model, viewName), jsonRequestBehavior);
}
return this.Json(this.GetAjaxResponse<T>(model), jsonRequestBehavior);
}
return this.View(viewName, model);
}
public ActionResult ViewOrAjax<T>(string viewName, string masterName, T model)
{
return this.ViewOrAjax<T>(viewName, masterName, model, JsonRequestBehavior.DenyGet);
}
public ActionResult ViewOrAjax<T>(string viewName, string masterName, T model, JsonRequestBehavior jsonRequestBehavior)
{
if (this.ControllerContext.IsChildAction)
{
return this.PartialView(model);
}
if (this.IsAjaxRequest)
{
if (this.IsAjaxHtmlRequest)
{
return this.Json(this.GetAjaxHtmlResponse(model, viewName), jsonRequestBehavior);
}
return this.Json(this.GetAjaxResponse(model), jsonRequestBehavior);
}
return this.View(viewName, masterName, model);
}
protected internal new ViewResult View(string viewName, string masterName, object model)
{
if (model != null)
{
ViewData.Model = model;
}
ViewResult result = new ViewResult
{
ViewName = viewName,
MasterName = masterName,
ViewData = ViewData,
TempData = TempData
};
return result;
}
}
The JsonResponse<>
global wrapper for Ajax Calls:
public class JsonResponse
{
public JsonResponse()
{
}
public bool IsValid { get; set; }
public bool IsAjaxRequestUnsupported { get; set; }
public string RedirectTo { get; set; }
public string CanonicalUrl { get; set; }
}
public class JsonResponse<T> : JsonResponse
{
public JsonResponse() : base()
{
}
public T Data { get; set; }
}
The Javascript global_getJsonResponse
code (require jQuery):
function global_getJsonResult(Controller, View, data, successCallback, completeCallback, methodType, returnType, jsonDataType) {
if (IsString(Controller)
&& IsString(View)
&& !IsUndefinedOrNull(data)) {
var ajaxData;
var ajaxType;
if (typeof (data) == "string") {
ajaxData = data;
ajaxType = "application/x-www-form-urlencoded"
}
else {
ajaxData = JSON.stringify(data);
ajaxType = "application/json; charset=utf-8";
}
var method = 'POST';
if (methodType) {
method = methodType;
}
var dataType = 'json';
if (returnType) {
dataType = returnType;
}
var jsonType = 'html';
if (jsonDataType) {
jsonType = jsonDataType;
}
var jqXHR = $.ajax({
url: '/' + Controller + '/' + View,
headers: { JsonDataType: jsonType },
data: ajaxData,
type: method,
dataType: dataType,
contentType: ajaxType,
success: function (jsonResult) {
if (!IsUndefinedOrNull(jsonResult)
&& jsonResult.hasOwnProperty("RedirectTo")
&& !IsUndefinedOrNull(jsonResult.RedirectTo)
&& jsonResult.RedirectTo.length > 0) {
$.fn.notify('error', 'Login Expired', 'You have been inactive for a prolonged period of time, and have been logged out of the system.');
window.setTimeout(function () { window.location = jsonResult.RedirectTo }, 5000);
}
else if (IsFunction(successCallback)) {
successCallback(jsonResult, Controller + '/' + View);
}
},
error: function (jqXHR, textStatus, errorThrown) {
if (errorThrown != 'abort') {
$.fn.notify('error', 'Whoops! Something went wrong.', 'We have been notified of the error.'/* textStatus + ': ' + errorThrown*/);
}
log('ERROR IN global_getJsonResult() : ', textStatus, errorThrown, jqXHR);
},
complete: function (jqXHR, textStatus) {
if (IsFunction(completeCallback)) {
completeCallback(jqXHR, textStatus, Controller + '/' + View);
}
}
});
return jqXHR;
}
}
This code supports both server side and client side TimeOuts via Handling session timeout in ajax calls, with a change like:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = new JsonResponse<bool>
{
IsValid = false,
RedirectTo = FormsAuthentication.LoginUrl
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
A couple of Extension Methods on controller to allow you to return rendered partial views as text in json (this code was from SO, I usually document such but I lost it):
internal static class ControllerExtensions
{
public static string PartialViewToString(this Controller instance, object model)
{
string viewName = instance.ControllerContext.RouteData.GetRequiredString("action");
return ControllerExtensions.PartialViewToString(instance, viewName, model);
}
public static string PartialViewToString(this Controller instance, string viewName, object model)
{
string result;
ViewDataDictionary viewData = instance.ViewData;
viewData.Model = model;
using (var sw = new StringWriter())
{
var viewResult = ViewEngines.Engines.FindPartialView(instance.ControllerContext, viewName);
var viewContext = new ViewContext(instance.ControllerContext, viewResult.View, viewData, instance.TempData, sw);
viewResult.View.Render(viewContext, sw);
viewResult.ViewEngine.ReleaseView(instance.ControllerContext, viewResult.View);
result = sw.GetStringBuilder().ToString();
}
return result;
}
}
Now derive (sadly) all your controllers from this base controller:
public HomeController : MyBaseController
{
public ActionResult Index()
{
var viewModel = new MyViewModel();
return this.ViewOrAjax(viewModel);
}
}
Now if the page is called by the browser as your standard get, you get the page rendered normally with a Layout (aka this.View(viewModel)
).
If you call it using Ajax via the Javascript:
global_getJsonResult("Home", // Controller or 'Area/Home' for areas
"Index", // View
$('#form').serialize(), // Json object or a serialized Form
jsCallBack, // call back function or null
"Post", // Get or Post
"Html"); // "Html" to return a Partial View in "Data"
// or "Json" to return a serialized view model in "Data"