I want to pass an ArrayList of Object from ARCCreate so the items can be added to the database as multiple entries, these are my codes:
My Model :
public class M_ARC : DbContext
{
[Key]
[Display(Name = "Periode")]
[Required]
[RegularExpression("[2][0]([1][4-9]|[2-9][0-9])(0[1-9]|1[012])", ErrorMessage = "Format tidak sesuai. Contoh format : 201407 (Juli 2014)")]
public int Periode { get; set; }
[Display(Name = "Email SPDT")]
[Required]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "dd/MM/yyyy", ApplyFormatInEditMode = true)]
public DateTime EmailSPDT { get; set; }
[Display(Name = "Jatuh Tempo")]
[Required]
public DateTime JatuhTempoDT { get; set; }
}
My View :
<tr>
<th>
#Html.Label("Agustus")
</th>
<td>
<input type="text" name="EmailSPDT" class="pengirimanDT" />
</td>
<td>
<input type="text" name="JatuhTempoDT" class="tanggapanDT" />
</td>
<td>
<input type="text" name="InformasiBankDT" class="informasiBankDT" />
</td>
</tr>
<tr>
<th>
#Html.Label("September")
</th>
<td>
<input type="text" name="EmailSPDT" class="pengirimanDT" />
</td>
<td>
<input type="text" name="JatuhTempoDT" class="tanggapanDT" />
</td>
<td>
<input type="text" name="InformasiBankDT" class="informasiBankDT" />
</td>
</tr>
<tr>
<th>
#Html.Label("Oktober")
</th>
<td>
<input type="text" name="EmailSPDT" class="pengirimanDT" />
</td>
<td>
<input type="text" name="JatuhTempoDT" class="tanggapanDT" />
</td>
<td>
<input type="text" name="InformasiBankDT" class="informasiBankDT" />
</td>
</tr>
And my Controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ARCCreate(List<M_ARC> ARCList)
{
foreach (M_ARC item in ARCList)
{
if (ModelState.IsValid)
{
db.Arcs.Add(item);
db.SaveChanges();
}
else return View();
}
return RedirectToAction("ARCIndex");
}
Is this a proper way for inserting multiple entries ? or Is there better solution for inserting multiple entries into my database?
I think you might have some misunderstandings about MVC and EF. You might want to read up a bit more on the two concepts.
First, your "model" is called M_ARC and inherits from DbContext. The class inheriting from DbContext should not be the model but provide a property to retrieve/update the model from/to the database.
Second, you cannot pass data from the view to the controller. It is the other way around. What happens when the user clicks a button is, the form fields are posted to the server and handled by the controller. I reckon what you try to achieve is rendering the view such that the form fields will have the correct names to be mapped into an array. This is not a new question though, you can find some help here: MVC .NET Model Binding to Array on the fly
I can see that you want to submit multiple entries where each entry contains these properties: Periode, EmailSPDT, JatuhTempoDT, and InformasiBankDT. Here's how you should create your model class
public class ARCCreateModel
{
public ARCCreateModel()
{
this.Details = new List<ARCCreateDetail>();
}
public List<ARCCreateDetail> Details { get; set; }
}
public class ARCCreateDetail
{
[Display(Name = "Periode")]
[Required]
[RegularExpression("[2][0]([1][4-9]|[2-9][0-9])(0[1-9]|1[012])", ErrorMessage = "Format tidak sesuai. Contoh format : 201407 (Juli 2014)")]
public int Periode { get; set; }
[Display(Name = "Email SPDT")]
[Required]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "dd/MM/yyyy", ApplyFormatInEditMode = true)]
public DateTime EmailSPDT { get; set; }
[Display(Name = "Jatuh Tempo")]
[Required]
public DateTime JatuhTempoDT { get; set; }
}
Then pass an instance of ARCCreateModel to the view in the controller get method
[HttpGet]
public ActionResult ARCCreate()
{
ARCCreateModel model = new ARCCreateModel();
// generate the multiple entries
ARCCreateDetail detail1 = new ARCCreateDetail();
detail1.Periode = 201408;
model.Details.Add(detail1);
// add more details if necessary
ARCCreateDetail detail2 = new ARCCreateDetail();
detail2.Periode = 201409;
model.Details.Add(detail2);
return View(model);
}
Your view should be like this based on the definition of ARCCreateModel above
#model ARCCreateModel
#using (Html.BeginForm())
{
<table>
#for (int i = 0; i < Model.Details.Count; i++)
{
<tr>
<th>
#Model.Details[i].Periode
</th>
<td>
<input type="text" name="Details[#i].EmailSPDT" class="pengirimanDT" />
</td>
<td>
<input type="text" name="Details[#i].JatuhTempoDT" class="tanggapanDT" />
</td>
<td>
<input type="text" name="Details[#i].InformasiBankDT" class="informasiBankDT" />
</td>
</tr>
}
</table>
<button type="submit">Submit</button>
}
and finally when you submit the form, you can get the multiple entries in model.Details as below
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ARCCreate(ARCCreateModel model)
{
foreach (ARCCreateDetail item in model.Details)
{
// get the entered values and save to database here
// assuming there's Arc table with properties similar to ARCCreateDetail
Arc arc = new Arc();
arc.Periode = item.Periode;
arc.EmailSPDT = item.EmailSPDT;
arc.JatuhTempoDT = item.JatuhTempoDT;
arc.InformasiBankDT = item.InformasiBankDT;
db.Arcs.Add(arc);
}
// submit the changes to EF
db.SaveChanges();
return RedirectToAction("ARCIndex");
}
Please note that the key is how you set the name attribute of the textboxes for each entry, i.e Details[0].EmailSPDT or Details[1].JatuhTempoDT so the entered values will be in the exact same order as in model.Details property.
Related
I'm trying to figure out how to bind form elements to model containing a property that is an array or enumerable. This form should be usable for adding a new object or editing an existing one.
Below is a stripped down example.
If Levels contains no elements (e.g. create), the row of Level fields is not rendered. Should you add an empty element to the Level array or should you add a row of empty fields? But how to do this using the asp-for attribute.
If Levels contains elements (e.g. edit), the rows of Level fields is rendered. However, when posting model.Levels in the Edit method is null.
Any ideas how to best implement this?
Model to bind
public class CarparkModel
{
[HiddenInput]
public int Id { get; set; }
public string Name { get; set; }
public Level[] Levels { get; set; }
}
public class Level
{
[HiddenInput]
public int Id { get; set; }
public string Description { get; set; }
public int NrOfSpaces { get; set; }
}
Main view
#model CarparkModel
<form method="POST">
<input asp-for="Id">
<div>
<label asp-for="Name"></label>
<input asp-for="Name">
</div>
<table>
<tr>
<th>Description</th>
<th>Nr of spaces</th>
</tr>
#foreach (Level level in Model.Levels)
{
<tr>
<td><input asp-for="Description"></td>
<td><input asp-for="NrOfSpaces"></td>
</tr>
}
<tr>
<td colspan="2>
<!-- Click = new row of Level fields added -->
<button type="button">Add level</button>
</td>
</tr>
</table>
<button type="submit">Save</button>
</form>
Controller
public class CarparkController
{
[HttpGet]
public IActionResult Create()
{
return View(new CarparkModel());
}
[HttpPost]
public IActionResult Create(CarparkModel model)
{
if (ModelState.IsValid)
{
_repository.Save(model);
return RedirectToAction("index");
}
return View(model);
}
[HttpGet]
public IActionResult Edit(int id)
{
return View(_repository.Get(id));
}
[HttpPost]
public IActionResult Edit(CarparkModel model)
{
if (ModelState.IsValid)
{
_repository.Save(model);
return RedirectToAction("index");
}
return View(model);
}
}
Don't use foreach if you want to be able to send the items back in the form. use for loop with indexed naming for the items.
#model CarparkModel
<form method="POST">
<input asp-for="Id">
<div>
<label asp-for="Name"></label>
<input asp-for="Name">
</div>
<table>
<tr>
<th>Description</th>
<th>Nr of spaces</th>
</tr>
#for(var index = 0, index < Model.Levels.Length, index++)
{
<tr>
<td><input asp-for="#Model.Levels[index].Description"></td>
<td><input asp-for="#Model.Levels[index].NrOfSpaces"></td>
</tr>
}
<tr>
<td colspan="2">
<!-- Click = new row of Level fields added -->
<button type="button">Add level</button>
</td>
</tr>
</table>
<button type="submit">Save</button>
</form>
The index is needed to reconstruct the collection when posting it in the form.
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 stuck on this issue for a while..
I've created a simple view model:
public class AddTranslationViewModel
{
public List<ProjectTranslation> ProjectTranslations { get; set; }
public AddTranslationViewModel()
{
ProjectTranslations = new List<ProjectTranslation>();
}
}
ProjectTranslation class:
public class ProjectTranslation
{
public int ProjectTranslationId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Address { get; set; }
public int LanguageId { get; set; }
public Language Language { get; set; }
public int ProjectId { get; set; }
public Project Project { get; set; }
}
A simple view which uses the AddTranslationViewModel
<table class="table">
#foreach (var item in Model.ProjectTranslations)
{
#Html.HiddenFor(modelItem => item.ProjectTranslationId)
<tr>
<td>
#Html.DisplayFor(modelItem => item.Language.LanguageCode)
</td>
<td>
#Html.EditorFor(modelItem => item.Title)
</td>
</tr>
}
</table>
<input type="submit" value="Send" />
and finally my POST Method:
public ViewResult AddTranslation(AddTranslationViewModel projectTranslations)
{
if (ModelState.IsValid)
{
//...
}
return View(projectTranslations);
}
The idea is very basic, I want to show a list of items where it should be possible to change/edit the values.
However, the model binding is not working, the projectsTranslations param in the HTTPPost-Method AddTranslation is always empty.
What's the mistake here?
Binding to a list of object requires creating input field structure with names containing indexes, i.e:
<input type="text" name="YourArrayOrList[0].SomeProperty" value="123" />
<input type="text" name="YourArrayOrList[0].SomeOtherProperty" value="321" />
<input type="text" name="YourArrayOrList[1].SomeProperty" value="123" />
<input type="text" name="YourArrayOrList[1].SomeOtherProperty" value="321" />
Moreover, you need to point the form to the proper Action Method in your Controller using Razor's Html.BeginFrom method (see documentation).
In you case it should look like this:
#using(Html.BeginForm("AddTranslation","YourControllerName"))
{
for (int i=0;i<Model.ProjectTranslations.Count; i++)
{
#Html.HiddenFor(model => model.ProjectTranslations[i].ProjectTranslationId)
<tr>
<td>
#Html.DisplayFor(model => model.ProjectTranslations[i].Language.LanguageCode)
</td>
<td>
#Html.EditorFor(model => model.ProjectTranslations[i].Title)
</td>
</tr>
}
}
If your method is not edit, but CREATE method, then obviously your List in model will have 0 elements. In this case, change the stop condition in for loop to desired count.
Keep in mind that this topic was discussed many times before:
ASP.NET MVC bind array in model
ASP.NET MVC - Can't bind array to view model
Before i started i would like to say i searched and found nothing similar
in my solution i have a model that contains a list of some of my objects
public class ModelView
{
public Owner owner = new Owner();
public Tenant tnt = new Tenant();
}
In my view i call that class as a model which is this way
#model WebApp.Models.ModelView
<form name="export_form" action="Export" method="post">
<table cellpadding="2" cellspacing="2" border="0">
#if (Condition_1)
{
<tr>
<td>
<!-- ID -->
</td>
<td>
#Html.HiddenFor(model => model.owner.ID)
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
#Html.EditorFor(model => model.owner.name)
</td>
</tr>
<tr>
<td>
Phone
</td>
<td>
#Html.CheckBoxFor(model => model.owner.is_Checked_Phone)
</td>
</tr>
}
else
{
<tr>
<td>
<!-- ID -->
</td>
<td>
#Html.HiddenFor(model => model.tnt.ID)
</td>
</tr>
<tr>
<td>
Name
</td>
<td>
#Html.EditorFor(model => model.tnt.name)
</td>
</tr>
<tr>
<td>
Adress
</td>
<td>
#Html.CheckBoxFor(model => model.tnt.is_Checked_Adress)
</td>
</tr>
}
</table>
<input type="submit" name="SaveStuff" value="Save" />
<input type="submit" name="ExportStuff" value="Export" />
</form>
In my controller i have a class that handles multiple submit buttons and depending on the button name it would redirect to a method. below is the SaveStuff method
[HttpPost]
[SubmitButtonClass(Name = "SaveStuff")]
public ActionResult Save_Definition(Owner owner, Tenant tnt)
{
/*
Stuff Here
*/
}
the problem here is i keep getting null values even thought the entities are not null. is there a reason why? no values are returned.
Update
Model A
public partial class Owner
{
public long ID { get; set; }
public bool is_Checked_Name { get; set; }
public bool is_Checked_Phone { get; set; }
}
Model B
public partial class Tenant
{
public long ID{ get; set; }
public bool is_Checked_Name { get; set; }
public bool is_Checked_Adress { get; set; }
}
these are auto generated using EF
You have multiple issues with your code. Firstly the model in your view is ModelView and you generating form controls prefixed with that models property names, for example
<input type="checkbox" name="owner.is_Checked_Phone" ... />
which means your POST method needs to match the model in the view
public ActionResult Save_Definition(ModelView model)
The next issue is that you model only has fields, not properties with { get; set; }, so the DefaultModelBinder cannot set any values. Your view model would need properties such as public Owner owner { get; set; } and you set the value in either the controller before you pass the model to the view, or in a parameterless constructor for ModelView.
However a view model should not contain properties which are data models, but rather be a flat structure containing only the properties you need. In your case, it would be
public class ModelView
{
public long ID { get; set; }
[Display(Name = "Name")]
public bool IsNameSelected { get; set; }
[Display(Name = "Phone")]
public bool IsPhoneSelected { get; set; }
[Display(Name = "Address")]
public bool IsAddressSelected { get; set; }
// additional property to define if the form is for an Owner or Tenant
public bool IsOwner { get; set; }
}
and in the GET method
var model = new ModelView()
{
IsOwner = true // or false
};
return View(model);
and in the view
#model ModelView
....
#using (Html.BeginForm("Save_Definition"))
{
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.IsOwner)
#Html.CheckBoxFor(m => m.IsNameSelected )
#Html.LabelFor(m => m.IsNameSelected )
if (Model.IsOwner)
{
#Html.CheckBoxFor(m => m.IsPhoneSelected)
#Html.LabelFor(m => m.IsPhoneSelected)
}
else
{
#Html.CheckBoxFor(m => m.IsAddressSelected)
#Html.LabelFor(m => m.IsAddressSelected)
}
.... // submit buttons
}
and the POST method, you can check the value of model.IsOwner to know if you have submitted an Owner or Tenant and take the appropriate action.
Side notes:
Recommend you read What is ViewModel in
MVC?
The <table> element is for tabular data. Do not use it for layout.
Your view has <form action="Export" .. > yet your POST method is
named Save_Definition so unsure which method you intending to
submit the form to.
If your post controller is changing original model data, you will need to issue a ModelState.Remove("propertyname") followed by model.propertyname = "new value" and then a simple return View(model) will update the changed value on your already posted view.
I have a class like as below.
public class ProductViewGridModel
{
public long Id { get; set; }
public string PackageCode { get; set; }
public string ProductName { get; set; }
public string ProductCategory { get; set; }
public IDictionary<string, string> Localizations { get; set; }
}
I rendered the model in to view like this.(foreach statement running into table, I'm not showing its here.)
#foreach (var localization in Model.Localizations)
{
var p = localization.Value;
<tr>
<td class="adminData">
<input class="k-textbox" type="text" value="#p" name="productView.Localizations[#localization.Key]" />
<input type="hidden" value="#p" name="productView.Localizations[#localization.Key]" />
</td>
</tr>
}
And I have a button, the button sending form values to controller with ajax call.
Other model bindings are okay, but I want to bind all localization inputs to localizations model.
But localizations always null. How can I bind this ?.
The input name is wrong. The default modelbinder will look for the property Localizations:
#foreach (var localization in Model.Localizations)
{
var p = localization.Value;
<tr>
<td class="adminData">
<input class="k-textbox" type="text" value="#p" name="Localizations[#localization.Key]" />
<input type="hidden" value="#p" name="Localizations[#localization.Key]" />
</td>
</tr>
}