#Html.DropDownListFor and viewmodel not posting back to controller? - c#

It's time to ask the internet. I'm a student and really new to MVC + coding in general, but I can't for the life of me figure out why this isn't working. Probably something really obvious. :/
I have a View (AddMemberToGroup) that is strongly-typed to a viewmodel (PersonGroupingViewModel). What am I trying to do is add a Person to a Group, with the user selecting a group from a dropdown list in the View.
Viewmodel:
public class PersonGroupingViewModel
{
public int PersonId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set;}
public List<Group> Memberships { get; set; }
public SelectList AllGroups { get; set; }
public int SelectedGroupId { get; set; }
}
Controller:
//GET People/AddToGroup
public ActionResult AddMemberToGroup(int? PersonId)
{
if (PersonId == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Person person = db.People.Find(PersonId);
if (person == null)
{
return HttpNotFound();
}
SelectList allthegroups = new SelectList(db.Groups, "GroupId", "Name");
PersonGroupingViewModel viewmodel = new PersonGroupingViewModel();
viewmodel.PersonId = person.PersonId;
viewmodel.FirstName = person.FirstName;
viewmodel.LastName = person.LastName;
viewmodel.AllGroups = allthegroups;
//viewmodel.Memberships cannot be empty
if (person.Memberships == null)
{
viewmodel.Memberships = new List<Group>();
}
else
{
viewmodel.Memberships = person.Memberships.ToList();
}
return View(viewmodel);
}
//POST: People/AddToGroup
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult AddMemberToGroup(PersonGroupingViewModel vm)
{
SelectList allthegroups = new SelectList(db.Groups, "GroupId", "Name");
vm.AllGroups = allthegroups;
int PersonId = vm.PersonId;
int GroupId = vm.SelectedGroupId;
Person person = db.People.Find(PersonId);
Group group = db.Groups.Find(GroupId);
group.Members.Add(person);
db.SaveChanges();
return View(vm);
}
View form (strongly-typed to PersonGroupingViewModel):
Add Member To Group
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div>
<h4>Groups #Html.DisplayFor(model => model.FirstName) is already in:</h4>
<ul>
#foreach (var group in Model.Memberships)
{
<li>#Html.DisplayFor(modelItem => group.Name)</li>
}
</ul>
</div>
<div class="form-horizontal">
<h4>Add #Html.DisplayFor(model => model.FirstName) #Html.DisplayFor(model => model.LastName) To Group:</h4>
#Html.HiddenFor(model => model.PersonId)
<div class="form-group">
<label>Group: </label>
#Html.DropDownListFor(m => m.SelectedGroupId, Model.AllGroups, String.Empty)
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Add To Group" class="btn btn-default" />
</div>
</div>
</div>
}
When I click 'submit' in the browser I get back a URL:
localhost:54696/People/AddMemberToGroup?PersonId=1
Along with exception "Object reference not set to an instance of an object" on the line Group group = db.Groups.Find(GroupId); in the controller.
Is the viewmodel coming back empty or not at all? I am not sure what is going on and would appreciate someone attempting explaining it in small words. I've done a lot of Googling and tried several suggestions from that but in the end I feel as though I'm going in circles.

Solved it...
group.Members was null so group.Members.Add(person) didn't work. Wrote in an if/else statement to instantiate a new member list if the list is null, and it works fine:
if (group.Members == null)
{
group.Members = new List<Person>();
group.Members.Add(person);
}
else {
group.Members.Add(person);
}

Related

Editing Objects and Related Objects in One View with ASP.Net MVC and Entity Frameworks

