I'm new to MVC and EF and I am experimenting a bit with its functionaltiy. I have a problem with my URL. I have 3 entity classes generated from existing DB with the EF. Those properties get filled but I keep seeing them in my URL even though I changed my routing.
routes.MapRoute(
null,
"Article{articleID}",
new { controller = "Article", action = "Article" }
);
My URL looks like this :
http://localhost:3629/Article2?User=System.Data.Entity.DynamicProxies.User_4AC672CE1F2946F8B58690EA73EF956F43E30746526AD255691FA5ABFC32BBFF&BlogComments=System.Collections........
So everything after the /Article2 should be removed,
can anyone tell me what's going on?
When you make your ActionLink, are you certain that you only send the ID as parameter, and not the entire Article instance?
Related
I have an existing API I'm moving over to WebAPI, so I'm not free to change the URL. Breaking existing clients is not an option for me.
Knowing that, the original API would accept either a Guid (an ID) or a string (a name) for a given action method. The old API handler would decipher the URL parameter and send the request to a controller action designed to accept the given parameter type.
As an example:
Get(Guid id)
versus
Get(string name)
With WebAPI, the parameter binding is greedy across value types, so depending on which is first in the controller source file, that action is the one invoked. For my needs, that's not working. I was hoping the binder would realize the conversion to a Guid would fail for a name and then select the more generic string-based action. No dice. The Guid simply comes across as a null value (interestingly since it's a value type, but that's what I'm getting in the debugger at a certain point in the processing).
So my question is how best to handle this? Do I need to go as far as implementing a custom IHttpActionSelector? I tried the attribute routing approach (with constraints), but that isn't working quite right (bummer as it looks cool). Is there a mechanism in WebAPI that accounts for this I don't (yet) know about? (I know I can hack it in by testing the string for Guid-ness and invoking the other controller method, but I'm hoping for a more elegant, WebAPI-based solution...)
I'd spent a lot of time trying to fit attribute-based routing in, but I've not got that to work. However, I did solve my particular issue using route constraints. If you register the more-constrained route first, WebAPI (like MVC) will apply the constraints and skip over more-constrained routes until it finds one that it can select, if any.
So, using my example, I'd set up routes like so:
_config.Routes.MapHttpRoute(name: "ById",
routeTemplate: "product/{id}",
defaults: new { controller = "ProductDetails" },
constraints: new { id = #"^\{?[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}\}?$" });
_config.Routes.MapHttpRoute(name: "ByName",
routeTemplate: "product/{name}",
defaults: new { controller = "ProductDetails" });
The first route accepts a constraint in the form of a regular expression for a Guid. The second accepts all other values, and the controller will deal with non-product names (returns a 404). I tested this in the self-hosted WebAPI server and it works fantastically.
I am sure attribute-based routing would be more elegant, but until I get that to work, it's routing the old way for me. At least I found a reasonable WebAPI-based solution.
I would like to better understand how a controler method knows when a parameter it recives should be retrived from the post data or the url.
Take the following example:
URL: /ModelController/Method/itemID
// Where itemID is the id (int) of the item in the database
POST: objectOrArray: {JSON Object/Array}
The controller would look something like this:
[HttpPost]
public ActionResult InputResources(int? id, Object objectOrArray)
Now, somehow the method is smart enough to look for the first parameter, the id, in the site URL and the Object in the HTTPPost.
Whilst this works, I don't know why, and as a result I sometimes run into unpredictable and erratic behaviour. For example, I seem to have found (although I am not 100% sure) that removing the ? from int? id makes the controller method inmediately assume it should look for the id in the HTTPPost and not the URL.
So I would like to clarify the following points:
What exactly is it that tells the method where to look for the data? (The [HttpPost] attribute preciding the method?)
Do naming conventions come into play? (for example removint the ? or not using id as the variable name?)
Does the order in which the variables are placed have an inpact? (ie placing the Object before the id)
I know I can more or less figure out some of this stuff by trial and error, but I would like a qualified explanation rather than to continiue to work of assumption based on observation.
Thank you
Chopo
Take a look at the default route from Global.asax:
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
The first thing that MVC will try to do is map your method parameters to values in the POST. If it doesn't find a match, it will cascade through other possibilities, including the route values.
Here's the order the model binder uses:
Previously bound action parameters, when the action is a child action
Form fields (Request.Form)
The property values in the JSON Request body (Request.InputStream), but only when the request is an AJAX request
Route data (RouteData.Values)
Querystring parameters (Request.QueryString)
Posted files (Request.Files)
The reason you're seeing the behaviour you describe is because POST data comes before route values. It seems that MVC can't bind a nullable int to a POST value, so it skips over it and keeps going until it reaches the RouteData mapping, at which point, it finds a match and gets the value from the route. When you make the parameter non-nullable, it is suddenly able to bind it to the POST value, which has higher precedence than RouteData, so it does.
Source: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
I'm very new to MVC still and am having a hard time figuring out how best to work around this issue.
I have a modified version of an auto-scaffolded "Create" (GET) action that takes an optional parameter (a default value for a drop-down list). It then submits it's form to the "Create" (HTTPPost) action.
public ActionResult Create(string id = null)
{
//use id to pick a default item in a drop down list.
}
[HttpPost]
public ActionResult Create(Registration registration)
{
//this method doesn't care one iota what was passed to the GET method
// but registration.id is invalid because it's trying to set it to the value that was passed to the get method. If id in the get method is not passed, then everything is fine
}
The problem is, if a value was passed to the GET create action, then ModelState.isValid is false on the POST create action because it's trying to stuff the value that was passed to the GET action into the primary key (Id) field of the model in the POST action (which should be omitted since it's auto-generated.
I know I could just change the primary key column name to something other than Id, but I don't have control over the database and am trying to figure out another remedy to this issue.
If I try renaming the parameter on the GET action from 'id' to something else then I have to change the links from CREATE/paramValue to CREATE/?paramName=paramValue and I'd rather not do that. (though I will if it's the only easy way).
Is there a way to make it so the POST action ignores the values passed to the GET action?
I think what you are trying to do is parameterize the Create method of your controller with the ID of the type of object it is supposed to create.
While there is no hard and fast convention on parameter names in ASP.NET MVC, I think most people expect the parameter called id to refer to the object that is being dealt with, rather than an attribute of it.
With this in mind, my suggestion to you would be to do the following:
Rename the parameter in the GET version of the Create method to something suitable like typeId or whatever, so the signature then looks like public ActionResult Create(string typeId = null)
Add a custom route to the MvcApplication, which will let you have the nicely formatted /Create/typeId, rather than forcing to to have /Create?typeId=value
You can do this by adding the following lines of code before you default route:
routes.MapRoute
(
name: "CreateGet",
url: "Controller/Create/{typeId}",
defaults: new
{
controller = "Controller",
action = "Create",
typeId = UrlParameter.Optional
}
);
The default route looks something like this:
routes.MapRoute
(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new
{
controller = "Site",
action = "Index",
id = UrlParameter.Optional
}
);
I think this will solve your problem, though make sure the custom route I am suggesting comes before the default route.
Update:
You have a Get method and a Post method.
The Get method (probably) creates a view model object and sends that to the view, which renders it. Alternatively, your view could have various inputs hard-coded in the HTML or injected in via an AJAX call. Regardless of how they got there, your rendered view that goes out to the web browser looks something like this:
<html>
<head />
<body>
<form>
<input id="Input1" ... />
<select id="Select1" ... />
</form>
</body>
</html>
The above is a vastly simplified structure of the HTML that goes out.
The IDs of the inputs match the names of the properties within the view model object that will be created when this page is posted back, i.e., an instance of the Registration class.
On the way back (to the Post method), a model binder takes the values from the request (query string parameters, form values, etc.) and binds them back into the model object that is given to the Post method.
In your case, most likely what was happening was that there was a mismatch between what id meant in the Get method (and in the rendered view) and what id meant in the Registration object given to Post method.
The reason for the mismatch was that the Registration.Id property name was dictated by the database and Create(string id) was being dictated by the default route, but they are actually different things, so should be named differently.
That left your need to have a clean URL of the /Create/id form, which is easily done by a custom route.
Now if you really wanted to exclude a parameter from the model binding logic, then you would go with the other answer which used an attribute on the Registration parameter of the Post method to explicitly tell the DefaultModelBinder to exclude that parameter from the binding logic.
Once you'd done that, the property it would have bound to would have retained its default value given to it by the CLR after instantiation and running constructors.
ASP.NET MVC also supports custom model binders which can alter the model binding logic significantly, if required, though that doesn't apply in your case.
Try use BindAttribute:
[HttpPost]
public ActionResult Create([Bind(Exclude = "RegistrationId")]Registration registration)
{
}
It will omit the RegistrationId POSTed with form when binding to model...
This is a point of curiosity rather than an actual problem I'm having: how does the ASP.NET MVC framework create the appropriate controller and view objects from the string name in the RouteCollection? I've tried searching through the actual ASP.NET MVC2 code to figure this out but get lost in the controller factory class.
When I register my routes, I map the first block to the controller, the second to an action, the third to an ID, but how does the program take string names and create new objects?
/Users/Update/15354 = New UserController, executes Update() - how?
The only thing I can imagine working is reflecting through all the classes in the project and creating one that matches the requested name, but then how would you resolve a conflict in class names without knowing which namespace to use?
The standard naming convention "FooController" dictates that all controllers should have the suffix "Controller"; thus, when your current route indicates the controller "Foo", ASP.NET MVC examines the types within your application which inherit from Controller and looks for one whose name matches "Foo" + "Controller".
You are correct to assume that ASP.NET MVC is using reflection to find the desired controller, but it is a relatively low impact because the reflection data is cached by the application during its initial startup.
If you have two controllers with the same name and different namespaces, you can use the alternative form of MapRoute() to indicate which controller you intend to resolve.
routes.MapRoute(
"Error_ServiceUnavailable",
"error/service-unavailable",
new { controller = "Error", action = "ServiceUnavailable" },
new[] { "Controllers" }
);
routes.MapRoute(
"Admin_Error_ServiceUnavailable",
"admin/error/service-unavailable",
new { controller = "Error", action = "ServiceUnavailable" },
new[] { "Areas.Admin.Controllers" }
);
Given these routes:
routes.MapRoute("Test", "test", new { controller = "Test", action = "Index" });
routes.MapRoute("Default", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
If I call RedirectToRoute("Default") from the Index action of the TestController it redirects to /test but I expected it to redirect to /
I checked the result of calling RedirectToRoute("Default") before returning it during a debugging session.
RedirectToRouteResult result = RedirectToRoute("Default");
It has a property RouteName with a value "Default" and a property RouteValues with no elements (Count = 0). I checked using Reflector, and null is passed internally as the RouteValueDictionary.
Again, I would expect that given the defaults for the route defined in my application, it would redirect to Index view on the HomeController.
Why doesn't it redirect to /?
The RouteValueDictionary filled in to the current action is being used to fill in the Controller, Action, ID. This is usually the functionality you want and lets you do things like <%:Html.ActionLink("MyAction")%> without needing to specify your controller in a view.
To force it to the complete fall back default just use:
RedirectToRoute("default", null");
The second argument is for your routevalues and by specifying it you override the preexisting values.
However I would say the preferred way would be to have a Home route which will not take any parameters. This means your redirects will not need a load of nulls floating around and a call to RedirectToRoute("Home") is also nice and explicit.
Edit
This is a really old answer and as a couple of people have mentioned doesn't seem to have been working for them. For what it's worth I now don't use this pattern and explicitly override controller and area when I need to break out. Not only does it apparently work better but when you come back to the code it's good to see explicitly that you're coming out of this context and mean to blank out certain routevalues.
For the record I have also tended more towards named route based URL generation over convention based generation.
I don't think that "null" parameter Chao mentions has ever worked for me. Perhaps it did in prior MVC versions, but now in MVC 5.2 this is the only way I can get it to work:
RedirectToRoute("Default", new { controller = "", action = "" });
Without looking at the actual code to see what is going on it can be a bit tricky to know why, but this forum post does shed some light. The answerer says that under the covers it might be creating a RouteValueDictionary which would contain the controller and action you are currently in, which in your case would be a controller of Test and an action of Index. Then, when you call RedirectToRoute("Default"), the controller and the action in the RouteValueDictionary will be used when matching the default route and you will be taken to the Index action in your Test controller.
Now you could always do a Redirect("/") to take you to the main page of your site.