MVC ViewModel null values in POST - c#

I am fairly new to MVC and I have run into a problem persisting values on the page POST. Below is a simplified version of what I am trying to do. Basically I have a form where I display the users name and other details. On the POST ActionResult if the business rules fail the page should return to the current view else will proceed on but on my POST the name and address I set in the GET do not appear in the POST for the view model resulting in null values being sent back in the return View(model). Are there ways I can persists the values without having go back to the database to retrieve them?
Model
public class HomeModel
{
public string Forename { get; set; }
public string Surname { get; set; }
public string AddressLine1 { get; set; }
public bool Continue { get; set; }
}
Controller
[HttpGet]
public ActionResult Index()
{
var model = new HomeModel();
model.Forename = "Joe";
model.Surname = "Boe";
model.AddressLine1 = "Unknown";
return View(model);
}
[HttpPost]
public ActionResult Index(HomeModel homeModel)
{
if (homeModel.Continue)
return RedirectToAction("Index", "Form2");
else
return View(homeModel);
}
View
#using (Html.BeginForm())
{
#Html.LabelFor(m => m.Forename, Model.Forename)<br />
#Html.LabelFor(m => m.Surname, Model.Surname)<br />
#Html.LabelFor(m => m.AddressLine1, Model.AddressLine1)<br /><br />
#Html.CheckBoxFor(m => m.Continue)<br /><br />
<input type="submit" value="Click" />
}

LabelFor doesn't create an HTML input type, so it's not included in the postback. You can make them TextBoxFors or, create HiddenFors alongside your labels so they persist back to the server

You can use a hidden field:
#Html.HiddenFor(model => model.ID)
This will then post the value back to the server. This will increase your data amounts for the post, and for large forms is inadvisable.
That said, you are probably better off rebuilding the data for the page load or doing server side caching, as otherwise you run the risk of injections of bad data. That may not matter to you if it is read only though.

Related

Get Query Parameter from URL into MVC View

I have an a href link to a page which adds a parameter to the link for example:
tsw/register-your-interest?Course=979
What I am trying to do is to extract the value in Course i.e 979 and display it in the view. When attempting with the below code, I only return with 0 rather than the course value expected. ideally I'd like to avoid using routes.
Here is the view:
<div class="contact" data-component="components/checkout">
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Model.Course;
}
And my controller:
public ActionResult CourseEnquiry(string Course)
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
model.Course = Request.QueryString["Course"];
return model
}
This is the View Model:
public class CourseEnquiryVM : PageContentVM
{
public List<OfficeLocation> OfficeLocations { get; set; }
public string Test { get; set; }
public string Course { get; set; }
public List<Source> SourceTypes { get; set; }
}
SOLUTION:
After some research and comments I've adjusted the code to the below which now retrieves the value as expected
#Html.HiddenFor(m => m.Course, new { Value = #HttpContext.Current.Request.QueryString["Course"]});
Thanks all
Based on the form code you provided you need to use #Html.HiddenFor(m => m.Course) instead of just #Model.Course. #Model.Course just displays the value as text instead of building a input element that will be sent back to your controller.
If your problem is with a link prior to the view you referenced above, here's what I'd expect to work:
View with link:
#model CourseEnquiryVM
#Html.ActionLink("MyLink","CourseEnquiry","CourseController", new {course = #Model.Course}, null)
CourseController:
public ActionResult CourseEnquiry(string course)
{
// course should have a value at this point
}
In your view, you are only displaying the value of Course.. which isn't able to be submitted. You need to incorporate the value of course with a form input element (textbox, checkbox, textarea, hidden, etc.).
I would highly suggest using EditorFor or Textboxfor, but because your controller action is expecting just a string parameter you could just use Editor or TextBox.
#using (Html.BeginUmbracoForm<CourseEnquiryPageSurfaceController>("PostCourseEnquiryForm", FormMethod.Post, new { id = "checkout__form" }))
{
//#Html.ValidationSummary(false)
#Html.TextBox(Model.Course, null, new { #class = "form-control"});
<input type="submit" value="Submit" />
}
Then you should just be able to do this in your controller:
public ActionResult CourseEnquiry(string course) // parameter variables are camel-case
{
var model = Mapper.Map<CourseEnquiryVM>(CurrentContent);
if(!string.IsNullOrWhiteSpace(course))
model.Course = course;
return model;
}
Let me know if this helps.

