ViewModel objects child is null on POST - c#

I have a viewmodel, with two properties, one is a POCO class, the other is a double, when the controller builds and send the viewmodel out all the values are present and working on the page. When I POST the viewmodel the double value is set, and the object is present but the POCO class inside is null, which was not when the object is sent.
In the examples below, why is ticket.flight null on the POSTed viewmodel object?
ViewModel:
public class PayViewModel
{
public virtual Ticket ticket { get; set; }
public double amountToPay = 0.0;
[Display(Name = "Amount To Pay")]
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:N2}", ApplyFormatInEditMode = true)]
public double AmountToPay
{
get
{
return amountToPay;
}
set
{
amountToPay = value;
}
}
}
Controller methods:
// GET: Tickets/Pay/5
public ActionResult Pay(int? id)
{
if (id == null)
{
return RedirectToAction("Index");
}
Ticket ticket = db.tickets.Find(id);
if (ticket == null)
{
return RedirectToAction("Index");
}
PayViewModel viewModel = new PayViewModel();
viewModel.ticket = ticket;
return View(viewModel);
}
// POST: Tickets/Pay/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Pay([Bind(Include = "AmountToPay,ticket,ticket.flight")]PayViewModel viewModel)
{
if (ModelState.IsValid)
{
viewModel.ticket.AmountPaid += viewModel.AmountToPay;
if (viewModel.ticket.AmountPaid >= viewModel.ticket.flight.ticketPrice)
{
viewModel.ticket.status = TicketStatusType.CONFIRMED;
}
db.Entry(viewModel.ticket).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel.ticket);
}
View code - https://gist.github.com/JonFriesen/f511a91b8e9a90c1b23c

During the form submit the browser only submits the fields of type input. This means that from your view only these statements create the fields of type input:
#Html.HiddenFor(model => model.ticket.id)
#Html.EditorFor(model => model.AmountToPay, new { htmlAttributes = new { #class = "form-control" } })
When the request arrives to an MVC application, the binding uses the arrived values ticket.id and AmountToPay to construct an object of type PayViewModel. Since it cannot find any property from ticket.flight amongst the submitted values, it assumes that it should not exist and therefore it is assigned a default value of null. This is the expected behavior of model binder.
If you want to have the initialized values, I suggest one of the following:
On POST initialize the object from the database code and use only the submitted value of AmountToPay to perform your processing.
In the view add the hidden variables for the missing fields, e.g. #Html.HiddenFor(model => model.ticket.flight.source.airportCode). In this case the variables would be submitted therefore the object would be fully populated.

Related

Show buttons on a partial view based on some query

I am showing search results same as searching groups on facebook
enter image description here
I have a relationship Table named CommunityUser in Database having attributes CommunityID and UserID.
Using Partial View I want to show if User not already joined that Community/Group that it will show Join Button else if user already joined that community it will show Leave button.
I have written IsMember() function in my controller that takes two parameters, CommunityID and UserID. This will return true if that Community ID exist against that user ID.
public bool IsMember(string UserID, int CommunityID) {
var Membership = db.Users.Include(x => x.CommunityUsers).Where(s => s.Id.Equals(UserID)).Count();
if(Membership>0)
return true;
else
return false;
}
Now what I actually need is, I want to call this function in an IF condition on my view class. It is not allowing me to call this function on my view Class.
#if (){
<button>#Html.ActionLink("Leave", "LeaveCommunity", new { id = ViewBag.ComID })</button>
}
else
{
<button>#Html.ActionLink("Join", "joinCommunity", new { id = ViewBag.ComID })</button>
}
In your controller you should have a method which will return this view. So in this method you call this function
public ActionResult Index(string UserID, int CommunityID)
{
var hasMembership = IsMember(serID, CommunityID);
return View(hasMembership);
}
In the View it self then you just grab this variable hasMembership you just passed from #model.
#if (Model){
<button>#Html.ActionLink("Leave", "LeaveCommunity", new { id = ViewBag.ComID })</button>
}
else
{
<button>#Html.ActionLink("Join", "joinCommunity", new { id = ViewBag.ComID })</button>
}
Note: it might be wise to create some DTO class for passing data to a view, because you might need to pass multiple value to a view at some point. Plus the whole condition would be more readable
public SomeDTO {
public bool IsMember {get;set}
public List<Community> Communities {get;set;}
}
public ActionResult Index(string UserID, int CommunityID)
{
var hasMembership = IsMember(serID, CommunityID);
var listOfCommunities = _repo.GetComunities();
var dto = new SomeDTO
{
IsMember = hasMembership,
Communities = listOfCommunities
}
return View(dto);
}
#if (Model.IsMember){
// do or do not something
}

