Default values for model fields in ASP.Net MVC3 - c#

I have a simple Action in my Controller:
public ActionResult Edit(int itemId)
{
return View(new EditModel() { ItemId = itemId + 1 });
}
public class EditModel
{
public int ItemId { get; set; }
public string Title { get; set; }
}
The problem comes in the View, when I try to display everything.
Model.ItemId: #Model.ItemId
#Html.EditorForModel()
Since action parameter and property on EditModel have the same name (itemId) I get the following:
Is this a correct behaviour? How can I change default value displayed inside a form in that case?

This may be somehow confusing for the first look, but yes, this is default(correct) behavior. Controller.ModelState is the privileged supplier for the values when you use EditorFor or similar editor helpers, over the model itself. But there's trickier point in your situation.
ModelState is populated with action parameters and values that take part in model binding. When you call that action, ModelState is populated with "ItemId" = action parameter(itemId) value. Later, EditorFor sees that is should draw editor for ItemId. As ModelState has already got ItemId, it does not look at model value, but extracts it from ModelState["ItemId"]. Tricky source of your error is that action parameter name matches the model property name and modelState prefers that over model value. The best solution (clean one) would be to just rename action parameter, so that it does not match the model property name.
public ActionResult Edit(int initialItemId)
{
return View(new EditModel() { itemId = initialItemId + 1 });
}
This will do the trick.

You can write
Model.ItemId: #Model.ItemId
#Html.EditorFor(x => x.Title)
Or hide ItemId for edit with metadata

Related

Casting (int)enum to a object property does not get passed when called by razor view model