Using .net MVC why don't my lists survive a post?

I'm just picking up .net MVC and I've come across something that I can't work out. I'm obviously missing some basic principle but would love some help.
I have a ViewModel with two IEnumerables that I want to use to create dropdownlistfors. My GET works fine, the lists are populated as expected.
Now I'm posting the ViewModel back to a POST method, not to do anything useful but just to try and understand how mvc works. I expected that I would simply be able to re-populate the dropdownlistfors from the model that was posted back - but I get a null reference exception.
Other values, such as partyid, in the ViewModel survive the POST so i'm confused.
I can get it to work if I repopulate the lists but that seems wrong.
Can someone give me a pointer?
My ViewModel
public class DemoViewModel
{
//properties
public IEnumerable<tbl_server_lookup> servers { get; set; }
public int serverId { get; set; }
public IEnumerable<tbl_site_lookup> sites { get; set; }
public int siteId { get; set; }
public int partyid { get; set; }
public string message { get; set; }
public DemoViewModel()
{
}
}
My Controller
// GET: /Demos/Test/
[HttpGet]
public ActionResult Test()
{
DemoViewModel demo = new DemoViewModel();
using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var serverList = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = serverList.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}
demo.message = "Enter the user id you would like to look up in the box below.";
return View(demo);
}
//
//POST: /Demos/Test/
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Test(DemoViewModel demo)
{
//It works if I uncomment this block...
/*using (var dbContext = new ADAPI.Models.db_ad_apiEntities2())
{
var myQuery = dbContext.tbl_server_lookup.Where(s => s.server_name != null);
demo.servers = myQuery.ToList();
var siteList = dbContext.tbl_site_lookup.Where(w => w.site_name != null);
demo.sites = siteList.ToList();
}*/
demo.message = "the user id you posted is: " + demo.partyid + ". The Server you selected is: ";// +demo.serverId;
return View(demo);
}
My View
#model ADAPI.ViewModels.DemoViewModel
<h2>Demos</h2>
<h3>#Model.message</h3>
#using (Html.BeginForm("Test","Demos"))
{
#Html.AntiForgeryToken()
<div class="">
<h4>Party ID</h4>
#Html.ValidationSummary(true)
<!-- input box for party id-->
#Html.TextBoxFor(model => model.partyid)
<!-- dropdown list of server types eg live vs test-->
#Html.DropDownListFor(model => model.serverId, new SelectList(Model.servers, "server_Id","server_name"))
#Html.DropDownListFor(model => model.siteId, new SelectList(Model.sites, "site_short_name","site_name"))
<input type="submit" value="Try" />
</div>
}
The Error
In MVC, model on the views are loaded in the controller action, they are not posted back along with the post action.
If you are used to ASPX's viewstate, there is no such thing in MVC, you need to load what you need for every view in the current action.
Dropdown lists are rendered into html as tag and returned to the server as plain single value.
You have to rebind/repopulate them on the server, wchich is annoying in scenarios like validation, where the same model should be returned to the client.
There is no support for that in the framework - you have to do it on your own.
One more thing - if you absolutely have to return the list items and want them back on the server, you can serialize tham and hide in some hidden field. But it's ugly and unsecure since anyone can change its value.

Rendering LabelFor doesn't display the overwritten getter function

