MVC Model is null in OnExecuted action filter ... or a more elegant way to set the model? - asp.net-mvc-3

I have an ActionFilter with an override on the OnActionExecuted method. The filterContext.Controller.ViewData.Model is always null on a POST operation. I did find the following article that seems to be saying that it should not be null but this must have been an earlier version of MVC. This is MVC3. What should I be getting?
Model availability inside ActionFilter
UPDATE:
I've figured out the answer to the original question. I had a custom ActionResult that outputs JSON with a custom date formatter. The problem was that the model is not being set in the controller.
In my custom ActionResult the ExecuteResult method get passed the ControllerContext which would be nice if I could set the Model there:
context.Controller.ViewData.Model = _data;
But this is to late in the cycle and the result is still null in the ActionFilter. This seems to mean that I need to manually set the model in the controller:
ControllerContext.Controller.ViewData.Model = model;
Or
View(model);
Which then means I need to remember to do this every time I use this custom ActionResult. Is there a more elegant way?
YET ANOTHER UPDATE:
I found a way to do this it just isn't as elegant as I hoped.
In my constructor for the comstom ActionResult I sending in the controller, that way at least it will alway be consistent:
public JsonNetResult(object data, Controller controller) {
SerializerSettings = new JsonSerializerSettings();
_data = data;
controller.ControllerContext.Controller.ViewData.Model = _data;
}

Another approach is to use a base controller to automatically handle the storing of the action parameters collection for later use:
public class BaseController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
filterContext.HttpContext.Items["ActionParms"] = filterContext.ActionParameters.ToDictionary(p => p.Key, p => p.Value);
base.OnActionExecuting(filterContext);
}
}
then in your attribute:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var dictionary = filterContext.HttpContext.Items["ActionParms"] as Dictionary<string, object>;
if (dictionary != null)
{
foreach (var o in dictionary.Keys)
{
// do something here
}
}
base.OnActionExecuted(filterContext);
}
It uses HttpContext items which is not very nice but I don't know that you can access your ViewBag or ViewData in the attribute.
In order to decide whether you want to handle the request in your attribute, you can interrogate the action name and other parameter information:
var action = filterContext.ActionDescriptor.ActionName;
var parms = filterContext.ActionDescriptor.GetParameters();
foreach (var parameterDescriptor in parms)
{
// do something here
}

I found a solution like yours using the OnModelUpdated event to set that property before.
I have the ModelBinder:
public class CustomModelBinder: DefaultModelBinder
{
protected override void OnModelUpdated(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
controllerContext.Controller.ViewData.Model = bindingContext.Model;
base.OnModelUpdated(controllerContext, bindingContext);
}
}
After that, you need to set the default binder to your new model binder in Application_Start() section in Global.asax:
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();
Finally you can access your Model in an ActionFilter:
public class TraceLog : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//filterContext.Controller.ViewData.Model now isn't null
base.OnActionExecuted(filterContext);
}
}

Related

Abort action execution in BindModel method

I have implemented custom model binder in my File Upload Action. Sometimes file upload is dropped by server and BindModel method is called with partial data (ContentLenght and TotalBytes do not match here). I would like to abort Action execution from custom model binder, how to do that?
public class OptionModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var optionModelName = GetOptionModelName(controllerContext);
if (optionModelName != null) return null// !!!How to abort Action execution?!!! here
Trace.TraceInformation(optionModelName);
var model = System.Reflection.Assembly.GetExecutingAssembly().CreateInstance(optionModelName);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType());
return base.BindModel(controllerContext, bindingContext);
}
public class OptionModelBinderAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new OptionModelBinder();
}
}
[HttpPost]
public ActionResult UploadFile(IEnumerable<HttpPostedFileBase> clientUpload, [OptionModelBinder]IOptionViewModel formData)
{
}
This is not something you want to do from the model binding.
The model binding should not control logic behavior. it does not make sense.
I suggest in the controller, you'll ask if something is null and return the appropriate result to the client.
It is not right to let the model binding do the controller's work.

Use Action method selector to differentiate between Ajax and non-ajax request instead of relying on if(Request.isAjaxRequest)?