How can I resolve this error 'IEnumerable<SelectListItem>' in the drop down list MVC [duplicate]

This question already has answers here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
(6 answers)
Closed 6 years ago.
In my MVC project I pass list of currencies to the view within the drop down list. However, once I try to post the view I get the following exception:
The ViewData item that has the key 'FromCurrencyId' is of type
'System.Int32' but must be of type 'IEnumerable'.
Currency Controller
namespace Project.Controllers
{
public class CurrencyController : Controller
{
[HttpGet]
// GET: Currency
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(model);
}
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
return View(cur);
}
}
}
Currencies VM
namespace Project.ViewModels
{
public class Currencies
{
public int Id { get; set; }
[Required]
public int FromCurrencyId { get; set; }
public SelectList FromCurrencies { get; set; }
[Required]
public int ToCurrencyId { get; set; }
public SelectList ToCurrencies { get; set; }
public string CurrencyName { get; set; }
public string CurrencyCountry { get; set; }
[Required]
public decimal ConversionRate { get; set; }
public decimal Value { get; set; }
public SelectList AvailableCurrencies { get; set; }
}
}
CurrencyClient web service VM
namespace Project.ViewModels
{
public class CurrenciesClient
{
private string base_Url = "http://localhost:51646/api/";
public IEnumerable<Currencies> findAll()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(base_Url);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("currencies").Result;
if (response.IsSuccessStatusCode)
{
var resposeData = response.Content.ReadAsStringAsync().Result;
var Currency = JsonConvert.DeserializeObject<List<Currencies>>(resposeData);
return Currency;
}
return null;
}
catch
{
return null;
}
}
}
}
Index View
model Project.ViewModels.Currencies
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "Currency", FormMethod.Post))
{
#Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
#Html.DropDownListFor(m => m.FromCurrencyId, Model.FromCurrencies as IEnumerable<SelectListItem>)
#Html.DropDownListFor(m => m.ToCurrencyId, Model.ToCurrencies as IEnumerable<SelectListItem>)
<button type="submit" class="btn btn-primary">Convert</button>
}
This problem is becuase you are passing null value from your dropdown.Means you are not selecting any value. Check that if you will pass some value from dropdown it will work fine.To solve this problem you need to add the same
code
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
in your controller index post method.Because if selectListitem would be null the following code will be executed
IEnumerable<SelectListItem> selectList = o as IEnumerable<SelectListItem>;
if (selectList == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
MvcResources.HtmlHelper_WrongSelectDataType,
name, o.GetType().FullName, "IEnumerable<SelectListItem>"));
}
Which will throw exception.
(It would be better if you will use
ViewBag.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
like this.)
The better description is given here:
The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
The whole explanation is also given in this link like how does this code work.
you will have to set the Dropdown data in the post controller as well, otherwise it will not be able to find the ViewBag values, as a result when after post action it calls the Index view, the ViewBag.FromCurrencies and ViewBag.ToCurrencies will be null which is obviously that we don't want.
For fixing the error, you will have to change you post action to be like:
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (ModelState.IsValid)
{
if (cur.FromCurrencyId == cur.ToCurrencyId)
{
//do something if same currecnies and return.
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else
{
some code .....
}
}
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll().ToList();
Currencies model = new Currencies();
model.FromCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency, "Id", "CurrencyName");
return View(cur);
}
You should not return the view from your POST action or you will run into many issues. Here is the problem with that:
When you submit the form the URL is pointing to your Index. The body of your http post will have a bunch of Currencies items in it. Therefore, MVC, will submit the form to your Index method with Currencies cur parameter.
If all is well you return the same view.
If you refresh the page, your browser will simply reissue the last request and guess what, it will submit the form again. But this is not what you intended by refreshing. You wanted to get the form as it was originally presented not resubmit it.
Therefore instead of returning a view from a POST, you should always, except if AJAX was used, return a redirect.
In this case if all goes well, you may want to send the user to a success page or some other page so you should do this:
return RedirectToAction("YourActionName", "YourControllerName");
This pattern is called the PRG pattern. What this does is this:
user submits a form
If all goes well on the server side, you tell the browser to issue another request and get another page.
The browser gets the other page which may be a success page.
Now the user is on the success page. If they hit refresh they will get the success page again. They will not be submitting the same form over and over.

