Can not pass data from a view to a controller - asp.net-mvc

I started working with MVC and i am currently working on an application. I ran into a small problem when I'm trying to pass some data from a view to a controller action.
This is my model :
public class TicketIndexModel
{
public IEnumerable<Ticket> ticketList { get; set; }
public Ticket newTicket { get; set; }
}
This is my controller action :
public ActionResult AddTicket(Ticket ticket)
{
string user = User.Identity.Name;
TicketDetail ticketDetails = new TicketDetail();
if (ModelState.IsValid)
{
ticket.DateCreated = DateTime.Now;
ticket.Status = "submitted";
ticket.UserName = user;
db.Tickets.Add(ticket);
db.SaveChanges();
//some other stuff
return RedirectToAction("Index");
}
And in the view, first i show all the tickets and after that I have a small form to add a new ticket. This is how the form looks like.
#using (Html.BeginForm("AddTicket","Ticket",FormMethod.Post)) {
<div class="editor-label">
#Html.LabelFor(model => model.newTicket.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.newTicket.Title)
#Html.ValidationMessageFor(model => model.newTicket.Title)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.newTicket.Description)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.newTicket.Description, new { rows= "8",cols="2"})
#Html.ValidationMessageFor(model => model.newTicket.Description)
</div>
<p>
<input type="submit" value="Submit" />
</p>
}
The problem I am facing is when I try to add a new ticket, the ticket parameter is always null, so If(ModelState.Isvalid) <- is always false and so i can not add new tickets to the DB.
Does anyone have any ideas why ?
Later Edit :
My model is now :
public IPagedList<Ticket> ticketList { get; set; }
//[Required(ErrorMessage ="Title required")]
[Display(Name = "Title")]
public string TicketTitle { get; set; }
//[Required(ErrorMessage="Description Required")]
[Display(Name = "Description")]
public string TicketDescription { get; set; }
My method is now :
[HttpPost]
public ActionResult AddTicket(TicketIndexModel newTicket)
{
string user = User.Identity.Name;
Ticket ticket = new Ticket();
if (ModelState.IsValid)
{
ticket.Title = newTicket.TicketTitle;
ticket.Description = newTicket.TicketDescription;
ticket.DateCreated = DateTime.Now;
ticket.Status = "open";
ticket.UserName = user;
db.Tickets.Add(ticket);
db.SaveChanges();
The problem i now have is the following :
If in the model, i comment [Required] i can add a ticket, if not, in the controller, the newTicket in the signature is null every time i submit a new ticket. But, the controller enters the if(ModelState.IsValid) and i get an error at db.SaveChanges(ticket).
I need both server and client side validation. Can you help me ?

Your AddTicket method should look like this
[HttpPost]
public ActionResult AddTicket(TicketIndexModel ticketIndexModel)
Then access newTicket from the object passed into it.

Related

Guid gets overwritten on form submit

When I load this form
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Category Description</legend>
#Html.HiddenFor(model => Model.ID)
#Html.HiddenFor(model => Model.CategoryID)
#Html.DisplayFor(model => Model.Language)<br />
<div class="editor-field">
#Html.TextAreaFor(model => model.Text, new {} )
#Html.ValidationMessageFor(model => model.Text)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
The fields have the following values:
But after submitting the form, in the POST Method, the ID gets overwritten by the other Guid and the Language Field is null.
The Data Model looks like this:
public partial class Category_Description
{
public System.Guid ID { get; set; }
public System.Guid CategoryID { get; set; }
public string Language { get; set; }
public string Text { get; set; }
public virtual Category Category { get; set; }
}
What did I do wrong?
EDIT:
On Request the Post-Method:
[HttpPost]
public ActionResult CategoryDescription_Edit(Category_Description model)
{
if (ModelState.IsValid)
{
var result = dataService.SaveChanges(model);
if (result)
return RedirectToAction("Index");
}
return View(model);
}
And just in case the dataService.SaveChanges as well:
public bool SaveChanges(object model)
{
Portal_Context.Entry(model).State = EntityState.Modified;
return Portal_Context.SaveChanges() > 0 ? true : false;
}
The hint of #DanielJ.G. set me into the right direction.
The problem was the name of the parameter I used for the
#Html.ActionLink( ...., new {id = Model.CategoryID}, .....)
and the corresponding
public ActionResult Category_Delete(Guid id)
As soon as I renamed "id" to "category_id", the ID did not get overwritten anymore.

The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'

Problem
I use the following code very similarily somewhere else in my application, but it is not working. I am completely stumped.
The ViewData item that has the key 'ShelfId' is of type 'System.Int32' but must be of type 'IEnumerable<SelectListItem>'
This is thrown during the post method. My model state is invalid.
Code
Models
Shelf
public class Shelf
{
[Key]
public int ShelfId
[Display(Name = "Shelf Id")]
[Required]
public string ShelfName
public virtual List<Book> Books {get; set;}
}
Book
public class Book
{
public int BookId
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string BookName
public int ShelfId
public Shelf shelf {get; set;}
}
Controller
// GET: Units/Create
public async Task<IActionResult> Create()
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
return View();
}
// POST: Units/Create
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
if (ModelState.IsValid)
{
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(book);
}
view
#model AgentInventory.Models.Book
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Create Unit</title>
</head>
<body>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal well bs-component" style="margin-top:20px">
<h4>Unit</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
<div class="control-label col-md-2">Room</div>
<div class="col-md-10">
#Html.DropDownListFor(model => model.ShelfId, (SelectList)ViewBag.SelectedShelves, "All", new { #class = "form-control" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.BookName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.BookName, "", new { #class = "text-danger" }
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Attempts
I tried:
Adding #Html.HiddenFor(model=>model.ShelfId) in the create view, but that didn't work.
I have looked at similar issues on stackoverflow, but none of the fixes worked for me. (IE - hiddenfor, different kinds of selectlists)
Since I am new to MVC framework, I would be grateful for any assistance. I don't understand why this code works for two other kinds of models (Building and room), but not my current two models? It's weird.
PS - Is there a way to do this easily without using viewbag as well?
The reason for the error is that in the POST method when you return the view, the value of ViewBag.SelectedShelves is null because you have not set it (as you did in the get method. I recommend you refactor this in a private method that can be called from both the GET and POST methods
private void ConfigureViewModel(Book book)
{
var shelves = await _db.Shelves.OrderBy(q => q.Name).ToListAsync();
// Better to have a view model with a property for the SelectList
ViewBag.SelectedShelves = new SelectList(shelves, "ShelfId", "Name");
}
then in the controller
public async Task<IActionResult> Create()
{
// Always better to initialize a new object and pass to the view
Book model = new Book();
ConfigureViewModel(model)
return View(model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Book book)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(book)
return View(book);
}
// No point setting these if the model is invalid
book.CreatedBy = User.Identity.GetUserName();
book.Created = DateTime.UtcNow;
book.UpdatedBy = User.Identity.GetUserName();
book.Updated = DateTime.UtcNow;
// Save and redirect
db.Units.Add(unit);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
Note your Book class contains only fields, not properties (no { get; set; }) so no properties will be set and the model will always be invalid because BookName has Required and StringLength attributes.
Also you have not shown all the properties in your model (for example you have CreatedBy, Created etc. and its likely that ModelState will also be invalid because you only generate controls for only a few properties. If any other properties contain validation attributes, then ModelState will be invalid. To handle this you need to create a view model containing only the properties you want to display edit.
public class BookVM
{
public int Id { get; set; }
[Required]
[StrengthLength(160, MinimumLength = 8)]
public string Name { get; set; }
public int SelectedShelf { get; set; }
public SelectList ShelfList { get; set; }
}
Then modify the private method to assign the SelectList to the view model (not ViewBag, and in the controller methods, pass a new instance of BookVM to the view, and post back to
public async Task<IActionResult> Create(BookVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model)
return View(model);
}
// Initialize a new Book and set the properties from the view model
}

how to select dropdownlist value and display in mvc3?

I have an MVC3 web application. On index.cshtml I have two dropdown lists. When I select from those lists I need to click on a next button and I want to display the selected values. How can i do this?
homecontroller.cs
DataRepository objRepository = new DataRepository();
public ActionResult Index()
{
ViewModel objViewModel = new ViewModel();
objViewModel.ID = objRepository.GetPricingSecurityID();
objViewModel.ddlId = objRepository.GetCUSIP();
return View(objViewModel);
}
ViewModel.cs
public class ViewModel
{
//DDL ID
[Required(ErrorMessage = "Please select a PricingSecurityID")]
public List<SelectListItem> ddlId { get; set; }
//DropDownList Values
[Required(ErrorMessage = "Please select a PricingSecurityID")]
public List<SelectListItem> ID { get; set; }
}
index.cshtml
<div class="editor-label">
#Html.Label("Pricing SecurityID")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ID,
new SelectList(Model.ID, "Value", "Text"),
"-- Select category --"
)
#Html.ValidationMessageFor(model => model.ID)
</div>
<div class="editor-label">
#Html.Label("CUSIP ID")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ddlId,
new SelectList(Model.ddlId, "Value", "Text"),
"-- Select category --"
)
#Html.ValidationMessageFor(model => model.ddlId)
</div>
<p>
<input type="submit" value="Next" />
</p>
How can I display selected values?
If your requirement is to build some kind of wizard, you need a way of maintaining state between steps.
ViewBag is no good for this because you should be following the PRG (Post/Redirect/Get) pattern for each wizard step.
TempData would work for navigating forward between steps but will fall over if the user goes back or navigates to a step directly.
You therefore need something with a longer lifetime. The ASP.NET Session object or a database are both good candidates for this.
Here's an example:
public class WizardController : Controller
{
public ActionResult Step1()
{
var session = GetWizardSession();
if (session.Step1 == null)
{
session.Step1 = new Step1View
{
PricingSecurityIds = new SelectList(new[] { 1, 2, 3, 4, 5 }),
SomeOtherIds = new SelectList(new[] { 1, 2, 3, 4, 5 })
};
}
return View(session.Step1);
}
[HttpPost]
public ActionResult Step1(Step1View cmd)
{
var session = GetWizardSession();
// save the wizard state
session.Step1.SelectedPricingSecurityId = cmd.SelectedPricingSecurityId;
session.Step1.SelectedSomeOtherId = cmd.SelectedSomeOtherId;
// now onto step 2
session.Step2 = new Step2View
{
PricingSecurityId = cmd.SelectedPricingSecurityId,
SomeOtherId = cmd.SelectedSomeOtherId,
Name = "John Smith"
};
return RedirectToAction("step2");
}
public ActionResult Step2()
{
return View(GetWizardSession().Step2);
}
public WizardSession GetWizardSession()
{
var session = Session["wizardsession"];
if (session == null)
{
session = new WizardSession();
Session["wizardsession"] = session;
}
return session as WizardSession;
}
}
public class Step1View
{
public SelectList PricingSecurityIds { get; set; }
public SelectList SomeOtherIds { get; set; }
public int SelectedPricingSecurityId { get; set; }
public int SelectedSomeOtherId { get; set; }
}
public class Step2View
{
public int PricingSecurityId { get; set; }
public int SomeOtherId { get; set; }
public string Name { get; set; }
}
public class WizardSession
{
public Step1View Step1 { get; set; }
public Step2View Step2 { get; set; }
}
In Step1 we make a call to GetWizardSession. This returns an object from the ASP.NET Session that contains all of the information we have collected for each step in the wizard. In this example we simply store the ViewModel for each step (i.e. session.Step1).
We check to see if Step1 exists in the session and create it if it doesn't. We then pass the Step1 model to our view.
When the user submits the form we update the "Selected" values in session.Step1. This ensures that if the user navigates back to /step1, we "remember" their values. We then build the model for Step2 and save it in the session.
When we navigate to /step2 we assume that a model exists in the session (because they should have got here from step1) so we just return return View(GetWizardSession().Step2);
The views:
Step 1
#model MvcWizardDemo.Controllers.Step1View
#{
ViewBag.Title = "Step1";
}
<h2>Step1</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Step1View</legend>
<div class="editor-label">
#Html.LabelFor(m => m.PricingSecurityIds)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.SelectedPricingSecurityId, Model.PricingSecurityIds)
#Html.ValidationMessageFor(m => m.PricingSecurityIds)
</div>
<div class="editor-label">
#Html.LabelFor(m => m.SomeOtherIds)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.SelectedSomeOtherId, Model.SomeOtherIds)
#Html.ValidationMessageFor(m => m.SomeOtherIds)
</div>
<p>
<input type="submit" value="Next" />
</p>
</fieldset>
}
Step 2
#model MvcWizardDemo.Controllers.Step2View
#{
ViewBag.Title = "Step2";
}
<h2>Step2</h2>
Hi, #Model.Name you selected the following values in the previous step:
<p>
<strong>Security Id:</strong> #Model.PricingSecurityId
</p>
<p>
<strong>Some other Id:</strong> #Model.SomeOtherId
</p>
Try this it should work :
[HttpPost]
public ActionResult Index(ViewModel model)
{
// put what you want to show
}

