I have 2 examples below:
#Html.ActionLink("Create New", "Create", new { id = Model.Id })
and,
return RedirectToAction("Index", new { id = review.RestaurantId });
My question is regarding the new { id = xxx} part in object route values. Why do we use "new" in this case? What exactly does it do? Does it initialize "id" variable in this case?
Also, it is strange that these methods, create and index definition can only take arguments as defined in the route values...
That is,
public ActionResult create { int id)
{ ...}
is correct but following is wrong....
public ActionResult create { int somethingelse)
{ ...}
So please tell me what is the new {id = xx} in my first 2 examples is doing?
Thanks
new {} creates a new object of type Object. The type is anonymous. You see that syntax when writing linq queries that end in " select new {x = "foo". y="bar"}". It is often used when setting an object to type "var".
What you are doing in your ActionLink is providing Route Values. MVC takes the properties and values in the object and puts them in the QueryString of the request. It is what you might refer to as "magic". You can set a break point in your controller Action and check "HttpContext.Request.QueryString" to see it.
The input values for you Action methods have to match the properties that are being passed in via the QueryString.
That is actually creating an anonymously typed object and passing it into ActionLink(). ActionLink then uses that object, coupled with your routing rules to generate the link. MVC will look for properties on that object that match the routing names (usually of route parameters) and figure out how to build it. Since you likely have the default MVC route (/controller/action/{id}) that is what links everything together.
Further, that is why id "is correct", but somethingelse "is wrong".
If you change "id" to "somethingelse" in your routing rule, you could then see new { soemthingelse = ""} work in your ActionLink().
Does that help?
In both cases your creating a new anonymous object to pass into the query string as a route value. You create a new object because one does not already exist on the view.
The MVC source code:
if (additionalViewData != null) {
foreach (KeyValuePair<string, object> kvp in new RouteValueDictionary(additionalViewData)) {
viewData[kvp.Key] = kvp.Value;
}
}
They use it to create new RouteValueDictionary parameters.
You don't have to use it the this manor. You could create an object on the model and pass it in:
public class SomeModel
{
public SomeModel()
{
MyObject = new { id = 10 };
}
public int Id {get;set;}
public object MyObject {get;set;}
}
#Html.ActionLink("Create New", "Create", Model.MyObject)
This would also work though is probably not something you would attempt.
For the second part of your question. The RouteValueDictionary searches by key and assigns the value to the key that was given.
So whatever you call the key in the anonymous object, MVC will attempt to assign the value to it on the action. The name must match or they key cannot assign the value.
Related
I have an action method already written in my web api 2.0 project. I would like to add a new parameter without disturbing the existing contract. What is the best way to do that? Appreciate any best practice hints on this :)
Here's the code sample of what I intend to do:
Existing code:
[Route("{myId}",Name="MyId")]
Public IHttpActionResult Get(String myId)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111
Expecting to do something like the below:
//I want to keep the route name same for backwards compatibility.
[Route("{myId}/{myName}",Name="MyId")]
Public IHttpActionResult Get(String myId,string? myName)
{
//Some more code here
}
Url: http://localhost:8888/webapi/1111/John
The Url mentioned above hits the method rightly, but I never get the second parameter (myName) populated with John.
Thanks everyone for any help towards this.
Sree.
In your example you have myName as string? which is not allowed as:
The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable'
A test controller was created to implement you action
[RoutePrefix("webapi")]
public class TestsController : ApiController {
[HttpGet]
[Route("{myId}/{myName}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName) {
//Some code to show the values of the parameters
return Ok(new { myId = myId, myName = myName });
}
}
When tested with webapi/1111/John the following response is returned
{"myId":"1111","myName":"John"}
which does include the value for MyName as John
If backwards uri webapi/1111 is tried, a NotFound response is returned as the template does not match the new action.
To fix this you need to make the myName parameter optional. To learn more about that check
Optional URI Parameters and Default Values
The new route will look like
//NOTICE THE `?` ON THE {myName} TEMPLATE
[Route("{myId}/{myName?}", Name = "MyId")]
public IHttpActionResult Get(string myId, string myName = null) {...}
You will notice that myName was made optional in the route {myId}/{myName?} and in the action parameter (string myId, string myName = null)
Now when tested with webapi/1111 the following response is returned
{"myId":"1111","myName":null}
Which would match your expected result for backwards compatibility.
String is a reference type so you don't need to make it nullable, it already is. Remove the '?' and remove the Name from the attribute. What happens then?
I am trying to add a new MVC ActionLink that when pressed goes to an action and gets bound to the input parameter but it doesn’t seem to be working
I Have this:
#Html.ActionLink("OPTIN","Optin", "Admin", new {id = Model.Publications[i].ID})
And then the Optin method on the Admin controller:
public ActionResult OptIn(int? id)
{
if (id.HasValue)
{
var temp = id.Value;
}
I want the id to get mapped to the input parameter but it is always NULL and Model.Publications[i].ID is always 5. What am I doing wrong? Do I have to make ID part of a model?
The way that you have your ActionLink method set up is wrong. I'm GUESSING the override you're using is this one. However, you'll want to be using is string, string, string, object, object.
By the URL in the comment on your question, the ID that you're setting is the HTML attribute, not a part of the URL.
If you change your call to the following, it should give you what you need:
#Html.ActionLink("OPTIN","Optin", "Admin", new {id = Model.Publications[i].ID}, new { })
I am creating a modular ASP.NET MVC application using areas. In short, I have created a greedy route that captures all routes beginning with {application}/{*catchAll}.
Here is the action:
// get /application/index
public ActionResult Index(string application, object catchAll)
{
// forward to partial request to return partial view
ViewData["partialRequest"] = new PartialRequest(catchAll);
// this gets called in the view page and uses a partial request class to return a partial view
}
Example:
The Url "/Application/Accounts/LogOn" will then cause the Index action to pass "/Accounts/LogOn" into the PartialRequest, but as a string value.
// partial request constructor
public PartialRequest(object routeValues)
{
RouteValueDictionary = new RouteValueDictionary(routeValues);
}
In this case, the route value dictionary will not return any values for the routeData, whereas if I specify a route in the Index Action:
ViewData["partialRequest"] = new PartialRequest(new { controller = "accounts", action = "logon" });
It works, and the routeData values contains a "controller" key and an "action" key; whereas before, the keys are empty, and therefore the rest of the class wont work.
So my question is, how can I convert the "/Accounts/LogOn" in the catchAll to "new { controller = "accounts", action = "logon" }"??
If this is not clear, I will explain more! :)
Matt
This is the "closest" I have got, but it obviously wont work for complex routes:
// split values into array
var routeParts = catchAll.ToString().Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
// feels like a hack
catchAll = new
{
controller = routeParts[0],
action = routeParts[1]
};
You need to know what part is what in the catchAll parameter. Then you need to parse it yourself (like you are doing in your example or use a regexp). There is no way for the framework to know what part is the controller name and what is the action name and so on, as you haven't specified that in your route.
Why do you want to do something like this? There is probably a better way.
I want to render the same view after a successful action (rather than use RedirectToAction), but I need to modify the model data that is rendered to that view. The following is a contrived example that demonstrates two methods that that do not work:
[AcceptVerbs("POST")]
public ActionResult EditProduct(int id, [Bind(Include="UnitPrice, ProductName")]Product product) {
NORTHWNDEntities entities = new NORTHWNDEntities();
if (ModelState.IsValid) {
var dbProduct = entities.ProductSet.First(p => p.ProductID == id);
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
entities.SaveChanges();
}
/* Neither of these work */
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
return View(product);
}
Does anyone know what the correct method is for accomplishing this?
After researching this further, I have an explanation why the following code has no effect in the Action:
product.ProductName = "This has no effect";
ViewData["ProductName"] = "This has no effect either";
My View uses HTML Helpers:
<% Html.EditorFor(x => x.ProductName);
HTML Helpers uses the following order precedence when attempting lookup of the key:
ViewData.ModelState dictionary entry
Model property (if a strongly typed view. This property is a shortcut to View.ViewData.Model)
ViewData dictionary entry
For HTTP Post Actions, ModelState is always populated, so modifying the Model (product.ProductName) or ViewData directly (ViewData["ProductName"]) has no effect.
If you do need to modify ModelState directly, the syntax to do so is:
ModelState.SetModelValue("ProductName", new ValueProviderResult("Your new value", "", CultureInfo.InvariantCulture));
Or, to clear the ModelState value:
ModelState.SetModelValue("ProductName", null);
You can create an extension method to simplify the syntax:
public static class ModelStateDictionaryExtensions {
public static void SetModelValue(this ModelStateDictionary modelState, string key, object rawValue) {
modelState.SetModelValue(key, new ValueProviderResult(rawValue, String.Empty, CultureInfo.InvariantCulture));
}
}
Then you can simply write:
ModelState.SetModelValue("ProductName", "Your new value");
For more details, see Consumption of Data in MVC2 Views.
The values are stored in ModelState.
This should do what you want:
ModelState.SetModelValue("ProductName", "The new value");
I wouldn't suggest doing that though... the correct method would be to follow the PRG (Post/Redirect/Get) pattern.
HTHs,
Charles
EDIT: Updated to reflect the better was of setting the ModelState value as found by #Gary
This will trigger the model to re-evaluate under simple conditions:
ModelState.Clear();
model.Property = "new value";
TryValidateModel(model);
Perform ModelState.Clear() before you change the model.
...
ModelState.Clear()
dbProduct.ProductName = product.ProductName;
dbProduct.UnitPrice = product.UnitPrice;
...
Consider an ASP.NET MVC 1.0 project using the Areas convention as described on this Nov. 2008 Phil Haack blog post. This solution works great once it's set up!
My trouble is starting thanks to my limited knowledge of ASP.NET MVC's routing rules.
My intention is to create an action method and URL structure like this:
http://mysite/Animals/Dogs/ViewDog/Buster
DogsController.ViewDog() looks like this:
public ActionResult ViewDog(string dogName)
{
if (dogName!= null)
{
var someDog = new DogFormViewModel(dogName); //snip a bunch more
return View(someDog);
}
else { return View("DogNotFound"); }
}
The task at hand is ensuring that the RegisterRoutes() has the correct entries.
UPDATE
Here's the new route being mapped:
routes.MapRoute("ViewDog", "Animals/{controller}/{action}/{dogName}",
new { controller = "Dogs",
action = "ViewDog", dogName = "" });
The link to the URL is created:
<%= Html.RouteLink("Brown Buster", "ViewDog", new RouteValueDictionary(new { controller="Dogs", action="ViewDog", dogName="Buster" }))%>
The URL is created as expected. Thanks to Craig Stuntz and his blog post on Html.RouteLink.
http://mySite/Animals/Dogs/ViewDog/Buster
New Problem: The param dogName doesn't pickup the string value "Buster" from the URL. The call to the method succeeds, but the argument evaluates to null.
Questions
How can you:
make this route work with a string, and remove the default convention int id in the route? I'd like to change the name of the parameter away from int.
Are you sure that ActionLink is actually matching the route you show them the question? When you have more than one route, I strongly recommend using RouteLink instead of ActionLink, as I explain in great detail in this post. When you use RouteLink, there is no possibility that you will match the wrong route, at least in URL generation.
The default parameter "id" doesn't have to be an int. It'll match whatever type you declare in your action method. Why not just do the following?
public ActionResult ViewDog(string id)
{
if (id!= null)
{
var someDog = new DogFormViewModel(id); //snip a bunch more
return View(someDog);
}
else { return View("DogNotFound"); }
}