Attribute to select Route/Action based on presence of Parameter - asp.net-mvc

I thought, but I'm not sure, that there were built-in Attributes we could decorate our Actions with, which would select/skip the Actions based on the presence of a Parameter or Model Bind Variable.
I was thinking something like this:
/* [Route("/Objectives"), MandatoryParam("CourseId")]
Or [Route("/Objectives", Required("CourseId")]
or something similar like below etc. */
[Route("/Objectives")]
[MandatoryParam("CourseId")]
public Action List<Objectives> FilteredByCourse(int courseId){
...
}
[Route("/Objectives")]
[MandatoryParam("CategoryId")]
public Action List<Objectives> FilteredByCategory(int categoryId){
...
}
The desired outcome being that /Objectives?courseId=5 and /Objectives?categoryId=15 would route to two different actions.
I can't seem to find the correct search terms to use for this - they end up with articles talking about Model Binding or basic Routing, but nothing specific to this scenario. Does this not exist?

You can do that through IActionConstraint extensibility point . Create a custom IActionConstraint :
public class RequiredFromQueryActionConstraint : IActionConstraint
{
private readonly string _parameter;
public RequiredFromQueryActionConstraint(string parameter)
{
_parameter = parameter;
}
public int Order => 999;
public bool Accept(ActionConstraintContext context)
{
if (!context.RouteContext.HttpContext.Request.Query.ContainsKey(_parameter))
{
return false;
}
return true;
}
}
Stitching it together via IParameterModelConvention:
public class RequiredFromQueryAttribute : FromQueryAttribute, IParameterModelConvention
{
public void Apply(ParameterModel parameter)
{
if (parameter.Action.Selectors != null && parameter.Action.Selectors.Any())
{
parameter.Action.Selectors.Last().ActionConstraints.Add(new RequiredFromQueryActionConstraint(parameter.BindingInfo?.BinderModelName ?? parameter.ParameterName));
}
}
}
Then apply to your route :
[Route("/Objectives")]
public IActionResult Action1([RequiredFromQueryAttribute] int courseId)
{
return View();
}
[Route("/Objectives")]
public IActionResult Action2([RequiredFromQueryAttribute]int categoryId)
{
return View();
}
Source code & article is from here .
The result : http://localhost:xxxx/Objectives?courseId=1 will be redirected to Action1 ;http://localhost:xxxx/Objectives?categoryId=1 will be redirected to Action2 ;http://localhost:xxxx/Objectives will get 404 error .

Related

WebAPI Model [ModelBinder] with interface class while specifying implementation