Using MVC 5 and Razor pages.
The scenario is when I cast (int)colour to a Item property called Colour which is of type INT in my HttpGet method. This value should be passed to the ViewModel, and i store it as a hiddenfield on razor view.
So when debugging my code:
return View(viewModel); the viewModel.Colour = 23 - which is correct.
This will now display the Razor view, and inside this razor view i store the model.House value into a hidden field, so i can perform a HttpPost.
At this stage my raw URL is:
applicationName/Orange/Item/Create?date=2014-08-05&_1236723545
Notice there is Orange in the Colour param in the url, and also a model which is at the end, which has a Colour property, which has the number 23.
Now once iv completed filling the form in, and press save, the ModelState is not valid at this stage, because for some odd reason in the razor view
#Html.HiddenFor(model => model.Colour)
stores the string value "Orange" instead of 23, which has been provided by the model. Now i understand the names clash in the URL and my object model, but surely this should work.
My Route Config
routes.MapRoute(
name: "Item",
url: "{colour}/{controller}/{action}/{id}",
defaults: new { controller = "Item", action = "Index", id = UrlParameter.Optional }
);
My Get method:
[HttpGet]
public ActionResult Create(Colour colour, DateTime? date)
{
var viewModel = new Item()
{
ItemDate = (!date.HasValue ? DateTime.Now.Date : date.Value),
Colour = (int) colour,
ItemTypeId = 1
};
return View(viewModel);
}
My Post Method
[HttpPost]
public ActionResult Create(Item item)
{
if (ModelState.IsValid)
{
_itemService.Create(item);
return RedirectToAction("Index", "Item", new { date = item.itemDate.ToString("yyyy-MM-dd") });
}
return View(item);
}
SO my question is how do i get my razor view to ignore the colour that is written in url and to use colour which is the Item property. So instead of "Orange" it should be 23.
Can anyone advise?
Explicitly excluding it is a good way to go:
public ActionResult YourMethod([Bind(Exclude = "Colour")] Item item{
//Implementation
}
Another option is to explicitly INCLUDE all of the parameters from the request that you want to bind to:
public ActionResult YourMethod([Bind(Include = "NotColour1, NotColour2")] Item item{
//Implementation
}
I tend to prefer this method because
Visual Studio stubs it out this way when it scaffolds stuff (not a technically sufficient reason, but good enough)
Gives me a bit more confidence security-wise, because you say what IS and not what CANT be and I therefore feel just a little bit more in control.
I agree with the suggestion, though, that you should look into choosing different names for non-related things (or at least put them in separate namespaces?)

Getting "Id" value from the URL in MVC 4

My URL is something like,
localhost:19876/PatientVisitDetail/Create?PatientId=1
I have to retrieve the PatientId from the URL and pass it along the request.
I tried,
Url.RequestContext.Values["PatientId"] => System.Web.Routing.RequestContext does not contain a definition for 'Values' and
no extension method 'Values' accepting a first argument of type 'System.Web.Routing.RequestContext'
Again I tried,
RouteData.Values["PatientId"] => an object reference is required for the non static field, method
or property 'System.Web.Routing.RouteData.Values.get'
EDIT:
Based on the Jason's comment below, I tried Request["SomeParameter"] and it worked. But, there is also a warning to avoid that.
Any ideas how to avoid this for my scenario ?
My scenario:
There is a Create action method in my controller for creating a new patient.
But, I need to go back to the last page,
If I give something like,
#Html.ActionLink("Back to List", "Index")
=> this wont work because my controller action method has the following signature,
public ActionResult Index(int patientId = 0)
So, I must pass along the patientId in this case.
You are effectively circumventing the whole point of MVC here. Have an action which accepts PatientId i.e.
public ActionResult Create(int patientId)
{
return View(patientId);
}
Then in your view use that value e.g.
#model int
#Html.ActionLink("Back", "LastAction", "LastController", new { patientId = #Model })
This is the MVC way.
From your controller, you could put the PatientId in a ViewBag and access it from your View
public ActionResult Create()
{
ViewBag.PatientId = 1;
return View();
}
View
Html.ActionLink("Back", "Index", new { PatiendId = ViewBag.PatientId })

MVC models don't persist in a form

I declare a model with 4 string fields. 3 of which are read-only on the form:
public class HomeModel
{
[ReadOnly(true)]
[DisplayName("Service Version")]
public string ServiceVersion { get; set; }
[ReadOnly(true)]
[DisplayName("Session Id")]
public string SessionId { get; set; }
[ReadOnly(true)]
[DisplayName("Visiting from")]
public string Country { get; set; }
[DisplayName("Search")]
public string SearchString { get; set; }
}
I pass the model, after populating it, to my form:
[HttpGet]
public ActionResult Index()
{
var model = new HomeModel
{
Country = "Australia",
SearchString = "Enter a search",
ServiceVersion = "0.1",
SessionId = "76237623763726"
};
return View(model);
}
And the form is displayed as I expect:
<h2>Simple Lookup</h2>
#Html.LabelFor(m=>m.ServiceVersion): #Model.ServiceVersion<br/>
#Html.LabelFor(m=>m.SessionId): #Model.SessionId<br/>
#Html.LabelFor(m=>m.Country): #Model.Country<br/>
<p>
#using(Html.BeginForm())
{
#Html.LabelFor(m => m.SearchString)
#Html.TextBoxFor(m => m.SearchString)
<button type="submit" name="btnSearch">Search</button>
}
</p>
But, when I submit the form, and get the model back from the form, only the value of the SearchString is populated.
[HttpPost]
public ActionResult Index(HomeModel model)
{
return View(model);
}
Is it right that the other fields have been 'lost'? Does MVC not preserve the other members of the model class? And if this is expected - is there a way to re-get these? Or would I need to go back to my database, populate the model with the old values, and then use the new values from the form model?
It's possible the validity of wanting to read 'read-only' fields back from the model is questioned.. which is fair - but in the event that I find something suspect about the posted data, maybe I want to re-show the screen, and not have to re-read the data from a database again?
This is the correct behavior. Only the elements inside form will be posted to your action. Since it is posting the form so your fields should be inside the form in order to get them on your post method.
Update
Also, you cannot read particular field on your action method if you have taken that field readonly on your view. eg: displaying using #Html.LabelFor. In order to get field back on your action use #Html.HiddenFor if field is not to be edited.

Asp.net Create ActionResult parameters dictionary contains a null entry for parameter

I have not changed my code, which used to work, I even clarified this with an earlier build of the project. However, I now get this error:
The parameters dictionary contains a null entry for parameter 'recipeID' of non-nullable type 'System.Int32' for method 'System.Web.Mvc.ActionResult Create(Int32, BareCupboard.Models.RecipeStep)' in 'BareCupboard.Controllers.RecipeStepController'. An optional parameter must be a reference type, a nullable type, or be declared as an optional parameter.
Parameter name: parameters
My code is:
[HttpPost]
public ActionResult Create(int recipeID, RecipeStep newRecipeStep)
{
try
{
var recipe = db.Recipes.Single(r => r.recipeID == recipeID);
recipe.RecipieSteps.Add(newRecipeStep);
db.SaveChanges();
return RedirectToAction("Index", "Recipe");
}
catch
{
return View();
}
}
I tried: int? recipeID, but this fails to work.
Any ideas what might have happened as all I can see is mysticism at play here!
Check your view code for the order of the parameters. The model binder needs it come in proper order. It is very easy to make mistake there.
Update
Here one way of resolving this. Create a view model as below:
public class RecipeViewModel
{
public int RecipeId { get; set; }
public RecipeStep RecipeStep { get; set; }
}
In the controller you will have the following:
public ActionResult Create()
{
var recipeId = 10 // however you want to select that Id
var recipeViewModel = new RecipeViewModel {RecipeId = 10}
return View(recipeViewModel);
}
[HttpPost]
public ActionResult Create(int recipeID, RecipeStep newRecipeStep)
{
//your code
}
In the view you can do the following:
#model MvcApplication3.Models.RecipeViewModel
#using (Html.BeginForm(null,null,FormMethod.Post))
{
<div>
<p>#Html.HiddenFor(x=>x.RecipeId) </p>
<p>#Html.TextBox("RecipeStepData1")</p>
<p>#Html.TextBox("RecipeStepData2")</p>
<p>#Html.TextBox("RecipeStepData3")</p>
</div>
<input type="submit" value="Submit" id="btn" />
}
Notice the order. I put the id first and then rest of the recipe step data so it gets bind properly.
For the begin form, You dont have to specify action and controller as you are doing the post on the same page. It doesnt hurt if you put there.
Hope this helps... :)
Why recipeID is null in in your posted data? It is your problem and
var recipe = db.Recipes.Single(r => r.recipeID == recipeID) cannot get recipeID=null because r.recipeID is not nullable.

Display text value of ASP.NET MVC SelectList on following page

I am working on a website that allows people to register for events. On the registration page, I have a form that captures the data, validates in an MVC controller, persists it between Views, and then is supposed to display a preview page.
Below is a simplified example of what I am trying to do.
Person ViewModel
public class Person{
public int TitleId {get; set;}
public SelectList Titles {get; set;}
}
Register Controller
public class RegisterController:Controller
{
[HttpGet]
public ActionResult Register(){
return View(new Person());
}
[HttpPost]
public ActionResult Register(Person person){
if(ModelState.IsValid){
TempData["Person"] = person
RepopulateSelectLists(); //Gets the data for the select list again
return RedirectToAction("Preview");
}
return View();
}
[HttpGet]
public Actionresult Preview(){
Person person = (Person)TempData["Person"];
}
}
Preview View
#Html.DisplayFor(m => m.TitleId); //Will display int, not selected value
Assuming my SelectList has all of the values, and I have the key/Id of the value selected on the previous page, how do I redisplay just the text of what they selected?
It doesn't work that way. In your Preview() page you'd take the given ID and find its associated name. You'll likely want to build a custom View Model (VM) in this case to keep the ID and the Name in the VM.
Also, the moment you hit F5 on the preview page, everything will be GONE. TempData doesn't persist and any refreshing of the page will lose the data.
I would recommend, instead, that on successful postback, you just return a different view based on the initial post data. For example:
[HttpPost]
public ActionResult Register(Person person){
if(ModelState.IsValid){
RepopulateSelectLists(); //Gets the data for the select list again
var previewVM = new PreviewVM();
// populate it with the model or whatever else you need for the preview
return View("Preview", previewVM);
}
return View();
}
An example VM of a person
public class Person { public int TitleId { get; set; } }
public class PersonVM : Person { public string TitleName { get; set; } }
// ... then in your Register method
var previewVM = new PersonVM { TitleId = person.TitleId, TitleName = GetTitle(person.TitleId) };
Assuming GetTitle does a lookup based on the TitleID. It's very coarse code, but I hope you get the gist of it. THen you'd use Html.DisplayFor(m => m.Title); And if your preview has a post to the next page, you'd want to have Html.HiddenFor(m => m.TitleId) to make sure the value passes on but you'd have to do that even with your current method. Alternatively you could store the Title in the ViewBag (or ViewData -- same thing) and simply not useDisplayForto display the value and pass in the value from theViewBag`
Notice I scrapped the "Preview" method completely. That way, if you hit F5 at this point, it just re-posts with the same data you used to get to the preview page. And assuming you're not saving (I don't see where you are) then there's no harm in posting back. Make sense?

Categories

Resources