I am creating an application where a Note can be created and one to many Parts can be added to the note. (The application is for a tractor salvage yard where customers call for tractor parts). I know similar questions have been asked before. But I couldn't find anything very relevant to my situation with EF and all.
I am having a lot of difficulty with creating/editing a Note with its Parts in one view. I want to focus on editing for this question, though.
I have two simple CLR classes with a relation.
public class Note
{
public int ID { get; set; }
public string CustomerName { get; set; }
public string CustomerPhone { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateUpdated { get; set; }
public string CreatedBy { get; set; }
public string AssignedTo { get; set; }
public virtual ICollection<Part> Parts { get; set; }
}
public class Part
{
public int PartID { get; set; }
public string PartNumber { get; set; }
public string Description { get; set; }
public int NoteID { get; set; }
public virtual Note Note { get; set; }
}
And the DbContext:
public class CallNoteContext : DbContext
{
public CallNoteContext() { }
public DbSet<Note> Notes { get; set; }
public DbSet<Part> Parts { get; set; }
}
My problem is binding the data from both entities to the edit view, accessing the data in the view for editing and saving the note and and multiple parts to the database in the httppost action.
I have tried a lot of things, but after reading a lot of articles, I keep coming back to this for the controller and view. To me it seems like this should work. But obviously I am missing something.
Here is the edit and post actions from my controller.
private CallNoteContext db = new CallNoteContext();
// GET: Note/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Note note = db.Notes.Find(id);
var model = new Note()
{
CustomerName = note.CustomerName,
CustomerPhone = note.CustomerPhone,
DateCreated = note.DateCreated,
DateUpdated = note.DateUpdated,
CreatedBy = note.CreatedBy,
AssignedTo = note.AssignedTo,
Parts = note.Parts
};
if (note == null)
{
return HttpNotFound();
}
return View(model);
}
// POST: Note/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ID,CustomerName,CustomerPhone,DateCreated,DateUpdated,CreatedBy,AssignedTo,Parts")] Note note)
{
if (ModelState.IsValid)
{
foreach(var p in note.Parts)
{
db.Entry(p).State = EntityState.Modified;
db.SaveChanges();
}
db.Entry(note).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(note);
}
When I try to make editors for p.PartNumber and p.Description in my view below, it breaks with the exception that it can't find these properties. I have a feeling that I am doing something wrong in the "get" action of the controller. But I am having a hard time figuring out what is wrong.
By the way, IntelliSense is saying No Issues Found for the controller.
Here is my Edit view.
#model CallNote.Models.Note
<head>
<script src="~/Scripts/jquery-3.4.1.js" type="text/javascript"></script>
</head>
<h2>Edit</h2>
#using (Html.BeginForm())
{
#Html.HiddenFor(model => model.ID)
<div class="form-group">
#Html.LabelFor(model => model.CustomerName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CustomerName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomerName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.CustomerPhone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CustomerPhone, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CustomerPhone, "", new { #class = "text-danger" })
</div>
</div>
#*There are editors here for all of the properties, but I didn't list them to save space.*#
#*The app always breaks when it gets to this foreach because it says it can't find p.PartNumber. What is wrong?
#foreach (var p in Model.Parts)
{
<div>
#*I also tried just using p.PartNumber, but it says p doesn't exist in current context.
#Html.EditorFor(p => p.PartNumber)
#Html.EditorFor(p => p.Description)
</div>
}
<div id="partInfo" style="display:none">
#Html.EditorFor(p => p.PartNumber)
#Html.EditorFor(p => p.Description)
</div>
<div id="btnWrapper">
<input id="btnAddPart" type="button" value="Add Part" />
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
#*The script below works, allowing you to add part editors*#
<div>
#Html.ActionLink("Back to List", "Index")
</div>
<script>
$(document).ready(function () {
$("#btnAddPart").click(function () {
var partinfo = $("#partInfo").html();
$("#partInfo").append(partinfo);
});
});
</script>
Also I am unsure if the httppost action will work. I have not been able to try it yet as I cannot get the Edit view to even load yet. So if you have any suggestions for that too, let me know.
I am just getting started with MVC, so a detailed answer would be super!
you have to include Parts in the Note
....
Note note = db.Notes
.Include(i=> i.Parts)
.FirstOrDefault(i=>i.ID==id);
if (note == null)
{
return HttpNotFound();
}
.....
and since you are using editor, replace foreach loop by for loop
#if( Model.Parts!=null && Model.Parts.Count >0)
{
#for (var i=0; i< Model.Parts.Count; i++)
{
<div id="partInfo" style="display:none">
#Html.EditorFor(model => model.Parts[i].PartNumber)
#Html.EditorFor(model => model.Parts[i].Description)
</div>
...... and so on for all properties
}
}
and remove bind from the action
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Note note)
{
if (!ModelState.IsValid) return View(note);
var existedNote = db.Notes
.Include(i=> i.Parts)
.FirstOrDefault(i=>i.ID==note.ID);
if(existedNote!=null)
{
db.Entry(existedNote).CurrentValues.SetValues(note);
if(note.Parts!=null && note.Parts.Count > 0)
{
foreach( var part in note.Parts)
{
var existingPart = existingNote.Parts.FirstOrDefault(p => p.PartID == part.PartID);
if (existingPart == null)
{
existingNote.Parts.Add(part);
}
else
{
context.Entry(existingPart).CurrentValues.SetValues(part);
}
}
}
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(note);
}
````

Currency conversion api web service

I am a new to MVC and still learning! I am trying to create a very basic App in my web which allows users to convert money value according to their preference. I made the web APi and was successful to call the service to my forms. However, in my controller I managed to get the currencies (names) to the index view, but cannot post the form back once entering a value in the text box to generate the partial view! What am I doing wrong in my codes?!
Currency Controller
namespace MVC_ATM.Controllers
{
public class CurrencyController : Controller
{
[HttpGet]
// GET: Currency
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll();
SelectList list = new SelectList(listCurrency,"Id", "CurrencyName");
ViewBag.listCurrencies = list;
return View();
}
[HttpPost]
public ActionResult Index(Currencies cur)
{
if (!ModelState.IsValid)
{
string errors = string.Join("<br />", ModelState.Values
.SelectMany(x => x.Errors)
.Select(x => x.ErrorMessage));
return new ContentResult { Content = errors };
var rate = Convert.ToDecimal(cur.ConversionRate);
if (cur.CurrencyName == cur.CurrencyName)
{
ModelState.AddModelError("CurrencyCountry", "Can't make the conversion for the same value");
}
else if (cur.CurrencyName != cur.CurrencyName)
{
foreach (var currency in cur.CurrencyName)
{
ViewBag.Theresult = rate * cur.Value;
}
return PartialView("_CurrencyValue");
}
}
return View();
}
}
}
Currencies Model
namespace Project.Model
{
public class Currencies
{
public int Id { get; set; }
public string CurrencyName { get; set; }
public string CurrencyCountry {get; set;}
public decimal Value { get; set; }
public string ConversionRate { get; set; }
}
}
Index View
#model Project.Model.Currencies
#{
ViewBag.Title = "Index";
}
<h2>Currency</h2>
<body>
<div class="converter">
Convert: #Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
<div class="form-group">
#Html.Label("Convert from", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Currency List", ViewBag.listCurrencies as SelectList, "Please Select a currency")
</div>
</div>
<div class="form-group">
#Html.Label("Convert to", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Currency List", ViewBag.listCurrencies as SelectList, "Please Select a currency")
</div>
</div>
<div>
<button type="submit" class="btn btn-primary">Convert</button>
</div>
</div>
</body>
Couple of things to notice, is the POST action and missing form tag in the view . You created a POST action that accepts Currencies model but the form doesn't post that. Only ConversionRate will bind to the model. To get the "Currency From" and "Currency To" and the "Conversion Rate" you will require a different approach/small changes.
ConversionModel.cs a new Model for index page that will capture your required fields.
public class ConversionModel
{
[Required]//decimal would be better but up to you requirement
public decimal ConversionRate { get; set; }
[Required]
public int FromCurrencyId {get;set;}
public SelectList FromCurrencies {get;set;}
[Required]
public int ToCurrencyId {get;set;}
public SelectList ToCurrencies {get;set;}
}
Get: while there is nothing wrong with what you've done, lets use a model approach and tightly bind it.
public ActionResult Index()
{
CurrenciesClient Cur = new CurrenciesClient();
var listCurrency = Cur.findAll();
ConversionModel model = new ConversionModel();
model.FromCurrencies = new SelectList(listCurrency,"Id", "CurrencyName");
model.ToCurrencies = new SelectList(listCurrency,"Id", "CurrencyName");
return View(model);
}
Post: Important thing here to notice is the SelectList will not be posted back. Only the ConversionRate, FromCurrencyId and ToCurrencyId are sent back not the Lists. If error occurs you will need to rebuild the lists and send it back in the model.
[HttpPost]
public ActionResult Index(ConversionModel curModel)
{
if(ModelState.IsValid)
{
if(curModel.FromCurrencyId ==curModel.ToCurrencyId)
{
//do something if same currecnies and return.
}
else
{
//Get the currencyList with rates from db
//use currency ToCurrencyId and FromCurrencyId to fetch the 2 currencies
// perform conversion with curModel.ConversionRate with existing logic
}
}
//Don'f forget to rebuild the Select Lists...
return View(curModel);
}
View:
#model Project.Model.ConversionModel
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm("Index", "Currency", FormMethod.Post)
{
#Html.TextBoxFor(m => m.ConversionRate, new { #size = "5" })
#* Please check the syntax *#
#Html.DropDownListFor(m => m.FromCurrencyId , Model.FromCurrencies as SelectList)
#Html.DropDownListFor(m => m.ToCurrencyId , Model.ToCurrencies as SelectList)
<button type="submit" class="btn btn-primary">Convert</button>
}
Not a CUT_COPY_PASTE. please do check for errors if any. It is only an approach.
ajax POST probably the next thing to learn... Let us know.
You need to put your items inside a form like this:
#using (Html.BeginForm("Index", "Currency", FormMethod.Post)
{
// Your form items
}