Is it possible to pass into the ModelBinder which implementation you want to use inline?
Given the following definitions:
public interface ISomeInterface
{
string MyString{get;set;}
}
public class SomeInterfaceImplementation_One : ISomeInterface
{
private string _MyString;
public string MyString
{
get {return "This is implementation One " + _MyString ; }
set { _MyString = value; }
}
}
public class SomeInterfaceImplementation_Two : ISomeInterface
{
private string _MyString;
public string MyString
{
get {return "This is implementation Two" + _MyString ; }
set { _MyString = value; }
}
}
Given this route in asp.net mvc core:
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder))]ISomeInterface SomeInterface)
{
//Return actionresult
}
I do not want a different ModelBinder class for each implementation rather I would like each route to specify which implementation inline.
So something like:
[UseImplementation(SomeInterfaceImplementation_One)]
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder))]ISomeInterface SomeInterface)
{
}
Or:
public ActionResult InterfaceWithInlineImplementation([ModelBinder(typeof(SomeBinder), ConcreteType = SomeInterfaceImplementation_Two )]ISomeInterface SomeInterface)
{
}
This way the SomeBinder class can access which implementation is being requested in the BindModelAsync method of SomeBinder : IModelBinder class.
public class SomeBinder : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder
{
public Task BindModelAsync(Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
string valueFromBody = string.Empty;
using (var sr = new StreamReader(bindingContext.HttpContext.Request.Body))
{
valueFromBody = sr.ReadToEnd();
}
if (string.IsNullOrEmpty(valueFromBody))
{
return Task.CompletedTask;
}
var settings = new JsonSerializerSettings()
{
ContractResolver = new InterfaceContractResolver(), // Need requested implementation from InterfaceWithInlineImplementation() method
};
var obj = JsonConvert.DeserializeObject(valueFromBody, [**Need Requested Implementation from Method**], settings);
bindingContext.Model = obj;
bindingContext.Result = ModelBindingResult.Success(obj);
return Task.CompletedTask;
}
Use generics.
public class SomeBinder<TConcreteType> : IModelBinder
{
}
Then your signature becomes
public ActionResult InterfaceWithInlineImplementation(
[ModelBinder(typeof(SomeBinder<SomeInterfaceImpelemtation_One>))]ISomeInterface SomeInterface)
Then deserialization is:
JsonConvert.DeserializeObject<TConcreteType>(json)
However based on your last comment it sounds like you just need to Prevent overposting instead of this convoluted model binding.
So lets say the client knows that the server implementation has security methods and tries to match the signature hoping everything get deseriazled for example. Its being explicit as to what you're expecting. And you're explicitly expecting only the contract definition and nothing more.
Excerpt:
Mass assignment typically occurs during model binding as part of MVC. A simple example would be where you have a form on your website in which you are editing some data. You also have some properties on your model which are not editable as part of the form, but instead are used to control the display of the form, or may not be used at all.
public class UserModel
{
public string Name { get; set; }
public bool IsAdmin { get; set; }
}
So the idea here is that you only render a single input tag to the markup, but you post this to a method that uses the same model as you used for rendering:
[HttpPost]
public IActionResult Vulnerable(UserModel model)
{
return View("Index", model);
}
However, with a simple bit of HTML manipulation, or by using Postman/Fiddler , a malicious user can set the IsAdmin field to true. The model binder will dutifully bind the value, and you have just fallen victim to mass assignment/over posting:
So how can you prevent this attack? Luckily there's a whole host of different ways, and they are generally the same as the approaches you could use in the previous version of ASP.NET. I'll run through a number of your options here.
Continue to article...

Custom Route constraint called multiple times or called when no constraint mentioned at all

I have written a custom route but found that its called for Action methods which have not specified the constraints in the attribute routing.
[RoutePrefix("api/v1/Orders")]
public class OrdersController : BaseController
{
[Route("{orderId}/archive")]
public HttpResponseMessage Put(Guid orderId, [FromBody]List<ComplexObject> c)
{
}
public HttpResponseMessage Get([FromUri]ComplexObject c)
{
}
[Route("{orderId:checkGuid(orderId,BadRequest)}/{personId:checkGuid(personId,BadRequest)}")]
public HttpResponseMessage Get(Guid orderId, Guid personId)
{
}
}
public class CheckGuidRouteConstraint : IHttpRouteConstraint
{
private readonly HttpStatusCode _statusCode;
private readonly string _parameterName;
private HttpStatusCode _default = HttpStatusCode.BadRequest;
public CheckGuidRouteConstraint(string parameterName,string statusCode)
{
if (!Enum.TryParse(statusCode, true, out _statusCode))
{
_statusCode = _default;
}
_parameterName = parameterName;
}
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
bool isRouteWrong = doSomeCheck();
if(isRouteWrong)
{
//throw custom exception with proper message
}
return true;
}
}
And added it in
var constraintResolver = new DefaultInlineConstraintResolver();
constraintResolver.ConstraintMap.Add("checkGuid", typeof(CheckGuidRouteConstraint));
config.MapHttpAttributeRoutes(constraintResolver);
Now when I access the Put() method, the route constaraint's Match() method is called more than 1 times and finally fails.
Please note that I haven't mentioned any route constraint in my Put() attribute route.
This way does not work if you have many custom IHttpRouteConstraint and controller with same methods. Seems WebAPI not parsing route parameters and just trying all of them before any of registered IHttpRouteConstraint return true. So, if one of them throwing exception it automatically stopping processing other constraints. If bad constrain was registered first you will get error response even this constraint not used in route template.
[HttpGet]
[Route("{code:enum(System.Net.HttpStatusCode)}")]
public IHttpActionResult Enum(string code)
{
return Ok();
}
[HttpGet]
[Route("{period:values(days|weeks|months)}")]
public IHttpActionResult Value(string period)
{
return Ok();
}
for example if 'enum' constraint has similar implementation same as yours and registered first
constraintsResolver.ConstraintMap.Add("enum", typeof(EnumConstraint));
constraintsResolver.ConstraintMap.Add("values", typeof(ValuesConstraint));
config.MapHttpAttributeRoutes(constraintsResolver);
Value action will never return Ok.

ViewBag in static method of controller

