Ok first my actual Book and Author Object definitions:
public class Book
{
[Key]
public virtual int BookID { get; set; }
[Required]
[DataType(DataType.Text)]
[MaxLength(100)]
public virtual String Title { get; set; }
[DataType(DataType.MultilineText)]
public virtual String Abstract { get; set; }
[RegularExpression(#"\d{4}")]
public virtual String Year { get; set; }
[Required]
[Display(Name="Genre")]
public virtual int GenreID { get; set; }
public virtual Genre Genre { get; set; }
public virtual List<Author> Authors { get; set; }
public Book()
{
Authors = new List<Author>();
}
}
public class Genre
{
[Key]
public virtual int GenreID { get; set; }
[Required]
[DataType(DataType.Text)]
[MaxLength(30)]
public virtual String Name { get; set; }
public virtual List<Book> Books { get; set; }
}
public class Author
{
[Key]
public virtual int AuthorID { get; set; }
[Required]
[DataType(DataType.Text)]
[MaxLength(50)]
public virtual String Firstname { get; set; }
[Required]
[DataType(DataType.Text)]
[MaxLength(50)]
public virtual String Lastname { get; set; }
public virtual List<Book> Books { get; set; }
public Author()
{
Books = new List<Book>();
}
[NotMapped]
public string Fullname
{
get { return String.Format("{0}, {1}", Lastname, Firstname); }
}
public override string ToString()
{
return Fullname;
}
}
Using EF-Migrations I get a configuration.cs file into which I entered my Data-Seed and then using add-migration to get the first migration to initialize the database EF properly creates the according tables Authors, Books, Genres and last but not least AuthorBooks to hold the many-to-many relations between books and authors.
So far I got my whole demo application working. The only problem I have is that Entity Framework seems to refuse saving the AuthorBooks relations. I've set my app up so that at book-creation/editing I can choose the related Authors via a MultiSelectList-ListBox.
BookControllerViewModel:
public class BookControllerViewModel
{
public Book actualBook { get; set; }
public String redirectUrl { get; set; }
public MultiSelectList Authors { get; set; }
public int[] AuthorIDs { get; set; }
public SelectList Genres { get; set; }
}
The ViewModel contains the book that is subject to the create/edit operation, the MultiSelectList of Authors with proper Selection-Info based on the book and I added int[] AuthorIDs to hold the selected IDs the user would select in the ListBox of the Create/Edit view.
#model BookStoreInternet.ViewModels.BookControllerViewModel
<div class="editor-label">
#Html.LabelFor(model => model.actualBook.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.actualBook.Title)
#Html.ValidationMessageFor(model => model.actualBook.Title)
</div>
<div class="editor-label">
<label>Author#( Model.actualBook.Authors.Count > 1 ? "s" :"" )</label>
</div>
<div class="editor-field">
#Html.ListBox("AuthorIDs", Model.Authors)
#Html.ValidationMessageFor(model => model.actualBook.Authors)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.actualBook.Abstract)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.actualBook.Abstract)
#Html.ValidationMessageFor(model => model.actualBook.Abstract)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.actualBook.Year)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.actualBook.Year)
#Html.ValidationMessageFor(model => model.actualBook.Year)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.actualBook.GenreID, "Genre")
</div>
<div class="editor-field">
#Html.DropDownList("actualBook.GenreID", Model.Genres)
#Html.ValidationMessageFor(model => model.actualBook.GenreID)
</div>
That too works well in that the ViewModel that is posted to the controller-Action
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(BookControllerViewModel bookVM)
{
if (ModelState.IsValid)
{
bookVM.actualBook.Authors = db.Authors.Where(x => bookVM.AuthorIDs.Contains(x.AuthorID)).ToList();
db.Entry(bookVM.actualBook).State = EntityState.Modified;
db.SaveChanges();
return Redirect(bookVM.redirectUrl);
}
bookVM.Authors = new MultiSelectList(db.Authors, "AuthorID", "Fullname", bookVM.actualBook.Authors.Select(x => x.AuthorID));
bookVM.Genres = new SelectList(db.Genres, "GenreID", "Name", bookVM.actualBook.GenreID);
return View(bookVM);
}
is properly filled with the values I would expect. What does not work though is that db.SaveChanges() seems to "silently" refuse to save the authors I added to the book.
Debugging the operation I can see that bookVM.ActualBook.Authors is actually set properly and contains the selected Authors... but the according entries to the Jointable AuthorBooks are not created...
Would anyone have an idea why this doesn't work or maybe even how to make it work?
Thanks in advance!
Thanks to Gerts answers I got this working by modifying the Controller-Action to
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(BookControllerViewModel bookVM)
{
if (ModelState.IsValid)
{
var book = db.Books.Find(bookVM.actualBook.BookID);
book.Update(bookVM.actualBook);
book.Authors.Clear();
foreach(var aid in bookVM.AuthorIDs)
book.Authors.Add(db.Authors.Find(aid));
db.SaveChanges();
return Redirect(bookVM.redirectUrl);
}
bookVM.Authors = new MultiSelectList(db.Authors, "AuthorID", "Fullname", bookVM.actualBook.Authors.Select(x => x.AuthorID));
bookVM.Genres = new SelectList(db.Genres, "GenreID", "Name", bookVM.actualBook.GenreID);
return View(bookVM);
}
and adding a method to the Book model
internal void Update(Book book)
{
Title = book.Title;
Abstract = book.Abstract;
Year = book.Year;
GenreID = book.GenreID;
}
Thanks again to Gert!
Because Book-Author is a many to many association you have to load actualBook.Authors first for the change tracker to be able to notice changes. Then you can add/remove items from it or replace it by a new collection. Setting its state is not necessary.
Related
I am trying to update supplier in database when users on webform insert data into textboxes.
First users in textbox for supplierID insert value, and on screen shows particular supplier from database. Then user can change supplier and when he is done he have to click on submit button.
I use EntityState.Modifier, but supplier doesn't change in database, and also I have no errors in the view. I think that's not working because my Supplier have foreign key from Adress table.
Does somebody know how to update using Entity state modified if a have a foreign key to another table?
I appreciate any help!
public partial class Supplier
{
public int SupplierID{ get; set; }
public string Name{ get; set; }
public string Phone{ get; set; }
public string Email { get; set; }
public Nullable<int> TownID{ get; set; }
public Nullable<int> StreetID{ get; set; }
public Nullable<int> AdressNumber{ get; set; }
public virtual Adress Adress { get; set; }
}
public partial class Town
{
public int TownID{ get; set; }
public string Name{ get; set; }
}
public partial class Street
{
public int TownID{ get; set; }
public int StreetID{ get; set; }
public string Name{ get; set; }
}
public partial class Adress
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Adress()
{
this.Supplier= new HashSet<Supplier>();
}
public int TownID{ get; set; }
public int StreetID{ get; set; }
public int AdressNumber{ get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Supplier> Suppliercs{ get; set; }
}
This is my View:
#model FpisNada.Models.Supplier
#{
ViewBag.Title = "Index";
Layout = null;
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
#Html.TextBoxFor(model => model.SupplierID, new { #placeholder = "pib dobavljaca", style = " float:left" })
<div class="col-md-9">
#if (ViewBag.ListTown!= null)
{
#Html.DropDownListFor(m => m.TownID, ViewBag.ListTown as SelectList, "--select town--", new { #class = "form-control", style = " float:left" })
}
#Html.DropDownListFor(m => m.StreetID, new SelectList(""), "--select street--", new { #class = "form-control", style = " float:left" })
<div class="container">
#Html.TextBoxFor(model => model.AdressNumber, new { #class = "form-control"})
#Html.TextBoxFor(model => model.Email, new { #class = "form-control" })
#Html.TextBoxFor(model => model.Name, new { #class = "form-control" })
#Html.TextBoxFor(model => model.Phone, new { #class = "form-control"})
</div>
</div>
<input type="submit" value="Edit" />
}
My controller method:
[HttpGet]
public ActionResult Edit(int id)
{
Supplier supplier= db.Supplier.Find(id);
return View(supplier);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit( Supplier supplier)
{
try
{
if (ModelState.IsValid)
{
db.Entry(supplier).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("ChangeSupplier");
}
}
catch (DataException /* dex */)
{
//Log the error (uncomment dex variable name after DataException and add a line here to write a log.)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
}
return View(supplier);
}
i'm new to mvc and trying to get idea of what i'm doing wrong i create a Dbcontaxt class
public class DataBaseContext : DbContext
{
public DataBaseContext()
: base("DefaultConnection")
{
}
public DbSet<Membership> Membership { get; set; }
public DbSet<OAuthMembership> OAuthMembership { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<Category> Categorys { get; set; }
public DbSet<SubCategory> SubCategorys { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Color> Colors { get; set; }
public DbSet<Size> Sizes { get; set; }
public DbSet<Company> Companys { get; set; }
public DbSet<UsersInRoles> UsersInRoles { get; set; }
}
}
and i create a model class to create a strongly type view
[Bind(Exclude = "AddUserToRoleID")]
public class AddUserToRole
{
[ScaffoldColumn(false)]
public int AddUserToRoleID { get; set; }
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[Display(Name = "Role name")]
public string RoleName { get; set; }
}
}
in the controller i'm trying to create the Details view by adding view and select AddUserToRole as my model for the strongly type view
public ActionResult Details(int id = 0)
{
var UserInRole = db.UserProfiles
.Join(db.UsersInRoles,
u => u.UserId,
uir => uir.UserId,
(u, uir) => new {u = u,uir = uir})
.Join(db.Roles,
temp0 => temp0.uir.RoleId,
r => r.RoleId,
(temp0, r) => new { temp0 = temp0,r = r })
.Where(temp1 => (temp1.temp0.u.UserId == id))
.Select(temp1 => new AddUserToRole {
AddUserToRoleID = temp1.temp0.u.UserId,
UserName = temp1.temp0.u.UserName,
RoleName = temp1.r.RoleName
});
return View(UserInRole);
}
it give me this error
The model item passed into the dictionary is of type
'System.Data.Entity.Infrastructure.DbQuery`1[SeniorProject.Models.AddUserToRole]', but
this dictionary requires a model item of type 'SeniorProject.Models.AddUserToRole'.
and when i cast return View((UsersInRoles)UserInRole); it give me this error
Unable to cast object of type
'System.Data.Entity.Infrastructure.DbQuery`1[SeniorProject.Models.AddUserToRole]'
to type'SeniorProject.Models.UsersInRoles'.
and the view
#model SeniorProject.Models.AddUserToRole
#{
ViewBag.Title = "Details";
}
<h2>Details</h2>
<fieldset>
<legend>AddUserToRole</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.UserName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.UserName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.RoleName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.RoleName)
</div>
</fieldset>
<p>
#Html.ActionLink("Edit", "Edit", new { id=Model.AddUserToRoleID }) |
#Html.ActionLink("Back to List", "Index")
</p>
what should i do in this case?
You need to materialize it. Put FirstOrDefault() at the end of the query
var materializedUser = UserInRole.SingleOrDefault();
return View(materializedUser);
Edit: Following pjotr comment replacing FirstOrDefault() with SingleOrDefault()
I have three models: VehicleType, VehicleModel, and VehicleManufacturer.
Both VehicleType and VehicleManufacturer point to VehicleModel in the model, like so:
public class VehicleModel
{
[Key]
public int ModelId { get; set; }
[Required(ErrorMessage = "Field is Required")]
public int TypeId { get; set; }
[Required(ErrorMessage = "Field is Required")]
public int ManufacturerId { get; set; }
public string ModelName { get; set; }
public VehicleType VehicleType { get; set; }
public VehicleManufacturer Manufacturer { get; set; }
}
From there, VehicleModel points to the InventoryModel:
public class Inventory
{
[Key]
public int InventoryId { get; set; }
public int Price { get; set; }
public int Mileage { get; set; }
public int Year { get; set; }
public int ModelId { get; set; }
public VehicleModel VehicleModel { get; set; }
}
My problem is when I try to get client-side validation working on all three dropdownlists (VehicleType, VehicleManufacturer, VehicleModel), it only works with VehicleModel.
What needs to be done to validate these two dropdownlists using these models?
Here is my controller (fyi):
// GET: /Inventory/Create
public ActionResult Create()
{
ViewBag.TypeId = new SelectList(db.Types, "TypeId", "TypeName"); //(Object List, Value Field (usually Id), Column)
ViewBag.ModelId = new SelectList(db.Models, "ModelId", "ModelName"); //(Object List, Value Field (usually Id), Column)
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "ManufacturerName"); //(Object List, Value Field (usually Id), Column)
return View();
}
// POST: /Inventory/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Inventory inventory, VehicleManufacturer VehicleManufacturer, VehicleType VehicleType)
{
if (ModelState.IsValid)
{
db.Inventorys.Add(inventory);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TypeId = new SelectList(db.Types, "TypeId", "TypeName");
ViewBag.ModelId = new SelectList(db.Models, "ModelId", "ModelName");
ViewBag.ManufacturerId = new SelectList(db.Manufacturers, "ManufacturerId", "ManufacturerName");
return View(inventory);
}
View:
<div class="editor-label">
#Html.LabelFor(model => model.VehicleModel.TypeId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("TypeId", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleModel.TypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ModelId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("ModelId", String.Empty)
#Html.ValidationMessageFor(model => model.ModelId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.VehicleModel.ManufacturerId, "Some name for column")
</div>
<div class="editor-field">
#Html.DropDownList("ManufacturerId", String.Empty)
#Html.ValidationMessageFor(model => model.VehicleModel.ManufacturerId)
</div>
Please someone help. I've been on this for many, many hours!
There are actually two problems That I see above
1) That you're not mapping the DropDownList and the ValidationMessageFor to the same model attribute.
#Html.ValidationMessageFor(model => model.VehicleModel.ManufacturerId)
The above is binding it to VehicleModel_ManufacturerId where as:
#Html.DropDownList("ManufacturerId", String.Empty)
the above is mapping the DropDown to just ManufacturerId
You need to change one or the other to match each other.
2) In the above code, I don't see any Validation Attributes. did you forgot them when you copied the code over here?
Hope this helps, Let me know if you needed more details.
I have the following model on my website
[Table("UserProfile")]
public class UserProfile
{
[Key]
public int UserId { get; set; }
[DisplayName("E-Mail")]
[MaxLength(60)]
[RegularExpression(#"[a-zA-Z0-9_\\.-]+#([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}", ErrorMessage = "Invalid e-mail")]
public string MailAddress { get; set; }
public UserInfo userInfo{ get; set; }
}
public class UserInfo
{
[Key, ForeignKey("UserProfile")]
public int UserProfileUserId { get; set; }
public virtual UserProfile UserProfile { get; set; }
[UIHint("PhoneNumbers")]
public List<PhoneNumber> PhoneNumbers { get; set; }
[...]
}
public class PhoneNumber
{
public int Id { get; set; }
[MaxLength(20)]
[DisplayName("Téléphone")]
[RegularExpression(#"^[+]?[0-9\s]{2,20}$", ErrorMessage = "Invalid")]
public string Phone { get; set; }
public int UserInfoId { get; set; }
[ForeignKey("UserInfoId")]
public Physician UserInfo { get; set; }
}
When I register a user, I have the following view:
<div class="editor-label">
#Html.LabelFor(model => model.MailAddressTemp)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.MailAddressTemp)
#Html.ValidationMessageFor(model => model.MailAddressTemp)
</div>
And the validation is ok
However, when I try to modify the mail address from another view
<div class="editor-label">
#Html.LabelFor(model => model.UserProfile.MailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.UserProfile.MailAddress)
#Html.ValidationMessageFor(model => model.UserProfile.MailAddress)
</div>
It doesn't work with the exact same mail address
Can someone explain it to me why regex validation doesn't work on sub properties?
Btw, I do have validation for the phone numbers during the registration and the modification of the account.
Thank you
To make the answer more elaborate:
You can't have client side validation on nested objects.
You can replace DataAnnotations with some third-party validation library like e.g. FluentValidatio.
In general DataAnnotations are used for simple scenarios. In case of more complex scenarios like this with nested objects you could implement IValidatableObject interface.
Hi I am trying to add my metada in separate file in order to keep my models as clean as posiblen but something seems to be wrong becuase now it seems that some properties do not display the validation.Here is my model clasS:
public partial class BookModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Author { get; set; }
public string Description { get; set;}
public DateTime PublicationDate { get; set; }
public int CategoryId { get; set; }
public decimal Price { get; set; }
public string BookUrl { get; set; }
}
Here is my metada partial class:
[MetadataType(typeof(BookModel))]
public partial class BookModelMetada
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public string Author { get; set; }
[Required]
[DataType(DataType.MultilineText)]
public string Description { get; set; }
[Required]
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
public DateTime PublicationDate { get; set; }
[Required]
public int CategoryId { get; set; }
[Required]
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:c}")]
public decimal Price { get; set; }
public string BookUrl { get; set; }
}
The wierd field is that the PublicationDate and Price display the validation error but the other properties do not.
What am I doing wrong?
EDIT:
View Code:
<p>
#Html.LabelFor(model => model.Book.Name, "Book Name")
#Html.TextBoxFor(model => model.Book.Name)
</p>
<p>
#Html.LabelFor(model => model.Book.Author)
#Html.TextBoxFor(model => model.Book.Author)
</p>
<p>
#Html.LabelFor(model => model.Book.PublicationDate ,"Publication Date")
#Html.TextBoxFor(model => model.Book.PublicationDate, new { #class="datepicker" })
</p>
<p>
#Html.LabelFor(model => model.Book.Price)
#Html.TextBoxFor(model => model.Book.Price)
</p>
<p>
#Html.LabelFor(model => model.Book.CategoryId, "Select category")
#Html.DropDownListFor(model => model.Book.CategoryId, new SelectList(Model.Categories, "Id", "Name"))
</p>
<p>
#Html.LabelFor(model => model.Book.Description)
#Html.TextAreaFor(model => model.Book.Description)
</p>
<p>
<input type="submit" value="Create" class="link"/>
#Html.ActionLink("Back to List", "Books", "ProductManager", null, new { #class = "link" })
</p>
The problem was that I added [MetadataType(typeof(BookModel))] on the metada class instead of the model class.
There can be one or more causes:
But at first make sure that you included jquery.unobtrusive and jquery.validate in the view or in the layout of the view.
Your View however seems to be unclear, you have to use #Html.LabelFor(model =>model.Book.Author). Please post your full view.