I am currently in the process of creating a website which requires it to work with and without JavaScript enabled. All callbacks to the server with JavaScript enabled will be made through Ajax callbacks. In order to keep my code very DRY, I wanted to reduce the amount of views that was required to be created.
For that purpose I created the following code:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class UnobtrusiveAjax : ActionFilterAttribute
{
public enum ActionResultType
{
Json,
PartialView,
View,
}
/// <summary>
/// The type of Model to be generated from JsonResults.
/// </summary>
public Type ModelType { get; set; }
/// <summary>
/// The type of ActionResult to be returned if the action was requested via an ajax callback.
/// </summary>
public ActionResultType ViewType { get; set; }
/// <summary>
/// Determines if we will allow the request to be fulfilled through a Http-Get request.
/// </summary>
public bool AllowGet { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.IsRequest("GET")
&& !AllowGet
&& !filterContext.ActionDescriptor.IsDefined(typeof(HttpGetAttribute), false))
{
throw new ArgumentException("Unable to perform action via a Http-Get request, please perform action via a different Http request method.",
nameof(filterContext.ActionDescriptor));
}
base.OnActionExecuted(filterContext);
if (filterContext.IsAjaxRequest())
{
switch (ViewType)
{
case ActionResultType.View:
filterContext.Result = ConvertToView(filterContext);
break;
case ActionResultType.PartialView:
filterContext.Result = ConvertToPartialView(filterContext);
break;
case ActionResultType.Json:
default:
filterContext.Result = ConvertToJson(filterContext);
break;
}
}
}
private ActionResult ConvertToJson(ActionExecutedContext filterContext)
{
if (filterContext.Result is ViewResultBase)
{
var result = filterContext.Result as ViewResultBase;
var serializer = new JavaScriptSerializer();
return new JsonResult()
{
Data = serializer.Serialize(result.Model),
JsonRequestBehavior =
(AllowGet) ? JsonRequestBehavior.AllowGet : JsonRequestBehavior.DenyGet
};
}
return filterContext.Result;
}
private ActionResult ConvertToPartialView(ActionExecutedContext filterContext)
{
if (filterContext.Result is JsonResult)
{
var partial = new PartialViewResult();
var result = filterContext.Result as JsonResult;
var viewName = filterContext.ActionDescriptor.ActionName;
dynamic model = null;
if ((result.Data is string))
{
model = DeserializeModel(result.Data as string);
}
partial.ViewData.Model = model ?? result.Data;
partial.ViewName = viewName;
partial.ViewEngineCollection = ViewEngines.Engines;
return partial;
}
else if (filterContext.Result is ViewResultBase)
{
return ConvertToPartialView(filterContext.Result as ViewResultBase);
}
return filterContext.Result;
}
private ActionResult ConvertToPartialView(ViewResultBase result)
{
return new PartialViewResult()
{
TempData = result.TempData,
ViewData = result.ViewData,
ViewName = result.ViewName
};
}
private ActionResult ConvertToView(ActionExecutedContext filterContext)
{
if (filterContext.Result is JsonResult)
{
var result = filterContext.Result as JsonResult;
var viewName = filterContext.ActionDescriptor.ActionName;
object model = null;
if ((result.Data is string))
{
model = DeserializeModel(result.Data as string);
}
var view = new ViewResult();
view.ViewData.Model = model ?? result.Data;
view.ViewName = viewName;
return view;
}
else if (filterContext.Result is ViewResultBase)
{
return ConvertToView(filterContext.Result as ViewResultBase);
}
return filterContext.Result;
}
private ActionResult ConvertToView(ViewResultBase result)
{
return new ViewResult()
{
TempData = result.TempData,
ViewData = result.ViewData,
ViewName = result.ViewName
};
}
private object DeserializeModel(string model)
{
if (ModelType == null)
{
throw new ArgumentNullException("The 'ModelType' cannot be null.",
nameof(ModelType));
}
var serializer = new JavaScriptSerializer();
return serializer.Deserialize(model, ModelType);
}
}
public static class ControllerContextExtensions
{
public static bool IsAjaxRequest(this ControllerContext that)
{
return that.HttpContext.Request.IsAjaxRequest();
}
public static bool IsRequest(this ControllerContext that, string method)
{
return (that.HttpContext.Request.HttpMethod.Equals(method, StringComparison.CurrentCultureIgnoreCase));
}
}
The code itself works like you'd expect it to work. I'm wondering if there is a more optimized version or if this has been done before and I'm just reinventing the wheel.