How to Update using MVC2 RC2 - asp.net-mvc

I am trying to edit a record. I have the default route.
When I click the submit button I get an exception on the UpdateModel line:
The model of type 'MyProject.Mvc.Models.Product' could not be updated.
On the page the validation of the ProductId field is prompting the value is invalid:
The value '9' is invalid. 9 is the id of the record I am trying to edit. What could be wrong?
public ActionResult Edit(int id)
{
Product product = productRepository.GetProduct(id);
return View(new ProductFormViewModel(product));
}
[HttpPost]
public ActionResult Edit(int id, FormCollection productFormViewModel)
{
Product product = productRepository.GetProduct(id);
try
{
// TODO: Add update logic here
UpdateModel(product, "Product");
productRepository.Save();
return RedirectToAction("Index");
}
catch (Exception ex)
{
return View(new ProductFormViewModel(product));
}
}
If I change the update model line to:
UpdateModel(product);
then no exception is thrown and the data is not updated in the database.
[Edit]
I am using Entity Framework
namespace MyProject.Mvc.Models
{
[MetadataType(typeof(ProductMetaData))]
public partial class Product
{
public Product()
{
// Initialize Product
this.CreateDate = System.DateTime.Now;
}
}
public class ProductMetaData
{
[Required(ErrorMessage = "Product name is required")]
[StringLength(50, ErrorMessage = "Product name must be under 50 characters")]
public object ProductName { get; set; }
[Required(ErrorMessage = "Description is required")]
public object Description { get; set; }
}
public class ProductFormViewModel
{
public Product Product { get; private set; }
public ProductFormViewModel()
{
Product = new Product();
}
public ProductFormViewModel(Product product)
{
Product = product;
}
}
}

Do you need to edit the ID? if the ID is the PK of the product in your table then it could be a binding issue.
Try
[MetadataType(typeof(ProductMetaData))]
[Bind(Exclude="ID")]
public partial class Product
{
public Product()
{
// Initialize Product
this.CreateDate = System.DateTime.Now;
}
}

could you post your Model source code? does model have fields of class you want to update or just this class as object(Product)?
the problem could exists becouse when your model has object Product, you should pass to UpdateModel method prefix with name of the class...

The problem with UpdateModel(product, "Product"); is that you are using the same prefix (Product) as the Product class name. Try using a different prefix. For this you might need to rename the Product property of the ProductFormViewModel class.

Related

Why the server side validation is not working?

This is my Movies controller.....
public class MoviesController : Controller
{
MoviesEntities db = new MoviesEntities();
public ActionResult Index()
{
var movies = from m in db.Films
where m.ReleaseDate > new DateTime(1989, 12, 20)
select m;
return View(movies.ToList());
}
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult Create(Film newFilm)
{
..some code for adding new movie in the database
}
}
and created Movie class in the model
namespace Movies.Models
{
[MetadataType(typeof(MovieMetadata))]
public partial class Movie
{
class MovieMetadata
{
[Required(ErrorMessage = "*")]
public string Title { get; set; }
[Required(ErrorMessage = "*")]
[Range(5, 100, ErrorMessage = "Movies cost between $5 and $100.")]
public decimal Price { get; set; }
}
}
}
This should give me proper validations.. but the range is not working..
also... they are getting added into database
[HttpPost]
public ActionResult Create(Film newFilm)
{
if (ModelState.IsValid)
{
..some code for adding new movie in the database
}
}
Do this. And with regard to the comment of Aman who is saying or JQuery validation. Clientside validation cannot be a replacement for serverside validation. So always use the ModelState validation next to clientside.

ModelState.IsValid with DTO and Entity Framework code first

