Interesting Dilemna

  • Download Gist
0.Interesting Dilemna with Progressive Enhancement.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
Say you are in a world where you want to use progressive enhancement (sometimes for accessibility)
with ASP.NET MVC 3+.
 
Progressive Enhancement means the site WORKS WITHOUT JAVASCRIPT FOLKS.
 
Now say you have something like comments that will be on multiple pages.
 
You want the site to be maintainable so you want to use a partial with
form submission.
 
This means that the partial will submit to a different controller than
the page you are currently on.
 
When Javascript is turned on, you use progressive enhancement
to ajax the form submission (jquery post) and the only part
of the page that reloads is the form area. You also use
client side validation.
 
When Javascript is turned off, you are submitting to a different
url. But when there are validation errors you need to present
the same page you were on. But how do you do that?
 
This outlines a possible solution.
 
You still need a way to tell the server that Javascript is on
and we've implemented this, but we are not happy with it, so
I left that part out of this example. Please see
http://stackoverflow.com/a/5188833 for what we based our solution
on.
 
We have an explicit opt in to share the ModelState, but every action
implicitly will opt in to merge it automatically. And if Javascript
is enabled, we don't even use this code.
1.ModelStateFilters.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// This is based loosely on http://stackoverflow.com/a/12024227
public class SetTempModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
 
filterContext.Controller.TempData[ApplicationParameters.Web.ModelState] = filterContext.Controller.ViewData.ModelState;
}
}
 
public class MergeModelStateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (!filterContext.Controller.TempData.ContainsKey(ApplicationParameters.Web.ModelState)) return;
 
var tempModelState = filterContext.Controller.TempData[ApplicationParameters.Web.ModelState] as ModelStateDictionary;
if (tempModelState != null)
{
filterContext.Controller.ViewData.ModelState.Merge(tempModelState);
}
}
}
2.OptInForHoldingModelState.cs
C#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
[HttpPost]
[SetTempModelState]
public ActionResult AddComment(CommentModel model)
{
// One might pull the url the request is from (we will need to deal with this eventually) or pass it as a parameter.
// What we've done with it as part of the model is a short circuit that we are not happy with using.
if(!ModelState.IsValid)
{
return ReturnPartialOrRedirect("_CommentAdd", model, model.Url);
}
 
//actions for saving...
 
return ReturnPartialOrRedirect("_Success", new SuccessModel{Message="Some message"}, model.Url);
}
3.GlobalFilter.cs
C#
1 2 3 4 5 6
//no explicit opt in necessary
 
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new MergeModelStateAttribute());
}
4.ReturnPartialOrRedirect.cs
C#
1 2 3 4 5 6 7 8 9 10 11
//this goes in a global controller that inherits controller that all controllers for your site inherit from
public ActionResult ReturnPartialOrRedirect<T>(string viewName, T model, string redirectUrl)
{
if (ApplicationParameters.IsJavaScriptEnabled)
{
return PartialView(viewName, model);
}
return Redirect(redirectUrl);
}

Instead of checking whether the user can use javascript (or has it enabled), Could you not simply check whether the call was an Ajax call?

Request.IsAjaxRequest(); will be true if the request was made using Ajax:
http://stackoverflow.com/questions/4523827/asp-net-mvc-what-does-isajaxrequest-actually-mean

The chances that you are using a JS library that does not set the required header is low.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.