MVC Create page returning exception

I am having problems getting my create function to work right. I am trying to create an Order object, which has a SalesPerson and Customer object in it. My order model looks like
public class Order
{
public int ID { get; set; }
public SalesPerson SalesPerson { get; set; }
public bool PreviousWork { get; set; }
public OrderStatus Status { get; set; }
public Customer Customer { get; set; }
public List<OrderLineItem> LineItems { get; set; }
}
I then created a view model:
public class OrderViewModel
{
private sunburstdb db = new sunburstdb();
public Order originalOrder { get; set; }
public IList<SelectListItem> SalesPeopleList { get; set; }
public IList<SelectListItem> CustomersList { get; set; }
public IList<SelectListItem> OrderStatusList { get; set; }
public OrderViewModel(Order order)
{
originalOrder = order;
}
}
In my controller I have the following:
//
// GET: /Order/Create
public ActionResult Create()
{
Order order = new Order();
OrderViewModel viewModel = new OrderViewModel(order);
//IList<SelectListItem> result = new List<SelectListItem>();
viewModel.SalesPeopleList = new List<SelectListItem>();
foreach (SalesPerson person in db.SalesPeople)
{
var temp = new SelectListItem();
temp.Text = person.FullName;
temp.Value = person.ID.ToString();
viewModel.SalesPeopleList.Add(temp);
}
//viewModel.SalesPeopleList = new SelectList(result);
//result.Clear();
viewModel.CustomersList = new List<SelectListItem>();
foreach (Customer person in db.Customers)
{
var temp = new SelectListItem();
temp.Text = person.FullName;
temp.Value = person.ID.ToString();
viewModel.CustomersList.Add(temp);
}
//viewModel.CustomersList = new SelectList(result);
return View(viewModel);
}
//
// POST: /Order/Create
[HttpPost]
public ActionResult Create(Order order)
{
if (ModelState.IsValid)
{
db.Orders.Add(order);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(order);
}
Finally my view is pretty standard with a couple of fields to populate the data in the order.
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Order</legend>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.SalesPerson)
</div>
<div class="editor-field">
#Html.DropDownList("Order.SalesPerson", Model.SalesPeopleList)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.Customer)
</div>
<div class="editor-field">
#Html.DropDownList("Order.Customer", Model.CustomersList);
</div>
<div class="editor-label">
#Html.LabelFor(model => model.originalOrder.PreviousWork)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.originalOrder.PreviousWork)
#Html.ValidationMessageFor(model => model.originalOrder.PreviousWork)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
When I run this and try to create a new order I get the following: The model item passed into the dictionary is of type 'Models.Order', but this dictionary requires a model item of type 'Models.OrderViewModel'. I thought maybe I needed to change the parameter in the create method to public ActionResult Create(OrderViewModel order) however when I do this the error is: No parameterless constructor defined for this object. Can someone provide some help to an MVC Noob about what I am doing wrong?
in the action pass the viewmodel.
The error you get is because you created only a constructor with parameters, but MVC wnat also a parameterless contructor.
Aps.net 4 will create it automatically for you if you don't specify any constructor, but if you define one, then it don't take initiative creating one that maybe you don't want.
Look here. That should explain better than me
So that happens during model binding as for me.
What is the best way to debug such circumstances is to implement model binder which is inherited from default one and set it as default model binder for your object (Order).
Try to provide more information for more specific answer.
You can use the link below as a sample of custom model binder and registration
http://www.markeverard.com/blog/2011/07/18/creating-a-custom-modelbinder-allowing-validation-of-injected-composite-models/
PS: in my opinion it's not the best way to put models into viewmodels.
but there still are implementations that contain commands and services so it's up to you.