Prepopulate HtmlTextBoxFor with value from another field?

I have a web page that is using a number of different partial views and different viewmodels for each partial. One of the partials is for the ability for a user to change their username. Since we are going to allow email addresses as usernames now, I would like to pre-populate the textbox with their current email address from another partial view on the page. How can this be done properly so the value will be populated in the text box and then saved when the submit to the controller happens? Here is a brief code snippet from what I'm looking at:
The view:
#Html.TextBoxFor(model => model.NewUsername, new { id = "uName_change" }) #Html.ValidationMessageFor(model => model.NewUsername)
It's worth noting here that I have tried setting the current email address into ViewData and ViewBag and putting "#value = viewdatahere" into the textbox for properties, but this didn't populate the string, it just left it blank.
I just need to know how this is done. Any help is appreciated.
Edit to add controller POST and GET methods:
[HttpGet]
[ClaimsPrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Resource = Resources.User, Operation = Operations.Edit)]
public ActionResult ChangeUsername(Guid uniqueUserId)
{
User user = UserManager.GetUser(uniqueUserId);
AuthorizationHelper.ConfirmAccess(Resources.User, Operations.Edit, user);
ChangeUsername model = new ChangeUsername(){UserGuid = user.Identifier,NewUsername = user.Username};
return View(model);
}
[HttpPost]
[ClaimsPrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Resource = Resources.User, Operation = Operations.Edit)]
public ActionResult ChangeUsername(ChangeUsername changeUsername)
{
User usr = UserManager.GetUser(changeUsername.UserGuid);
AuthorizationHelper.ConfirmAccess(Resources.User, Operations.Edit, usr);
if (!ModelState.IsValid)
{
return View(changeUsername);
}
//setup request
SupportUser su = (SupportUser)ViewData["SupportUser"];
RequestData rqd = new RequestData(GetApplicationIdentifer(usr), su.clientIP, su.lanID);
UserUsernameChangeRequest request = new AdminUsernameChangeRequest(rqd,usr,changeUsername.NewUsername);
UserUsernameChangeResponse response = UserManager.ChangeUserUsername(request);
if (response.Status == UserProcessorStatus.Success)
{
var message = ("User has been updated successfully");
TempData["message"] = message;
return PartialView("_NewUsername", changeUsername);
}
switch (response.Status)
{
case UserProcessorStatus.DuplicateUsername:
{
ModelState.AddModelError("NewUsername", "Duplicate username");
changeUsername.AlternateUsernames = response.AlternateUsernames;
return PartialView(changeUsername);
}
default:
{
ModelState.AddModelError("NewUsername", String.Format("An unexpected error occured. Error Code:{0}, Message:{1}", response.Status, response.Message));
return PartialView(changeUsername);
}
}
}
There it is.
The last time that I tried to do this was with MVC 3, but I do know that this technique worked then. It's a stupid difference, that's why I pointed that out first.
You were very close with your attempt at it, but you need to set the property as #Value, not #value. I never looked to hard into why the capital made a huge difference. (Like I said, it's a stupid difference.)
var val = "Dummy Value";
#Html.TextBoxFor(model => model.NewUsername, new { id = "uName_change", #Value = val })
Here's how I'd do it.... in the main page set up a viewmodel that contains the other view models like this:
public class VMMain(){
public void VMMain(){
//don't forget null CTOR as MVC requires it...
}
public void VMMain(Guid userguid, string newusername){
UserGuid = userguid;
NewUserName = newusername;
Part1 = new VM1(UserGuid, NewUserName);
Part2 = new VM2();
Part3 = new VM3();
}
public VM1 Part1 {get;set;}
public VM2 Part2 {get;set;}
public VM3 Part3 {get;set;}
Guid UserGuid {get;set;}
string NewUsername {get;set;}
}
In your main view you can bind to anyone of these viewmodels and properties within them. Just make sure you have a property available to bind.
This would change your controller to look like this:
[HttpGet]
[ClaimsPrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Resource = Resources.User, Operation = Operations.Edit)]
public ActionResult ChangeUsername(Guid uniqueUserId)
{
User user = UserManager.GetUser(uniqueUserId);
AuthorizationHelper.ConfirmAccess(Resources.User, Operations.Edit, user);
var model = new VMMain(){UserGuid = user.Identifier,NewUsername = user.Username};
And now you can bind to say Partial ViewModel 1 that has that property like this:
#Html.TextBoxFor(p => model.Part1.NewUsername,
new { id = "uName_change" })
#Html.ValidationMessageFor(p => model.Part1.NewUsername)
return View(model);
}

