Есть ли у вас советы о том, как выполнять действия контроллера, которые хорошо работают при обычном вызове и через Ajax? Это раздражающая проблема при написании методов действий.
Да, да, я делаю. Мы также работали над аналогичной проблемой - мы хотели, чтобы приложение имело набор форм, которые обычно вызывались бы через ajax, но могли попадать как обычно. Более того, мы не хотели, чтобы в JavaScript был целый набор дублирующейся логики. В любом случае, мы разработали методику использования пары ActionFilterAttributes для перехвата форм, а затем небольшого javascript для подключения формы для обработки ajax.
Во-первых, для запроса ajax мы просто хотели поменять главные страницы:
private readonly string masterToReplace;
/// <summary>
/// Initializes an Ajax Master Page Switcharoo
/// </summary>
/// <param name="ajaxMaster">Master page for ajax requests</param>
public AjaxMasterPageInjectorAttribute(string ajaxMaster)
{
this.masterToReplace = ajaxMaster;
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest() || !(filterContext.Result is ViewResult)) return;
ViewResult vr = (ViewResult) filterContext.Result;
vr.MasterName = masterToReplace;
}
}
Что касается обратной стороны, мы используем xVal для проверки клиентской стороны, поэтому нельзя недооценивать недопустимые данные, но некоторые все же можно получить. Для этого мы просто используем обычную проверку и имеем метод aciton, возвращающий форму с сообщениями проверки. Успешные сообщения вознаграждаются перенаправлениями в целом. В любом случае, мы делаем небольшую json-инъекцию для успеха:
/// <summary>
/// Intercepts the response and stuffs in Json commands if the request is ajax and the request returns a RedirectToRoute result.
/// </summary>
public class JsonUpdateInterceptorAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
JsonResult jr = new JsonResult();
if (filterContext.Result is RedirectResult)
{
RedirectResult rr = (RedirectResult) filterContext.Result;
jr.Data = new {command = "redirect", content = rr.Url};
}
if (filterContext.Result is RedirectToRouteResult)
{
RedirectToRouteResult rrr = (RedirectToRouteResult) filterContext.Result;
VirtualPathData vpd = RouteTable.Routes.GetVirtualPath(filterContext.RequestContext, rrr.RouteValues);
jr.Data = new {command = "redirect", content = vpd.VirtualPath};
}
if (jr.Data != null)
{
filterContext.Result = jr;
}
}
}
}
Последний трюк - использовать маленький объект javascript, чтобы связать все вместе:
function AjaxFormSwitcher(form, outputTarget, doValidation) {
this.doValidation = doValidation;
this.outputTarget = outputTarget;
this.targetForm = form;
}
AjaxFormSwitcher.prototype.switchFormToAjax = function() {
var afs = this;
var opts = {
beforeSubmit: this.doValidation ? afs.checkValidation : null,
complete: function(xmlHttp, status){ afs.processResult(afs, xmlHttp, status); },
clearForm: false
};
this.targetForm.ajaxForm(opts);
}
AjaxFormSwitcher.prototype.checkValidation = function(formData, jqForm, options) {
jqForm.validate();
return jqForm.valid();
}
AjaxFormSwitcher.prototype.processResult = function(afs, xmlHttp, status) {
if (xmlHttp == null) return;
var r = xmlHttp;
var c = r.getResponseHeader("content-type");
if (c.match("json") != null) {
var json = eval("(" + r.responseText + ")");
afs.processJsonRedirect(json);
}
if (c.match("html") != null) {
afs.outputTarget.html(r.responseText);
}
}
AjaxFormSwitcher.prototype.processJsonRedirect = function(data) {
if (data!=null) {
switch (data.command) {
case 'redirect':
window.location.href = data.content;
break;
}
}
}
Этот маленький скрипт обрабатывает такие вещи, как подключение формы для выполнения ajax и обработка результата (либо команда json, либо html, который отображается в цели). По общему признанию, я отстой в написании javascript, так что, вероятно, есть гораздо более изящный способ написать это.