Consider the following situation. In my controller I have:
public ActionResult Edit(int id)
{
...
}
[HttpPost]
public ActionResult Edit(Model model)
{
...
}
Also I have an ActionFilterAttribute, which applies to some other actions of the same controller. In the OnActionExecuting method I need to get the ActionDescriptor of the HttpGet Edit action:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// as this is called from the same controller, I use
ActionDescriptor action = filterContext.ActionDescriptor.ControllerDescriptor
.FindAction(filterContext.Controller.ControllerContext, "Edit");
...
}
The problem is, that the FindAction method returns "reference" to the HttpPost Edit action in case of POST requests. How do I make it to look only for HttpGet actions?
You can use maybe attribute ?
public class FooAttribute
{
}
[FooAttribute]
public ActionResult Edit(int id)
{
...
}
you can check OnActionExecution;
example;
var isHasAttribute= filterContext.ActionDescriptor.IsDefined(typeof(FooAttribute), true);
Related
I want for the Attributes associated to a controller action to be called during the test.
E.g. Code
[TestMethod]
public void UserRegistersWithNonMatchingPasswords()
{
var model = GetModel();
var controller= GetController();
controller.Register(model); //This is an ActionResult method
AccountRepository
.DidNotReceive()
.IsAccountExists(Arg.Any<string>());
AccountRepository
.DidNotReceive()
.CreateUser(Arg.Any<string>(), Arg.Any<string>());
}
Now the register method looks like
[HttpPost]
[ValidateModel]
public ActionResult Register(Model model)
{
//The validate model attribute checks the ModelState.IsValid and returns if it's not valid, so any code in the method shouldn't run because of the failed validation.
IsAccountExists();
CreateUser();
}
The ValidateModel attribute code (It's from Sitecore Habitat framework)
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var viewData = filterContext.Controller.ViewData;
if (!viewData.ModelState.IsValid)
{
filterContext.Result = new ViewResult
{
ViewData = viewData,
TempData = filterContext.Controller.TempData
};
}
}
}
However, it's the MVC framework that calls these attributes, so I wanted to know how to make NSubsitute call it, because it ignores attributes. Thanks.
I have 2 controllers Home with
public class HomeController : Controller
{
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some irrelevant stuff
base.OnActionExecuting(filterContext);
}
public ActionResult Index()
{
return View();
}
}
and Service with
public ActionResult Confirm()
{ return RedirectToAction("Index", "Home");}
And one ActionFilterAttribute with OnActionExecuting method
public class InvitationModeAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// do some stuff
base.OnActionExecuting(filterContext);
}
}
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new InvitationModeAttribute());
}
}
When I go to localhost/Service/Confirm , OnActionExecuting is fired, but then when RedirectToAction is called, OnActionExecuting is not fired.
How can I catch this after RedirectToAction?
Thanks
Refer this For More clarity
First of all
Remove OnActionExecuting method in controller level
public class HomeController : Controller
{
[InvitationModeAttribute]
public ActionResult Index()
{
return View();
}
}
2nd Controller
public class ServiceController : Controller
{
[InvitationModeAttribute]
public ActionResult Confirm()
{
return RedirectToAction("Index", "Home");
}
}
From MSDN
Scope of Action Filters
In addition to marking individual action methods with an action
filter, you can mark a controller class as a whole with an action
filter. In that case, the filter applies to all action methods of that
controller. Additionally, if your controller derives from another
controller, the base controller might have its own action-filter
attributes. Likewise, if your controller overrides an action method
from a base controller, the method might have its own action-filter
attributes and those it inherits from the overridden action method. To
make it easier to understand how action filters work together, action
methods are grouped into scopes. A scope defines where the attribute
applies, such as whether it marks a class or a method, and whether it
marks a base class or a derived class.
How to override an action method in a controller? Can anyone explain with a small example.
And one more thing to ask , can we do this without virtual keyword?
As far as i m understanding your question these are the answers :
First Answer :
it's not possible to have two controller actions with the same name but with a different result also:
For example:
ActionResult YourAction() { ... }
FileContentResult YourAction() { ... }
In MVC you can also do this :
[HttpGet]
[ActionName("AnyAction")]
ActionResult YourAction(firstModel model1) { ... }
[HttpPost]
[ActionName("AnyAction")]
FileContentResult YourAction(secondModel model1) { ... }
The main idea here is that you can use the ActionNameAttribute to name several action methods with the same name.
----------------------------------------------------------------OR--------------------------------------------------------------
Second Answer :
[NonAction]
public override ActionResult YourAction(FormCollection form)
{
// do nothing or throw exception
}
[HttpPost]
public ActionResult YourAction(FormCollection form)
{
// your implementation
}
You can do this the same as how the Filters will hook into it when you use filters in an mvc solution
public override void OnActionExecuting(ActionExecutingContext context)
{
if (Request.Headers.TryGetValue("api-key", out var value))
{
///
}
base.OnActionExecuting(context);
}
I have a controller called HotelsController to insert and edit hotels.
It has the following setup (method implementation removed for simplicity):
[RoutePrefix("{member_id:int}/hotels")]
public class HotelsController : ApplicationController
{
[Route("delete/{id:int}", Name = NamedRoutes.HotelDelete)]
public ActionResult Delete(int id)
{
}
[Route("new", Name = NamedRoutes.HotelNew)]
public ActionResult New()
{
}
[HttpPost]
[ValidateInput(false)]
public ActionResult New(HotelDataEntry hotel)
{
}
[Route("edit/{id:int}", Name = NamedRoutes.HotelEdit)]
public ActionResult Edit(int id)
{
}
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(HotelDataEntry hotel)
{
}
}
As you can see the following routes are using attribute routing:
Delete
New (without parameters)
Edit (without parameters)
The following routes use no attribute routing:
New (with parameters)
Edit (with parameters)
The routing is setup in Global.asax.cs as follows:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.IgnoreRoute("{resource}.aspx/{*pathInfo}");
routes.IgnoreRoute("{resource}.ashx/{*pathInfo}");
routes.IgnoreRoute("{resource}.asmx/{*pathInfo}");
routes.MapMvcAttributeRoutes();
routes.MapRoute(
Routen.Standard.ToString(),
"{member_id}/{controller}/{action}/{id}",
new { action = "browse", id = UrlParameter.Optional },
new { id = AllowedIdsRegExOptional }
);
}
Problem: Attribute routing works. I can call the Edit action with http://localhost:54868/301011/hotels/edit but the form on that page should post to the same uri and call the action that uses no attribute routing. But instead the action using the attribute based routing is called again. Why?
The form is supplied with method="post". Do you have any idea why the convention based route is not used? Thank you for your help.
Edit: I tried to add [HttpGet] in front of the attribute-routed New and Edit actions. The result is that on posting the form ASP.NET shows an error that the route is invalid. So for some reasons, the convention based routing is not working on the controller.
It seems that you cannot use both (attribute-based and convention-based) routing techniques in the same controller.
So what I did to resolve the issue is to add attribute-based routes to the two "unreachable" action methods. The route of these methods is the same as the route of the actions with the same name, but the name of the route is different (since route-names must be unique).
[RoutePrefix("{member_id:int}/hotels")]
public class HotelsController : ApplicationController
{
[Route("delete/{id:int}", Name = NamedRoutes.HotelDelete)]
public ActionResult Delete(int id)
{
}
[Route("new", Name = NamedRoutes.HotelNew)]
public ActionResult New()
{
}
[HttpPost]
[ValidateInput(false)]
[Route("new", Name = NamedRoutes.HotelNewPost)]
public ActionResult New(HotelDataEntry hotel)
{
}
[Route("edit/{id:int}", Name = NamedRoutes.HotelEdit)]
public ActionResult Edit(int id)
{
}
[HttpPost]
[ValidateInput(false)]
[Route("edit/{id:int}", Name = NamedRoutes.HotelEditPost)]
public ActionResult Edit(HotelDataEntry hotel)
{
}
}
I have a controller that inherits from a base controller. Both have an edit (post) action which take two arguments:
On Base controller:
[HttpPost]
public virtual ActionResult Edit(IdType id, FormCollection form)
And in the derived controller:
[HttpPost]
public ActionResult Edit(int id, SomeViewModel viewModel)
If I leave it like this I get an exception because there is an ambiguous call. However, I can't use override on the derived action, because the method signatures don't exactly match. Is there anything I can do here?
As addition to Developer Art's answer a workaround would be:
leave the base method as it is and in your derived class implement the base method and annotate it with [NonAction]
[NonAction]
public override ActionResult Edit(IdType id, FormCollection form)
{
// do nothing or throw exception
}
[HttpPost]
public ActionResult Edit(int id, SomeViewModel viewModel)
{
// your implementation
}
I'd chain it:
On Base controller:
[HttpPost]
public virtual ActionResult Edit(IdType id, FormCollection form)
And in the derived controller:
[HttpPost]
public virtual ActionResult Edit(IdType id, FormCollection form)
{
var newId = //some enum? transform
var boundModel = UpdateModel(new SomeViewModel(), form);
return Edit( newId, boundModel );
}
[HttpPost]
public ActionResult Edit(int id, SomeViewModel viewModel)
I haven't tested this, passing a Post method to another Post should work. There could be security implications this way.
That's all what you need to do
On Base controller :
adding virtual keyword
On Derived controller :
adding override keyword