is it possible to switch request validation in the view.
I would like to switch it off in the view because of this bit of code in the base controller
protected override void Execute(System.Web.Routing.RequestContext requestContext)
{
if (!this.CurrentStore.IsDefault)
{
IStoreRepository storeRepo = ObjectFactory.GetInstance<IStoreRepository>();
IStoreEntity store = storeRepo.GetById(this.CurrentStore.Id);
this.CurrentStore = store;
}
base.Execute(requestContext);
}
it fails in base.Execute(requestContext);
N.B I do not wish to switch it off for my entire site. I would like to switch it off in a few action methods.
In MVC you don't do it at the view level, you do it at the controller or a method of the controller level. You can use ValidateInput attribute for that, for example:
public class HomeController : Controller
{
[ValidateInput(false)] // prevent validation on this method
public ActionResult Index()
{
return View();
}
}
Related
I tried to do some simple thing. I wanted to have different action name and different method name:
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
}
Look that there is no Route attribute on the controller.
In such case tag helper asp-action works perfectly. But I thought that my action dosth would be placed in: localhost/Super/dosth
But it was not. So I figured it out that I probably should set the route for the whole controller, like this:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
But now asp-action stopped working. For example:
<a asp-controller="Super" asp-action="Register">
creates anchor to: localhost/Super and not to: localhost/Super/Register
When I remove Route tag from controller it works again.
My mappings are configured as standard says:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
So, how come asp-action does not work when there is a Route attribute on the whole controller
Applying [Route] attribute on a controller enables attribute routing for all controller methods. So, by doing so you force yourself to provide the route for every method (in one way or another).
With [Route("[controller]")] the base route template for your controller actions is just the controller name, so if you have multiple actions without [Route] or [HttpGet] (and other HTTP verbs) attributes applied on them:
[Route("[controller]")]
public class SuperController: Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
...you get yourself a AmbiguousMatchException because multiple controller actions will be matching same route:
/Super
You can either explicitly specify the route for every action:
[Route("[controller]")]
public class SuperController: Controller
{
[HttpGet("dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
[HttpGet("Register")]
public IActionResult Register()
{
return View();
}
}
or specify action name as an part of expected route already on controller level:
[Route("[controller]/[action]")]
public class SuperController : Controller
{
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
public IActionResult Register()
{
return View();
}
}
then, you don't have to specify the routes for actions, because you applied a route template on a controller level. Your actions will inherit that route template.
But, be aware that in order to overwrite it you'll have to overwrite it all, otherwise you'll append to the route template.
[HttpGet("/[controller]/dosth")]
public IActionResult DoSomethingWithThoseParameters(int id, string token)
{
}
Read more about routing in official documentation.
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());
}
}
I have a Home Controller with these actions:
public class HomeController : Controller
{
[Authorize]
public ActionResult Index()
{
return View();
}
public ActionResult Logoff()
{
HttpContext.Session.Remove("LogonTicket");
HttpContext.Session.Remove("PID");
return View("Index");
}
Now, when I logoff using the Logoff Action, I want the Authorize attribute of the Index to take effect but it doesn't when I return the Index View in the Logoff Action.
How would I handle this?
I'm using a custom MembershipProvider and not sure how to put Logoff functionality in it.
You should redirect, not return the view directly:
public ActionResult Logoff()
{
HttpContext.Session.Remove("LogonTicket");
HttpContext.Session.Remove("PID");
return RedirectToAction("Index");
}
Returning the view directly sends the view's markup to the client right away and the Authorize filter does not get invoked at all.
Done in the AccountController Logoff Action. They are public "globals" in a base controller now, too.
public ActionResult LogOff()
{
FormsAuthentication.SignOut();
LogonTicket = null;
ParticipantID = null;
return RedirectToAction("Index", "Home");
}
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");
}
}
If the authorize attribute has been applied to the controller is it possible to allow unauthorized access to an action/viewresult inside that controller?
Say for example I didn't want authorization to occur on Test2 in the following:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
public ViewResult Test2()
{
return View();
}
}
Thanks in advance.
No, this is not possible. You will have to apply the Authorize attribute on the Test action and not on the controller. Another possibility is to put the Test2 action on another controller which is not decorated with this attribute.
Back in MVC 3 it appears it was indeed not possible to do (as mentioned Darin Dimitrov), but if anyone using MVC 4 (and up) comes across this question, he\she should be able to use AllowAnonymous filter to achieve the result. So the code would become:
[Authorize]
public class TestController : Controller
{
public ViewResult Test()
{
return View();
}
[AllowAnonymous]
public ViewResult Test2()
{
return View();
}
}