Custom AuthorizeAttribute works on controller but not method - asp.net-mvc-3

I created a custom AuthorizeAttribute:
public AjaxAwareAuthorizeAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
base.OnAuthorization(filterContext);
if(filterContext.Result is HttpUnauthorizedResult && filterContext.HttpContext.Request.IsAjaxRequest()) {
filterContext.HttpContext.Items["RequestWasNotAuthorized"] = true;
}
}
}
When I apply this to a Controller Class the OnAuthorization event is fired and the handler above is executed as expected but when it is applied to any method within a controller, nothing happens !?

I'd like to give Robotsushi the credit here but he didn't respond with an Answer.
The real problem with my code was in the CustomFilterAttributeFilterProvider code.
protected override IEnumerable<FilterAttributes> GetActionAttributes(ControllerContext controllerContext, ActionDescriptor actionDescriptor) {
//incorrect code
//var attributes = base.GetControllerAction(controllerContext, actionDescriptor);
//correct code
var attributes = base.GetActionAttributes(controllerContext, actionDescriptor);

Robotsushi is on to something here!
I have the following in my bootstrapping code:
var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(oldProvider);
var newProvider = new CustomFilterAttributeFilterProvider(kernel);
FilterProviders.Providers.Add(newProvider);
This was code I added after adding the custom attribute. When I remove these lines of code then the attribute fires as expected.

Related

Extension methods cannot be dynamically dispatched error - how do I solve this?

Can't find the proper solution to this problem.
I am using [Serializable] (MVC3 Futures) in order to have a "wizard" with separate views. Here is the code in my controller to serialize:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null) //Form was posted containing serialized data
{
myData = (MyViewModel)new MvcSerializer().Deserialize(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
Further along in my controller I do something like this (just a snippet - code goes through wizard with next and back button strings):
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("Details");
else if ((nextButton != null) && ModelState.IsValid)
return RedirectToAction("Submitted");
else
return View(myData);
}
In my .cshtml view, I have this:
#using (Html.BeginFormAntiForgeryPost())
{
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed))
...
#Html.TextBoxFor(m => model.Step.EMail)
...
}
Because I am using dynamics, I have to use a variable instead in the view:
var model = (MyViewModel) Model.myData;
in order to do the #Html.TextBoxFor above. And herein lies my probelm, because if I do #model MyViewModel instead, then I can't do model.Step.EMail. But because of dynamics, the #Html.Hidden won't work and I get the following error:
Compiler Error Message: CS1973: 'System.Web.Mvc.HtmlHelper'
has no applicable method named 'Hidden' but appears to have an
extension method by that name. Extension methods cannot be dynamically
dispatched. Consider casting the dynamic arguments or calling the
extension method without the extension method syntax.
I can switch to some other way of doing this without [Serializable], but then I have to convert a LOT of code. Is there any way to make this work?
The extension method is not identifying the method because, the data type does not match.
Try cast as object.
#Html.Hidden("myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed) as Object)
or
#Html.Hidden("myData", (Object)new MvcSerializer().Serialize(Model, SerializationMode.Signed))
It will works.
You can call
#(InputExtensions.Hidden(Html, "myData", new MvcSerializer().Serialize(Model, SerializationMode.Signed)))
instead of #Html.Hidden(...)
It is calling the extension method without the extension method syntax.

Fluent Validation - MVC 3

I'm using fluent validation in MVC 3. Is it possible to turn off fluent validation for specific post action in controller?
Thanks
Assuming you are using the AttributedValidatorFactory and you wanted to disable validation for the Index action on Home controller for POST verbs you could write a custom validator factory:
public class MyAttributedValidatorFactory : AttributedValidatorFactory
{
private readonly Func<HttpContextBase> _contextAccessor;
public MyAttributedValidatorFactory(Func<HttpContextBase> contextAccessor)
{
_contextAccessor = contextAccessor;
}
public override IValidator GetValidator(Type type)
{
var context = _contextAccessor();
var rd = context.Request.RequestContext.RouteData;
var action = rd.GetRequiredString("action");
var controller = rd.GetRequiredString("controller");
if (string.Equals("post", context.Request.HttpMethod, StringComparison.OrdinalIgnoreCase) &&
string.Equals("index", action, StringComparison.OrdinalIgnoreCase) &&
string.Equals("home", controller, StringComparison.OrdinalIgnoreCase)
)
if (type == typeof(MyViewModel))
{
return null;
}
return base.GetValidator(type);
}
}
which will be used to replace the default one in your Application_Start:
FluentValidationModelValidatorProvider.Configure(config =>
{
Func<HttpContextBase> contextAccessor =
() => new HttpContextWrapper(HttpContext.Current);
config.ValidatorFactory = new MyAttributedValidatorFactory(contextAccessor);
});
and then if you have the following action on the Home controller:
[HttpPost]
public ActionResult Index(MyViewModel model)
{
...
}
FluentValidation won't kick in.
Hay men you have miss some important point of fluentvalidation called the Validator customization.
find here http://fluentvalidation.codeplex.com/wikipage?title=mvc
Validator customization
With FluentValidation v3 you can use the CustomizeValidatorAttribute to configure how the validator will be run. For example, if you want the validator to only run for a particular ruleset then you can specify that ruleset name by attributing the parameter that is going to be validated:
public ActionResult Save([CustomizeValidator(RuleSet="MyRuleset")] Customer cust) {
// ...
}
this

