I have a site with a lot of routes.
Some routes, e.g. /sector-overview are to a specific page that I want the user to see.
Other routes, e.g. /sectoroverview are to an an action that ultimately renders a partial which is included on the homepage.
the second route is only meant to be internal to the application, but if the user types that into their address bar (it's an easy mistake to make), the system sees that as a valid request and it'll return the HTML partial.
I could rename the second route to something like /internal-sectoroverview, but this isn't really fixing the problem, just hiding it.
Is there any way for me to prevent the request from being processed if the user types this? What's the best way for me to deal with this issue?
You can block the route by using route constraints. However, in your case I would decorate your internal Action with [ChildActionOnly] like this:
[ChildActionOnly]
public ActionResult Overview()
{
return View();
}
By doing this, the action will be only rendered when using #Html.Action or #Html.RenderAction. If you try to access it through a browser, you'll get an error.
UPDATE
To return a 404 instead of an error you can override the OnException method on the controller and handle it there. Something like this:
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
//check if filterContext.Exception was thrown by child action only (maybe by text)
filterContext.Result = new HttpStatusCodeResult(404);
}
If I understand right you should resolve the problem of the partial not being called using the attribute ChildActionOnly.just for reference if you don't want that a method in your action can be called at all use the NonActionAttribute
I have a similar problem issue that people finding this might also need - I want to return 404 if a certain criteria is met from a function that returns a PartialViewResult. The solution for me was
public PartialViewResult MyFunction()
{
if( criteria ) {
Response.StatusCode = 404;
return null;
}
}
Related
When I do Ajax.BeginForm posts to my actions returning a partial view I send error info in a ViewData item.
Currently in order to handle all errors I must wrap all of the method in a try catch statement.
[HttpPost, ValidateAntiForgeryToken]
public virtual ActionResult Save(int id, FormCollection form)
{
MyModel model;
try
{
...do stuff...
}
catch(Exception ex)
{
...log...
ViewData["ResultInfo"] = new ResultInfo(false, Resource.SAVE_NOT_SAVED, someErrorMessage);
}
return PartialView("Folder/SomeView", model);
}
I would like to do this with a custom HandleError attribute, but I realize that there must be many gotchas waiting to bite. Has anyone tried and want to share their experiences?
EDIT:
I've ended up doing error handling in a controller base class.
This ErrorHandlingController has 2 methods; RegisterErrorHandler and RegisterModel. If the error handler is found registered when an error is found in the base class OnException I just add the ResultInfo and marks the error as handled and uses the view and error caption I've set in RegisterErrorHandler.
This way it's very easy to get the model to the error handler and it's natural to use Resources directly since the error handler is registered as the first row inside the method as opposed to an attribute outside it.
I think the answer to this question will help you along: How to handle model state errors in ajax-invoked controller action that returns a PartialView.
Let's say I have a Controller that handles a CRUD scenario for a 'Home'. The Get would look something like this:
[HttpGet]
public ActionResult Index(int? homeId)
{
Home home = homeRepo.GetHome(homeId.Value);
return Json(home, JsonRequestBehavior.AllowGet);
}
So far so good. Then I add a post action for adding new ones.
[HttpPost]
public ActionResult Index(Home home)
{
//add the new home to the db
return Json(new { success = true });
}
Awesome. But when I use the same scheme to handle puts (updating an existing home)...
[HttpPut]
public ActionResult Index(Home home)
{
//update existing home in the db
return Json(new { success = true });
}
We run into a problem. The method signatures for Post and Put are identical, which of course C# doesn't like. I could try a few things, like adding bogus parameters to the signature, or changing the method names to directly reflect CRUD. Those are hacky or undesirable, though.
What is the best practice for going about preserving RESTful, CRUD style controllers here?
This is the best solution that I know of:
[HttpPut]
[ActionName("Index")]
public ActionResult IndexPut(Home home)
{
...
}
Basically the ActionNameAttribute was created to deal with these scenarios.
HttpPut and HttpDeletes are restricted by some firewalls so at times simply HttpPost and HttpGet are used. If a record ID is passed in (or some other criteria) you know its an update. Granted - this is for you to determine, httpput may work just fine for you, this is just a warning on it, it usually isn't a big deal.
Either method used - beware of users trying to inject false IDs into the page in order to forcing updates of records they don't have access to. I get around this issue by hashing in this case home.HomeId on the view when we render it
ViewData["IdCheck"] = Encryption.ComputeHash(home.HomeId.ToString());
in your view:
<%: Html.Hidden("IdCheck", ViewData["IdCheck"]) %>
in your HttpPost or HttpPut method (whichever is doing the update)
if (Encryption.ComputeHash(home.HomeId.ToString()) != (string)Request.Form["IdCheck"])
{
throw new Exception("Hashes do not match");
}
Again - this same security issue exists no matter which method you use to do your update if you are trusting form data.
I have multi-tenant ASP.NET MVC application which utilizes subdomains to determine the current tenant. Whether or not the domain is valid is determined via database table lookup.
Where would be the best place to have a function that checks if the domain is in the database?If the subdomain is not in the database, it should redirect to the Index action in the Error controller.
Placing the check in the Application_BeginRequest method in the Global.asax file doesn't work because a never ending redirect results.
Where would be the best place to have a function that checks if the domain is in the database?If the subdomain is not in the database, it should redirect to the Index action in the Error controller.
Placing the check in the Application_BeginRequest method in the Global.asax file doesn't work because a never ending redirect results.
That's the right place, you just need to check the request Url is not already /Error.
You might already be doing so, but I'd like to add that it seems pretty static information that you should cache instead of hitting the database for each request.
u can subclass actionFilter attribute and override onactionExecuting method. in this method u can make any database checks and redirect the user appropriately
public class CustomActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(DatabaseLookup)
{
return;
}
filterContext.Result = new RedirectResult("http://servername/Error");
}
}
now u can decorate ur action methods with this custom actionfilter attribute
[CustomActionFilter]
public ActionResult mymethod()
{
//action method goes here
}
What if a user hits my site with http://www.mysite.com/Quote/Edit rather than http://www.mysite.com/Quote/Edit/1000 In other words, they do not specify a value for {id}. If they do not, I want to display a nice "Not Found" page, since they did not give an ID. I currentl handle this by accepting a nullable int as the parameter in the Controller Action and it works fine. However, I'm curious if there a more standard MVC framework way of handling this, rather than the code I presently use (see below). Is a smoother way to handle this, or is this pretty mush the right way to do it?
[HttpGet]
public ActionResult Edit(int? id)
{
if (id == null)
return View("QuoteNotFound");
int quoteId = (int)id;
var viewModel = new QuoteViewModel(this.UserId);
viewModel.LoadQuote(quoteId);
if (viewModel.QuoteNo > 0)
{
return View("Create", viewModel.Quote.Entity);
}
else
return View("QuoteNotFound");
}
Your other options would be
Having two Edit actions ; One with int id as its parameters and another without any parameters.
Only having Edit(int id) as your action and letting your controller's HandleUnknownAction method to do what it's supposed to do when your entity is not found (this is a bit more complicated).
But I like your approach the best, as it's simple and correctly handles the situation.
BTW, you don't need that local variable, you can just do this for better readability :
//...
if (!id.HasValue)
return View("QuoteNotFound");
var viewModel = new QuoteViewModel(this.UserId);
viewModel.LoadQuote(id.Value);
//...
No real issue with the way you have it but semantically it's not really an invalid quote number, its that they have navigated to an invalid route that they should not have gone to.
In this case, I would tend to redirect to /quote and if you really want to show a message to the user just show an error banner or similar (assuming you have that functionality in your master page etc).
public ActionResult Edit(int? id)
{
if (id == null)
{
// You will need some framework in you master page to check for this message.
TempData["error"] = "Error Message to display";
return RedirectToAction("Index");
}
...
}
Use route constraint
You can always define a route constraint in your routes.MapRoute() call that won't pass through any requests with undefined (or non-numeric) id:
new { id = "\d+" }
This is a regular expression that checks the value of id to be numeric.
You will probably have to create a new route that defines this, because for other controller actions you probably don't want routes to be undefined. In this case, your controller action wouldn't need a nullable parameter, because id will always be defined.
Don't be afraid of using multiple routes. With real life applications this is quite common.
I have a problem with a sample routing with the preview 5 of asp.net mvc.
In the AccountController I have 2 actions:
public ActionResult Delete()
public ActionResult Delete(string username)
While trying to look for Account/Delete or Account/Delete?username=davide the ControllerActionInvoker throws a exception saying that Delete request is ambiguous between my tow actions methods.
The default route in the global.asax hasn't been changed.
Shouldn't the action invoker understand what's the method to call looking in the parameters list?
Using the preview 4 I hadn't these kind of problem performing the same operation.
Any idea?
Solution found!
With the introduction of the ActionNameAttribute, it's now necessary to filter manually which method to call depending on the request. This is done by the ActionSelectionAttribute.
Full explanation here: http://haacked.com/archive/2008/08/29/how-a-method-becomes-an-action.aspx
I can't say for sure why this is happening. But you might want to consider only having the Delete(string username) action and removing the parameter-less overload.
Because string is nullable my understanding is that simply calling Account/Delete will invoke the action with a null username parameter which you can then test for at the beginning of the action method.
What I'd do is ditch the blank Delete(), and only use Delete(string username)
In your url routing you'd have something similar to "/{Controller}/{Action}/{username}/" ?
If you have "/{Controller}/{Action}/{Id}/" you'd be better off doing Delete(string id) and that way just using the url to handle this "/Account/Delete/davide/"
That said use your default route which should be something like the default Id is ""
Then in your Delete(string id) method have:
public ActionResult Delete(string id)
{
if(string.IsNullOrEmpty(id)) return EmptyID();
// Continue normal Delete method
}
public ActionResult EmptyID()
{
// The method you were going to have on a blank delete.
}
That or just wrap it up in the one method on an if {} else {}
Either way I'd just be going with the one method and doing a default on your username/id in your route of an empty string and handle it that way.
If you want to contact me on further follow up to what I mean, or whatever will help, ping me at andrew# my domain on my info page.
Edit: Ah pretty much what Berko said anyway, I'm not sure how Named Attributes would help - so please post a comment here detailing it for other guys who find the same issues! :)
Its ambiguous because the two controller action are the same post method..
You can only used that in form posting scenario for example you are submitting a form data that uses HTTP post..