I'm following a book called 'Asp.Net MVC4 in Action'. And now at certain point they say, Instead of relying on if statement within our code to check if the request is Ajax or not, we could use an action method selector to differentiate it. And what they have done is create a class AcceptAjaxAttribute with following code
using System;
using System.Reflection;
using System.Web.Mvc;
namespace CustomAjax
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo)
{
return controllerContext.HttpContext.Request.IsAjaxRequest();
}
}
}
and the function in controller which looked like this before
var speaker = _repository.FindSpeaker(id);
if(Request.IsAjaxRequest())
{
return Json(speaker, JsonRequestBehaviour.AllowGet);
}
return View();
has changed to something like this
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return Json(speaker, JsonRequestBehavior.AllowGet);
}
[ActionName("Details")]
public ActionResult Details_NonAjax(int id)
{
var speaker = _repository.FindId(id);
return View();
}
To be honest I have no idea what is being done or why we created new class and used that[AcceptAjax] thingy. Can someone may be explain it to me.
Before you had one action with an if inside, after the refactoring you have 2 actions each returning a different type of result. The ActionMethodSelectorAttribute is used to select the proper action based on some condition. Since the 2 actions have the same name (Details), ASP.NET MVC will use this attribute to select the one or the other based on whether an AJAX request is being used.
But honestly I don't like this approach at all. You now have 2 actions and have repeated the var speaker = _repository.FindId(id); call twice which is not very DRY. Wouldn't it be better if you had this?
[AcceptAjax]
public ActionResult Details(int id)
{
var speaker = _repository.FindId(id);
return View(speaker);
}
If you are like me and think that this is better, then simply replace this AcceptAjaxAttribute you got from the book with an action filter:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AcceptAjaxAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
var result = filterContext.Result as ViewResultBase;
if (result != null && result.Model != null)
{
filterContext.Result = new JsonResult
{
Data = result.Model,
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
}
The OnActionExecuted method will be invoked after the controller action has finished running and returned some result. Inside this method we verify whether the controller action returned a ViewResultBase (ViewResult or PartialViewResult) and whether a model has been passed. If this is the case we simply replace this result by a JsonResult.
And if you want to avoid decorating all your controller actions with this [AcceptAjax] attribute, you could register it as a global action filter in your ~/App_Start/FilterConfig.cs:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new AcceptAjaxAttribute());
}
}

How can I apply an attribute that is already a global filter to a method without it running twice?

I have extended HandleErrorAttribute in MVC by creating my own attribute that handles exceptions raised during Ajax requests. That attribute is here:
public class AjaxAwareHandleErrorAttribute : HandleErrorAttribute
{
public string PartialViewName { get; set; }
public override void OnException(ExceptionContext filterContext)
{
ExceptionHelper.HandleException(filterContext.Exception, false); // custom static class and method
base.OnException(filterContext);
// Use partial view in case of AJAX request
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
var result = new PartialViewResult {ViewName = PartialViewName ?? "~/Views/Errors/DefaultAjaxError.cshtml"};
filterContext.Result = result;
}
}
}
As you can see, there is default behavior in the case when PartialViewName is null. This is because I have it registered as a global filter in my Global.asax file:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new AjaxAwareHandleErrorAttribute());
}
So the way this is set up, I have default behavior when an exception is raised during an Ajax call, namely that DefaultAjaxError.cshtml will be returned. In cases where I want a custom message, I create a view and pass it as PartialViewName to an attribute directly on the action, like this:
[AjaxAwareHandleError(PartialViewName = "~/Views/Errors/CustomAjaxError.cshtml")]
public ActionResult SomeAjaxControllerAction()
The problem in cases where I have a controller decorated with the controller action is that the code in the AjaxAwareHandleErrorAttribute class is running twice, presumably because it's registered globally and because it's on the method as well. Is there a way I can prevent it from running twice?
Thanks.
Set the filterContext.ExceptionHandled property to true.
Then, at the top of your override, check whether that property is true and return immediately.

Access action method parameters from custom Authorize attribute in MVC 3