ASP.NET 4.5, MVC 5, EF6 code first
I'm a newbie and probably asking something long-known but I couldn't find solution on the web, probably because I don't know correct terminology to formulate this question.
To simplify things, let's say I have two model classes Teacher and Kid; One kid can be assigned only to one teacher, but one teacher can have many kids. As I'm using code first, my database is constructed from these model classes:
public class Kid
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual Teacher { get; set; }
}
public class Teacher
{
[Required]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public virtual ICollection<Kid> Kids { get; set; }
}
Now, I need to have a view for adding new kid with:
Textbox for Kid's name;
Dropdown with list of Teachers
So, I'm creating a data transfer object, specifically for that view:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
public int SelectedTeacherId { get; set; }
public Kid Kid { get; set; }
}
I also have a method for populating IEnumerable Teachers:
public AddNewKidViewDTO LoadTeachersForDropDownList()
{
... //get the list of Teachers
AddNewKidViewDTO addNewKidViewDTO = new AddNewKidViewDTO();
List<SelectListItem> selectListItems = new List<SelectListItem>();
foreach (teacher in Teachers)
{
selectListItems.Add (new SelectListItem
{
Text = teacher.Name.ToString(),
Value = teacher.Id.ToString()
});
}
addNewKidViewDTO.Teachers = selectListItems;
return addNewKidViewDTO;
}
and in the view AddNewKid.cshtml
<form>
#Html.LabelFor(model => model.Kid.Name)
#Html.TextBoxFor(model => model.Kid.Name, new {id ="Name"}
<br/>
#Html.LabelFor(model => model.Kid.Teacher)
#Html.DropDownListFor(model => model.SelectedTeacherId, Model.Teachers)
</form>
Form gets submitted and in the controller I get my populated AddNewKidViewDTO model:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
//here is where the problem comes
}
}
ModelState.IsValid in my case will always return false.
Because when it starts validating AddNewKidViewDTO.Kid, Teacher is compulsory field but in my addNewKidViewDTO model it's null. I have the necessary teacher Id contained in addNewKidViewDTO.SelectedTeacherId only.
My question is, what is an elegant way to validate my model before passing to my inner business logic methods?
Any help is appreciated.
There are multiple possible solutions:
Changing your AddNewKidViewDTO and decorating it with the DataAnnotaions for validation:
public class AddNewKidViewDTO
{
public IEnumerable<SelectListItem> Teachers { get; set; }
[Range(1, 2147483647)] //Int32 max value but you may change it
public int SelectedTeacherId { get; set; }
[Required]
public string KidName { get; set; }
}
Then you can create Kid object manually in case that your model valid.
UPDATE (to address your comment)
If you use this approach your action will look like this:
[HttpPost]
public ActionResult SaveNewKid (AddNewKidViewDTO addNewKidViewDTO)
{
if (ModelState.IsValid)
{
using (var dbContext = new yourContext())
{
var teacher = dbContext.Teachers.FirstOrDefault(t=>t.id == addNewKidViewDTO.SelectedTeacherId );
if(teacher == default(Teacher))
{
//return an error message or add validation error to model state
}
//It is also common pattern to create a factory for models
//if you have some logic involved, but in this case I simply
//want to demonstrate the approach
var kid = new Kid
{
Name = addNewKidViewDTO.KidName,
Teacher = teacher
};
dbContext.SaveChanges();
}
}
}
Write a custom model binder for AddNewKidViewDTO that will initialize Teacher property in Kid object so once you actually use Model.IsValid the property will be initialized.

how to save and update a record in the same form using mvc viewmodel

