ASP.NET MVC 2: Avoiding loop when accessing 404 action directly - asp.net

Like many people using ASP.NET MVC, I've implemented my own custom 404 error handling scheme using an approach similar to the one described here: How can I properly handle 404 in ASP.NET MVC?
(I actually discovered that post after implementing my own solution, but what I came up with is virtually identical.)
However, I ran into one issue I'm not sure how to properly handle. Here's what my 404 action in my ErrorController class looks like:
public ActionResult NotFound(string url)
{
url = (url ?? "");
if (Request.Url.OriginalString.Contains(url) &&
Request.Url.OriginalString != url)
{
url = Request.Url.OriginalString;
}
url = new Uri(url).AbsolutePath;
// Check URL to prevent 'retry loop'
if (url != "/Error/NotFound")
{
Response.StatusCode = (int)HttpStatusCode.NotFound;
// Log 404 error just in case something important really is
// missing from the web site...
Elmah.ErrorSignal.FromCurrentContext().Raise(
new HttpException(Response.StatusCode,
String.Format("Resource not found: {0}", url)));
}
return View();
}
The part that's different from the answer in the other StackOverflow question I referenced above is how the 'retry loop' is prevented. In other other answer, the code that prevents the retry loop simply sets properties on a ViewModel, which doesn't seem to actually prevent the loop. Since the action is sending back a response code of 404, then accessing the action directly (by typing "/Error/NotFound" in the browser) causes an infinite loop.
So here's my question: Did I miss another, more obvious way to handle the retry loop issue, or is my approach a decent way to do this?

you can handel erros by enabling the customErrors mode in the web.config file and set it to redirect errors to your controller when any errors occurs.
see an example here

Related

ASP.NET return Http Response from function called outside controller

I just started working with asp.net mvc and am working on a legacy codebase with controllers that all have the same validation code at the front of every controller. (ie. multiple lines of the exact same code returning if the user id/key are not correct)
Is there any way to separate this out into an external function that can return an HTTP response immediately if validation fails?
Right now I can do something like this:
if (!validate()) {
return forbid("Your un or Password is incorrect");
}
or for a more specific message:
var errorMsg = validate();
if (errorMsg) {
return forbid(errorMsg);
}
but if I'm going to put this at the front of most of my controllers, I'd rather have a one-liner that I takes up as little room as possible. Is there an option for bypassing the rest of the controller function and returning an HTTP response from inside my validate() function? Or is there possibly a more appropriate way to do this?

Asp.Net MVC5 - Html.Action - Attribute Routing - Weird Behaviour causing Exceptions

Here is some weird behaviour that is consistent across different actions and views in my entire website:
Whenever I POST to some action method and Model.IsValid is false, I return the view. Whenever Html.Action() is called in the view that is returned I get this exception:
(System.Web.HttpException): No matching action was found on controller 'xyz'.
This can happen when a controller uses RouteAttribute for routing,
but no action on that controller matches the request.
I'm using Attribute Routing.
public class RouteConfig
{
// REGISTER ROUTES
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapMvcAttributeRoutes();
}
}
So even though the Html.Action call worked the first time in the GET action method return View(), Html.Action always throws this exception after POST return View(). This is a general pattern across my website.
Wtf? Any ideas what's gone wrong here? The only thing I can think of is that I added more routes over time and now it's confused. How would I fix or test that if that's the case?
It just occurred to me that I have many routes/action methods where the get and post versions of routes for action methods are identical except for the GET or POST attribute on the action method. I previously made sure every route was completely unique because I was getting some ambiguity but changed it back to the same routes for get and post action methods, the only difference being the get or post attribute... I'm becoming convinced it's a routing issue but I don't know what's wrong specifically. I have route attributes on hundreds of action methods.
I have never seen anything as subtle as this before and have no idea even how to start solving something like this. I have no idea if it's simple or complicated, if it's my code or the framework. Any help would be greatly appreciated.
UPDATE:
Some sample code, not sure it will help, because the same things happens as a pattern across many utterly different action methods and views, regardless of GET, POST, authorized, unauthorized, in a role or not, antiforgerytoken...
Standard Html.Action called from a view. Works fine most of the time. (Different overloads make no difference.)
#Html.Action("CategoryDropDowns", "Category")
Here is what gets called (exactly what gets returned makes no difference, could be a ViewResult, could be an int).
// GET: /category/category-drop-downs
[HttpGet]
[Route("category/category-drop-downs")]
public ViewResult CategoryDropDowns()
{
}
If validation fails the view is returned:
public ActionResult CreateListing(ListDetails listDetails)
{
if (ModelState.IsValid)
{
}
else
{
return View("List", model);
}
}
And upon debugging through the view that gets returned, the call to Html.Action that worked fine the first time throws the exception. Same thing happens as a pattern across my website. Return View(), hit the Html.Action, bang, exception. Every time.
Remove the [HttpGet] attribute from child actions!
The problem was that Html.Action() always seemed to hit an exception not after a GET, return View(), but a POST, return View().
Ages ago I went through my whole site and marked every action method that wasn't a post with the [HttpGet] attribute. I didn't realise this would cause a problem. Always test!
Removing the [HttpGet] attribute from action methods called from Html.Action() has solved the problem.

