OK, this may just me being ignorant, but I have the following route in my MVC3 application:
routes.MapRoute("Directory","{aid}/{controller}/{action}/{year}/{quarter}",
new { aid = "sf", controller = "Lobbyist", action = "Index",
year = CurrentYear(), quarter = CurrentQuarter() });
In my Global.asax.cs, I have these two methods:
public static int CurrentQuarter()
{
int quarter = 0;
//...use some internal business logic to determine the value of
//'quarter' based on the current date...
return quarter;
}
public static int CurrentYear()
{
return DateTime.Now.Year;
}
This code works great almost all the time. At one point in time, in our production environment (running IIS7), the route value for CurrentQuarter() became a value of zero, when it should have been 1, 2, 3, or 4. It works just fine in production except for that one point in time. An IISRESET 'fixed' the problem.
What I know:
At the time CurrentQuarter() was failing, CurrentYear() was still
returning correctly
The CurrentQuarter() method was not throwing an
exception which would have prevented setting the 'quarter' variable
The business logic which drives the CurrentQuarter() method works
for every date between DateTime.MinValue and DateTime.MaxValue
My question really gets down to is:
Is it BAD to call static methods to generate route values?
Is there a potential for the application to 'forget' the result of the static method, and cause it to return a garbage value? Could an application pool recycling cause this?
I'm sort of grasping at straws here!
Thanks for any advice.
I would not call static values here. I completely disagree with Igor above. Its not standard and is hard to track down where these values are coming from to someone that doesn't know the app. Call it either from your controller, or even better yet - a service layer (ie business logic) your controller calls to that gets this value.
A routes purpose is not to call a business method.
On the second question, if there is an app pool recycle, the value will simply be reset. However if multiple threads are calling into this method and potentially changing the value in the same method I would implement some locking in there to prevent updates from overlapping.
You should make the route values year and quarter optional, and provide default values for them in the Action method. I think this makes everything cleaner, and easier to maintain.
public class LobbyistController
{
public ActionResult Index(int? year, int? quarter)
{
if (!currentYear.HasValue)
{
currentYear = GetCurrentYear();
}
if (!currentQuarter.HasValue)
{
currentQuarter = GetCurrentQuarter();
}
// the rest
}
}
Related
I am trying to standardize an extension model for our REST API development team. We need to provide default implementation of routes, while allowing for custom implementations of routes that replace the default as well.
As a simple example if we have a GET route api/users like this:
public class DefaultUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = 0)]
public IEnumerable<string> DefaultGetUsers()
{
return new List<string>
{
"DefaultUser1",
"DefaultUser2"
};
}
}
We expect the default work like this:
Now a developer wants to change the behavior of that route, he should be able to simply define the same route with some mechanism to imply their implementation should be the one used, instead of the default. My initial thinking was to use the Order property on the Route attribute since that's what it appears to be there for, as a way to provide a priority (in ascending order) when an ambiguous route is discovered. However it's not working that way, consider this custom implementation that we want to override the default api/users route:
public class CustomUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = -1)]
public IEnumerable<string> CustomGetUsers()
{
return new List<string>
{
"CustomUser1",
"CustomUser2"
};
}
}
Notice the Order property is set to -1 to give it a lower priority value than the default, which is set to 0. I would have thought this would be used by the DefaultHttpControllerSelector, but it isn't. From the DefaultHttpControllerSelector:
And we end up with this exception being returned in the response:
Is it possible Microsoft just missed the logic/requirement to use Order as a route disambiguator and this is a bug? Or is there another simple way to override a route, hopefully with an attribute?
I have pretty much the same problem. I am creating a starter site, but I want users to be able to redefine to behaviour of a Controller, especially if there is a bug.
I use Autofac to resolve the Controller, but even when I register the new controller as the old one, the original one gets selected.
What I'll do is probably go with URL Rewriting. Especially since this issue is temporary in my case. However, I would be interested if someone has a better option.
I have some very strange behaviour in one of my spring controller which I can not explain.
So this is what I have. Very simple controller.
#RequestMapping(value="/doSomething")
public void doSomething(#RequestParam int value, HttpSession session) {
System.out.println("Lorem");
// Some stuff later on done here
System.out.println("ipsum");
}
When I request the mapped URL the controller behaves as it whould have an infiniate loop inside. So it starts with the output "Lorem" then "ipsum" and instead of leaving the method it starts right from the beginning of the method again. It is not called multiple times from external. Does anybody know this behaviour or has any clue? Furthermore I could observe that the output speed slows down as memory drastically increases up to about 1.5 GB and 100% CPU usage spread all over every single core.Thanks for your help.
Your handler method doesn't seem right to me. Typically you need to return a string that will be resolved into a View by ViewResolver, eg:
#RequestMapping(value="/doSomething")
public String doSomething(#RequestParam int value, HttpSession session) {
// ....
return "somethingdone";
}
This is my understanding.
If a controller declares a void return type , Spring will attempt to infer the view name from the request URL.
In your case, it will assume the view name is "doSomething", and proceed on that assumption.
It does this using an implementation of RequestToViewNameTranslator, The default implementation of RequestToViewNameTranslator is DefaultRequestToViewNameTranslator
See this
I have three routes that look like that:
all.html
all/page-{numpage}.html
all/page-{numpage}-limit-{limit}.html.
First two work ok - which means that in my controller I'm getting the value of numpage or 1 if none is given:
public ViewResult All(int numpage = 1, int limit = 10) {}
numpage is whatever I typed in the address bar
Third route doesn't work at all - as if I went to the first route (all.html), so it's value is equal to 1, and limit is 10 - the defaults. However when I go to all/page-4.html?limit=3 I get correct values. What am I doing wrong? :D
One more thing - I create my routes dynamically, so code for registering them looks like that (rcache returns correct list of routes):
List<Tuple<Dictionary<string, string>, string, string, string>> routes = rcache.GetRoutes();
foreach (var route in routes) {
foreach (KeyValuePair<string, string> kvp in route.Item1) {
context.MapRoute(
route.Item4,
kvp.Value,
new { controller = route.Item2, action = route.Item3, id = UrlParameter.Optional, name = UrlParameter.Optional, numpage = UrlParameter.Optional, limit = UrlParameter.Optional }
);
}
}
ok well you are fighting with yourself. Why don't you put the last rule as first, and see what happens? Order of route rules makes a difference.
Just a quick note. Simplify your design and avoid complexity. What you are doing might work, but it's a terrible choice. Don't do it.
first of all you shouldnt allow anyone to add/edit/delete your routes. if someone doesnt really understand how it works, you are allowing them to break your application. i would definetely avoid it.
Well lets say you have to do it. then you need to validate the routes and rules as well, you cant just push it to your route table.
Also, lets say they add a route and it is not working, because your controller doesnt support it. you will take the blame. let them come to you with a request and then you add it to your route table.
You are the author of this code, no one else will be able to maintain what you wrote or what you were thinking while you were writing.
I have an ASP.NET MVC3 website setup using fluent validation and ninject. The validation code is working. However, I set a break point in the validation class constructor and I noticed that when I request my view that uses the validation the constructor gets hit multiple times. Based on very basic testing it seems that the number of times the constructor is hit is equal to the number of properties that exist on the object. Has anyone else come across something similar? Or can someone shed more insight on how this type of validation works behind the scenes? -Thanks
Here is the constructor...
public class PersonValidator : AbstractValidator<Person> {
public PersonValidator() {
RuleFor(x => x.Id).NotNull();
RuleFor(x => x.Name).Length(0, 10);
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Age).InclusiveBetween(18, 60);
}
}
Here are the libraries/resources that I am using (I just got the NuGet packages and configured everything based on the info from the two links below):
http://fluentvalidation.codeplex.com/wikipage?title=mvc
https://github.com/ninject/ninject.web.mvc.fluentvalidation
I figured out how to prevent this issue. Even though this solves my issue, I would like input from others on whether there are any consequences in doing this?
So on the second link you will see instructions on how to set up Ninject.
On the second step you need to apply the "InRequestScope()" extension method. Then the constructor will only be hit once per http request that uses your validator. That obviously means that only one instance of the validator object is created per http request, which makes sense to me. I don't know if there are any consequences to using this solution?
Bind(match.InterfaceType).To(match.ValidatorType).InRequestScope();
Resurrecting this thread.
I had the same problem with SimpleInjector. My solution was include LifeTime.Scoped on the collection register.
private static void WarmUpMediatrAndFluentValidation(this Container container)
{
var allAssemblies = GetAssemblies();
container.RegisterSingleton<IMediator, Mediator>();
container.Register(typeof(IRequestHandler<,>), allAssemblies);
RegisterHandlers(container, typeof(INotificationHandler<>), allAssemblies);
RegisterHandlers(container, typeof(IRequestExceptionAction<,>), allAssemblies);
RegisterHandlers(container, typeof(IRequestExceptionHandler<,,>), allAssemblies);
//Pipeline
container.Collection.Register(typeof(IPipelineBehavior<,>), new[]
{
typeof(RequestExceptionProcessorBehavior<,>),
typeof(RequestExceptionActionProcessorBehavior<,>),
typeof(RequestPreProcessorBehavior<,>),
typeof(RequestPostProcessorBehavior<,>),
typeof(PipelineBehavior<,>)
});
container.Collection.Register(typeof(IRequestPreProcessor<>), new[] {typeof(EmptyRequestPreProcessor<>)});
container.Collection.Register(typeof(IRequestPostProcessor<,>), new[] {typeof(EmptyRequestPostProcessor<,>)});
container.Register(() => new ServiceFactory(container.GetInstance), Lifestyle.Singleton);
container.Collection.Register(typeof(IValidator<>), allAssemblies, Lifestyle.Scoped);
}
container.Collection.Register(typeof(IValidator<>), allAssemblies, Lifestyle.Scoped); <- Workers fine for me, calling only once per request.
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. :)