Dynamically add ICollection Items to Model while "creating" in MVC - c#

Found a solution, but a very ugly one, see at the bottom, can someone improve that solution?
So I have a dummy Model like this one
public class TestModel
{
public int TestModelID { get; set; }
public string Name { get; set; }
}
And another one like this one
public class Collector
{
public int CollectorID { get; set; }
public string CollectorString { get; set; }
public ICollection<TestModel> MyList { get; set; }
}
I would like to (in simple CRUD style) create a new object Collector and populate (later with dynamic addition of new fields, for now only one) the ICollection.
This is my view
#model TestApplication.Models.Collector
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Collector</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.CollectorString, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.CollectorString, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.CollectorString, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => Model.MyList.ToList()[0].Name)
<div class="col-md-10">
#Html.EditorFor(model => model.MyList.ToList()[0].Name, new { htmlAttributes = new { #class = "form-control" } })
</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>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
And the controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Collector collector)
{
if (ModelState.IsValid)
{
db.Collectors.Add(collector);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(collector);
}
This is the resulting HTML code (only relevant part I hope)
<div class="form-group">
<label for="">Name</label>
<div class="col-md-10">
<input class="form-control text-box single-line" name="[0].Name" type="text" value="" />
</div>
</div>
However, the MyList in the controller when creating is always null, why? (Yes I know Haackeds Blog entry, still can't figure out why it doesn't work)
So the problem is, that this line
#Html.EditorFor(model => model.MyList.ToList()[0].Name, new { htmlAttributes = new { #class = "form-control" } })
although very recommended to use in MVC, generates this here
<input class="form-control text-box single-line" name="[0].Name" type="text" value="" />
which is obviously not working. How do I get razor to change [0].Name to MyList[0].Name?
**Update: **
So I found a solution, if a hard-code this here
<input class="form-control text-box single-line" name="MyList[0].Name" type="text" value="" />
The controller understands it and I don't get null. How to solve it using razor?

ICollection<T> does not have an indexer. IList<T> does
public class Collector {
public Collector() {
MyList = new List<TestModel>();
}
public int CollectorID { get; set; }
public string CollectorString { get; set; }
public IList<TestModel> MyList { get; set; }
}
This would allow
#Html.EditorFor(model => model.MyList[0].Name, new { htmlAttributes = new { #class = "form-control" } })
in the view to generate the desired markup
<input class="form-control text-box single-line" name="MyList[0].Name" type="text" value="" />
Which can also be used in a loop for multiple items
<div class="form-group">
#for(int i = 0; i < Model.MyList.Count; i++) {
#Html.LabelFor(model => Model.MyList[i].Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MyList.ToList()[i].Name, new { htmlAttributes = new { #class = "form-control" } })
</div>
}
</div>
When returning the model from the controllers initial call, just make sure that calls to the model's collection index has an item.
[HttpGet]
public ActionResult Create() {
var model = new Collector();
model.MyList.Add(new TestModel());
return View(model);
}

Try changing your collector class to include initialization.
public class Collector
{
public Collector()
{
set MyList = new Collection<TestModel>();
}
public int CollectorID { get; set; }
public string CollectorString { get; set; }
public ICollection<TestModel> MyList { get; set; }
}

You can do by this using razor syntax
<div class="form-group">
#for(int i = 0; i < Model.MyList.Count; i++) {
#Html.LabelFor(model => Model.MyList[i].Name, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.MyList[i].Name, new { htmlAttributes = new { #class = "form-control" } })
</div>
}
</div>

Related

How to save one specific value in database