Spring MVC retain error meesage after redirect

I have a jsp with 2 input boxes that searches on 2 different scenarios. So , i have two different Get requests mapping to /search1 and /search2 but have created only one POJO for this.
As soon as user submits a search, I check if there is any results for that, and if not i add error message in model and "redirect:"(redirection because if i simply return a page and now make a new search the url will be /search1/search2) to the same basic page.
But everytime i load the page the error message persists.
Any workaround for this? how do i display the messaeg only on search.
Use Flash Attributes.
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
Add RedirectAttributes parameter to your controller's handler method.
then redirectAttributes.addFlashAttribute("modelAttributeName", "value");
then redirect
The controller handler that you redirect to should have "modelAttributeName" model attribute available to it.
Essentially your a putting a value into session which is removed as soon as it is read on the next request.
Another solution would be
you can use request parameter to pass message id and write utility to read message from properties file using message id in the get request of redirected controller.
Controller 1
return "redirect:your_redirection_url?messageId=1";
Controller 2 (where redirected)
if (messageid != null && !(messageid.equals(""))) { MessageUtility.addMessage(Integer.parseInt(messageid), model, locale); }

Request.Querystring is empty

Strange one this..I'm looking for areas to investigate as much as an actual solution, but this is the issue:
In my asp.net webform page I am testing for a querystring which then applies some logic, however, the querystring appears to be stripped from the Url.
If I pass this
..blah.aspx?blah=123
then I can see the querystring in the Request.RawUrl no problem, but when I pass my proper querystring
..blah.aspx?Id=123
the querystring is empty. There is no request filtering on IIS either at the site or application level. Where else might this be stripped? Two hours of head scratching and counting....
Update:
There is no redirect, the logic is in the blah.aspx Pre Init event:
protected override void OnPreInit(EventArgs e)
{
if (!string.IsNullOrEmpty(RequestLEID) || !string.IsNullOrEmpty(RequestLEExternalReference))
DisableNavigationContextCheck = true;
base.OnPreInit(e);
}
Breaking on this event, I am testing the Request.RawUrl property in the immediate window
If you're using sharepoint (eg. a sharepoint control) with your solution, then ID is a reserved keyword.
Nasty. Elsewhere in our code we have a navigation module. This had been updated to use RedirectPermanent which in the case of a url with a repeated querystring pattern (note, not the parameter itself) does not send the querystring with the Request object.
RedirectPermanent is a new method in asp.net 4.0 and certainly not something that we should be using here.
are you pulling the value(s) out of the request?
var RequestLEExternalReference = Request.QueryString["LEExternalReference"];

Good error handling practice

