I have a route:
routes.MapRoute("ResetPasswordConfirm", "reset-password-confirm", new { controller = "Membership", action = "ResetPasswordConfirm" });
and the code
public ActionResult ResetPasswordConfirm(int userid, string key)
{
// ...
}
in my application. So that i have url to be executed like this:
http://localhost/reset-password-confirm?userid=1&key=bla_bla_something
That is absolutely okay, until someone decides to go to
http://localhost/reset-password-confirm
...and look what will happen. ASP.NET will generate predictable error:
The parameters dictionary contains a null entry for parameter 'userid' of non-nullable type 'System.Int32'...
It could be done also by a search robot trying to grab all the possible urls. It's okay, but generates a lot of errors during usage of application in the wild. I want to avoid that, but feel uncomfortable with writing a stub for every possible case for such kind of errors.
Is there any elegant way of doing that? Thanks.
Another way is to handle global errors, just set <customErrors mode="On"></customErrors> on your web.config and create an Error.cshtml on your Shared view folder. MVC3 templates actually include that page.
On the other hand, if you want to be more specific, you should try Action Filters, that's a cool way to handle errors.
[HandleError(View = "YourErrorView", ExceptionType=typeof(NullReferenceException))]
public ActionResult ResetPasswordConfirm(int? userid, string key)
{
if (!userid.HasValue)
throw new NullReferenceException();
// ...
}
Use nullables for your parameters, i.e.:
public ActionResult ResetPasswordConfirm(int? userid, string key)
Related
I have a webforms project, and am attempting to run some code that allows me to make a call to an MVC route and then render the result within the body of the web forms page.
There are a couple of HttpResponse/Request/Context wrappers which I use to execute a call to an MVC route, e.g.:
private static string RenderInternal(string path)
{
var responseWriter = new StringWriter();
var mvcResponse = new MvcPlayerHttpResponseWrapper(responseWriter, PageRenderer.CurrentPageId);
var mvcRequest = new MvcPlayerHttpRequestWrapper(Request, path);
var mvcContext = new MvcPlayerHttpContextWrapper(Context, mvcResponse, mvcRequest);
lock (HttpContext.Current)
{
new MvcHttpHandlerWrapper().PublicProcessRequest(mvcContext);
}
...
The code works fine for executing simple MVC routes, for e.g. "/Home/Index". But I can't specify any query string parameters (e.g. "/Home/Index?foo=bar") as they simply get ignored. I have tried to set the QueryString directly within the RequestWrapper instance, like so:
public class MvcPlayerHttpRequestWrapper : HttpRequestWrapper
{
private readonly string _path;
private readonly NameValueCollection query = new NameValueCollection();
public MvcPlayerHttpRequestWrapper(HttpRequest httpRequest, string path)
: base(httpRequest)
{
var parts = path.Split('?');
if (parts.Length > 1)
{
query = ExtractQueryString(parts[1]);
}
_path = parts[0];
}
public override string Path
{
get
{
return _path;
}
}
public override NameValueCollection QueryString
{
get
{
return query;
}
}
...
When debugging I can see the correct values are in the "request.QueryString", but the values never get bound to the method parameter.
Does anyone know how QueryString values are used and bound from an http request to an MVC controller action?
It seems like the handling of the QueryString value is more complex than I anticipated. I have a limited knowledge of the internals of the MVC Request pipeline.
I have been trying to research the internals myself and will continue to do so. If I find anything I will update this post appropriately.
I have also created a very simple web forms project containing only the code needed to produce this problem and have shared it via dropbox: https://www.dropbox.com/s/vi6erzw24813zq1/StackMvcGetQuestion.zip
The project simply contains one Default.aspx page, a Controller, and the MvcWrapper class used to render out the result of an MVC path. If you look at the Default.aspx.cs you will see a route path containing a querystring parameter is passed in, but it never binds against the parameter on the action.
As a quick reference, here are some extracts from that web project.
The controller:
public class HomeController : Controller
{
public ActionResult Index(string foo)
{
return Content(string.Format("<p>foo = {0}</p>", foo));
}
}
The Default.aspx page:
protected void Page_Load(object sender, EventArgs e)
{
string path = "/Home/Index?foo=baz";
divMvcOutput.InnerHtml = MvcWrapper.MvcPlayerFunctions.Render(path);
}
I have been struggling with this for quite a while now, so would appreciate any advice in any form. :)
MVC framework will try to fill the values of the parameters of the action method from the query string (and other available data such as posted form fields, etc.), that part you got right. The part you missed is that it does so by matching the name of the parameter with the value names passed in. So if you have a method MyMethod in Controller MyController with the signature:
public ActionResult MyMethod(string Path)
{
//Some code goes here
}
The query string (or one of the other sources of variables) must contain a variable named "Path" for the framework to be able to detect it. The query string should be /MyController/MyMethod?Path=Baz
Ok. This was a long debugging session :) and this will be a long response, so bear with me :)
First how MVC works. When you call an action method with input parameters, the framework will call a class called "DefaultModelBinder" that will try and provide a value for each basic type (int, long, etc.) and instance of complex types (objects). This model binder will depend on something called the ValueProvider collection to look for variable names in query string, submitted forms, etc. One of the ValueProviders that interests us the most is the QueryStringValueProvider. As you can guess, it gets the variables defined in the query string. Deep inside the framework, this class calls HttpContext.Current to retrieve the values of the query string instead of relying on the ones being passed to it. In your setup this is causing it to see the original request with localhost:xxxx/Default.aspx as the underlying request causing it to see an empty query string. In fact inside the Action method (Bar in your case) you can get the value this.QueryString["variable"] and it will have the right value.
I modified the Player.cs file to use a web client to make a call to an MVC application running in a separate copy of VS and it worked perfectly. So I suggest you run your mvc application separately and call into it and it should work fine.
Being kind of a newb to MVC 4 (or really any of the MVC's for ASP.NET) I cant help but feel theres more to the URL helper than what I'm seeing.
Basically I've read the tutorials on populating the attributes in a controllers methods using a query string in the URL.
I dont liek query strings though and prefer a sectioned "folder" like style.
Without much further adu, this is the sample URL:
http://something.com/DataTypes/Search/searchString
this approach is actually pretty safe as there will only ever be single worded searches
I have tried in the DataTypes controller
[HttpGet]
public ActionResult Search(String q)
{
ViewBag.ProvidedQuery = q;
return View();
}
and a few other small variations, right now im just trying to get the string to show up in the view but I dont seem to be getting anything there.
Is there anyway to inject the 3rd string in the url into an attribute?
If not, which URL helper class am I supposed to use to acquire the string data in the URL? Even if I have to parse the whole URL manually so be it, i just need to acquire that 3rd element in the URL as a string
Extremely n00b question im sure, but either im not finding this simple guide somewhere, or im not searching google correctly...
What you're missing is that the default route parameter name is "id". So you want to do this:
[HttpGet]
public ActionResult Search(String id)
{
ViewBag.ProvidedQuery = id;
return View();
}
If you don't want to use the variable name id, then you can modify your Route to look like this:
routes.MapRoute(
name: "Search",
url: "DataTypes/Search/{searchString}",
defaults: new { controller = "DataTypes", action = "Search",
searchString = UrlParameter.Optional });
If you don't want the string to be optional, then you can remove the last field from the defaults object.
you can use RouteTable.Routes.GetRouteData(new HttpContextWrapper(httpContext)) to get the routedata
String URL to RouteValueDictionary
You need to look at the routing in the Global.asax.cs. For example for your case you could add a route to the routes collection like this:
routes.MapRoute("Search",
"DataTypes/Search/{q}",
new { controller = "DataTypes", action = "Search" }
);
Then the q parameter will automatically get mapped to your action. The default controller mapping is likely mapping it to "id".
The populated catList is always Count=0 when the code jumps to CreateProduct() so I take it it does not get delivered.
Considering RouteValueDictionary does not do this ? Any other way?
public ActionResult GetCats(int CatID)
{
List<Category> catList = new List<Category>();
if (CatID >= 0 )
{
catList = context.Categories.Where(x => x.PCATID == CatID).ToList();
}
return RedirectToAction("CreateProduct", "Admin", new { catList });
}
public ActionResult CreateProduct(List<Category> catList) { }
You are actually trying to use controllers to do data access.
Move the "GetCats" data retrieval into your business layer (Service object, Repository, whatever suits you).
Then, CreateProduct will need to be there twice (2 signatures). One with no parameters in which you are going to call "GetCats" from your business layer and send it to the view.
The other implementation is going to be the flagged with the HttpPostAttribute and will contain in parameters all the necessary information to create a cat.
That's all. Simple and easy.
You could place any items that you need in TempData then call RedirectToAction.
RedirectToAction simply returns a "302" code to the browser with the URL to redirect to. When this happens your browser performs a GET with that URL.
Your RouteValues need to be simple. You can't really pass complex object or collections using a route value.
if you don't care about the browser url changing you could just
return CreateProduct(catList)
I have a table with records that has delete links. Basically I followed the NerdDinner tutorial for that part in MVC. But I don't want to have the confirm view, I'd rather have a confirm dialog, and then if confirmed let the HttPost action method delete the record directly.
I have something working, but being new to both MVC and jQuery, I'm not sure if I'm doing it right.
Here's the jQuery:
$(function () {
$('.delete').click(function () {
var answer = confirm('Do you want to delete this record?');
if (answer) {
$.post(this.href, function () {
window.location.reload(); //Callback
});
return false;
}
return false;
});
});
And the delete actionlink:
<%: Html.ActionLink("Delete", "Delete", new { id = user.UserID }, new { #class = "delete" })%>
And finally, the delete action method:
[HttpPost]
public ActionResult Delete(int id)
{
_db = new UsersEntities();
try
{
//First delete the dependency relationship:
DeleteUserVisits(id);
//Then delete the user object itself:
DeleteUser(id);
}
catch (Exception)
{
return View("NotFound");
}
return RedirectToAction("Index");
}
Now, I have a few questions about this:
I found the function for making the link POST instead of GET on the internet: `$.post(this.href); return false; And I'm not sure what the "return false" part does. I tried to modify it for my needs with a callback and so on, and kept the return part without knowing what it's for.
Secondly, the action method has the HttpPost attribute, but I have also read somewhere that you can use a delete verb. Should you? And what about the RedirectToAction? The purpose of that was originally to refresh the page, but that didn't work, so I added the window.location.reload instead in a callback in the jQuery. Any thoughts?
The Delete action method calls a couple of helper methods because it seems with the Entity Framework that I use for data, I can't just delete an record if it has relationship dependencies. I had to first delete the relationships and then the "User" record itself. That works fine, but I would have thought it would be possible to just delete a record and all the relationships would be automatically deleted...?
I know you're not supposed to just delete with links directly because crawlers etc could accidentally be deleting records. But with the jQuery, are there any security issues with this? Crawlers couldn't do that when there is a jQuery check in between, right?
Any clarifications would be greatly appreciated!
The return false statement serves
the same purpose as
e.preventDefault() in this case.
By returning false JavaScript is preventing
the browser from executing the link's
default click handler.
The DELETE verb is for RESTful web
services, it is most likely not what
you want in this situation because
not all browsers fully implement it.
Read more about them here: Creating
a RESTful Web Service Using ASP.Net
MVC
The deletion rules for child
entities depend on the entity
OnDelete/OnUpdate rules in the
context definition and also on
foreign key constraints in the
database. You can learn more about
foreign key constraints here.
A crawler will not be able to
activate your delete link in this
fashion because you have specified
the delete action be issued via a
POST. If the delete were a GET then
it would be a concern. Still, it's
not wise to keep links that directly
modify your content on front-facing
pages that do not require
authentication as a human could most
certainly exploit the process.
There you go, with full code :
http://haacked.com/archive/2009/01/30/delete-link-with-downlevel-support.aspx
Or just the html helper :
public static string DeleteLink(this HtmlHelper html, string linkText, string routeName, object routeValues) {
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string url = urlHelper.RouteUrl(routeName, routeValues);
string format = #"<form method=""post"" action=""{0}"" class=""delete-link"">
<input type=""submit"" value=""{1}"" />
{2}
</form>";
string form = string.Format(format, html.AttributeEncode(url), html.AttributeEncode(linkText), html.AntiForgeryToken());
return form + html.RouteLink(linkText, routeName, routeValues, new { #class = "delete-link", style = "display:none;" });
}
For your questions :
When you do a return false at the end of a javascript click handler on a link, the browser executes the javascript but doesn't follow the link, because of the return false.
For (maximum) cross browser compatibility, you shouldn't use the DELETE verb.
I don't know EF well enough, but doesn't it provide mapping configuration for cascading delete ?
HTTP GET should only be used to get information, for example, provide parameters for a search. Crawlers usually don't care about javascript, so they will blindly follow you links. If it is http://mysite/delete/everything, then, bad for you, if you hadn't the [HttpPost] on you controller action
If you also want to throw the jQuery dialog to the party, this Ricardo Covo post does a good job
I have just realized that, by chance, I implemented a category browse type situation in my project:
// GET: /Category/Browse?Category=Fruit
public ActionResult Browse(string category)
{
...
}
It turns out this is special case and there must be something behind the scenes. My next one I want to implement something like
//GET: /Category/Browse?Color=Blue
public ActionResult Browse(string color)
{
...
}
You get the idea...
Where/how do I register the different url values?
You don't need to register anything. Action parameters are automatically mapped to URL values by the default model binder. You can also map to complex type, list and dictionary parameters.