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

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());
}
}

Related

webapi actionfilters, how to inject a value when using different argument types that inherit from a base type

I have a base request type..
class RequestBase
{
public string inputId;
public string derivedid;
}
and types that inherit ..
class RequestA : RequestBase
{
public string name;
}
and
class RequestB : RequestBase
{
public string color;
}
I have a webapi service, some actions take an input parameter of RequestA, some take RequestB
[HttpPost]
[MyFilter]
[ActionName("Process1")]
public HttpResponseMessage Process1(RequestA request)
{
//do something with request.derivedId
}
[HttpPost]
[MyFilter]
[ActionName("Process2")]
public HttpResponseMessage Process2(RequestB request)
{
//do something with request.derivedId
}
I have an actionfilter that takes the inputId from the request and generates a derivedId
public override void OnActionExecuting(HttpActionContext actionContext)
{
RequestBase request = (RequestBase)actionContext.ActionArguments["request"];
string inputId = request.inputId;
string derivedId = inputId + "123";
// ?? somehow inject derivedId back into the actionContext so that my controller methods can access?
}
As my comment states above, I'd like to populate the derivedId field and have it accessible to my controller methods.
Thanks in advance
There's a few solutions to this problem already described in this thread - one of them should suit you:
ASP.NET MVC Pass object from Custom Action Filter to Action

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");
}
}

ActionMethodSelectorAttribute unable to access types?

I am relatively new to MVC3, and am developing a website that will need to handle pre-loaded accounts in the default Microsoft membership provider, using SQL Server, EF4, etc. Some progress has been made, and with the help of someone on SO, I have got the ActionMethodSelectorAttribute working correctly to help me with that.
I.e., when we see someone's ID as part of their attempt to load a profile page (www.mysite.com/profile/4) we will look to see if that ID/account has been 'claimed' or not. (My original posting is here: MVC3 using routes or using controller logic?)
Unfortunately, inside the ActionMethodSelectorAttribute, I am having a heck of a time doing a relatively simple database call to determine if the account is claimed/not claimed.
Here is my current state of the code:
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
bool isActivated = profile;// some code to get this state
return isActivated;
}
}
The line
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
errors on the db. section, with error message as follows:
Cannot access a non-static member of outer type 'MySite.Controllers.HomeController' via nested type 'MySite.Controllers.HomeController.UserAccountActivatedAttribute'
...with the error being highlight under the db.
Does anyone know why, inside the ActionMethodSelectorAttribute, I cannot seem to make this call? (NOTE: inside the same Home controller, I am making many similar calls in Public ActionResult and ViewResult classes without any errors.)
EDIT
My HomeController.cs looks like this:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using MySite.Models;
namespace MySite.Controllers
{
public class HomeController : Controller
{
private MySiteEntities db = new MySiteEntities();
public ActionResult Index()
{
ViewBag.Message = "Welcome to MySite.com!";
return View();
}
//several other ActionResults - create, delete, etc.
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
// get profile id first
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var profile = db.Profiles.Where(q => q.ProfileId == id).FirstOrDefault();
bool isActivated = profile;// some code to get this state
return isActivated;
}
}
...definitely it falls inside the Home Controller.
EDIT #2:
Closer, but a small issue with the value always being TRUE.
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
private MySiteEntities db = new MySiteEntities();
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
int id = int.Parse((string)controllerContext.RouteData.Values["id"]);
var data = new MySiteEntities();
var claimed = db.Claimeds.FirstOrDefault(c => c.ProfileId == id);
bool isActivated = claimed.Claimed1.Value != null;
return isActivated;
}
}
The claimed.Claimed1.Value != null; gives me a warning: The result of the expression is always 'true' since a value of type 'bool' is never equal to 'null' of type 'bool?'
However, I have to have something there to handle a NULL value, right?
I think your code actually looks more like this, am I right?
public class HomeController : Controller
{
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
...
}
}
You need to make the attribute class a first-level class, not nested within the controller. You then need to give it its own db instance.
public class HomeController : Controller
{
...
}
public class UserAccountActivatedAttribute : ActionMethodSelectorAttribute
{
private readonly CustomDbContext db = new CustomDbContext();
public override bool IsValidForRequest(ControllerContext controllerContext,
MethodInfo methodInfo)
{
// original code here
}
}
The reason for this is because when the attribute class is nested within the controller class, it cannot access the db instance variable, because it is not a static variable. Your attributes should really not be nested classes, and should have their own separate instance variables. In other words, don't try to solve this by making the controller's db variable static.

MVC Model is null in OnExecuted action filter ... or a more elegant way to set the model?

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);
}
}

Resources