What is a good error handling practice for an asp.net site? Examples? Thanks!
As with any .net project I find the best way is to only catch specific error types if they are may to happen on the given page.
For example you could catch Format Exceptions for a users given input (just incase JavaScript validation fails and you have not use tryparse) but always leave the catching of the top level Exception to the global error handler.
try
{
//Code that could error here
}
catch (FormatException ex)
{
//Code to tell user of their error
//all other errors will be handled
//by the global error handler
}
You can use the open source elmah (Error Logging Modules and Handlers) for ASP.Net to do this top level/global error catching for you if you want.
Using elmah it can create a log of errors that is viewable though a simple to configure web interface. You can also filter different types of errors and have custom error pages of your own for different error types.
One practice that I find to be especially useful is to create a generic error page, and then set your defaultRedirect on the customErrors node of the web.config to that error page.
Then setup your global.asax for logging all unhandled exceptions and then put them (the unhandled exceptions) in a static property on some class (I have a class called ErrorUtil with a static LastError property). Your error page can then look at this property to determine what to display to the user.
More details here: http://www.codeproject.com/KB/aspnet/JcGlobalErrorHandling.aspx
Well, that's pretty wide open, which is completely cool. I'll refer you to a word .doc you can download from Dot Net Spider, which is actually the basis for my small company's code standard. The standard includes some very useful error handling tips.
One such example for exceptions (I don't recall if this is original to the document or if we added it to the doc):
Never do a “catch exception and do nothing.” If you hide an exception, you will never know if the exception happened. You should always try to avoid exceptions by checking all the error conditions programmatically.
Example of what not to do:
try
{
...
}
catch{}
Very naughty unless you have a good reason for it.
You should make sure that you can catch most of the errors that are generated by your application and display a friendly message to the users. But of course you cannot catch all the errors for that you can use web.config and defaultRedirect by another user. Another very handy tool to log the errors is ELMAH. ELMAH will log all the errors generated by your application and show it to you in a very readable way. Plugging ELMAH in your application is as simple as adding few lines of code in web.config file and attaching the assembly. You should definitely give ELMAH a try it will literally save you hours and hours of pain.
http://code.google.com/p/elmah/
Code defensively within each page for exceptions that you expect could happen and deal with them appropriately, so not to disrupt the user every time an exception occurs.
Log all exceptions, with a reference.
Provide a generic error page, for any unhandled exceptions, which provides a reference to use for support (support can identify details from logs). Don't display the actual exception, as most users will not understand it but is a potential security risk as it exposes information about your system (potentially passwords etc).
Don't catch all exceptions and do nothing with them (as in the above answer). There is almost never a good reason to do this, occasionally you may want to catch a specific exception and not do any deliberately but this should be used wisely.
It is not always a good idea to redirect the user to a standard error page. If a user is working on a form, they may not want to be redirected away from the form they are working on. I put all code that could cause an exception inside a try/catch block, and inside the catch block I spit out an alert message alerting the user that an error has occurred as well as log the exception in a database including form input, query string, etc. I am developing an internal site, however, so most users just call me if they are having a problem. For a public site, you may wish to use something like elmah.
public string BookLesson(Customer_Info oCustomerInfo, CustLessonBook_Info oCustLessonBookInfo)
{
string authenticationID = string.Empty;
int customerID = 0;
string message = string.Empty;
DA_Customer oDACustomer = new DA_Customer();
using (TransactionScope scope = new TransactionScope())
{
if (oDACustomer.ValidateCustomerLoginName(oCustomerInfo.CustId, oCustomerInfo.CustLoginName) == "Y")
{
// if a new student
if (oCustomerInfo.CustId == 0)
{
oCustomerInfo.CustPassword = General.GeneratePassword(6, 8);
oCustomerInfo.CustPassword = new DA_InternalUser().GetPassword(oCustomerInfo.CustPassword, false);
authenticationID = oDACustomer.Register(oCustomerInfo, ref customerID);
oCustLessonBookInfo.CustId = customerID;
}
else // if existing student
{
oCustomerInfo.UpdatedByCustomer = "Y";
authenticationID = oDACustomer.CustomerUpdateProfile(oCustomerInfo);
}
message = authenticationID;
// insert lesson booking details
new DA_Lesson().BookLesson(oCustLessonBookInfo);
}
else
{
message = "login exists";
}
scope.Complete();
return message;
}
}

Resources