Setting different default values for dropdownlist(MVC5)

I have flights timetable - Schedule view with list of flights
Where I just return View and have some filters and sorting.
Here is part of my view:
#model IEnumerable<AirPortIS.Models.Flight>
<div class="page-header">
<h3>Flights</h3>
</div>
<div id="modDialog" class="modal fade">
<div id="dialogContent" class="modal-dialog"></div>
</div>
<table class="table-bordered table-condensed">
<thead>
<tr>
<th>Flight №</th>
<th>Departure</th>
<th>Destination</th>
<th>#Html.ActionLink("Day", "Schedule", new { sort = ViewBag.SortDay, company = ViewBag.FiltrC, destination = ViewBag.FiltrD })</th>
<th>Departure Time</th>
<th>Arrival Time</th>
<th>Company</th>
<th>#Html.ActionLink("Seats", "Schedule", new { sort = ViewBag.SortSeats, company = ViewBag.FiltrC, destination = ViewBag.FiltrD })</th>
<th>#Html.ActionLink("Cost", "Schedule", new { sort = ViewBag.SortCost, company = ViewBag.FiltrC, destination = ViewBag.FiltrD })</th>
<th>Book ticket</th>
</tr>
</thead>
#foreach (var f in Model)
{
<tr>
<td>#Html.ActionLink(f.FlightId.ToString(), "FlightDetails", new { id = f.FlightId }, new {#class = "flItem" } )</td>
<td>#f.Departure</td>
<td>#f.Destination</td>
<td>#f.Day</td>
<td>#f.DepartureTime</td>
<td>#f.ArrivalTime</td>
<td>#f.Company.Name</td>
<td>#f.Seats</td>
<td>#f.Cost</td>
<td>#Html.ActionLink("Link for booking ticket")</td>
</tr>
}
</table>
I need to do that by clicking on a button "Book ticket" user is getting a page where dropdownlist have a preset value of FlightId.
For example we have a flight №1 and a link "Book ticket",so when user goes the booking ticket page he gets a droptdownlist with preselected value "1"
Here is my ticket Model
public class Tickets
{
public int Id { get; set; }
public int TicketId { get; set; }
public Flight Flight { get; set; }
public string Seat {get;set; }
public string Passenger { get; set; }
public int Flightid { get; set; }
public string Status { get; set; }
}
And part of TicketsController:
public class TicketsController : Controller
{
private readonly AirportContext _db = new AirportContext();
[Authorize]
public ActionResult Tickets()
{
var ticket = _db.Tickets.Include(t => t.Flight);
return View(ticket);
}
[HttpGet]
public ActionResult BookTicket()
{
IEnumerable<SelectListItem> statusList = new SelectList(new List<string> { "Book", "Buy" });
IEnumerable<SelectListItem> flights = new SelectList(_db.Flights.ToList(), "FlightId", "FlightId");
ViewData["flights"] = flights;
ViewData["statusList"] = statusList;
return View();
}
[HttpPost]
public ActionResult BookTicket(Tickets ticket)
{
IEnumerable<SelectListItem> statusList = new SelectList(new List<string> { "Book", "Buy" });
IEnumerable<SelectListItem> flights = new SelectList(_db.Flights.ToList(), "FlightId", "FlightId");
ViewData["flights"] = flights;
ViewData["statusList"] = statusList;
foreach (var c in _db.Tickets.ToList())
{
if ((_db.Tickets.ToList().Exists(x => c.TicketId == ticket.TicketId)) || (ticket.TicketId <= 0))
{
ModelState.AddModelError("TicketId", "Wrong ticket id");
}
if ((_db.Tickets.ToList().Exists(x => c.Seat == ticket.Seat)) && (_db.Tickets.ToList().Exists(x => c.Flightid == ticket.Flightid))
&& (_db.Tickets.ToList().Exists(x => c.TicketId == ticket.TicketId)))
{
ModelState.AddModelError("Seat", "The seat is unavailable");
}
if (_db.Tickets.ToList().Exists(x => c.Passenger == ticket.Passenger))
{
ModelState.AddModelError("Passenger", "The ticket has already bought");
}
}
if (ModelState.IsValid)
{
_db.Tickets.Add(ticket);
_db.SaveChanges();
return RedirectToAction("Tickets");
}
else return View(ticket);
}
And my BookTikcet View:
#model AirPortIS.Models.Tickets
#{
ViewBag.Title = "Book ticket";
}
<h2>Book ticket:</h2>
<form class="form-inline" method="post">
<div>
#Html.ValidationSummary()
</div>
<div class="form-group col-md-2">
Ticket №<br/>
#Html.EditorFor(model => Model.TicketId)
</div>
<div class="form-group col-md-1">
Flight №<br />
#Html.DropDownListFor(model => Model.Flightid, ViewData["flights"] as IEnumerable<SelectListItem>)
</div>
<div class="form-group col-md-2">
Место<br />
#Html.EditorFor(model => Model.Seat)
</div>
<div class="form-group col-md-2">
Passenger Name<br />
#Html.EditorFor(model => Model.Passenger)
</div>
<div class="form-group col-md-2">
Status<br />
#Html.DropDownListFor(model => Model.Status, ViewData["statusList"] as IEnumerable<SelectListItem>)
</div>
<div>
<input class="btn-success" type="submit" value="Book Ticket"/>
</div>
</form>
<div>
<form method="get" action="Tickets">
<button class="btn-danger" type="submit">Cancel</button>
</form>
</div>
I have no idea how to do it,so this whole code above just a standart code for creating a new ticket.
How I should modify code or add something to have this (For example we have a flight №1 and a link "Book ticket",so when user goes the booking ticket page he gets a droptdownlist with preselected value "1",for flight №2 on a page dropdownlist has a preselected value "2" for FlightId.
Hope that my question is clear,sorry if something is wrong written or not quite clear.
You need to pass the value of FlightId as a route (or query string) value to the BookTicket method. You link should be
#Html.ActionLink("Book ticket", "BookTicket", new { id = f.FlightId })
and modify the method to
[HttpGet]
public ActionResult BookTicket(int ID)
{
... // set you SelectLists as above
// Initialize your model and set the Flightid property
var model = new Tickets()
{
Flightid = ID
};
return View(model); // return the model to the view
}
Your dropdownlist will now have the option identified by Flightid selected when you first generate the view.
Note. I recommend you use a view model rather than your Tickets data model which will contain properties IEnumerable<SelectListItem> Flights and IEnumerable<SelectListItem> StatusList rather than using ViewData so that your view are strongly typed using
#Html.DropDownListFor(m => m.Flightid, Model.Flights)
You should also consider refactoring the code to populate the SelectLists into a private method so that you do not repeat code, for example
private void ConfigureViewModel(TicketVM model)
{
model.Flights = new SelectList(...);
model.StatusList = new SelectList(...);
}
Note also that it is a waste of resources to be calling your database to get the SelectList's in the POST method if ModelState is valid. Your code should be
if (!ModelState.IsValid)
{
ConfigureViewModel(model); // only necessary if you need to return the view
return View(model);
}
// save and redirect
Side note: It's unclear why you actually need a dropdownlist for Flightid in the BookTicket view. The user has already selected the flight so why are you giving the option to change it? It might be more appropriate to just render the Flightid as a hidden or readonly input so its submitted back to the POST method.

MVC4 Validate Item Selected In DropDownList

I have a page to create objects, and in this I have a DropDownList. If I select an item from the list my page will save correctly, however if I don't select an item it looks like it fails on a postback as the objects will be null.
What I want is to try and validate whether the user has selected an item (default is "Please Select...").
I have code that will check and see in the controller if the item is null, but it's how do I then display a message? Keeping all other details if they exist.
public ActionResult Create(int objectId = 0)
{
var resultModel = new MyObjectModel();
resultModel.AllObjects = new SelectList(_system.GetAllObjects(objectId));
// GetAllObjects juts returns a list of items for the drop down.
return View(resultModel);
}
[HttpPost]
public ActionResult Create(int? objectId, FormCollection collection)
{
try
{
int objectIdNotNull = 0;
if (objectId > 1)
{
objectIdNotNull = (int) objectId;
}
string objectName = collection["Name"];
int objectTypeSelectedResult = 1;
int.TryParse(collection["dllList"], out objectTypeSelectedResult);
if (!Convert.ToBoolean(objectTypeSelectedResult))
{
// So here I have discovered nothing has been selected, and I want to alert the user
return RedirectToAction("Create",
new {ObjectId = objectIdNotNull, error = "Please select an Object Type"});
}
....
return RedirectToAction(...)
}
catch
{
return View();
}
}
The above code just goes to the Create page but doesn't display an error. In my View for Create I have the following line which I assumed would display any errors:
#ViewData["error"]
Additional code
Model:
using System.Collections.Generic;
using System.Web.Mvc;
using System.ComponentModel.DataAnnotations;
namespace MyNameSpace
{
public class MyObjectModel
{
[Required(ErrorMessage = "Please select an Object Type")]
public SelectList AllObjects { get; set; } // I populate the drop down with this list
}
}
View:
#model MyNameSpace.MyObjectModel
#{
ViewBag.Title = "Create";
}
<h2>Create </h2>
<p class="text-error">#ViewData["Message"]</p>
<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>
<div class="editor-label">
#Html.LabelFor(model => model.MyObject.Name)
</div>
<div class="editor-field">
#Html.TextBoxFor(model=>model.MyObjectType.Name, new {style="width: 750px"})
#Html.ValidationMessageFor(model => model.MyObjectType.Name)
</div>
<div>
<label for="ddlList">Choose Type</label>
#if (#Model != null)
{
#Html.DropDownList("ddlList", Model.AllObjects, "Please Select...")
#Html.ValidationMessageFor(model => model.AllObjects, "An object must be selected", new { #class = "redText"})
}
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
You are validating the SelectList which is wrong
[Required(ErrorMessage = "An object must be selected")]
public SelectList AllObjects { get; set; }
Your Model should be
[Required(ErrorMessage = "Please select an Object Type")]
public int ObjectId { get; set; }
public string ObjectName { get; set; }
Your Controller(no need for form collection thats the whole point of MVC)
public ActionResult Create(int Id = 0)
{
MyObjectModel resultModel = new MyObjectModel();
var ObjectResultList = _system.GetAllObjects(Id);
var ObjectSelectList = new SelectList(ObjectResultList, "id", "Name");
ViewBag.ObjectList = ObjectSelectList;
return View(resultModel);
}
Your Post controller:
[HttpPost]
public ActionResult Create(MyObjectModel o)
{
try
{
if (ModelState.IsValid)
{
//It's valid , your code here!
return RedirectToAction("ObjectCreated", new { id = o.objectId });
}
else
{
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.Select(x => new { x.Key, x.Value.Errors })
.ToArray();
}
}
}
catch (Exception ex)
{
Response.Write(ex.InnerException.Message);
}
//If we get here it means the model is not valid, We're in trouble
//then redisplay the view repopulate the dropdown
var ObjectResultList = _system.GetAllObjects(objectId);
var ObjectSelectList = new SelectList(ObjectResultList, "id", "value");
ViewBag.ObjectList = ObjectSelectList;
return View(o);
}
Your View should be strongly Typed
<div class="editor-label">
#Html.LabelFor(model => model.ObjectId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ObjectId,
(IEnumerable<SelectListItem>)ViewBag.ObjectList, "-- Select One Object --")
#Html.ValidationMessageFor(model => model.ObjectId)
</div>

