Can't post (create) 3rd level nested model in ASP.NET Core 3.1 MVC - database

I'm at the end of my thoughts. It's been days. I can't figure out how to validate and then create a new database model from deep-nested models.
I have Offer and Order tables which share Company/Currency/Contact tables so I decided, instead of having Offer.Company, Order.Company, to create a table SharedInfo and have it Offer.SharedInfo.Company and Order.SharedInfo.Comapany which can be the same.
Shortened for brevity
Company.cs:
namespace memo.Models
{
public partial class Company
{
[Key]
public int CompanyId { get; set; }
[Required(ErrorMessage = "You have to fill the name of the Company")]
public string Name { get; set; }
public string City { get; set; }
}
}
SharedInfo.cs:
namespace memo.Models
{
public class SharedInfo
{
public SharedInfo()
{
Company = new Company();
}
[Key]
public int SharedInfoId { get; set; }
[Required]
public string Subject { get; set; }
[Required]
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
Offer.cs:
namespace memo.Models
{
public partial class Offer
{
public Offer()
{
SharedInfo = new SharedInfo();
}
[Key]
public int OfferId { get; set; }
public int SharedInfoId { get; set; }
public virtual SharedInfo SharedInfo { get; set; }
}
}
My view model for Create.cshtml file is (again, for brevity shortened but this fills my combobox and references the Offer):
namespace memo.ViewModels
{
public class OfferViewModel
{
public Offer Offer { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
}
}
Now I want to create a new offer where I select CompanyId from a combobox.
Offers/Create.cshtml:
#model memo.ViewModels.OfferViewModel
<div class="container d-flex justify-content-center">
<div class="col-lg-10 personal-info">
<form class="card" role="form" autocomplete="on" asp-action="Create" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group row">
<label asp-for="Offer.SharedInfo.Subject" class="required col-lg-3 col-form-label form-control-label control-label"></label>
<div class="col-lg-9">
<span class="has-float-label">
<input asp-for="Offer.SharedInfo.Subject" class="form-control" type="text" />
<label asp-for="Offer.SharedInfo.Subject"></label>
</span>
<span asp-validation-for="Offer.SharedInfo.Subject" class="text-danger"></span>
</div>
</div>
<div class="form-group row">
<label class="required col-lg-3 col-form-label form-control-label control-label"></label>
<div class="col-lg-4">
<select class="form-control selectpicker" asp-for="Offer.SharedInfo.Company.CompanyId" asp-items="#Model.CompanyList"></select>
<span asp-validation-for="Offer.SharedInfo.Company.CompanyId" class="text-danger"></span>
</div>
</div>
Here I select a company which gives me the CompanyId, no problem there.
But now, when I click on Create Offer button to Post the form, the validation fails...
Offers/OffersController.cs
[HttpPost]
public async Task<IActionResult> Create(Offer offer)
{
if (ModelState.IsValid)
{
offer.CreatedBy = User.GetLoggedInUserName();
offer.CreatedDate = DateTime.Now;
await _db.AddAsync(offer);
await _db.SaveChangesAsync();
}
}
Validation tells me that:
SubKey={Name}, Key="offer.SharedInfo.CompanyName", ValidationState=Invalid
What I want to achieve is to Create 2 new tables. SharedInfo which will reference the Company by ID I selected and then Offer, which will reference the new SharedInfo record.
Am I doing something horribly wrong? It's telling me I have to "it's required" specify Company.Name but when I select the company by it's ID, it should know the name...
I even tried in the controller to specify _context.Offer.SharedInfo.Company = _context.Company.Where(x => x.CompanyId == offer.SharedInfo.Company.CompanyId) but that fails in >> Validation << because, I think, the SharedInfo is not yet created? Don't know. I'm totally at the end with my thoughts.

Model validation will validate the whole model if you specify any of their property in your razor view.For you contains input named Offer.SharedInfo.Company.CompanyId and Offer.SharedInfo.Subject,model validation will validate all the properties in Company model and 'SharedInfo' model although you do not set CompanyName input.
And I think you may be confused that Offer.SharedInfo.CompanyId which is required but you also do not set input in razor view.Because it is an int type,you could see that it will receive default value 0 if you do not pass the value to backend.And 0 is a valid value for int type,that is why the validation for Offer.SharedInfo.CompanyId succeed.
For your requirement,you want to skip the validation of CompanyName,you could use ModelState.Remove(keyname) like below:
[HttpPost]
public async Task<IActionResult> Create(Offer offer)
{
ModelState.Remove("Offer.SharedInfo.CompanyName");//from your error message,it should be the key name
if (ModelState.IsValid)
{
//...
}
//...
}

Related

Save ASP.NET core Identity User attributes in another table

I am developing an ASP .NET core web application. I employ Identity UI framework for the user registration and authorization in the application. I inherit Identity User to my ApplicationUser class as follows. It creates a table called as ApplicationUser and save the relevant data under the given attributes successfully: (I am using Microsoft SQL database)
ApplicationUser.cs
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
#nullable disable
namespace WebApp.Models
{
public partial class ApplicationUser : IdentityUser<int>
{
public ApplicationUser()
{
}
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastNam { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Bio { get; set; }
public virtual UserAddress UserAddress { get; set; }
}
}
Then I implemented UserAddress model class as follows. It creates another table in the database named as "UserAddress"\
UserAddress.cs
using System;
using System.Collections.Generic;
#nullable disable
namespace WebApp.Models
{
public partial class UserAddress
{
public int UserId { get; set; }
public string BuildingNo { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public virtual ApplicationUser User { get; set; }
}
}
Next in under Areas folder in Identity UI frame work, I change the Index.cshtml file as follows. i inserting new entry to enter the building number of the user, that should be saved in UserAddress table in database.
Index.cshtml
#page
#model IndexModel
#{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h4>#ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<div class="row">
<div class="col-md-12">
<form id="profile-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="=row d-flex flex-row">
<div class="form-group col-md-6">
<label asp-for="Input.UserName"></label>
<input asp-for="Input.UserName" class="form-control" />
</div>
</div>
<div class="=row d-flex flex-row">
<div class="form-group col-md-6">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
</div>
<div class="form-group col-md-6">
<label asp-for="Input.MiddleName"></label>
<input asp-for="Input.MiddleName" class="form-control" />
</div>
</div>
<div class="=row d-flex flex-row">
<div class="form-group col-md-6">
<label asp-for="Input.LastNam"></label>
<input asp-for="Input.LastNam" class="form-control" />
</div>
</div>
<div class="=row d-flex flex-row">
<div class="form-group col-md-6">
<label asp-for="Input.DateOfBirth"></label>
<input asp-for="Input.DateOfBirth" class="form-control" />
</div>
<div class="form-group col-md-6">
<label asp-for="Input.PhoneNumber"></label>
<input asp-for="Input.PhoneNumber" class="form-control" />
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
</div>
<div class="=row d-flex flex-row">
<div class="form-group col-md-6">
<label asp-for="Input.BuildingNo"></label>
<input asp-for="Input.BuildingNo" class="form-control" />
</div>
</div>
<div class="=row d-flex flex-row">
<div class="do-md-6">
<button id="update-profile-button" type="submit" class="btn btn-primary">Save</button>
</div>
</div>
</form>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
And in the Index.cshtml.cs file, I tried to save the building number in the UserAddress table as follows but it fails to enter the data into the table.
Index.cshtml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using WebApp.Models;
namespace WebApp.Areas.Identity.Pages.Account.Manage
{
public partial class IndexModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
public IndexModel(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
public string BuildingNo { get; set; }
public string Username { get; set; }
[TempData]
public string StatusMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
public class InputModel
{
[DataType(DataType.Text)]
[Display(Name = "User Name")]
public string UserName { get; set; }
[DataType(DataType.Text)]
[Display(Name = "First name")]
public string FirstName { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Middle name")]
public string MiddleName { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Last name")]
public string LastNam { get; set; }
[Display(Name = "Date of Birth")]
[DataType(DataType.Date)]
public DateTime DateOfBirth { get; set; }
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
[Display(Name = "Building No:")]
[DataType(DataType.Text)]
public string BuildingNo { get; set; }
}
private async Task LoadAsync(ApplicationUser user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
UserName = user.UserName,
FirstName = user.FirstName,
MiddleName = user.MiddleName,
LastNam = user.LastNam,
PhoneNumber = phoneNumber,
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
if(Input.FirstName != user.FirstName)
{
user.FirstName = Input.FirstName;
}
if (Input.MiddleName != user.MiddleName)
{
user.MiddleName = Input.MiddleName;
}
if (Input.LastNam != user.LastNam)
{
user.LastNam = Input.LastNam;
}
if (Input.DateOfBirth != user.DateOfBirth)
{
user.DateOfBirth = Input.DateOfBirth;
}
if (Input.UserName != user.UserName)
{
user.UserName = Input.UserName;
}
user.UserAddress.BuildingNo = Input.BuildingNo; // I tried to enter the building address to the UserAddress table by using this code
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
}
Please ignore the above lengthy Index.cshtml.cs code I have provided, I have commented near the only code that I used to insert the input building number to the UserAddress table. which is:
user.UserAddress.BuildingNo = Input.BuildingNo;
This is the part of view of the entry: The entry view
And this is the error I am getting when above code is run: The error message
For the record: all the fields in ApplicationUser table is updated. but this error occurs when I am trying to insert data into UserAddress table.
I am pretty user this is a very simple question for a person who knows ASP.NET core identity user very well.
I kindly request if somebody can please help me to save the input building number in another table named User Address?
Thanks in advance !!!
You can change your code like below:
First use code get current user include UserAddress:
var user = await _userManager.Users
.Include(x => x.UserAddress)
.SingleAsync(x => x.UserName== Input.UserName);
Then determine whether UserAddress exists and update it.
if (user.UserAddress == null)
{
user.UserAddress = new Models.UserAddress
{
BuildingNo = Input.BuildingNo
};
}
else
{
user.UserAddress.BuildingNo = Input.BuildingNo;
}
The whole code is like below:
public async Task<IActionResult> OnPostAsync()
{
//get the current user
var user = await _userManager.Users
.Include(x => x.UserAddress)
.SingleAsync(x => x.UserName== Input.UserName);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
if(Input.FirstName != user.FirstName)
{
user.FirstName = Input.FirstName;
}
if (Input.MiddleName != user.MiddleName)
{
user.MiddleName = Input.MiddleName;
}
if (Input.LastNam != user.LastNam)
{
user.LastNam = Input.LastNam;
}
if (Input.DateOfBirth != user.DateOfBirth)
{
user.DateOfBirth = Input.DateOfBirth;
}
if (Input.UserName != user.UserName)
{
user.UserName = Input.UserName;
}
//insert the BuildingNo
if (user.UserAddress == null)
{
user.UserAddress = new Models.UserAddress
{
BuildingNo = Input.BuildingNo
};
}
else
{
user.UserAddress.BuildingNo = Input.BuildingNo;
}
await _userManager.UpdateAsync(user);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}

I am getting 'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException' .....when i tried to update a row in my table

i am trying to update a specific row in my table but i get An exception of type 'System.Data.Entity.Infrastructure.DbUpdateConcurrencyException' occurred in EntityFramework.dll but was not handled in user code
{"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions."}
my action method is
public ActionResult createedit()
{
int id = Convert.ToInt32( Session["UserID"]);
var Certtt = (from cert in db.TBL_UserRegistration where cert.UserLoginID == id select cert).FirstOrDefault();
TBL_UserRegistration u = db.TBL_UserRegistration.Find(Certtt.UserRegistrationID);
return View(u);
}
[HttpPost]
public ActionResult createedit(TBL_UserRegistration user, HttpPostedFileBase imageup)
{
if(imageup != null)
{
user.UserImage = new byte[imageup.ContentLength];
imageup.InputStream.Read(user.UserImage, 0, imageup.ContentLength);
}
db.Entry(user).State = EntityState.Modified;
db.SaveChanges();
return View(user);
}
my view
#using (Html.BeginForm("createedit", "UserRegistration", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<form class="wiwet-checkout">
<div class="row">
<div class="col-sm-6">
<!-- <input type="text" placeholder="First Name" />-->
#*#Html.LabelFor(model => model.UserFirstName, htmlAttributes: new { #class = "text-label", #placeholder = "Password" })*#
#Html.EditorFor(model => model.UserFirstName, new { htmlAttributes = new { #class = "form-control", #placeholder = "First Name" } })
#Html.ValidationMessageFor(model => model.UserFirstName, "")
</div>
.
.
.
}
my model TBL_UserResgistration is
public partial class TBL_UserRegistration
{
public TBL_UserRegistration()
{
this.TBL_Payment = new HashSet<TBL_Payment>();
}
public int UserRegistrationID { get; set; }
public Nullable<System.DateTime> UserRegistrationDate { get; set; }
public string Username { get; set; }
public string UserPassword { get; set; }
public string UserFirstName { get; set; }
public string UserLastName { get; set; }
public string UserEmail { get; set; }
public string UserType { get; set; }
public string UserCity { get; set; }
public string UserState { get; set; }
public string UserCountry { get; set; }
public Nullable<int> UserZip { get; set; }
public string UserAddressLine1 { get; set; }
public string UserAdressLine2 { get; set; }
public Nullable<long> UserPhone1 { get; set; }
public Nullable<long> UserPhone2 { get; set; }
public byte[] UserImage { get; set; }
public Nullable<int> UserLoginID { get; set; }
public virtual TBL_Login TBL_Login { get; set; }
public virtual ICollection<TBL_Payment> TBL_Payment { get; set; }
}
You are getting this exception because entity framework can't find your user object in the database to update it READ MORE, that's because you are passing a user entity with only first name as I can see from your view, what you can do is this, pass your id as a hidden field, get user model by id from you database context, set the new user first name, update, done.
this is how you do it
in your view, pass the id as hidden
#using (Html.BeginForm("createedit", "UserRegistration", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<!-- here is the hidden field keeping your id -->
#Html.Hidden("UserRegistrationID ",Model.UserRegistrationID )
<form class="wiwet-checkout">
<div class="row">
<div class="col-sm-6">
<!-- <input type="text" placeholder="First Name" />-->
#*#Html.LabelFor(model => model.UserFirstName, htmlAttributes: new { #class = "text-label", #placeholder = "Password" })*#
#Html.EditorFor(model => model.UserFirstName, new { htmlAttributes = new { #class = "form-control", #placeholder = "First Name" } })
#Html.ValidationMessageFor(model => model.UserFirstName, "")
</div>
.
.
.
}
in your controller
// get the real entity from the database using the id passed to your controller
// as i mentioned before you should keep it in a hidden field
TBL_UserRegistration u = db.TBL_UserRegistration.Find(user.UserRegistrationID);
// update the entity user name according to the new one passed from your view
u.UserFirstName = user.UserFirstName;
// update and save
db.Entry(u).State = EntityState.Modified;
db.SaveChanges();

Many-to-many relationship read and write

I just want to get some clarity about link table for a many-to-many relationship in a ASP.NET MVC project. When the Controller and the Views are created, it seems like there are not any code for read and write to the link table!? The only thing that is autogenrated is the table OrderEmployee.
If I have understood it right, for each order I create, I also need to add the ID of the Employee who handled it in the OrderEmployee table? And when I want to list the Oders and want to know each Employee who handled that Order, I need to read from the OrderEmployee table? I can't find any tutorials about how to read and write to and from a link table.
Do I have to add this read and write code on my own in the controller? Preciate if I can get some clarity about this!
public class Order
{
public int ID { get; set; }
public string Name { get; set; }
public int? ManufacturerID { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public int EmployeeNumber { get; set; }
public virtual ICollection<Timeunit> Timeunits { get; set; }
public virtual ICollection<Order> Orders { get; set; }
}
For reading and writing in a many to many relationship is not really difficult but hard to get it right in the beginning.
You don't have to add anything in your models, they are right.
Writing
I'll show you with an example of how to add a single order to an employee with a dropdown list. The concept for add multiple orders to employees is nearly the same, you'll find a link at the bottom with a tutorial.
First of all you should create view models like this :
public class CreateEmployeeViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public int EmployeeNumber { get; set; }
public int SelectedOrderID { get; set; }
public SelectList OrdersList { get; set; }
}
Then in your EmployeesControler :
// GET: Employees/Create
public ActionResult Create()
{
/* Add this */
CreateEmployeeViewModel model = new CreateEmployeeViewModel();
model.OrdersList = new SelectList(db.Orders.ToList(), "ID", "Name");
return View(model);
}
The first parameter will be the list of objects in which you want to select something (an Order in this case). The second parameter is the name of the propertie of your Order you want to pass to the view model (as SelectOrderID) and finally the third parameter is the value you want to display in the dropdownlist.
And in your Create.cshtml replace the first line with this :
#model TestStackOverflow.Models.CreateEmployeeViewModel
Next add the dropdown list in the Html.BeginForm :
<div class="form-group">
#Html.LabelFor(model => model.OrdersList, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.SelectedOrderID, Model.OrdersList, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.SelectedOrderID, "", new { #class = "text-danger" })
</div>
</div>
Now you should have this in the create view.
Then go back to your EmployeesControler and find the POST method for create and replace it with this :
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CreateEmployeeViewModel model)
{
if (ModelState.IsValid)
{
Employee employee = new Employee()
{
Name = model.Name,
ID = model.ID,
EmployeeNumber = model.EmployeeNumber,
Orders = new List<Order>()
};
employee.Orders.Add(db.Orders.Where(x => x.ID == model.SelectedOrderID).FirstOrDefault());
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
It will create a new Employee based on the view model values and add an order to this Employee. Now if you check your OrderEmployees table you should see that a new entry is created when you're adding an Employee.
Reading
Reading the values is really simpler than writing.
If you want to read all the employees that are handling an order, just do it like this (I took Index.cshtml from the Orders to demonstrate it)
Add this at the top of your file:
#using YourNameSpace.Models
And now a little further modify the file. It should look like this:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.ManufacturerID)
</td>
<td>
#foreach (Employee e in item.Employees)
{
#e.Name <br />
}
</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>
}
Here you can find a tutorial on how to use SelectList to handle one to many relationships and many to many relationships in your views.
Hope this answer will help you.

AngularJS service's POST to web api controller results in Object properties being null

I am trying to form an object and post it to the web api controller. The action looks like this:
public HttpResponseMessage Post(Contacts contact)
{
db.Add(contact);
db.SaveChanges();
var response = new HttpResponseMessage(HttpStatusCode.OK);
return response;
}
AngularJS controller posts like this:
$scope.submitData = function submitData() {
console.log($scope.contact);
$http.post('api/Contacts', $scope.contact);
}
The problem is that the data which needs to be posted has properties which are shown as Object in the console (Addresses in this example).
After the post all the Addresses count is null on the web api controller side, but not the other plain properties have the corresponding value.
How can I pass the Objects to be readable on the web api side?
Contacts model:
public partial class Contacts
{
public Contacts()
{
this.Addresses = new HashSet<Addresses>();
this.Emails = new HashSet<Emails>();
this.PhoneNumbers = new HashSet<PhoneNumbers>();
this.Tags = new HashSet<Tags>();
}
public int ContactID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public virtual ICollection<Addresses> Addresses { get; set; }
public virtual ICollection<Emails> Emails { get; set; }
public virtual ICollection<PhoneNumbers> PhoneNumbers { get; set; }
public virtual ICollection<Tags> Tags { get; set; }
}
How i fill the addresses object:
<fieldset class="container" ng-repeat="i in getAddress() track by $index">
<legend>Address {{$index + 1}}</legend>
<label>Street:</label>
<input ng-model="contact.Addresses[$index].Street" class=" form-control" type="text" /><br />
<label>Number:</label>
<input ng-model="contact.Addresses[$index].Number" class="form-control" type="text" /><br />
<label>City:</label>
<input ng-model="contact.Addresses[$index].City" class="form-control" type="text" /><br />
<label>Country:</label>
<input ng-model="contact.Addresses[$index].Country" class="form-control" type="text" /><br />
</fieldset>

ASP.NET MVC 4 How to edit data that comes from a foreign key

Here is my problem :
I have a database that contains several attributes like name etc. But I also have 2 specifics attributs, they are IISSettings and SQLSettings.
public class Environment
{
public int ID { get; set; }
public string Name { get; set; }
public virtual SQLSettings SQL { get; set; }
public virtual IISSettings IIS { get; set; }
...
}
IISSettings and SQLSettings contains both a name and an ID for foreign keys.
When I try to update a Environment, and that I only change attributs in IISSettings or SQLSettings, Visual Studio is telling me that nothing has changed, probably because in "standards" attributs, nothing has changed. The only thing that changed are the values inside IISSettings or SQLSettings.
So, I wanted to know how could I save changes to my database, when I only want to update IISSettings or SQLSettings?
My viewModel :
public class EnvironmentViewModel : ViewModelBase
{
public Environment Environment { get; set; }
public IISSettings IISSettings { get; set; }
public SQLSettings SQLSettings { get; set; }
//create/details/delete functions etc.
internal void Edit()
{
try
{
db.Entry(Environment).State = EntityState.Modified;
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Debug.WriteLine("\nDbUpdateConcurrencyException : " + ex);
}
}
...
}
ViewModelBase is where my db is set. Standard DbSet :
public class ViewModelBase
{
protected MyDBContext db = new MyDBContext ();
}
and
public class MyDBContext : DbContext
{
public MyDBContext()
: base()
{
}
public DbSet<Environment> Environments { get; set; }
}
Working code :
<div class="editor-label">
#Html.LabelFor(model => model.Environment.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Environment.Name)
#Html.ValidationMessageFor(model => model.Environment.Name)
</div>
Not working code :
<div class="editor-label">
#Html.LabelFor(model => model.Environment.IIS.IISServer)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Environment.IIS.IISServer)
#Html.ValidationMessageFor(model => model.Environment.IIS.IISServer)
</div>
It's when I try to save the changes into my database and that I only change the name of an SQLSettings that I get an error. A DbUpdateConcurrencyException.
Any suggestions?
(if I forgot something please tell me) :)
I found out how to do it.
Just needed to add
#Html.HiddenFor(model => model.Environment.ID)
#Html.HiddenFor(model => model.Environment.IIS.IISSettingsID)
#Html.HiddenFor(model => model.Environment.SQL.SQLSettingsID)
to my edit View.
And this to my Edit() method inside EnvironmentController :
db.Environments.Attach(Environment);
db.Entry(Environment).State = EntityState.Modified;
db.Entry(Environment.IIS).State = EntityState.Modified;
db.Entry(Environment.SQL).State = EntityState.Modified;

Resources