I am binding objects in a razor foreach in the index.html:
VIEW
#using (Ajax.BeginForm("Save", "Unit", new AjaxOptions { OnSuccess = "onSuccess" }))
{
<button type="submit" class="btn btn-default" id="saveUnits"><i class="fa fa-save"></i></button>
<table>
<tbody>
#foreach (var item in Model)
{
<tr>
#Html.HiddenFor(modelItem => item.UnitId)
<td>
#Html.EditorFor(modelItem => item.Name)
</td>
<td>
#Html.EditorFor(modelItem => item.ErrorText)
</td>
</tr>
}
</tbody>
</table>
}
I have grabbed the data sent to my action parameter with fiddler and got this:
item.UnitId=5&
item.Name=111111111111&
item.ErrorText=fsdddddddddddddddd+&
item.UnitId=5&
item.Name=+&
item.ErrorText=dddddd+&
ACTION
public ActionResult Save(List<Unit> units )
{
return new EmptyResult();
}
VIEWMODEL
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set;
}
Why is my units instance null? The properties match so they should be bound!
Did I overlook something?
You need to use a for loop not a foreach loop. Also, it would be better to make your Model class have a property which is a collection.
Your model could be something like:
public class UnitsViewModel
{
public List<Unit> Units { get; set; }
public class Unit
{
[HiddenInput(DisplayValue = false)]
public int UnitId { get; set; }
[DataType(DataType.MultilineText)]
public string Name { get; set; }
[DataType(DataType.MultilineText)]
public string ErrorText { get; set; }
}
}
And you could do the following in your cshtml:
#for (int i = 0; i < Model.Count; i++)
{
<tr>
#Html.HiddenFor(m => m.Units[i].UnitId)
<td>
#Html.EditorFor(m => m.Units[i].Name)
</td>
<td>
#Html.EditorFor(m => m.Units[i].ErrorText)
</td>
</tr>
}
Related
I want to post data to database but my viewmodel is empty when posting it to the controller, i tried different approaches, but everytime my viewmodel is null.
These are my classes :
public class Player
{
[Key]
public int Id { get; set; }
public string Name{ get; set; }
public string PreName{ get; set; }
}
public class Activitity
{
[Key]
public int Id { get; set; }
public string WhichActivity { get; set; }
public List<Player> Players { get; set; }
}
public class Aanwezigheid
{
[Key]
public int Id { get; set; }
public ReasonEnum Reason{ get; set; }
public int PlayerId{ get; set; }
public Player Player{ get; set; }
public List<Player> Players{ get; set; }
public int ActivityId { get; set; }
}
My View Model :
public class PresenceVM
{
public int PlayerId{ get; set; }
public int ActivityId { get; set; }
public string Name{ get; set; }
public string PreName { get; set; }
public ReasonEnum Reason { get; set; }
}
My HTTPGET for a list of players and I want to put the absence reason with the player in the database.
[HttpGet]
public ActionResult Presence(int id)
{
var sp = _context.Players.ToList();
foreach(Players s in sp)
{
var van = new PresenceVM
{
PlayerId = s.Id,
Name = s.Name,
PreName = s.PreName,
ActivityId = id
};
list.Add(van);
}
return View(list);
}
My HttpPost
[HttpPost]
public ActionResult Presence(List<PresenceVM> list)
{
var sp = _context.Players.ToList();
var list = new List<Presence>();
foreach (Players s in sp)
{
var van = new Aanwezigheid
{
PlayerId = s.Id,
ActivityId = vm.ActivityId,
Reason = vm.Reason
};
list.Add(van);
_context.Presence.Add(van);
//_context.SaveChanges();
}
return RedirectToAction("Index", "Presence");
}
The problem is that my PresenceVm (viewmodel) does not get any data in true my controller. I don't understand why? Is it because of a list? With one item it's easy to post it to database. Maybe multiple items?
Edit 1:
The viewmodel for the Get & Post
#model IEnumerable<....ViewModels.PresenceVM>
#{
ViewBag.Title = "Presence";
}
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.PreName)
</th>
<th>
#Html.DisplayName("Reason")
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.PreName)
</td>
<td>
#Html.EnumDropDownListFor(modelItem => item.Reason, "Present", new { #class = "form-control" })
</td>
</tr>
}
</table>
<form action="/Presences/Presence" method="post">
<div class="form-horizontal form-details">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
You're putting the form scope in wrong place (only include submit button). The correct way should include all properties you want to submit with indexes (since you're using IEnumerable<PresenceVM>, like this example:
<form action="/Presences/Presence" method="post">
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.PreName)
</th>
<th>
#Html.DisplayName("Reason")
</th>
</tr>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td>
#Html.EditorFor(modelItem => item[i].Name)
</td>
<td>
#Html.EditorFor(modelItem => item[i].PreName)
</td>
<td>
#Html.EnumDropDownListFor(modelItem => item[i].Reason, "Present", new { #class = "form-control" })
</td>
</tr>
}
</table>
<div class="form-horizontal form-details">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
Note that if you want to allow user input, you need to change all DisplayFor into EditorFor.
Your Presence method is returning an object that does not exist (list).
It must return the view model if you want to use it in the post method.
[HttpGet]
public ActionResult Presence(int id)
{
List<PresenceVM> model = context.Players.Select(u => new PresenceVM
{
PlayerId = s.Id,
Name = s.Name,
PreName = s.PreName,
ActivityId = id
}).ToList();
return View(model);
}
Seems that you don't use the Form tag
VIEW
#model MyViewModel
#using (Html.BeginForm(("Presence"))
{
#Html.AntiForgeryToken()
//divs
<div class="form-horizontal form-details">
<input type="submit" value="Save" class="btn btn-default" />
</div>
}
And it's better to pass a Model than a list of Model
VIEWMODEL
public class MyViewModel{
public IList<PresenceVM> MyList {get;set;}
}
CONTROLLER
public ActionResult Presence(MyViewModel xxx)
{
//whatever
}
I have the following entity (domain) object and model that contain an enum. The display name appears correctly and works for a EnumDropdownList but for some reason not for the DisplayFor helper, all that is shown is the actual enum name.
Not sure what I am missing, asp.net MVC 5.1 added display name support for this so I shouldn't need to create my own helper methods. See: https://aspnet.codeplex.com/SourceControl/latest#Samples/MVC/EnumSample/EnumSample/Models/Enums.cs
public class Addon
{
public int Id { get; set; }
public AddonType AddonType { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsActive { get; set; }
}
public enum AddonType : byte
{
[Display(Name = "Cake Theme")]
CakeTheme,
[Display(Name = "Cake Flavour")]
CakeFlavour,
[Display(Name = "Cupcake Icing")]
CupcakeIcing,
[Display(Name = "Party Addon")]
AddOn
}
MODEL
public class AddonModel
{
public int Id { get; set; }
public AddonType AddonType { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
public bool IsActive { get; set; }
}
VIEW
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>Type</th>
<th>Name</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(model => item.AddonType)
</td>
<td>
#Html.DisplayFor(model => item.Name)
</td>
<td>
#Html.DisplayFor(model => item.Price)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })
</td>
</tr>
}
</table>
Create new folder Views/Shared/DisplayTemplates
Add empty Partial View named Enum, to the folder
Replace Enum View code with:
#model Enum
#if (EnumHelper.IsValidForEnumHelper(ViewData.ModelMetadata))
{
// Display Enum using same names (from [Display] attributes) as in editors
string displayName = null;
foreach (SelectListItem item in EnumHelper.GetSelectList(ViewData.ModelMetadata, (Enum)Model))
{
if (item.Selected)
{
displayName = item.Text ?? item.Value;
}
}
// Handle the unexpected case that nothing is selected
if (String.IsNullOrEmpty(displayName))
{
if (Model == null)
{
displayName = String.Empty;
}
else
{
displayName = Model.ToString();
}
}
#Html.DisplayTextFor(model => displayName)
}
else
{
// This Enum type is not supported. Fall back to the text.
#Html.DisplayTextFor(model => model)
}
Here is the link to detailed article by Shahriar Hossain
I'm new to MVC and sorry for this beginners question. I have following Model classes:
public class ReturnBookHedModel
{
public int RefferenceID { get; set; }
public int BorrowedRefNo { get; set; }
public int MemberId { get; set; }
public DateTime ReturnDate { get; set; }
public bool IsNeedToPayFine { get; set; }
public DateTime CurrentDate { get; set; }
public virtual List<ReturnBookDetModel> RetunBooks { get; set; }
public virtual MemberModel member { get; set; }
}
public class ReturnBookDetModel
{
public int BookID { get; set; }
public int RefferenceID { get; set; }
public bool IsReturned { get; set; }
public virtual ReturnBookHedModel ReturnBookHed { get; set; }
public virtual BookModel book { get; set; }
}
I have following controller methods:
public ActionResult SaveReturnBook(int refNo)
{
ReturnBookHedModel model = ReturnBookFacade.GetReturnBookBasedOnRefference(refNo);
return View(model);
}
//
// POST: /ReturnBook/Create
[HttpPost]
public ActionResult SaveReturnBook(ReturnBookHedModel model)
{
try
{
// TODO: Add insert logic here
return RedirectToAction("Index");
}
catch
{
return View();
}
}
in my model i define as follows:
<div class="control-label">
#Html.LabelFor(model => model.BorrowedRefNo)
#Html.TextBoxFor(model => model.BorrowedRefNo, new { #class = "form-control" ,#readonly = "readonly" })
#Html.ValidationMessageFor(model => model.BorrowedRefNo)
</div>
// rest of the header details are here
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.RetunBooks.FirstOrDefault().IsReturned)
</th>
<th>
#Html.DisplayNameFor(model => model.RetunBooks.FirstOrDefault().BookID)
</th>
<th>
#Html.DisplayNameFor(model => model.RetunBooks.FirstOrDefault().book.BookName)
</th>
<th></th>
</tr>
#foreach (var item in Model.RetunBooks)
{
<tr >
<td>
#Html.CheckBoxFor(modelItem => item.IsReturned)
</td>
<td>
#Html.HiddenFor(modelItem => item.BookID);
#Html.DisplayFor(modelItem => item.BookID)
</td>
<td>
#Html.DisplayFor(modelItem => item.book.BookName)
</td>
</tr>
}
</table>
this is working fine.. but these table details (complex objects) are not in the controller's post method. when i searched i found that i can use this detail data as follows: but i cant use it as follows.
#for (var i = 0; i < Model.RetunBooks.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(x => x.RetunBooks.)
</td>
</tr>
}
how can i send these information to controller
In order for the collection to be posted back you need to index them in the following way for the model binder to pick them up.
This should do the trick:
#for (var i = 0; i < Model.RetunBooks.Count; i++)
{
...
#Html.CheckBoxFor(model => Model.RetunBooks[i].IsReturned)
...
}
Complex objects require the indexing in the above manner.
For more info on it see here:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
Would like to create a similar ticketing system.
Having difficulty on how the ticket creator can add new comment.
In the current code below, I am adding new record in the collection and display it as editable. However, it does not bind the whole model, only the hidden idCR.
I thought of additional member in the CRCase model for handling the new comment.e.g. public CRParticipation NewCRParticipantion {get;set;} but I received Invalid Column error because it does not exist in the table.
Any guidance is highly appreciated.
Model:
public class CRCase
{
[Key]
public int idCR { get; set; }
public string CRNo { get; set; }
public DateTime SubmittedDate { get; set; }
public string Responsible { get; set; }
public virtual ICollection<CRParticipation> CRParticipations { get; set; }
}
public class CRParticipation
{
[Key]
public int IdCRParticipation { get; set; }
public string CRStatus { get; set; }
public DateTime UpdatedDate { get; set; }
public string Comments { get; set; }
public int CR { get; set; }
[ForeignKey("CR")]
public CRCase CRCase { get; set; }
}
Controller:
public ActionResult Details(int id = 0)
{
CRCase crcase = db.CRCases.Find(id);
if (crcase == null)
{
return HttpNotFound();
}
else
{
//adding new record for new comments. might not be the proper approach.
crcase.CRParticipations.Add(new CRParticipation());
}
return View(crcase);
}
[HttpPost]
public ActionResult NewComment(CRCase crcase)
{
if (ModelState.IsValid)
{
//code here
return RedirectToAction("Details", new { id = crcase.idCR});
}
return View(crcase);
}
Display and Adding new comment View:
#model CRManagement1.Models.CRCase
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
#using (Html.BeginForm("NewComment", "CRCase")) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.idCR)
<fieldset>
<legend>CRCase</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.CRReferenceNo)
</div>
<!-- more fields here t display -->
<div class="display-field">
<table>
<tr>
<th>CRStatus</th>
<th>UpdatedDate</th>
<th>Comments</th>
</tr>
#foreach (var item in Model.CRParticipations)
{
<tr>
#if( item.IdCRParticipation != 0 )
{
<td>
#Html.DisplayFor(modelItem => item.CRStatus)
</td>
<td>
#Html.DisplayFor(modelItem => item.UpdatedDate)
</td>
<td>
#Html.DisplayFor(modelItem => item.Comments)
</td>
}
else
{
<td colspan="3" class="editor-field">
#Html.EditorFor(model => model.CRParticipations.Last().Comments)
#Html.ValidationMessageFor(model => model.CRParticipations.Last().Comments)
</td>
}
</tr>
}
</table>
</div>
</fieldset>
<p>
<input type="submit" value="Post" />
</p>
}
<p>
#Html.ActionLink("Back to List", "Index")
</p>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
View Output. If you can notice the new comment is inputted but it will not be bonded in the model when "Post" button is clicked.
I have a ViewModel which contains a collection of type of my Model, like so:
public class OrderConfirm
{
public ICollection<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
}
My QuoteLine Model looks like so:
public class QuoteLine
{
public int QuoteLineId { get; set; }
public int LostReasonId { get; set; }
public virtual LostReason LostReason { get; set; }
public string ItemName { get; set; }
}
In my View, I then Iterate through each of these QuoteLines, within a form, like so:
#using (Ajax.BeginForm("ConfirmLostOrder", new AjaxOptions()
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "LostOrders",
OnBegin = "LostOrderConfirm"
}))
{
<table class="completed-enq-table">
<tr>
<th>
Item Number
</th>
<th>
Reason
</th>
</tr>
#foreach (var sales in Model.SalesLines)
{
<tr>
<td>#sales.ItemName
#Html.HiddenFor(model => sales.QuoteLineID)
</td>
<td>#Html.DropDownListFor(model => sales.LostReasonId, ((IEnumerable<myApp.Models.LostReason>)ViewBag.LostReasons).Select(option => new SelectListItem
{
Text = (option == null ? "None" : option.LostReason),
Value = option.LostReasonId.ToString(),
Selected = (Model != null) && (option.LostReasonId == sales.LostStatusId)
}))
</td>
</tr>
}
</table>
<input type="submit" style="float: right;" value="Submit Lost Order" />
}
Then my HttpPost action looks like so:
[HttpPost]
public ActionResult ConfirmLostOrder(List<QuoteLine> models)
{
// process order
return PartialView("Sales/_ConfirmLostOrder");
}
The problem is, models is null. If I use a FormCollection I can see each of the values submitted but I'd like to use my model and not a FormCollection as I'd like to process and edit each of the line submitted individually as they may have different reason's
You can't use a foreach in this instance, it needs to be a for loop so that the name attributes of the fields contain the correct index so that default model binding knows it's binding to a list.
Firstly, I'm going to move your dropdown values out of the ViewBag (they should really be in there). That'll also take out some of that nasty logic in your view :)
So your model is now:
public class OrderConfirm
{
public List<QuoteLine> SalesLines { get; set; }
public string Currency { get; set; }
public int EnquiryID { get; set; }
public SelectList LostReasons { get; set; }
}
Try this instead of your foreach:
#for (var i = 0; i < Model.SalesLines.Count; i++)
{
<tr>
<td>
#Model.SalesLines[i].ItemName
#Html.HiddenFor(m => m.SalesLines[i].QuoteLineId)
#Html.HiddenFor(m => m.SalesLines[i].ItemName) //assuming you want this
</td>
<td>
#Html.DropDownListFor(m => m.SalesLines[i].LostReasonId, Model.LostReasons)
</td>
</tr>
}
Then change your post method to take your OrderConfirm model type:
[HttpPost]
public ActionResult ConfirmLostOrder(OrderConfirm model)