MVC3 Return View By Default

Basically, I was wondering if anyone knows of a way that you can set up MVC3 in a way that it will first look for an action, and if none exists, it will automatically return the view at that location. Otherwise each time I make a page, I will have to rebuild it after adding the action.
It isn't something that's stopping the project from working nor is it an issue, it would just be a very nice thing to include in the code to help with speed of testing more than anything.
EDIT:
Just for clarity purposes, this is what I do every time I create a view that doesn't have any logic inside it:
public ActionResult ActionX()
{
return View();
}
Sometimes I will want some logic inside the action, but majority of the time for blank pages I will just want the above code.
I would like it if there was any way to always return the above code for every Controller/Action combination, UNLESS I have already made an action, then it should use the Action that I have specified.
Thanks,
Jake
Why not just create a single action for this. This will look for a view with the specified name and return a 404 if it doesn't exist.
[HttpGet]
public ActionResult Page(string page)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, page, null);
if (result == null)
{
return HttpNotFound();
}
return View(page);
}
Then make your default route fall back to this:
routes.MapRoute("", "{page}", new { controller = "Home", action = "Page" });
So a request to http://yoursite.com/somepage will invoke Page("somepage")
I'm not altogether sure how useful this will be (or whether its really a good idea) but I guess if you have pages which are purely static content (but maybe use a layout or something so you can't use static html) it could be useful
This is how it could be done though anyway (as a base class, but it doesn't have to be)
public abstract class BaseController : Controller
{
public ActionResult Default()
{
return View();
}
protected override IActionInvoker CreateActionInvoker()
{
return new DefaultActionInvoker();
}
private class DefaultActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor == null)
actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, "Default");
return actionDescriptor;
}
}
}

MVC 3 json request should receive json response on exception

I'm looking for a good/smart/clean way to globally handle errors so that if a request is Json and an exception occurs, the result should be json and not html.
Looking for either existing solutions or some info of how to build my own.
One common way to do this is to write a custom exception filter:
public class MyErrorHandlerAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new JsonResult
{
Data = new { success = false, error = filterContext.Exception.ToString() },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
which could be registered as a global filter in Global.asax. And then simply query some action:
$.getJSON('/someController/someAction', function (result) {
if (!result.success) {
alert(result.error);
} else {
// handle the success
}
});
This is probably doable with a custom attribute... maybe even a subclass of HandleErrorAttribute. The trick will be how to know if a controller action was going to return JSON or not. This could be as simple as suffixing those method names such as GetCustomerDetailsJson.

ActionFilterAttribute: Where is the 'Cancel' property?

Whatever happened to the Cancel property on the ActionExecutingContext? How would one abort a RenderAction when using an ActionFilterAttribute or is there another way to skin this cat?
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
return;
}
base.OnActionExecuting(filterContext);
}
The code above continues to execute the Action it has been applied to despite exiting the OnActionExecuting operation?
--- Further To original post:
Thanks for the answers below, however, I don't think I have made the context clear enough, I am trying to invalidate the following call:
<% Html.RenderAction("Menu", "Shared", new { id = Model.OtherUserId }); %>
When a user is not authenticated this action should return nothing, I could easily put an 'if' block on the view, however, I would like to keep the rule in the controller.
This worked great Mattias the result is this:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Result = new EmptyResult();
return;
}
base.OnActionExecuting(filterContext);
}
No, you can not cancel a rendering from a action filter. There are many reasons that you should not do that. What would the client see? A error page? Nothing?
I guess you are building a authorize action filter that would render something else if you are not signed in. There is one in the framework already (AuthorizeAttribute) that redirects you to the login page if you are not signed in. The way that they do it in the framework is to change the result that is being executed (filterContext.Result = [[new result]];). If you don't like how it works you can build your own implementation.
If you still need to cancel the rendering or something like that you will need to build your own ActionResult and do whatever logic you need in the Execute method.
-- Update --
If you want to use render action you should just put the logic in the controller and return empty result if you are not signed in (there is a action result called "EmptyResult" in the framework). That kind of logic belongs in the controller action.
Mattias and rjarmstrong already anwswer question. Here is full code for filter and controller:
public class CancelFilter : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//before execution
var id = filterContext.RequestContext.HttpContext.Request.Params["id"];
if (id == "0")
{
filterContext.Result = new EmptyResult();
return;
}
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
//after execution
}
}
[CancelFilter]
public class HomeController : Controller
{
public ActionResult DoSome(string id)
{
return View();
}
...
}

Resources