I am new to mvc and I load ViewBag in a method of controller as,
HomeController: Controller
{
Public ActionResult Index()
{
loadViewBag();
return View();
}
public void loadViewBag()
{
ViewBag.aaa = "something";
}
}
It works fine.
What is my problem is, Now I want to call loadViewBag() method form another controller( say Account) so that I can reuse same method and need to make loadViewBag() method static due to some static variables as:
public static void loadViewBag()
If I make loadViewBag method static, there appear error on ViewBag " An object reference is required for the non-static field, method, or property 'System.Web.Mvc.ControllerBase.ViewBag.get' ".
Is there any solution/suggestion.
Thank You.
Just make it an extension method of ControllerBase e.g.
public static void ControllerExt
{
public static void LoadViewBag(this ControllerBase controller)
{
controller.ViewBag.aaa = "something";
...
}
}
That way you can use it in any controller
public class HomeController : Controller
{
public ActionResult Index()
{
this.LoadViewBag();
return View();
}
}
public class AccountController : Controller
{
public ActionResult Index()
{
this.LoadViewBag();
return View();
}
}
If its only specific to some controllers then it would be more flexible to pass the ViewBag property in e.g.
public static class ControllerHelper
{
public static void LoadViewBag(dynamic viewBag)
{
viewBag.aaa = "something";
}
}
public class HomeController : Controller
{
public ActionResult Index()
{
ControllerHelper.LoadViewBag(ViewBag);
return View();
}
}
ViewBag is a property of your controller (more specifically of ControllerBase), and since a static method has no knowledge of a class instance, you can't access it.
You could pass the controller instance to the method if you want to use a static method or even make it an extension method, but depending on your problem, this solution could be sub-optimal. You may be able to get a better answer if you add more details to your question.
Public ActionResult Index()
{
this.loadViewBag();
return View();
}
public static void loadViewBag(this ControllerBase target)
{
target.ViewBag.aaa = "something";
}
Do you need that to allow different controllers/views to use some common properties?
Then I'd rather recommend a common base controller, while also wrapping ViewBag code into type safe properties (to let the compiler control the data consistency - as you know, ViewBag is not type safe, so any typos and data mismatches won't be noticed until the code gets executed).
1. Introduce a common controller with those wrapper properties
public abstract class MyBaseController : Controller
{
internal long CurrentUserId
{
get { return ViewBag.CurrentUserId; }
set { ViewBag.CurrentUserId = value; }
}
internal Role CurrentUserRole
{
get { return ViewBag.CurrentUserRole; }
set { ViewBag.CurrentUserRole = value; }
}
...
}
Thus, your inherited controllers could simply set the properties - or, with lots of common code just introduce a method in your base controller - similar to what you already have.
2. Introduce a common view class with those wrapper properties
public abstract class MyBaseViewPage<T> : WebViewPage<T>
{
public string Title
{
get { return (string)ViewBag.Title; }
set { ViewBag.Title = value; }
}
public long CurrentUserId
{
get { return (long)ViewBag.CurrentUserId; }
}
public Role CurrentUserRole
{
get { return ViewBag.CurrentUserRole; }
}
}
public abstract class MyBaseViewPage : MyBaseViewPage<dynamic>
{
}
and update web.config to let MVC know you're using a custom base view:
<configuration>
...
<system.web.webPages.razor>
...
<pages pageBaseType="MyRootNamespace.Views.MyBaseViewPage">
...
</pages>
</system.web.webPages.razor>
Now you can use them as normal properties in your controllers and views.

Routing to the actions with same names but different parameters

I have this set of routes:
routes.MapRoute(
"IssueType",
"issue/{type}",
new { controller = "Issue", action = "Index" }
);
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults
);
Here is the controller class:
public class IssueController : Controller
{
public ActionResult Index()
{
// todo: redirect to concrete type
return View();
}
public ActionResult Index(string type)
{
return View();
}
}
why, when i request http://host/issue i get The current request for action 'Index' on controller type 'IssueController' is ambiguous between the following action methods:
I expect that first one method should act when there is no parameters, and second one when some parameter specified.
where did i made mistake?
UPD: possible duplicate: Can you overload controller methods in ASP.NET MVC?
UPD 2: due to the link above - there is no any legal way to make action overloading, is it?
UPD 3: Action methods cannot be overloaded based on parameters (c) http://msdn.microsoft.com/en-us/library/system.web.mvc.controller%28VS.100%29.aspx
I would have one Index method that looks for a valid type variable
public class IssueController : Controller
{
public ActionResult Index(string type)
{
if(string.isNullOrEmpty(type)){
return View("viewWithOutType");}
else{
return View("viewWithType");}
}
}
EDIT:
How about creating a custom attribute that looks for a specific request value as in this post StackOverflow
[RequireRequestValue("someInt")]
public ActionResult MyMethod(int someInt) { /* ... */ }
[RequireRequestValue("someString")]
public ActionResult MyMethod(string someString) { /* ... */ }
public class RequireRequestValueAttribute : ActionMethodSelectorAttribute {
public RequireRequestValueAttribute(string valueName) {
ValueName = valueName;
}
public override bool IsValidForRequest(ControllerContext controllerContext, MethodInfo methodInfo) {
return (controllerContext.HttpContext.Request[ValueName] != null);
}
public string ValueName { get; private set; }
}
I ran into a similar situation where I wanted my "Index" action to handle the rendering if I had an ID specified or not. The solution I came upon was to make the ID parameter to the Index method optional.
For example, I originally tried having both:
public ViewResult Index()
{
//...
}
// AND
public ViewResult Index(int entryId)
{
//...
}
and I just combined them and changed it to:
public ViewResult Index(int entryId = 0)
{
//...
}
You can do it using an ActionFilterAttribute that checks the parameters using reflection (I tried it) but it's a bad idea. Each distinct action should have its own name.
Why not just call your two methods "Index" and "Single", say, and live with the limitation on naming?
Unlike methods that are bound at compile time based on matching signatures, a missing route value at the end is treated like a null.
If you want the [hack] ActionFilterAttribute that matches parameters let me know and I'll post a link to it, but like I said, it's a bad idea.
All you have to do is mark your second Action with [HttpPost]. For instance:
public class IssueController : Controller
{
public ActionResult Index()
{
// todo: redirect to concrete type
return View();
}
[HttpPost]
public ActionResult Index(string type)
{
return View();
}
}