pardon me if this is a noob question but I have already searched on SO, google, and spend over an hour with an ASP.NET MVC 4 PRO book reading on Rendering Stronly Typed Helpers etc. I am just beginning learning ASP.NET 4 MVC so please go easy on me.
I have a model class called USER and a property called Name with an overwritten getter and setter (its me calling it this way, im not sure if this is the proper naming in the case)
//using...
namespace MvcMyApplication1.Models
{
public class User
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name
{
get
{
return WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
set
{
Name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
[Required(ErrorMessage="Password is required")]
public string Password { get; set; }
}
}
So, In my View I am trying to display the result of the get function, but I am lost and not sure how to do this.
#using(Html.BeginForm())
{
#Html.ValidationSummary(excludePropertyErrors: true);
// changed from LabelFor to DisplayFor
#Html.DisplayFor(m => m.Name)<br />
#Html.PasswordFor(m => m.Password)<br />
<input type="submit" value="Log in" />
}
I have tried to add attributes but then I am not sure how to assign the Name= to the get function
[DisplayName(Name="I want to call the get function here")]
public string Name { get; set; }
In my controller I have this code:
[HttpGet]
public ActionResult Index()
{
User newUser = new User();
return View();
}
[HttpPost]
public ActionResult Index(User m)
{
if (ModelState.IsValid)
{
return View("Report", m);
}
{
return View(m);
}
}
And this goes to the Report View
which normally displays the Windows login
#using (Html.BeginForm())
{
#Html.DisplayForModel()
}
EDIT: After swapping the LabelFor with DisplayFor the windows login is rendered but only after clicking the log in button. It does not render the first time I open the page
You're not passing in the model to the initial view:
return View();
should be
return View(newUser);
You should be using DisplayFor not LabelFor if you want to retrieve the property value.
#Html.DisplayFor(m => m.Name)
To get current authenticated user in the view, you can use #User.Identity.Name.
In controller, use User.Identity.Name
To display "I want to call the get function here" in DisplayName attribute in the view, use #Html.DisplayNameFor(m => model.Name)
Use WindowsIdentity in your class is not a good idea. For MVC, better use User.Identity.Name (use this in controller), the reason is this way it is get the user name under current HttpRequest, use WindowsIdentity might be fine, but careful. Depending on how you use it, it might return error or return the service account which ran the application pool.
Normally, getter and setter are written like this by calling a private property:
public class User{
private string _name;
public string Name
{
get{
return _name;
}
set{
_name = WindowsIdentity.GetCurrent().Name.Split('\\')[1];
}
}
It looks like your MVC app is using Windows Authentication, which is an intranet app, so your user will be automatically authenticated, which means logged on already.

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.

How should I validate different sections of MVC3 page

I am trying to understand how I should validate on the client sections of my MVC3 page independently and have come up with a simplyfied version of what I am trying to achieve.
If I use one form:
Pros: When I submit back to the "PostData" controller method I receive all data contained within the form. In this case both values "name" and "description", which means that I can instantiate "PersonHobbyModel" and assign the data I have received. I can either store in the database or I can return the same view.
Cons: I cant validate independently. So if "name" isn't completed and I complete "description" I can still submit the page. (This is a simplyfied version of what I am trying to do and I would have more fields than just "name" and "description")
With two forms:
Pros: I can validate independently.
Cons: The controller method only receives the subitted forms data which, in this case either "Persons name" or "Hobby description" which means that I can't recreate a full instance of "PersonHobbyModel".
This is the model:
public class Person {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
}
public class Hobby {
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
public class PersonHobbyModel {
public PersonHobbyModel() {
this.Person = new Person();
this.Hobby = new Hobby();
}
public Person Person { get; set; }
public Hobby Hobby { get; set; }
}
This is the controller:
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
public ActionResult Index()
{
var model = new PersonHobbyModel();
return View(model);
}
public ActionResult PostData(FormCollection data) {
var model = new PersonHobbyModel();
TryUpdateModel(model.Person, "Person");
TryUpdateModel(model.Hobby,"Hobby");
return View("Index", model);
}
}
This is the view:
#model MultipleFORMStest.PersonHobbyModel
#{
ViewBag.Title = "Index";
}
<h2>
Index</h2>
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
}
#using (Html.BeginForm("PostData", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
UPDATE 1
I didnt mention, as I wanted to keep the question as simple as possible, but for one of the sections I am using "jquery ui dialog". I initially used a DIV to define the dialog, which I had inside my main form. This would of caused one problem as I wouldn't have been able to validate on the client the "JQuery dialog form" independently from the rest of the form.
Saying this jquery did removed the "div jquery ui dialog" from the main form which made me include the dialog in it's own form. For this reason I have ended up with two forms. The advantage is that I can now independently validate the "jquery dialog ui form".
But I am confused as to how should I handle on the server data submited from various forms on the client as there is a chance that the user has JS disabled. If I submit from one form I can't access the data in other forms.
UPDATE 2
Thanks for the replies. I believe I do need two forms and two entities as I want to validate them independently on the client, (apart from being kind of forced to by "Jquery UI Dialog"). For instance if I have, instead of one hobby I have a list of hobbies, which I could posible display in a grid in the same view. So I could not fill in the person name, but continue to add hobbies to the grid, If I do not complete the hobby description I'd get a validation error. (Sorry as I should of included both of my updates in the initial question but for the purpose of clarity I wanted to keep it as simple as posible)
From my perspective, you have a single view model that corresponds to two entity models. In your place I would use a single form and validate the view model and not really think about it as two (dependent) entities. Receive back the view model in your action, instead of a generic form collection, and use model-based validation via data annotation attributes. Once you have a valid, posted model you can then translate that into the appropriate entities and save it to the database.
Model
public class PersonHobbyViewModel {
[Display(Name = "Person name:")]
[Required(ErrorMessage = "Person name required.")]
public string Name { get; set; }
[Display(Name = "Hobby description:")]
[Required(ErrorMessage = "Hobby description required.")]
public string Description { get; set; }
}
Controller
public class PersonHobbyController : Controller
{
//
// GET: /PersonHobby/
[HttpGet] // mark as accepting only GET
public ActionResult Create() // Index should probably provide some summary of people and hobbies
{
var model = new PersonHobbyViewModel();
return View(model);
}
[HttpPost] // mark as accepting only POST
public ActionResult Create(PersonHobbyViewModel model) {
if (ModelState.IsValid) {
var person = new Person { Name = model.Name };
var hobby = new Hobby { Description = model.Description };
person.Hobbies = new List<Hobby> { hobby };
db.Persons.Add( person );
db.SaveChanges();
}
return RedirectToAction( "details", new { id = person.Id } ); // view the newly created entity
}
}
View
#model MultipleFORMStest.PersonHobbyViewModel
#{
ViewBag.Title = "Create";
}
<h2>
Create</h2>
#using (Html.BeginForm("Create", "PersonHobby")) {
<div>
#Html.LabelFor(model => model.Person.Name)
#Html.TextBoxFor(model => model.Person.Name)
#Html.ValidationMessageFor(model => model.Person.Name)
<input type="submit" value="Submit person" />
</div>
<div>
#Html.LabelFor(model => model.Hobby.Description)
#Html.TextBoxFor(model => model.Hobby.Description)
#Html.ValidationMessageFor(model => model.Hobby.Description)
<input type="submit" value="Submit hobby" />
</div>
}
I think your ViewModel should be only only specific to that view you are representing. In this case, i would use a ViewModel like this
public class AddPersonHobbyViewModel
{
[Required]
[Display (Name="Person Name")]
public string PersonName { set;get;}
[Required]
[Display (Name="Hobby Description")]
public string HobbyDescription { set;get;}
}
And in my PostData ActionMethod, I will check for Model Validation
[HttpPost]
public ActionResult PostData(AddPersonHobbyViewModel objVM)
{
if(ModelState.IsValid)
{
// Everything is fine. Lets save and redirect to another get View( for PRG pattern)
}
return View(objVm);
}
And you use only one Form in your View which is strongly typed to AddPersonHobbyViewModel
#model AddPersonHobbyViewModel
#using (Html.BeginForm("PostData","Person"))
{
#Html.TextBoxFor(m=>m.PersonName)
#Html.ValidationMessageFor(m => m.PersonName)
#Html.TextBoxFor(m=>m.HobbyDescription )
#Html.ValidationMessageFor(m => m.HobbyDescription )
<input type="submit" value="Save" />
}

Categories

Resources