I have this Issue class. Is there
public class Issue
{
public int IssueId { get; set; }
[Required]
public string ReporterName { get; set; }
[Required]
public string Description { get; set; }
public int? IssueTypeId { get; set; }
public virtual IssueType type { get; set; }
}
Then this is the view I have so I can change the value of the IssueTypeId. However when I try to save it in the database with that code in the controller, I am having an error saying that the ReporterName and Description are still required. How can I only update one specific value in database, in this case IssueTypeId?
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Issue</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(model => model.IssueId)
<div class="form-group">
#Html.LabelFor(model => model.IssueTypeId, "IssueTypeId", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("IssueTypeId", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.IssueTypeId, "", new { #class = "text-danger" })
</div>
</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>
}
My code in controller
public ActionResult AssignFixer(int issueId, int issueTypeId)
{
var issue = new Issue { IssueId = issueId , IssueTypeId= issueTypeId};
using (var newContext = new SystemContext())
{
newContext.Issues.Attach(issue);
newContext.Entry(issue).Property(i => i.IssueTypeId).IsModified = true;
newContext.SaveChanges();
}
return View(issue);
}
Instead of attaching the issue in newContext.Issues. First get the instance and then update it. Like:
var existingIssue = newContext.Issues.Where(i => i.IssueId == issueId).FirstOrDefault();
existingIssue.IssueTypeId = issueTypeId;
newContext.SaveChanges();

Varying amount of fields in Razor page form, returns null properties in view model

I've come up with problem, when trying to fill data to my model. I have an "Resource" entity, which can have no-to-many "attributes". I have templates set up, which holds names for those attributes. When Resource is created, user chooses on of templates, then Attributes are created(empty) and program generates form for those attributes.
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.HiddenFor(Model => Model.Resource)
#for (int i = 0; i < Model.Attributes.Count(); i++)
{
<div class="form-group">
#*Html.LabelFor(d => d.Attributes.ToArray()[i].Name, htmlAttributes: new { #class = "control-label col-md-2" })*#
<h4>#Html.Raw(Model.Attributes.ToList()[i].Name)</h4>
<div class="col-md-10">
#Html.TextBoxFor(Model => Model.Attributes.ToList()[i].Value, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(Model => Model.Attributes.ToList()[i].Value, "", new { #class = "text-danger" })
</div>
</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>
}
This form uses this View model:
public class ResourceAttributesViewModel
{
public virtual Resource Resource { get; set; }
public virtual ICollection<_Attribute> Attributes { get; set; }
}
problem is that when i hit "submit" button, it gives me view model with null Resource and Attributes properties
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Fill(ResourceAttributesViewModel AttributeSet)
{
if(ModelState.IsValid)
{
foreach (var attr in AttributeSet.Attributes)
{
db.Entry(attr).State = EntityState.Modified;
db.SaveChanges();
}
return RedirectToAction("Index");
}
return View(AttributeSet);
}
if it helps, there is POST string sent by browser
__RequestVerificationToken=XoVM9h_3njX5x2m35b_vKKHY3m5UDaYm9_2ZMfNkglouqHJCSw2NO56Tv2Sb3kXy8qC8XBLXawoQv0ft0xc-LxYmQGfi4EAqroq2b63Wb9Q1&Resource=System.Data.Entity.DynamicProxies.Resource_7639327FA0332BEBC7FB6836F70C3D62C3D744D76F2C3F8DDFCE679AA8CA31DC&%5B0%5D.Value=100&%5B1%5D.Value=200

Adding another form to a view on button clicked

