I need to transform some url parameters while creating link on server side.
Example:
#html.ActionLink("text","index","Home",null,new { id=Model.Id });
Now i have to transform id parameter so i can simply convert it and pass it into object objectRoute parameter or i can simply override ActionLink.But problem is that i have to make refactor on whole project.
So i am looking a way to intercepting mechanism or handler mechanism.
Is there any solution for this ?
You could try using an ActionFilterAttribute:
public class ConversionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
var idValue = filterContext.RouteData.Values["id"];
var convertedIdValue = ConvertId(idValue);
var newRouteValues = new RouteValueDictionary(filterContext.RouteData.Values);
newRouteValues["id"] = convertedIdValue;
filterContext.Result = new RedirectToRouteResult(newRouteValues);
}
}
Then you'll need to apply the attribute to the action where you want this to happen:
[Conversion]
public ActionResult Index(int id)
{
// Your logic
return View();
}
Related
I am learning ASP.Net MVC 5 and I came up to a case where I need to restrict access to controller action under some situations. Suppose I have 5 actions in my controller and I want to restrict two of them in certain scenarios.How to achieve this I know we have inbuilt attributes like [Authorize]. Can I create user-defined restrictions to the controller actions.
Something like:
[SomeRule]
public ActionResult Index()
{
return View();
}
And if I could create a function or class named "SomeRule" and then add some rules there.Can I add a function/method/class where I can add some logic and restrict the access and redirect to a genreal page if condition does not match. I am a beginner please guide me.
What you'd want to do is create a custom Action Filter, which would allow you to define custom logic within your action to determine if a given user could / could not access the decorated action:
public class SomeRuleAttribute : System.Web.Mvc.ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Define some condition to check here
if (condition)
{
// Redirect the user accordingly
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOn" } });
}
}
}
You can also extend these even further and set properties on them as well if you need to apply some values to check against where the attribute is defined:
public class SomeRule: ActionFilterAttribute
{
// Any public properties here can be set within the declaration of the filter
public string YourProperty { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
// Define some condition to check here
if (condition && YourProperty == "some value")
{
// Redirect the user accordingly
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Account" }, { "action", "LogOn" } });
}
}
}
This would look like the following when used:
[SomeRule(YourProperty = "some value")]
public ActionResult YourControllerAction()
{
// Code omitted for brevity
}
What are my options for passing variables into my MyCustomAttribute class?
Currently the class is defined as:
public class MyCustomAttribute : ActionFilterAttribute
{
public string MyIDParam { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myID = filterContext.ActionParameters[MyIDParam] as Int32?;
// logic
}
}
Now in my controller I have this:
[MyCustomAttribute(MyIDParam = "id")]
public ActionResult Report(int id)
{
Guid userId = GetUserInfo();
// logic
}
In this case I would like to be able to pass id and userId to MyCustomAttribute class.
Is this possible? Doesn't the MyCustomAttribute get executed before the contents of the Report method?
What are my options for passing variables into my MyCustomAttribute class?
Well you're writing a custom ActionFilter which MVC passes the ActionExecutingContext. That variable contains a ton of information at your disposal.
public class MyCustomAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var myID = filterContext
.RouteData["id"] as int?
var user = filterContext.Controller.User;
}
}
In this case I would like to be able to pass "id" and "userId" to MyCustomAttribute class.
No you can't pass them in, you have to retreive them from the ActionExecutingContext.
Doesn't the MyCustomAttribute get executed before the contents of the Report method?
Maybe? Depends on which method your override.
The ActionFilterAttribute has a few different points at which it executes. Here is a break down:
ActionFilterAttribute.OnActionExecuting() // Action Filter
public ActionResult Report(int id) // MVC Action is Called
ActionFilterAttribute.OnActionExecuted() // Action Filter
ActionFiltrAttribute.OnResultExecuting() // Action Filter
ActionResult.Execute() // MVC ActionResult Execute is Called
ActionFilterAttribute.OnResultExecuted() // Action Filter
You can see that these methods have been available to access to all this data all the way back since asp.net-mvc version 1.
You can gain that ability by adding support for referencing action parameters within your custom attribute.
[MyCustomAttribute("The parameter for id is {id}")]
public ActionResult Report(int id)
{
Guid userId = GetUserInfo();
... logic ...
}
At runtime, your custom attribute can substitute the value of the referenced parameter for the placeholder.
So for doing this ability, you need only to hook into the ActionExecuting method to grab those actions parameters:
private IDictionary<string, object> _parameters;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_parameters = filterContext.ActionParameters;
base.OnActionExecuting(filterContext);
}
Then inside your OnActionExecuted method, you can create a variable to processed field. Then you can go through each parameter in the dictionary and substitute in the parameter's value:
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var data = MyIDParam;
foreach(var kvp in _parameters)
{
data = data.Replace("{" + kvp.Key + "}",
kvp.Value.ToString());
}
}
I am using SelfHost/Katana/Owin for my WebServer.
I have a Controller in there that I want to enable/disable by code depending on a command line argument at launch time.
Is there a simple way of doing this in MVC?
Right now I'm thinking in the Controller's code to return HTTP-NotFound status code when this config is disabled, any better ideas?
You could decorate your controller with a custom Action Filter.
public class ConfigActionFilter : ActionFilterAttribute {
// This method is called before a controller action is executed.
public override void OnActionExecuting(ActionExecutingContext filterContext) {
if(someConfigSetting) {
filterContext.Result = new RedirectToRouteResult("Error", someRouteValues);
}
}
...
}
Usage:
[ConfigActionFilter]
public class MyController : Controller {
...
}
More here.
You could perform a redirecttoaction that will take users to a different controller explaining what's happening.
ie:
public class MyController : Controller {
private IConfigReader _configReader;
public MyController(IConfigReader configReader){ //not sure if you're doing dependency injection or not, so I'm injecting it
_configReader = configReader;
}
public ActionResult Index() {
if(!_configReader.IsEnabled) {
return RedirectToAction("Denied", "AuthController");
}
//etc
return View();
}
}
You could create an attribute, apply it to the controller and set a static property on that attribute at startup time, and deny access (or return "Not found") when the flag is set.
Alternatively, you can implement a custom AuthorizationAttribute and put it on your controller
public class AuthorizationAdminAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (/*check for argument*/)
{
return false;
}
return true;
}
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
filterContext.Result = new HttpNotFoundResult();
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
I'm using a custom filter to validate the content type, like:
public override void OnActionExecuting(HttpActionContext httpActionContext)
{
List<String> errors = new List<String>();
// a
if (httpActionContext.Request.Content.Headers.ContentType.MediaType == "application/json")
{
}
else
{
errors.Add("Invalid content type.");
}
// more checks
}
The above code is working fine, but the validation should check the request http verb, because it should validate the content type only for put or post. I don't want to remove the custom filter from httpget actions because I have more checks inside it, and I don't want to split the filter in two parts, meaning I have to check the http verb inside the filter, but I can't find how.
Any tips?
You can get the method type (post or put) from this:
public override void OnActionExecuting(HttpActionContext actionContext)
{
string methodType = actionContext.Request.Method.Method;
if (methodType.ToUpper().Equals("POST")
|| methodType.ToUpper().Equals("PUT"))
{
// Your errors
}
}
If you need to get the HTTP Method of the request being validated by the filter, you can inspect the Method property of the request:
var method = actionContext.Request.Method;
I would recommend however that you break the filter apart, as you are quickly headed towards a big ball of mud scenario.
You really should be using the standard HTTPVerb attributes above your controller methods:
[HttpGet]
[HttpPut]
[HttpPost]
[HttpDelete]
[HttpPatch]
MVC Controllers for multiple:
[AcceptVerbs(HttpVerbs.Get, HttpVerbs.Post)]
WebAPI Controlelrs for multiple
[AcceptVerbsAttribute("GET", "POST")]
In the constructor of the action filter, you can pass in options/named parameters that will set the settings for the OnActionExecuting logic. Based on those settings you can switch up your logic.
public class MyActionFilterAttribute : ActionFilterAttribute
{
private HttpVerbs mOnVerbs;
public MyActionFilterAttribute(HttpVerbs onVerbs)
{
mOnVerbs = onVerbs;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var currentVerb = filterContext.HttpContext.Request.HttpMethod;
if (mOnVerbs.HasFlag(HttpVerbs.Post)) { }
else if (mOnVerbs.HasFlag(HttpVerbs.Get)) { }
base.OnActionExecuting(filterContext);
}
}
[MyActionFilter(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult Index()
{
}
I'm trying to create a custom ActionFilter which operates on a set of parameters that would be passed to it from the controller.
So far, my customer ActionFilter looks like this:
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
I know I still need to check for nulls, etc. but I can't figure out why gr and memberGuid aren't successfully being passed. I'm calling this Filter like this:
[CheckLoggedIn(gr = genesisRepository, memberGuid = md.memberGUID)]
public ActionResult Home(MemberData md)
{
return View(md);
}
genesisRepository and md are being set in the controller's constructor.
I'm not able to get this to compile. The error I get is:
Error 1 'gr' is not a valid named attribute argument because it is not a valid attribute parameter type
Error 2 'memberGuid' is not a valid named attribute argument because it is not a valid attribute parameter type
I double checked that gr and memberGuid were the same types as genesisRepority and md.memberGUID, What is causing these errors?
Solution
Thanks to jfar for offering a solution.
Here's the Filter I ended up using:
public class CheckLoggedIn : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var thisController = ((MemberController)filterContext.Controller);
IGenesisRepository gr = thisController.GenesisRepository;
Guid memberGuid = ((MemberData)filterContext.HttpContext.Session[thisController.MemberKey]).MemberGUID;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier >= bottomMember.Role.Tier)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(
new {
controller = "Member",
action = "Login"
}));
}
base.OnActionExecuting(filterContext);
}
}
This is a way to make this work. You have access to the ControllerContext and therefore Controller from the ActionFilter object. All you need to do is cast your controller to the type and you can access any public members.
Given this controller:
public GenesisController : Controller
{
[CheckLoggedIn()]
public ActionResult Home(MemberData md)
{
return View(md);
}
}
ActionFilter looks something like
public class CheckLoggedIn : ActionFilterAttribute
{
public IGenesisRepository gr { get; set; }
public Guid memberGuid { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
/* how to get the controller*/
var controllerUsingThisAttribute = ((GenesisController)filterContext.Controller);
/* now you can use the public properties from the controller */
gr = controllerUsingThisAttribute .genesisRepository;
memberGuid = (controllerUsingThisAttribute .memberGuid;
Member thisMember = gr.GetActiveMember(memberGuid);
Member bottomMember = gr.GetMemberOnBottom();
if (thisMember.Role.Tier <= bottomMember.Role.Tier)
{
filterContext
.HttpContext
.Response
.RedirectToRoute(new { controller = "Member", action = "Login" });
}
base.OnActionExecuting(filterContext);
}
}
Of course this is assuming the ActionFilter isn't used across multiple controllers and you're ok with the coupling. Another Option is to make a ICheckedLoggedInController interface with the shared properties and simply cast to that instead.
You can only use constant values for attribute properties; see a this page for a full explanation.
Attributes are essentially metadata added to a type. They can only use const values, instead of instance variables. In your case you are tying to pass in your instance variables of genisisRepository, etc. This will fail to compile as they are not compile time constants.
You should look into Dependency Injection for Action Filters to achieve this, typically using an IoC container.
Also, if your ActionFilter is performing a post ActionResult action, such as OnActionExecuted, you could probably get away with storing something in the route data:
public ActionResult Index()
{
ControllerContext.RouteData.DataTokens.Add("name", "value");
return View();
}