Saving a model within a model

So, I have this Property class that is defined by ID Name and DataType.
DataType is already populated with static values, and is being used as a drop down list.
Now when a user picks certain value from the list, List value to be precise, application opens additional textbox and button, for populating that list.
Models go like this.
Property model
public class Property
{
public int ID {get; set;}
public string Name {get; set;}
public int DTypeID {get; set;}
public virtual DType DTypes {get; set;}
}
List model
public class DList
{
public int ID {get; set;}
public int PropertyID {get; set;}
public string ListValue {get; set;}
}
And this is what I've done so far.
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<div class="labels tableRow">
<div class="editor-label tableCell">
<p>
Name
</p>
<p>
Data Type
</p>
</div>
<div class="editor-field tableCell">
<p>
#Html.TextBoxFor(model => model.Name, new { #class = "textEntry" })
#Html.ValidationMessageFor(model => model.Name)
</p>
<p>
#Html.DropDownList("DTypeID", (SelectList)ViewBag.DTypeID, new { #class = "dropdown", #onchange = "DropDownChange()"})
#Html.ValidationMessageFor(model => model.DTypeID)
</p>
</div>
</div>
<div id="StaticListUI" class="invis">
<div class="labels tableRow">
<div class="editor-label tableCell">
<p>
#*here goes list*#
</p>
</div>
<div class="editor-field tableCell">
<p>
#*here goes textbox*#
#Html.TextBox("textboxList")
</p>
<p>
#*here goes button*#
<button name="button" value="add">Add</button>
</p>
</div>
</div>
</div>
<p class="labels">
#*<input type="submit" value="Create" />*#
<button name="button" value="create">Create</button> |
<input type="button" value="Back" onClick='javascript:location.href = "#Url.Action("Index", "Property")";' />
</p>
</fieldset>
}
So in order to capture which button is clicked I did this for my controller.
[HttpPost]
public ActionResult Create(string button, string textboxList, Property property)
{
if (button == "add")
{
var lastProperty = db.Properties.OrderByDescending(p => p.PropertyID).FirstOrDefault();
int propID;
if (lastProperty == null)
{
propID = 1;
}
else
{
propID = 1 + lastProperty.PropertyID;
}
DList dList = new DList();
dList.PropertyID = propID;
dList.ListValue = textboxList;
db.DLists.Add(dList);
return View(property);
}
string projectID = System.Web.HttpContext.Current.Session["_SelectedProjectID"].ToString();
int projID = Convert.ToInt32(projectID);
property.ProjectID = projID;
property.DateCreated = DateTime.Now;
property.DateEdited = DateTime.Now;
if (ModelState.IsValid)
{
db.Properties.Add(property);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DTypeID = new SelectList(db.DType, "ID", "Name", property.DTypeID);
return View(property);
}
And the problem is that when I click Add button, it still sends a check for ModelState.IsValid, which shouldn't. What have I done wrong, or have I done anything right :(
Note: Everything else basically works.
EDIT
So I've altered the controller to accept parameters from button clicked differently. But still something is missing...
[ActionName("Create")]
[AcceptVerbs(HttpVerbs.Post)]
[AcceptParameter(Name = "button", Value = "add")]
public ActionResult Create_Add(string textboxList)
{
var lastProperty = db.Properties.OrderByDescending(p => p.PropertyID).FirstOrDefault();
int propID;
if (lastProperty == null)
{
propID = 1;
}
else
{
propID = 1 + lastProperty.PropertyID;
}
DList dList = new DList();
dList.PropertyID = propID;
dList.ListValue = textboxList;
db.DLists.Add(dList);
db.SaveChanges();
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
[AcceptParameter(Name="button", Value="create")]
public ActionResult Create(Property property)
{
string projectID = System.Web.HttpContext.Current.Session["_SelectedProjectID"].ToString();
int projID = Convert.ToInt32(projectID);
property.ProjectID = projID;
property.DateCreated = DateTime.Now;
property.DateEdited = DateTime.Now;
if (ModelState.IsValid)
{
db.Properties.Add(property);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.DTypeID = new SelectList(db.DType, "ID", "Name", property.DTypeID);
return View(property);
}
public class AcceptParameterAttribute : ActionMethodSelectorAttribute
{
public string Name { get; set; }
public string Value { get; set; }
public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo)
{
var req = controllerContext.RequestContext.HttpContext.Request;
return req.Form[this.Name] == this.Value;
}
}
You have db.DLists.Add(dList); but do don't save it in the database, you just return the view. You could put db.Configuration.ValidateOnSaveEnabled = false; above the db.DLists.Add(dList); and see if that works, however I think it should look like this:
db.Configuration.ValidateOnSaveEnabled = false;
db.DLists.Add(dList);
db.SaveChanges();
return View(property);
You may want to turn the Validate On Save back on before the return.
Update
Perhaps you could try this:
#{
var textBoxData = form.find('input[name="textboxList"]').val();
}
<input type="button" value="Add" title="Add" onclick="location.href='#Url.Action("Create_Add", "Controller", new { textboxList = textBoxData })'" />

Categories

Resources