Metadata in separate file does not display as expected - c#

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.

Related

Suddenly I am getting error on not null able hidden fields

My project was working but from tomorrow I am getting required field validation error on the ModelState.IsValid
My Model:
public class CategoryModel
{
public int CategoryId { get; set; }
public int UserId { get; set; }
public int CategoryParent { get; set; }
[Required]
[Display(Name = "Category Name")]
public string CategoryName { get; set; }
public bool Status { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedOnString { get; set; }
public DateTime? UpdatedOn { get; set; }
}
MVC Page:
#model LSB.Common.Model.CategoryModel
#{
ViewBag.Title = "Category";
Layout = "~/Areas/Restaurant/Views/Shared/_RestaurantLayout.cshtml";
}
#using (Html.BeginForm("Category", "menu", FormMethod.Post))
{
<div class="form-group row">
<div class="col-sm-6">
#Html.LabelFor(x => x.CategoryName)
#Html.TextBoxFor(x => x.CategoryName, new { #class = "form-control", #placeholder = "First Name", #maxlength = "20" })
#Html.ValidationMessageFor(x => x.CategoryName)
</div>
</div>
#Html.HiddenFor(x => x.CategoryId)
<input type="submit" value="test" />
}
Controller:
[Authorize(Roles = "Restaurant")]
[HttpPost]
public ActionResult Category(CategoryModel categoryModel)
{
//ModelState.Remove("CategoryId");
if (!(ModelState.IsValid))
{
return View(categoryModel);
}
}
Model State

MVC client-side validation for multiple models

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.

Regex on data annotation behaves differently on sub properties

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.

Add/Remove related Entities to an objects List<Entity>

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.

Entity Framework 4.1 - Code First - Cannot update nullabe DateTime values while resolving DbUpdateConcurrencyException

I currently have an entity that I'm trying to edit through my MVC 3 web app. I receive an DbUpdateConcurrencyExceptionwhen trying to perform a the client wins approach I got from MSDN's post Using DbContext in EF 4.1 Part 9: Optimistic Concurrency Patterns. The weird part is that this only happens on this particular entity and there I'm not doing anything different from the other. Also, it only happens when updating from a null to a value. The Properties giving the error when updating from null value to a DateTime value are DispositionLetterDate and DateDisposition.
Class:
public class A22
{
public A22()
{
this.IsArchived = false;
this.A22StatusId = (int)AStatus.Open;
}
[Key]
public int Id { get; set; }
[Required]
[StringLength(100, ErrorMessage="A22 Number cannot exceed 100 characters")]
public string Number { get; set; }
[Display(Name="Manual")]
public int ManualId { get; set; }
[Display(Name="SGMLID")]
public string SGMLId { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name="Date Received")]
public DateTime DateReceived { get; set; }
[Display(Name= "Status")]
[EnumDataType(typeof(A22Status))]
public int A22StatusId { get; set; }
[Display(Name="Priority")]
[EnumDataType(typeof(A22Priority))]
public int A22PriorityId { get; set; }
[Display(Name="Providing Disposition")]
public string ProvidingDisposition { get; set; }
[Display(Name="Final Disposition")]
public bool FinalDisposition { get; set; }
[Display(Name="Is Archived")]
public bool IsArchived { get; set; }
[Display(Name="Created By")]
public int CreatedById { get; set; }
[Display(Name="Date Created")]
public DateTime DateCreated { get; set; }
[ConcurrencyCheck]
[Display(Name="Date Modified")]
public DateTime DateModified { get; set; }
[Display(Name = "Disposition Date")]
public DateTime? DateDisposition { get; set; }
[Display(Name = "Date Disposition Letter Sent")]
public DateTime? DispositionLetterDate{ get; set; }
// Virtual Properties
[ForeignKey("CreatedById")]
public virtual User CreatedBy { get; set; }
[ForeignKey("ManualId")]
public virtual Manual Manual { get; set; }
public virtual ICollection<A22Manual> A22ManualsImpacted { get; set; }
public virtual ICollection<A22Task> A22TasksImpacted { get; set; }
public virtual ICollection<A22Comment> Comments { get; set; }
public virtual ICollection<A22HistoryLog> HistoryLogs { get; set; }
}
Controller:
[HttpPost]
public ActionResult Edit(A22 a22)
{
var d = new A22Repository().Find(a22.Id);
var changes = TrackChanges(d, a22);
if (ModelState.IsValid) {
if (!string.IsNullOrEmpty(changes))
{
repository.InsertOrUpdate(a22);
this.repository.AddHistory(a22, changes);
repository.Save();
}
return RedirectToAction("Details", new { id = a22.Id });
} else {
ViewBag.PossibleManuals = d.ManualId == default(int) ? manualRepo.GetManualList() :
manualRepo.GetManualList(d.ManualId);
ViewBag.APriority = repository.GetAPriorityList(d.APriorityId);
ViewBag.AStatus = repository.GetAStatusList(d.APriorityId);
return View();
}
}
}
View:
#model TPMVC.Web.Models.A22
#{
Layout = "~/Views/Shared/_BlendLayoutLeftOnly.cshtml";
ViewBag.Title = "Edit";
}
<h2>Edit A22# #Model.Number</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.CreatedById)
#Html.HiddenFor(model => model.DateCreated)
#Html.HiddenFor(model => model.DateModified)
#Html.Partial("_CreateOrEdit")
<div class="newItemLabel">
<strong style="padding-right: 145px;">#Html.LabelFor(model => model.AStatusId)</strong>
#{
Html.Telerik().DropDownList()
.Name("AStatusId")
.BindTo((IEnumerable<SelectListItem>)ViewBag.AStatus)
.HtmlAttributes(new { #style = "width: 200px;" })
.Render();
}
#Html.ValidationMessageFor(model => model.AStatusId)
</div>
<div class="newItemLabel">
<strong style="padding-right: 77px;">#Html.LabelFor(model => model.FinalDisposition)</strong>
#Html.EditorFor(model => model.FinalDisposition)
</div>
<div class="newItemLabel">
<strong style="padding-right: 44px;">#Html.LabelFor(model => model.DateDisposition)</strong>
#{
Html.Telerik().DatePickerFor(model => model.DateDisposition)
.Render();
}
</div>
<div class="newItemLabel">
<strong style="padding-right: 44px;">#Html.LabelFor(model => model.DispositionLetterDate)</strong>
#{
Html.Telerik().DatePickerFor(model => model.DispositionLetterDate)
.Render();
}
</div>
<div class="newItemLabel">
<strong style="padding-right: 110px;">#Html.LabelFor(model => model.IsArchived) </strong>
#Html.EditorFor(model => model.IsArchived)
</div>
<p>
<input type="submit" value="Save" />
</p>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
Thinking it was could have been something with the data annotations, I decided to define the properties with this issue to optional using the Fluid API.
modelBuilder.Entity<A22>().Property(a => a.DateDisposition).IsOptional();
modelBuilder.Entity<A22>().Property(a => a.DispositionLetterDate).IsOptional();
I basically need a fresh pair of eyes to see if I'm missing something. Is there other property that is making it behave this way?
Thanks in advance.
I'm mapping nullable DateTime properties like following without IsOptional methods. Also it works fine with MS SQL and MySQL by me.
this.Property(t => t.CreatedDate).HasColumnName("CreatedDate");
this.Property(t => t.ModifiedDate).HasColumnName("ModifiedDate");
in class derived from EntityTypeConfiguration. I'm using Code First approach.

Categories

Resources