I'm using Visual Studio 2013, MVC 5.1, EF 6.0, targeting .NET 4.5. Code-first, with a default connection string.
I've got a really simple entity, named SchoolModel, listed below.
public class SchoolModel
{
[HiddenInput(DisplayValue = false)]
[Key]
public int Id { get; private set; }
[Display(Name="School Name")]
[DataType(DataType.Text)]
public string Name { get; private set; }
}
I used the scaffolding feature to create the standard MVC5 view/controller combo, named School and SchoolController respectively. I'm trying to add a few school records to the database.
The Create view:
#model OddsOnEnglish.GamingIELTS.Web.Models.SchoolModel
[snip]
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>SchoolModel</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.Name, new {#class="col-md-2"})
<div class="col-md-offset-2 col-md-10">
#Html.EditorFor(model => model.Name)
</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>
}
SchoolController.Create:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "Id,Name")] SchoolModel schoolModel)
{
if (ModelState.IsValid)
{
db.Schools.Add(schoolModel);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(schoolModel);
}
db above is an instance of SchoolContext:
public class SchoolContext : DbContext
{
public SchoolContext() : base("name=DefaultConnection")
{
}
public DbSet<Models.SchoolModel> Schools { get; set; }
}
All pretty straightforward, and based off of various tutorials. My issue is that when I submit the create school form, the index page is shown again with a new row for the new school, but no name is shown. Looking into the database, rows do get added to the corresponding table, but with only a value in the Id column - nothing for Name. If I manually enter a name in the database, that will then show up in the web interface. Clearly the record is being created, but the Name field is not being populated.
Just Correct your model as :
public class SchoolModel
{
[HiddenInput(DisplayValue = false)]
[Key]
public int Id { get; set; }
[Display(Name="School Name")]
[DataType(DataType.Text)]
public string Name { get; set; }
}
this problem you are facing because of private set block just correct it.
Related
Very new to MVC Core and C# and just as I think I'm getting the hang of something there's a new curve ball. I have a form which is based on a model which has a foreign key. When I submit the form to the controller the modelState is invalid because the form is passing something back which isn't in the model it is based on. Here is the model:
public partial class Agreement
{
public Agreement()
{
AgreementAmendments = new HashSet<AgreementAmendment>();
Bundles = new HashSet<Bundle>();
Invoices = new HashSet<Invoice>();
}
public int Id { get; set; }
public int OrgId { get; set; }
public string AgreementNumber { get; set; } = null!;
public string? IrespondReference { get; set; }
public string? DocumentLink { get; set; }
public virtual Organization Org { get; set; }
public virtual ICollection<AgreementAmendment> AgreementAmendments { get; set; }
public virtual ICollection<Bundle> Bundles { get; set; }
public virtual ICollection<Invoice> Invoices { get; set; }
}
This is the Get Create Action Method:
public IActionResult Create()
{
ViewData["OrgId"] = new SelectList(_context.Organizations, "Id", "ShortName");
return View();
}
This is the form:
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="OrgId" class="control-label">Organization</label>
<select asp-for="OrgId" class ="form-control" asp-items="ViewBag.OrgId"></select>
</div>
<div class="form-group">
<label asp-for="AgreementNumber" class="control-label">Agreement Number</label>
<input asp-for="AgreementNumber" class="form-control" />
<span asp-validation-for="AgreementNumber" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="IrespondReference" class="control-label">Internal Reference</label>
<input asp-for="IrespondReference" class="form-control" />
<span asp-validation-for="IrespondReference" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="DocumentLink" class="control-label">Document Link</label>
<input asp-for="DocumentLink" class="form-control" />
<span asp-validation-for="DocumentLink" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
And this is the HttpPost Create Action Method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("OrgId,AgreementNumber,IrespondReference,DocumentLink")] Agreement agreement)
{
if (ModelState.IsValid)
{
_context.Add(agreement);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["OrgId"] = new SelectList(_context.Organizations, "Id", "Id", agreement.OrgId);
return View();
}
When I look at the results of the ModelState it shows an error with the Org Key but as far as I can see the form should just be returning the OrgId as per the model. Can someone please let me know where I am going wrong.
Created a View Model for Agreements to handle the form input and then passed that to the base Agreement Model which seems like unnecessary work. Why can't EF Core handle this stuff without having to constantly build View Models just because there is a foreign key?
Anyway, this is the final HttpPost code for others who run into the same issue:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(AgreementWriteViewModel newagreement)
{
if (ModelState.IsValid)
{
var model = new Agreement
{
OrgId = newagreement.OrgId,
AgreementNumber = newagreement.AgreementNumber,
IrespondReference = newagreement.IrespondReference,
DocumentLink = newagreement.DocumentLink,
};
_context.Add(model);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["OrgId"] = new SelectList(_context.Organizations, "Id", "ShortName", newagreement.OrgId);
return View();
}
I have project with ASP.Net Core MVC, EF Core 2.0. There is a Person and Phone entity with a "one-to-many" relationship, i.e. each Person entity can contain many phones or none. When generating a standard controller, a view was also generated. The problem is that when creating the Person entity, the user should be able to add a phone, one or more. Many-day google did not give anything, probably because I do not know how to designate this in the search.
How to create a view with the ability to dynamically add related entities? In other words, how to create and add programmatically to the ICollection<Phone> Phone collection new Phone entities?
Model:
public partial class Person {
public Person() {
Phone = new HashSet<Phone>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Phone> Phone { get; set; }
}
}
public partial class Phone {
public int Id { get; set; }
public int Type { get; set; }
public int Number { get; set; }
public int? PersonId { get; set; }
public Person Person { get; set; }
}
public partial class ModelContext : DbContext {
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Person>(entity => {
entity.Property(e => e.Name).HasMaxLength(50).IsRequired();
});
modelBuilder.Entity<Phone>(entity => {
entity.HasOne(d => d.Person)
.WithMany(p => p.Phone)
.HasForeignKey(d => d.PersonId)
.HasConstraintName("FK_Phone_Person");
});
}
}
Generated View:
#model xxx.Models.Person
#{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>Person</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="FirstName" class="control-label"></label>
<input asp-for="FirstName" class="form-control" />
<span asp-validation-for="FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
I'm working on an application for insurance requests. When I log in as CommitteeMember and try to Edit a request(editing only its status to "approved" for ex) I get this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
The line posing a problem is a like in the context class in the Data Project.
public virtual void Commit()
{
base.SaveChanges();
}
Here's my model:
public class Request
{
[Key]
public int RequestId { get; set; }
public DateTime Date { get; set; }
public string nomBeneficiary { get; set; }
[DataType(DataType.Upload)]
public byte[] file { get; set; }
public Member beneficiary { get; set; }
public string status { get; set; }
public string eMailBeneficiairy { get; set; }
}
And here's the controller:
public ActionResult EditRequest (int id)
{
RequestService requestService = new RequestService();
Request request =requestService.GetRequestById(id);
return View(request);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult EditRequest([Bind(Exclude = "RequestId,Date,nomBeneficiairy,file,beneficiare,eMailBeneficiairy")]Request request)
{
if (ModelState.IsValid) {
RequestService requestService = new Requestervice();
requestService.UpdateRequest(request);
return View("requests");
}
return View(request);
}
And here's the view:
#model Mutuelle.Domain.Entities.Request
#{
ViewBag.Title = "EditRequest";
}
<h2>EditRequest</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Request</h4>
<hr />
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.RequestId)
<div class="form-group">
#Html.LabelFor(model => model.status, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.status)
#Html.ValidationMessageFor(model => model.status)
</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>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Since you are editing, the object should already exist. But it's not attached to the context; try using Attach to attach it first, or query the object from the context and copy the properties over. See this for more info.
EDIT: Per your date comment, you need to either change this to nullable:
public DateTime? Date { get; set; }
And also make null in the DB, or in your controller, before attaching, default it to a date greater than 1/1/1753 (a minimum value allowed in SQL for datetime).
I'm passing a ViewModel back from my View to the Controller via a form HttpPost. However, the values returned are always NULL.
ViewModel
public class vmCompanyAddress
{
public StatelyTechAdmin.Models.Company Company { get; set; }
public StatelyTechAdmin.Models.CompanyAddress Address { get; set; }
public SelectList Counties { get; set; }
}
Company Class Model
public class Company
{
[Key]
public virtual long CompanyId { get; set; }
[Required]
[Display(Name = "Company Name")]
public virtual string Name { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual IEnumerable<CompanyAddress> CompanyAddresses { get; set; }
}
CompanyAddress Class Model
public class CompanyAddress
{
[Key]
public virtual long CompanyAddressId { get; set; }
[Required]
public virtual long CompanyId { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
[Required]
public virtual int CopmanyAddressTypeId { get; set; }
[ForeignKey("CopmanyAddressTypeId")]
public virtual CompanyAddressType CompanyAddressType { get; set; }
[Display(Name = "Address 1")]
public virtual string Address1 { get; set; }
[Display(Name = "Address 2")]
public virtual string Address2 {get; set; }
[Display(Name = "Town")]
public virtual string Town { get; set; }
[Display(Name = "City")]
public virtual string City { get; set; }
[Required]
public virtual long CountyId { get; set; }
[ForeignKey("CountyId")]
[Display(Name = "County")]
public virtual County County { get; set; }
[Required]
[Display(Name = "Postal Code")]
public virtual string PostalCode { get; set; }
public virtual DateTime CreatedDate { get; set; }
}
Controller (get):
// GET: /Company/Create
public ActionResult Create()
{
vmCompanyAddress vm = new vmCompanyAddress();
vm.Counties = new SelectList(db.County, "CountyId", "Name", -1);
//vm.Address = new CompanyAddress();
//vm.Company = new Company();
return View(vm);
}
Controller (post):
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(vmCompanyAddress company)
{
if (ModelState.IsValid)
{
db.Companies.Add(company.Company);
//Amend Address Company & Address Type before save to DB
company.Address.CompanyId = company.Company.CompanyId;
company.Address.CopmanyAddressTypeId = 1;
db.CompanyAddress.Add(company.Address);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(company);
}
View (create)
#model StatelyTechAdmin.ViewModels.vmCompanyAddress
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Company</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Company.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Company.Name)
#Html.ValidationMessageFor(model => model.Company.Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Company.CreatedDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Company.CreatedDate)
#Html.ValidationMessageFor(model => model.Company.CreatedDate)
</div>
#* Invoice Address *#
<div class="editor-label">
#Html.LabelFor(model => model.Address.Address1)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.Address1)
#Html.ValidationMessageFor(model => model.Address.Address1)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Address.Address2)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.Address2)
#Html.ValidationMessageFor(model => model.Address.Address2)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Address.Town)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.Town)
#Html.ValidationMessageFor(model => model.Address.Town)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Address.City)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.City)
#Html.ValidationMessageFor(model => model.Address.City)
</div>
#*<div class="editor-label">
#Html.LabelFor(model => model.Address.County)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Address.CountyId, Model.Counties)
</div>*#
<div class="editor-label">
#Html.LabelFor(model => model.Address.PostalCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Address.PostalCode)
#Html.ValidationMessageFor(model => model.Address.PostalCode)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
Can anyone please offer any advice as to why my return ViewModel values are NULL when all fields are populated?
I've checked in Google Chrome browser using the Network Record feature and all values ARE posted back in JSON format.
Many thanks.
------------ EDIT ---------------
Here's part of what I can see from the Google Chrome Network Monitor
Company.Name:ABC123
Company.CreatedDate:2014/05/13 00:00:00
....
So it is definitely being returned.
I was able to reproduce your issue and was confused because I know that the default MVC Model Binder understands complex types. I stripped away most of the code and just tried to do it with the Company object, which still failed. I then noticed that in vmCompanyAddress that the name of the class was also the name of the property:
public class vmCompanyAddress
{
public StatelyTechAdmin.Models.Company Company { get; set; }
I changed the name of the property to something different from the class name and it started working:
public class vmCompanyAddress
{
public StatelyTechAdmin.Models.Company TheCompany { get; set; }
We had the same problem today. The accepted answer in this question is only a dirty workaround for the actual problem.
ClassName and PropertyName in a form model can be the same, there is no limitation in the model binder. The limitation is the parameter of the action in your controller. You must not name the parameter like a property with complex type in your form model. Cause the binder will try to bind the HTTP POST form value of company to this paramter in your controller. It will not work for you, cause the binder tries to bind the values of a Company Type to CompanyAddress type.
To fix your problem, you simply have to rename the parameter company to companyAddressModel - or anything which is not a property in your model class.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompanyAddress company)
change to:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CompanyAddress companyAddressModel)
See here for more information about model binding: http://aspnetmvc.readthedocs.org/projects/mvc/en/latest/models/model-binding.html
MVC will try to bind request data to the action parameters by name.
MVC will look for values for each parameter using the parameter name
and the names of its public settable properties. [...] In addition to route values
MVC will bind data from various parts of the request and it does so in
a set order. Below is a list of the data sources in the order that
model binding looks through them:
Form values: These are form values that go in the HTTP request using the POST method.
Route values: The set of route values provided by routing.
Query strings: The query string part of the URI.
A good example from ASP.NET WebAPI documentation, which is using the same technique:
HttpResponseMessage Put(int id, Product item) { ... }
Here the Id property of Product is mapped to the id parameter in the controller. Which will work, cause in the action the same primitive data type is used as in the model class.
Have not tried this myself but had a lot of similar issues a long time ago that I solved with custom ModelBinder:s which I do not recommend.
I guess your data does not look like: { Company: {...}, Address: {...} }?
I think the solution is to have MVC to understand the structure of the data using templates and EditorFor(). See http://lostechies.com/jimmybogard/2011/09/07/building-forms-for-deep-view-model-graphs-in-asp-net-mvc/ for a good example!
Ensure your ViewModel is exposing properties and not just fields.
This works:
public DAL.Models.Account acct {get;set;}
This doesn't:
public DAL.Models.Account acct;
I'm very new with MVC, so bear with me, but I can't seem to bind a value from a SelectList to an instance of the selected object during a postback in MVC 4.
Suppose I have to create a Teacher as a member of a School. I have a ViewModel class defined as such:
public class RegisterTeacherModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
[Required]
[Display(Name = "School")]
public School SelectedSchool { get; set; }
[ScaffoldColumn(false)]
public Guid UserId
{
get;
set;
}
public SelectList PossibleSchools
{
get;
private set;
}
public RegisterTeacherModel(IRepository<School> schoolRepo)
{
PossibleSchools = new SelectList(schoolRepo, "Id", "Name");
}
}
And my View:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>RegisterTeacherModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Email)
#Html.ValidationMessageFor(model => model.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SelectedSchool)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedSchool, Model.PossibleSchools)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And finally, my Controller method:
[HttpPost, ActionName("Create")]
public ActionResult CreateTeacher(RegisterTeacherModel teacherModel)
{
if (ModelState.IsValid)
{
try
{
...
}
}
}
But when I receive the RegisterTeacherModel object back in my Create method in the Controller, SelectedSchool is always null. I must be missing something in the way the model binder re-creates the object references on postback. Can anyone point me in the right direction?
I think you touched on it with your second post: Try pointing the initial code to Model.SelectedSchool.<IdProperty>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedSchool.**<IdProperty>**, Model.PossibleSchools)
</div>
Well, I found a workaround. I still don't know if I'm missing something, but instead of using a School object in my ViewModel, I replaced it with the SelectedSchoolId as such:
public class RegisterTeacherModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
[Required]
[Display(Name = "School")]
public int SelectedSchoolId { get; set; }
...
}
And change my View dropdown to use this instead:
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedSchoolId, Model.PossibleSchools)
</div>
And then in my controller, when creating the real model objects I can simply pull the School object from the School repository and associate it with the real Teacher object.