.NET MVC 3 Custom Controller Attribute - asp.net-mvc-3

This may be pie in the sky but I'm wondering if the following could be accomplished with a custom controller attribute.
For a majority of my controllers, I will be passing in an URL parameter called "r" to each action within the controller. "r" is tied to a race id in the races table in my database.
What I would like to happen is that any time a controller action is invoked, it'll automatically check for the existence of "r", query the database to make sure "r" belongs to the logged in user and set a viewbag variable called ViewBag.RaceId equal to "r".
If any of those conditions aren't met, it'll redirect them back to the login page.
I'm trying to make my code as DRY as possible.
Any guidance would be greatly appreciated.

You could write a custom Authorize attribute:
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var isAuthorized = base.AuthorizeCore(httpContext);
if (isAuthorized)
{
var request = httpContext.Request;
// Fetch "r" from the route data or request
var r = request.RequestContext.RouteData.Values["r"]
?? request["r"];
var currentUser = httpContext.User.Identity.Name;
if (!CheckIfRBelongsToTheCurrentLoggedInUser(currentUser, r))
{
return false;
}
}
return isAuthorized;
}
}
Now all that's left is to decorate your controllers/actions with this custom attribute:
[MyAuthorize]
public ActionResult Foo()
{
//...
}
And if you wanted to put something into the ViewBag you could temporarily store it in the httpContext.Items inside the AuthorizeCore method in case of success and then override the OnAuthorization method as well and check for the presence of this item in the context. If it is present you could store it in the filterContext.Controller.ViewBag.

Related

How do you 'jump' out of a MVC ViewComponent to another controller?

I have a simple scenario where if a cart is empty, I'd like to redirect to another 'page'(controller) which states the cart is empty or just send them back to the shop.
Heres my code:
public async Task<IViewComponentResult> InvokeAsync()
{
CartFunctions cartf = new CartFunctions(_logger, AppSettings, _httpContextAccessor);
Cart c = new Cart();
c = cartf.GetShopingCart();
if (c.CartItems == null)
{
// How do I get out of here to a differnet Controller
}
return View(c.CartItems);
}
If it was a controller I could return RedirectToAction
but that is not available here.
I think the main problem is i need to either get out OR return a Cartitems and I can't find a way to do both.
In the good ole days it was simple with response.redirect("Empty.aspx") but now that everything is 'easier' in MVC, it takes days of research to do the simplest things.
A view component does not sound like the ideal option do this redirect. View components are ideal for rendering some partial views. For example, rendering your cart item count or content, using the view component is a good idea.
In your case, you want to redirect to another action method when the cart is empty. You may create an action filter to do that. You can apply it on action method level or controller level as needed.
public class CheckCartValues : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (yourIfConditionToCheckCartIsEmpty)
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(new {
controller = "Shop", action = "index" }));
}
base.OnActionExecuting(context);
}
}
You can apply it on the controller level
[CheckCartValues]
public class HomeController : Controller
{
}
Make sure you do not have it on the ShopController or you will get infinite redirects. You can also update the action filter code to not do the redirect when the current request is for the ShopController if needed. I will leave it up to you :)
If you want to use an attribute you can derive from ActionMethodSelectorAttribute https://msdn.microsoft.com/en-us/library/system.web.mvc.actionmethodselectorattribute.isvalidforrequest(v=vs.118).aspx#M:System.Web.Mvc.ActionMethodSelectorAttribute.IsValidForRequest(System.Web.Mvc.ControllerContext,System.Reflection.MethodInfo).
For example you could create an attribute named CartStatus(bool isEmpty) and apply the attribute to the method(s) that need to behave differently based on cart status. Then you have your conditional logic in exactly one place (this attribute) and you can reuse it across your application. Similar to #shyju's approach but instead of redirect you just return true/false from this method for the action that is appropriate.

How to trigger a method in all pages request in Yii?