I am writing an MVC 3 app where users will be able to log in and manage their data. I want to prevent users from viewing or tampering with other user's data. My first instinct was to just verify access to the relevant object in each action method like this:
public ActionResult ShowDetails(int objectId)
{
DetailObject detail = _repo.GetById(objectId);
if (detail.User.UserID != (Guid)Membership.GetUser().ProviderUserKey)
{
return RedirectToAction("LogOff", "Account");
}
}
This works fine, but I thought it might be better to put the object authorization code into a custom Authorize attribute derived from AuthorizeAttribute, which I could then apply to the controller. Unfortunately, I have not been able to find a way to access the action method parameters from within my custom Authorize attribute. Instead, the only way I have found to access the incoming objectId is by examining httpContext.Request or filterContext.RequestContext.RouteData.Values:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
private int _objectId = 0;
private IUnitOfWork _unitOfWork;
public MyAuthorizeAttribute(IUnitOfWork uow)
{
_unitOfWork = uow;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
int.TryParse((string) filterContext.RequestContext.RouteData.Values["id"], out _objectId);
base.OnAuthorization(filterContext);
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
int objectId = 0;
if (httpContext.Request.Params.AllKeys.Contains("id", StringComparer.InvariantCultureIgnoreCase))
{
int.TryParse(httpContext.Request[idKey], out objectId);
}
if (objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
if (_objectId != 0)
{
if (!IsAuthorized(objectId, httpContext.User.Identity.Name))
{
return false;
}
}
return base.AuthorizeCore(httpContext);
}
private bool IsAuthorized(int objectId, string userName)
{
DetailObject detail;
detail = _unitOfWork.ObjectRepository.GetById(objectId);
if (detail == null)
{
return false;
}
if (userName != detail.User.UserName)
{
return false;
}
return true;
}
}
I find this approach to be very clunky. I really don't want to have to poke around in the RouteData or Request objects; it would be much cleaner to be able to access the action method parameters since model binding would have already pulled out the relevant data from the RouteData and Request.
I know I can access action method parameters from a custom Action Filter (as detailed here), but shouldn't data authorization code be placed in an Authorize Filter? The more examples I see of Authorize filters, the more I get the impression that they are intended only to handle roles.
My main question is: How do I access action method parameters from my custom Authorize Attribute?
Answer to your main question: no, unfortunately AuthorizationContext does not provide access to action parameters.
First off, you could use ValueProvider to not have to deal with whether the id is part of the route or a query parameter or HTTP posted, as follows:
public override void OnAuthorization(AuthorizationContext filterContext)
{
string id = filterContext.Controller.ValueProvider.GetValue("id").AttemptedValue;
...
}
This works for simple data types and introduces little overhead. However once you start using custom model binders for your action parameters, you have to inherit your filter from ActionFilterAttribute to avoid double binding:
[MyFilter]
public ActionResult MyAction([ModelBinder(typeof(MyModelBinder))] MyModel model)
{
...
}
public class MyFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var model = filterContext.ActionParameters["model"] as MyModel;
...
}
}
While semantically inheriting from AuthorizeAttribute for authorization purposes sounds better, there are no other reasons for doing this. Moreover, I find using ActionFilterAttribute easier, as all you have to do is override only one method, not keeping a state for subsequent methods.

How do I enforce proper globalization behavior for view models containing date properties in MVC3

I am attempting to pass a complex view model into a controller action. The object passed is of type Goal and contains among other things a datetime property (Goal.moddate). In my case the string representation of the date is following es-MX. Therefore February 29th, 2012 is represented as "29/02/2012" (I have the same issue with other dates).
The controller action is also annotated with an [CultureAwareAction] attribute - this one sets the culture info based on user preferences. In this case (updated to make the solution clearer)
public class CultureAwareActionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
Thread.CurrentThread.CurrentCulture = new CultureInfo("es-MX");
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-MX");
}
}
My preferred way to handle the action would be the following:
[HttpPost]
[CultureAwareAction]
[ValidateAntiForgeryToken]
public ActionResult Edit(Goal goal)
{
try
{
if (ModelState.IsValid)
{
{
...update logic ...
}
}
}
catch (DataException)
{
... error handling ....
}
return View();
}
Using this approach ModelState.IsValid returns false due to the date string not being parsed. Changing the controller action to the following I encounter no errors:
[HttpPost]
[CultureAwareAction]
[ValidateAntiForgeryToken]
public ActionResult Edit(int id, FormCollection formcollection)
{
try
{
Goal goal = unitOfWork.GoalRepository.GetByID(id);
if (TryUpdateModel(goal,formcollection))
{
{
... update logic ....
}
}
}
catch (DataException)
{
... error handling ...
}
return View();
}
My goal is to enforce proper globalization behavior on the first case as there are significant advantages going with that approach. It seems like this should work unless model binding happens before setting the user preferred culture using the [CultureAwareAction] attribute.
The model binder uses the CurrentCulture, not the CurrentUICulture when parsing dates. Also you haven't shown the code of this CultureAwareAction but chances are that it executes after the model binding so you are setting the culture too late.
If you want to ensure that it executes before model binding you could implement the IAuthorizationFilter interface:
public class CultureAwareActionAttribute : ActionFilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
// That's for displaying in the UI, the model binder doesn't use it
Thread.CurrentThread.CurrentUICulture = new CultureInfo("es-MX");
// That's the important one for the model binder
Thread.CurrentThread.CurrentCulture = new CultureInfo("es-MX");
}
}

Resources