All Dropdown Items have ZERO index MVC3

I have populated a dropdown list with values from Database Table. The list gets populated with correct table data but all values have ZERO index in the list. Here is the code to fill dropdown list:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
IEnumerable<SelectListItem> items = db.Attractions
.ToList()
.Select(c => new SelectListItem
{
Value = c.A_ID.ToString(),
Text = c.Name
});
ViewBag.Attractions = items;
return View();
}
And on Dropdown View Page:
<div class="editor-label">
#Html.LabelFor(model => model.Attraction)
</div>
<div class="editor-field">
#Html.DropDownList("Attractions")
</div>
For example if table have 3 values A,B, and C. These values are appearing in dropdown list but when I get its selected index in POST request function, it always returns ZERO. Here is the POST submit function:
//Post
[HttpPost]
public ActionResult NewBooking(BookingView booking)
{
try
{
BookingManager bookingManagerObj = new BookingManager();
bookingManagerObj.Add(booking);
ViewBag.BookingSavedSucess = "Booking saved!";
return View("WelcomeConsumer","Home");
}
catch
{
return View(booking);
}
}
booking.Attraction is always ZERO even user selected greater than ZERO index item.
Any suggestions?
I would guess that it is because you are getting a collection of SelectListItems back and not an actual SelectList. Try something like:
<div class="editor-field">
#Html.DropDownListFor(model => model.Attraction, new SelectList(ViewBag.Attractions, "Value", "Text");
It's best not to use ViewBag, you should always use a ViewModel.
Say you have a ViewModel like this:
public class AttractionViewModel
{
public int AttractionId { get; set; }
public SelectList Attractions { get; set; }
}
and modify your view like this - I presume you already have a form in there, the relevant bit is the #Html.DropDownListFor(...) and making sure you have the full namespace to the ViewModel if you haven't already included it in the Views web.config file:
#model AttractionViewModel
#using(Html.BeginForm("NewBooking", "ControllerName"))
{
<div class="editor-label">
#Html.LabelFor(model => model.AttractionId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.AttractionId, Model.Attractions)
</div>
<input type="submit" value="Submit">
}
and modify your HttpGet like this:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
var items = db.Attractions.ToList();
var attractionIdDefault = 0;// default value if you have one
var vm = new AttractionViewModel {
AttractionId = attractionIdDefault,// set this if you have a default value
Attractions = new SelectList(items, "A_ID", "Name", attractionIdDefault)
}
return View(vm);
}
and create an HttpPost ActionResult like this:
// Post
public ActionResult NewBooking(AttractionViewModel vm)
{
var attractionId = vm.AttractionId; // You have passed back your selected attraction Id.
return View();
}
Then it should work.
I know that you have already selected your answer but here is an alternative way of doing what you did. When I started off with ASP.NET MVC I struggled with SelectListItem and found another way of populating my drop down list. I have stuck to this way ever since.
I always have a view model that I bind to my view. I never send through a domain model, always a view model. A view model is just a scaled down version of your domain model and can contain data from multiple domain models.
I have made some modifications to your code and tips, but like I mentioned, it's just an alternative to what you already have.
Your domain model could look like this. Try and give your property names some meaningful descriptions:
public class Attraction
{
public int Id { get; set; }
public string Name { get; set; }
}
You view model could look something like this:
public class BookingViewModel
{
public int AttractionId { get; set; }
public IEnumerable<Attraction> Attractions { get; set; }
// Add your other properties here
}
Do not have your data access methods in your controllers, rather have a service layer or repository expose this functionality:
public class BookingController : Controller
{
private readonly IAttractionRepository attractionRepository;
public BookingController(IAttractionRepository attractionRepository)
{
this.attractionRepository = attractionRepository;
}
public ActionResult NewBooking()
{
BookingViewModel viewModel = new BookingViewModel
{
Attractions = attractionRepository.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult NewBooking(BookingViewModel viewModel)
{
// Check for null viewModel
if (!ModelState.IsValid)
{
viewModel.Attractions = attractionRepository.GetAll();
return View(viewModel);
}
// Do whatever else you need to do here
}
}
And then your view will populate your drop down like this:
#model YourProject.ViewModels.Attractionss.BookingViewModel
#Html.DropDownListFor(
x => x.AttractionId,
new SelectList(Model.Attractions, "Id", "Name", Model.AttractionId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.AttractionId)
I hope this helps.

Range validation for textbox not working in MVC3

I am trying to raise validation error in an ASP.Net MVC 3 application.
When the user enters a number that is greater than 1000 an error message should be displayed. with the following code on a view model it doesn't seem to be working.
What do i need to change?
[Range(0, 1000, ErrorMessage = "Total number of rows to display must be between 0 to 1000")]
public int DisplayTop { get; set; }
cshtml :
#model Calc.Models.CountingVM
#{
ViewBag.Title = "Reports";
Layout = "~/Views/Shared/_reportsLayout.cshtml";
}
#using (Html.BeginForm("Reports", "Calc", FormMethod.Post, new { #id = "frmReport" }))
{
.........
#Html.TextBoxFor(c => c.DisplayTop, "1000")
#Html.ValidationMessageFor(c => c.DisplayTop)
}
Action Method :
public ActionResult Reports(string ReportName, CalcCriteria criteria)
{
if ((criteria == null) || (criteria.nId == null))
{
criteria = TempData["criteria"] as CalcCriteria;
TempData["criteria"] = criteria; // For reload, without this reloading the page causes null criteria.
}
ReportType c = (ReportType)Enum.Parse(typeof(ReportType), ReportName, true);
JavaScriptSerializer serializer = new JavaScriptSerializer();
string vmJson = string.Empty;
switch (c)
{
.....
int displayTop;
..........
case ReportType.Inventory_Counts_Report:
..............
displayTop = Convert.ToInt32(Request["DisplayTop"]);
........
return View("Counting", CountingVM);
default:
return View();
}
return View(); }
Thanks
BB
You also need to display the validation message:
#Html.ValidationMessageFor(c => c.DisplayTop)
Try
#Html.EditorFor(c => c.DisplayTop, "1000")
I guess, that the problem occures, becase your property is of type int, and the range is for type int, byt you are showing it in a textbox.
Also you need to add ModelState.IsValid in your controller. For example:
[HttpPost]
public ActionResult Create(YourModel model)
{
if(ModelState.IsValid)
{
// Put your logic here
}
return View(create)
}

Categories

Resources