In the header section of my website I want to show new message. I have a method that fetches new methods and return them. The problem is that header section is in thelayout section and I don't want to repeat one method in all of my controllers.
How to achieve this by not copying the method to all of my controllers? I want to trigger newMessages() method on every page request to gather new messages for logged in user. How to do this the right way?
In your controller overwrite the oOntroller class function beforeAction()
protected function beforeAction($event)
{
$someResult = doSomething()
if ($someResult == $someValue)
{
return true;
}
else
{
return true;
}
}
The return value can be used to stop the request dead in its tracks. So if it returns false, the controller action is not called, and vice versa().
References : http://www.yiiframework.com/doc/api/1.1/CController#beforeAction-detail
You can use import controller in another controller action. something like below
class AnotherController extends Controller
{
public function actionIndex()
{
Yii::import('application.controllers.admin.YourController'); // YourController is another controller in admin controller folder
echo YourController::test(); // test is action in YourController
}
}

Show validation messages on GET

We have a possibility that data loaded from a GET operation could be invalid for posting, and would like to be able to display the validation messages when the data is first loaded. The validation all takes place on server side using ValidationAttributes.
How can I force the validation summary to be displayed when the data is first loaded? I am guessing that I need to force errors into ModelState somehow, but I first need to get them out of the model class.
I ended up adding a validation method for the model class which adds errors to the ModelState. Then I created and added a custom ModelValidator and AssociatedValidatorProvider
for calling it during the normal validation that takes place during form binding. That way the controller actions that don't bind to the Model class directly can still have a call to the model's .Validate(ModelState) method to fake a validation. This approach works well for server-side-only validation.
UserInfo Model class:
private IEnumerable<RuleViolation> GetRuleViolations()
{
List<RuleViolation> violationList = new List<RuleViolation>();
if (String.IsNullOrWhiteSpace(FirstName))
violationList.Add(new RuleViolation("First Name is required.", FirstName"));
return violationList;
}
public void Validate(System.Web.Mvc.ModelStateDictionary ModelState)
{
foreach (RuleViolation violation in GetRuleViolations())
{
ModelState.AddModelError(violation.PropertyName, violation.ErrorMessage);
}
}
This is how it can be used directly from a controller action. In this action the Model class object is returned as part of the UserSearch model.
public ActionResult Search(UserSearch model)
{
if (this.ModelState.IsValid)
{
model.Search();
if (model.UserInfo != null )
{
model.UserInfo.Validate(ModelState);
}
}...
That is all I had to do for the particular use case I was working on. But I went ahead and completed the work to do "normal" validation on a postback: created a simple ModelValidator, with the Validate override looking like this. If you followed the above pattern in all of your Model classes you could probably reusue this for them, too.
public override IEnumerable<ModelValidationResult> Validate(object container)
{
var results = new List<ModelValidationResult>();
if (Metadata.Model != null)
{
UserInfoViewModel uinfo = Metadata.Model as UserInfoViewModel;
foreach (var violation in uinfo.GetRuleViolations())
{
results.Add(new ModelValidationResult
{
MemberName = violation.PropertyName,
Message = violation.ErrorMessage
});
}
}
return results;
}
Finally, extend AssociatedValidationProvider to return this ModelValidator and add it to the ModelValidationProviders collection in Application_Start. There is a writeup of this at http://dotnetslackers.com/articles/aspnet/Customizing-ASP-NET-MVC-2-Metadata-and-Validation.aspx#s2-validation
I don't know if understand what you need, but here is it...
run validation to display the validation summary when the form is loaded, using jquery
$(document).ready(function() {
$('#FormId').valid();
});

Global redirect based on logic in ASP.NET MVC3

I am building an ASP.NET MVC3 computer support ticketing portal.
There is a maintenance state, where it is best to forbid the users from interacting with EF/Database, to avoid "collisions" I am currently getting.
I have an IMaintenanceDispatcher that has a boolean property IsOnMaintenance set to true by the business logic, whenever a background logic puts the portal in that state.
I need to redirect client requests to a parking page for the time of maintenance.
Where do I place the logic that will check if the IsOnMaintenance is true, and if so, do a redirect to a URL?
You could put it in an ActionFilterAttribute and apply that attribute to any applicable actions/controllers or globally.
public class IsOnMaintenanceAttribute : ActionFilterAttribute
{
//You'll need to setup your IoC to inject this
public IMaintenanceDispatcher InjectedMaintenanceDispatcher { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
object ticketIdObj;
if (!filterContext.ActionParameters.TryGetValue("ticketId", out ticketIdObj))
return;
//Make sure it exists
if (InjectedMaintenanceDispatcher.IsOnMaintenance(int.parse(ticketIdObj)))
{
var routeValues = new RouteValueDictionary(new {
action = "parkingpage",
controller = "maintenance",
area = "ticket" });
filterContext.Result = new RedirectToRouteResult(routeValues);
return;
}
}
}
Note, your action method parameters needs to contain a variable named ticketId for the filterContext.ActionParameters.TryGetValue to work.
Note: I had assumed that an individual ticket is put into maintenance mode and you were wanting to check for that... but re-reading the question it seems like you want to put the whole area/site on hold. Even with that case, the ActionFilterAttribute example still holds... just not as different.

MVC Routes based on POST parameters

We have an a PHP application that we are converting to MVC. The goal is to have the application remain identical in terms of URLs and HTML (SEO and the like + PHP site is still being worked on). We have a booking process made of 3 views and in the current PHP site, all these view post back to the same URL, sending a hidden field to differentiate which page/step in the booking process is being sent back (data between pages is stored in state as the query is built up).
To replicate this in MVC, we could have a single action method that all 3 pages post to, with a single binder that only populates a portion of the model depending on which page it was posted from, and the controller looks at the model and decides what stage is next in the booking process. Or if this is possible (and this is my question), set up a route that can read the POST parameters and based on the values of the POST parameters, route to a differen action method.
As far as i understand there is no support for this in MVC routing as it stands (but i would love to be wrong on this), so where would i need to look at extending MVC in order to support this? (i think multiple action methods is cleaner somehow).
Your help would be much appreciated.
I have come upon two solutions, one devised by someone I work with and then another more elegant solution by me!
The first solution was to specify a class that extends MVcRouteHandler for the specified route. This route handler could examine the route in Form of the HttpContext, read the Form data and then update the RouteData in the RequestContext.
MapRoute(routes,
"Book",
"{locale}/book",
new { controller = "Reservation", action = "Index" }).RouteHandler = new ReservationRouteHandler();
The ReservationRouteHandler looks like this:
public class ReservationRouteHandler: MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
var request = requestContext.HttpContext.Request;
// First attempt to match one of the posted tab types
var action = ReservationNavigationHandler.GetActionFromPostData(request);
requestContext.RouteData.Values["action"] = action.ActionName;
requestContext.RouteData.Values["viewStage"] = action.ViewStage;
return base.GetHttpHandler(requestContext);
}
The NavigationHandler actually does the job of looking in the form data but you get the idea.
This solution works, however, it feels a bit clunky and from looking at the controller class you would never know this was happening and wouldn't realise why en-gb/book would point to different methods, not to mention that this doesn't really feel that reusable.
A better solution is to have overloaded methods on the controller i.e. they are all called book in this case and then define your own custome ActionMethodSelectorAttribute. This is what the HttpPost Attribute derives from.
public class FormPostFilterAttribute : ActionMethodSelectorAttribute
{
private readonly string _elementId;
private readonly string _requiredValue;
public FormPostFilterAttribute(string elementId, string requiredValue)
{
_elementId = elementId;
_requiredValue = requiredValue;
}
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
if (string.IsNullOrEmpty(controllerContext.HttpContext.Request.Form[_elementId]))
{
return false;
}
if (controllerContext.HttpContext.Request.Form[_elementId] != _requiredValue)
{
return false;
}
return true;
}
}
MVC calls this class when it tries to resolve the correct action method on a controller given a URL. We then declare the action methods as follows:
public ActionResult Book(HotelSummaryPostData hotelSummary)
{
return View("CustomerDetails");
}
[FormFieldFilter("stepID", "1")]
public ActionResult Book(YourDetailsPostData yourDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[FormFieldFilter("stepID", "2")]
public ActionResult Book(RoomDetailsPostData roomDetails, RequestedViewPostData requestedView)
{
return View(requestedView.RequestedView);
}
[HttpGet]
public ActionResult Book()
{
return View();
}
We have to define the hidden field stepID on the different pages so that when the forms on these pages post back to the common URL the SelectorAttributes correctly determines which action method to invoke. I was suprised that it correctly selects an action method when an identically named method exists with not attribute set, but also glad.
I haven't looked into whether you can stack these method selectors, i imagine that you can though which would make this a pretty damn cool feature in MVC.
I hope this answer is of some use to somebody other than me. :)

Resources