ASP.NET MVC DropDownFor Validation (Value cannot be null. Parameter name: source)

I am still struggling with learning ASP.NET MVC. All my form entries are required so I would like to do validation on them. For brevity I have paired my model down to Description (textbox) and Paradigm (dropdown). I am including Entry.cs, Paradigm.cs and EntryViewModel.cs Model classes and the Display.cshtml View.
[Bind(Exclude = "EntryId")]
public class Entry
{
[ScaffoldColumn(false)]
public int EntryId { get; set; }
[Required(ErrorMessage = "You must include a description.")]
public string Description { get; set; }
[Display(Name = "Type")]
[Required(ErrorMessage = "You must select a type.")]
public int ParadigmId { get; set; }
public virtual Paradigm Paradigm { get; set; }
}
public class Paradigm
{
[ScaffoldColumn(false)]
public int ParadigmId { get; set; }
[Required]
public string Name { get; set; }
public List<Entry> Entries { get; set; }
}
public class EntryViewModel
{
public Entry Entry { get; set; }
public IEnumerable<Entry> Entries { get; set; }
}
#model Pylon.Models.EntryViewModel
#{
ViewBag.Title = "Display";
}
<hr />
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Entry</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Entry.Description)
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.Entry.Description)
#Html.ValidationMessageFor(model => model.Entry.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Entry.ParadigmId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Entry.ParadigmId, ((IEnumerable<Pylon.Models.Paradigm>)ViewBag.PossibleParadigms).Select(option => new SelectListItem {
Text = (option == null ? "None" : option.Name),
Value = option.ParadigmId.ToString(),
Selected = (Model != null) && (option.ParadigmId == Model.Entry.ParadigmId)
}))
<img src="../../Content/Images/add_icon.gif" />
#Html.ValidationMessageFor(model => model.Entry.ParadigmId)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
If I submit the form without entering a description I would like validation to kick in and say "You must include a description." However instead I receive an ArgumentNullException on the DropDownFor line. http://www.wvha.org/temp/ArgumentNullException.png
What should I be doing? As an aside any decent books that cover ASP.NET MVC 3/Razor. I can follow along the basic tuts, but I go astray when I need to deviate to more advance features.
public class EntriesController : Controller
{
private readonly PylonContext _context = new PylonContext();
public ActionResult Display()
{
// DropDown
ViewBag.PossibleParadigms = _context.Paradigms;
var viewModel = new EntryViewModel {Entries = _context.Entries.ToList()};
return View(viewModel);
}
[HttpPost]
public ActionResult Display(EntryViewModel viewModel)
{
if (ModelState.IsValid)
{
_context.Entries.Add(viewModel.Entry);
_context.SaveChanges();
return RedirectToAction("Display");
}
return View(viewModel);
}
}
It's quite difficult to say without seeing your controller code, but looks like your ViewBag.PossibleParadigms might be null.
Does your insert/update controller action look something like this?
if (ModelState.IsValid) {
///...
} else {
return View(model);
}
If so, you need to put the PossibleParadigms back into the ViewBag (so to speak) before you return back to the view.
If you can post the relevant controller action code, it would be easier to know for sure.

Resources