ASP .NET MVC Securing a Controller/Action

If I want only administrator to access the action called "ManagerUser", I know I can do this:
[Authorize( Roles = Constants.ROLES_ADMINISTRATOR )]
public ActionResult ManageUser( string id )
{
}
What if I want to give everyone access except to administrator? I do not want to write all roles up there on function :|.
Any recommendations/way outs?
You can create your own custom Authorize attribute, something like "AuthorizeAllExceptAdmin." Within that class you would simply need to check whether or not the current user was an admin, and if they were reject it, otherwise accept it.
Here's a good tutorial, but you'll probably end up with something like:
public class AuthorizeAllExceptAdmin : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !httpContext.User.IsInRole(Constants.ROLES_ADMINISTRATOR);
}
}
Then your controller method becomes:
[AuthorizeAllExceptAdmin]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
Here's an example of the custom attribute that takes in roles to deny.
public class DoNotAuthorize : AuthorizeAttribute
{
private IEnumerable<string> _rolesToReject;
public DoNotAuthorize(IEnumerable<string> rolesToReject)
{
_rolesToReject = rolesToReject;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
foreach (var role in _rolesToReject)
{
if (httpContext.User.IsInRole(role))
return false;
}
return true;
}
}
Then your controller method becomes:
[DoNotAuthorize(new [] {Constants.ROLES_ADMINISTRATOR})]
public ActionResult SomethingOnlyNonAdminsCanDo()
{
}
I would put some thought into it before choosing one of the above options. If you think you'll have several methods (or entire controllers) with similar authorization requirements (i.e, several actions an admin can not perform) then I would stick with the non-parameterized custom attribute. This way, you can evolve them all together (by only changing the custom attribute) later on. For example, maybe later on you want admins to be able to go into a special mode where they can perform these actions.
Alternatively, if the autorization is more varied amongst the actions, then using the parameterized list makes sense, since they'll evolve relatively independently.
Besides creating a custom AuthorizeAttribute, suggested by manu, you could use PrincipalPermission, with a Deny-SecurityAction:
[PrincipalPermission(SecurityAction.Deny, Role="Administrator")]
In my app I don't use roles so I have to query the database to determine whether the user has access or not. The benefits of the code below is that you can redirect the user to a certain action very easily. I explained the code in my blog post at http://blog.athe.la/2009/12/implementing-permission-via-windows-authentication-in-asp-mvc-using-action-filters/
public class DatabaseRepository()
{
private readonly DatabaseDataContext db = new DatabaseDataContext();
public bool UserHasPermission(string userLogon) {
return (from permission this.db.Permissions
where permission.HasPermissionSw == true
select permission).Contains(userLogon);
}
}
public class UserHasPermission: ActionFilterAttribute
{
private readonly DatabaseRepository databaseRepository = new DatabaseRepository();
private readonly string redirectAction;
public UserHasPermission(string redirectTo)
{
this.redirectAction = redirectTo;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
string userLogon = filterContext.HttpContext.User.Identity.Name;
if (!this.databaseRepository.UserHasPermission(userLogon))
{
string routeController = filterContext.Controller.ControllerContext.RouteData.Values["controller"];
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { controller = routeController, action = this.redirectAction }));
}
}
}
Your controller would then look something like this:
[UserHasPermission("NoAccess")]
public ActionResult SecretArea()
{
// run all the logic
return View();
}
public ActionResult NoAccess()
{
return View();
}

Resources