public int ID { get; set; }
[Required]
public string Surname{get;set;}
[Required (ErrorMessage="FirstName Required")]
public string Firstname{get;set;}
[Required(ErrorMessage="please enter your OtherNames")]
public string Othername{get;set;}
[Required(ErrorMessage="please enter your sex")]
public string Sex{get;set;}
[Required (ErrorMessage="please enter your DateOfBirth")]
public DateTime DateOfBirth{get;set;}
[Required(ErrorMessage="Address is required")]
public string Address{get;set;}
[Required(ErrorMessage="city is required")]
public string City{get;set;}
[Required(ErrorMessage="State is required")]
public string State{get;set;}
public string Country{get;set;}
}
i have created an interface which acts as the middleman between my Domain Layer and my WEBUI
public interface IStudentRepository
{
IQueryable<Student> Student { get; }
//we using a method to save the record into our database
void Save(Student student);
}
the interface is being implented by my repository class which perfoms all the actions of saving the records into the database and retrieving it..
public class EFRepository:IStudentRepository
{
private EFDBContext context = new EFDBContext();
public IQueryable<Student> Student{ get { return context.Students; } }
public void Save(Student student)
{
if (student.ID == 0)
{
context.Students.Add(student);
}
context.SaveChanges();
}
and then i have a view which creates an instance of the student class and uses the interface to store the records in the database, which works perfectly..
[HttpPost]
public ActionResult Registration(Student student)
{
if (ModelState.IsValid)
{
studentrepository.Save(student);
}
return View(student);
}
this works perfectly for me..
But i want to be able to update this same fields incase the student make changes to the fields and it should overwrite the existing record in the database.
i have seen my tutorials where the student id number is passed as an action link before it overwrites the existing record.
however,what i want is the ability for the student to edit the same form using the same button and the same view being display.any help would be appreciated.
You just need to change you repository method to look something like this:
public class EFRepository:IStudentRepository
{
private EFDBContext context = new EFDBContext();
public IQueryable<Student> Student{ get { return context.Students; } }
public void Save(Student student)
{
if (student.ID == 0)
{
context.Students.Add(student);
}
else
{
Student existingStudent = context.Students.FirstOrDefault(x => x.ID == student.ID);
if(existingStudent != null)
{
context.Entry(existingStudent).CurrentValues.SetValues(student);
context.Entry(existingStudent).State = System.Data.Entity.EntityState.Modified;
}
}
context.SaveChanges();
}
or you can use the Entity Framework Attach method as this will automatically track the object state and update the details

How to edit validation on Models generated by EF from a database?

I have a database with a table users. I generate the EF .edmx from the database and I can then access the users via:
public ActionResult Index()
{
var obj = context.Users.ToList();
return View(obj);
}
Which is fine my problem is that when I do the edit:
public ActionResult Edit(Guid id)
{
var obj = context.Users.Where(c => c.UserId == id).SingleOrDefault();
return View(obj);
}
I have no idea how to access the User model to add validation message? I was hoping to see something like:
public class User
{
[Required]
[Display(Name = "username")]
public string UserName { get; set; }
{
But I don't and I'm novice and don't really understand how to access / edit this model when generate from a database. Any advice tutorials would be appreciated.
see if u were using code first u could have used what user MISHA has suggested, u r using database first (as u are generating edmx from DB). In this case for mvc-validation to hook automatically- you have provide model's metadata.
You can create a metadata as below
namespace Your_Models_NameSpace
{
[MetadataType(typeof(UserMetaData))]
public partial class User
{
public class UserMetaData
{
[DisplayName("User Name")]
[Required(ErrorMessage = "Please provide a valid username")]
public object UserName { get; set; }
}
}
}
What you can do is, in the same namespace as your model, create a partial class with metadata like so:
[MetadataType(typeof(IPConfigMetadata))]
public partial class IPConfig
{
internal sealed class IPConfigMetadata
{
private IPConfigMetadata() { }
[RegularExpression(#"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", ErrorMessage = "Must be a valid IP Address")]
public string CommanderIP { get; set; }
[Range(1024, 65535)]
public int IPPort { get; set; }
}
}
Check this post.
You could do something like:
public class User
{
[Required(ErrorMessage = "Username is required")]
public string UserName { get; set; }
{
Then in your action you would do:
public ActionResult Edit(User user)
{
if(ModelState.IsValid)
{
// Save user
}
return View(user);
}

ASP.NET EditorTemplate DropdownList

Every time I add a new App It creates a new AppCategory. I am seriously screwing this up somehow
code first entity framework objects
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<App> apps { get; set; }
}
public class App
{
public int ID { get; set; }
public string Name { get; set; }
public AppCategory Category { get; set; }
}
Editor Template (I would love to just make just one Foreign Key EditorTemplate)
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("Category", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
and of course the repository
public static IEnumerable<SelectListItem> GetAppCategoriesSelect()
{
return (from p in GetAppCategories()
select new SelectListItem
{
Text = p.Name,
Value = p.ID.ToString(),
});
}
public static ICollection<AppCategory> GetAppCategories()
{
var context = new LIGDataContext();
return context.AppCategories.ToList();
}
Every time I add a new App It creates a new AppCategory I am seriously screwing this up somehow
Adding more debug info
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
gives me a validation message on the post
Parameters application/x-www-form-urlencoded
Category 1
Name 8
Validation error The value '1' is invalid.
This makes sense because Category should be an object not an integer.
Controller Code as asked for
pretty sure this isnt the problem as it came from MVCScaffold
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
My model was incorrectly set up ... virtual ICollection and just the foreign key id for the sub and everything worked... changes below
Model
public class AppCategory
{
public int ID { get; set; }
public string Name { get; set; }
public **virtual** ICollection<App> Apps { get; set; }
}
public class App
{
public int ID { get; set; }
********************************************
[UIHint("AppCategory")]
public int AppCategoryID { get; set; }
********************************************
public string Name { get; set; }
}
public class LIGDataContext : DbContext
{
public DbSet<AppCategory> AppCategories { get; set; }
public DbSet<App> Apps { get; set; }
}
/Views/Shared/EditorTemplates/AppCategory.cshtml
#inherits System.Web.Mvc.WebViewPage
#Html.DropDownList("", LIG2010RedesignMVC3.Models.Repo.GetAppCategoriesSelect())
AppController
[HttpPost]
public ActionResult Create(App d)
{
if (ModelState.IsValid)
{
this.repository.Add(d);
this.repository.Save();
return RedirectToAction("Index");
}
return View();
}
If you bind your dropDownList to Category.Id, you'll at least get the selected value into that filed, but nothing else in your Category Object.
The model binder cannot create the AppCategory object from the form collection in your Create action because the form only has an ID for that object (the other properties of AppCategory are not there).
The quickest solution would be setting the Category property of your App object manually, like this :
[HttpPost]
public ActionResult Create(App d) {
int categoryId = 0;
if (!int.TryParse(Request.Form["Category"] ?? String.Empty, out categoryId) {
// the posted category ID is not valid
ModelState.AddModelError("Category",
"Please select a valid app category.")
} else {
// I'm assuming there's a method to get an AppCategory by ID.
AppCategory c = context.GetAppCategory(categoryID);
if (c == null) {
// couldn't find the AppCategory with the given ID.
ModelState.AddModelError("Category",
"The selected app category does not exist.")
} else {
// set the category of the new App.
d.Category = c;
}
}
if (ModelState.IsValid)
{
context.Apps.Add(d);
context.SaveChanges();
return RedirectToAction("Index");
}
return View();
}

Resources