I have a Create view which contains a form. I want to give the user two options, save which simply saves the form and redirects to Index or add more tickets which will show another identical form in the view. I am unsure where to start with this, should I save the first ticket details then refresh the page? Or is there a way to have a second form hidden until add more tickets is selected then save all forms at once? I know this is quite vague but I've no idea where to start, but any advice would be great
Here is my model;
public class Ticket
{
public int TicketID { get; set; }
[Required]
[ForeignKey("Event")]
//foreign key
public int EventID { get; set; }
[Required]
public string Description { get; set; }
[Required]
public float Price { get; set; }
//navigation property
public virtual Event Event { get; set; }
//navigation property
public ICollection<OrderDetails> OrderDetails { get; set; }
}
Here is my view so far;
#model GeogSocSite.Models.Ticket
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Ticket</h4>
<hr />
#Html.HiddenFor(model => model.EventID)
<div class="form-group">
#Html.LabelFor(model => model.Description, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Description, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Description, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Price, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Price, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Price, "", 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>
My choice in this situation is using appropriate view model(as #StephenMuecke ) sugested) and using Bootsrtaap modal(here) to show hidden fields.

Send Data From View to Model With ViewModel

I have this ViewModel:
public class Rapport
{
[Key]
public int RapportId { get; set; }
public RapportAnomalie rapport { get; set; }
public IEnumerable<RefAnomalie> refAnomalies { get; set; }
}
which has two models in it, RapportAnomalie :
public class RapportAnomalie
{
[Key]
public int codeRapport { get; set; }
public DateTime date { get; set; }
public String heure { get; set; }
public String etat { get; set; }
[ForeignKey("codeAgence")]
public virtual Agence agence { get; set; }
public int codeAgence { get; set; }
public IEnumerable<LigneRapportAnomalie> lignesRapport { get; set; }
}
and RefAnomalie.
However when I want to send data from view to controller from a form, I keep getting an exception.
The view :
#model InspectionBanque.Models.Rapport
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>RapportAnomalie</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.rapport.date, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.date, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.heure, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.heure, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.etat, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.rapport.etat, new { htmlAttributes = new { #class = "form-control" } })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.rapport.codeAgence, "codeAgence", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("codeAgence", null, htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.rapport.codeAgence, "", 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>
}
#for (int i = 0; i < Model.refAnomalies.Count(); i++)
{
<div class="col-md-10">
#Html.DisplayFor(model => model.refAnomalies.ElementAt(i).libele)
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
and then the controller :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create( Rapport rapportAnomalie)
{
if (ModelState.IsValid)
{
RapportAnomalie rp = new RapportAnomalie();
db.rapportAnomalies.Add(rapportAnomalie.rapport);
db.SaveChanges();
return RedirectToAction("Index");
}
var refanomal = from r in db.refAnnomalies
select r;
Rapport rapport = new Rapport { rapport = rapportAnomalie.rapport, refAnomalies = refanomal.ToArray() };
ViewBag.codeAgence = new SelectList(db.Agences, "codeAgence", "intituleAgence", rapportAnomalie.rapport.codeAgence);
return View(rapport);
}
Any ideas what's wrong with it?
I think you are getting the problem because the lignesRapport field is not initialized in your model RapportAnomalie
Create a constructor and initialize lignesRapport . I believe the problem will go away.
public RapportAnomalie()
{
lignesRapport = new List <LigneRapportAnomalie>();
}
Good luck

NullReferenceException ASP.NET MVC 5

I have a web application with this controller:
public class ServiceRequestController : Controller
{
[Authorize(Roles = "Customer")]
public ActionResult Create()
{
return View();
}
[Authorize(Roles = "Customer")]
public ActionResult CreateNewUserAccount()
{
return View();
}
[Authorize(Roles = "Customer")]
[HttpPost]
public ActionResult CreateNewUserAccount(ServiceRequest serviceRequest)
{
if (ModelState.IsValid)
{
serviceRequest.Log.Id = User.Identity.GetUserId().ToString();
serviceRequest.Log.DateTimeLogged = System.DateTime.Now;
serviceRequest.LogID = db.Logs.Max(item => item.LogID);
serviceRequest.EstimatedResolveDate serviceRequest.CalculateEstimatedResolveDate();
db.ServiceRequests.Add(serviceRequest);
db.SaveChanges();
return RedirectToAction("AllServiceRequests", "Log");
}
return View(serviceRequest);
}
The serviceRequest.Log.Id = User.Identity.GetUserId().ToString(); (And any preceding line if this is commented out) throws a null reference exception. I presume the serviceRequest is somehow null?
The ActionLink which requests the CreateNewUserAccount() page is:
#Html.ActionLink("New User Account", "CreateNewUserAccount", "ServiceRequest")
I'm not sure how to resolve this exception?
The model is:
public partial class ServiceRequest
{
public int ServiceRequestID { get; set; }
public Nullable<int> LogID { get; set; }
public string RequestType { get; set; }
[DisplayName("Additional Information")]
[Required]
[StringLength(200)]
public string AdditionalInformation { get; set; }
public DateTime EstimatedResolveDate { get; set; }
[Required]
[DisplayName("Delivery Date")]
public DateTime DeliveryDate { get; set; }
public virtual Log Log { get; set; }
public DateTime CalculateEstimatedResolveDate()
{
return System.DateTime.Now.AddDays(3);
}
}
View code:
#model OfficiumWebApp.Models.ServiceRequest
#{
ViewBag.Title = "New User Account";
}
#using(Html.BeginForm("CreateNewUserAccount", "ServiceRequest", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true)
<div class="form-group">
#Html.LabelFor(model => model.RequestType, new { #class = "control-label col-md-2" })
<div class="col-md-3">
<div class="editor-field">
#Html.TextBoxFor(model => model.RequestType, new { #Value = ViewBag.Title, #readonly = "readonly" })
#Html.ValidationMessageFor(model => model.RequestType)
</div>
</div>
</div>
<div class="form-group">
#Html.Label("Name of Account Holder", new { #class = "control-label col-md-2" })
<div class="col-md-3">
<div class="editor-field">
#Html.TextBox("AccountName")
#Html.ValidationMessageFor(model => model.RequestType)
</div>
</div>
</div>
<div class="form-group">
#Html.Label("Department", new { #class = "control-label col-md-2" })
<div class="col-md-3">
<div class="editor-field">
#Html.TextBox("Department")
#Html.ValidationMessageFor(model => model.RequestType)
</div>
</div>
</div>
<div class="form-group">
#Html.Label("Location", new { #class = "control-label col-md-2" })
<div class="col-md-3">
<div class="editor-field">
#Html.TextBox("Location", null, new { id = "Location" }))
#Html.ValidationMessageFor(model => model.RequestType)
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.AdditionalInformation, new { #class = "control-label col-md-2" })
<div class="tags">
<div class="col-md-10">
#Html.TextAreaFor(model => model.AdditionalInformation)
#Html.ValidationMessageFor(model => model.AdditionalInformation)
</div>
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.DeliveryDate, new { #id = "VisitDateLabel", #class = "control-label col-md-2" })
<div class="col-md-3">
<div class="editor-field">
#Html.JQueryUI().DatepickerFor(model => model.DeliveryDate).Inline(false)
#Html.ValidationMessageFor(model => model.DeliveryDate)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-floppy-save"></span></button>
</div>
</div>
</div>
}
You need to return the view i.e.
[Authorize(Roles = "Customer")]
public ActionResult CreateNewUserAccount()
{
var model = new ServiceRequest();
model.Log = new Log();
return View(model);
}
In your view you need to add a model reference too at the top i.e.
#model ServiceRequest
You could also initialise the Log object in your model as follows:
public class ServiceRequest
{
public ServiceRequest()
{
Log = new Log();
}
....
}
An action link will not post your model back, you need to include it within a form and include all the model values that you want to be updated on the client i.e.
#using (Html.BeginForm("CreateNewUserAccount", "ServiceRequest", FormMethod.Post)){
#Html.EditorFor(m => m.AdditionalInformation)
...
<input type="submit" value="submit" />
Update
Taken from the below comments, this was resolved by creating a new log on post i.e.
var log = new Log {
Id = User.Identity.GetUserId().ToString(),
DateTimeLogged = System.DateTime.Now,
LogID = db.Logs.Max(item => item.LogID) };
serviceRequest.Log = log;

Categories