Given following ASP.NET MVC controller code:
[HttpPost]
public ActionResult Create(FormCollection collection)
{
string[] whitelist = new []{ "CompanyName", "Address1", "Address2", ... };
Partner newPartner = new Partner();
if (TryUpdateModel(newPartner, whitelist, collection))
{
var db = new mainEntities();
db.Partners.AddObject(newPartner);
db.SaveChanges();
return RedirectToAction("Details/" + newPartner.ID);
}
else
{
return View();
}
}
The problem is with the Entity Framework 4: the example Partner entity is mapped to a database table with it's fields NOT ALLOWED to be NULL (which is ok by design - they're required).
Unfortunately, invoking TryUpdateModel when some of the properties are nulls produces as many ConstraintExceptions what is not expected! I do expect that TryUpdateModel return false in this case.
It is ok that EF wouldn't allow setting a property's value to null if it should not be, but the TryUpdateMethod should handle that, and add the error to ModelState errors collection.
I am wrong, or somebody screwed up the implementation of TryUpdateModel method?
It's not "screwed up". It's by design. My preferred way of dealing with this is to bind to an edit model rather than directly to an entity. If that's not an option for you, then you can write an associated metadata provider or initialize the properties.
Related
I am new to asp.net mvc and recently I came across a method called AsNoTracking(). I did some research and found out that AsNoTracking() helps to speed up performance. It should be used with queries in which we do not want to save the data back to the database. So I know that AsNoTracking() helps to boost performance if you are returning hundreds to thousands of records. However, What I was wondering is that What if you are only returning one record?. For example the record of a specific employee. Is there a need to use AsNoTracking()?
Its will be useful to know that where AsNoTracking() could be needful.
In certain scenario where you post an Edit call and before actually updating the model to database you are trying to check if particular record already exists or not and then you want to place Update call it will throw exception. Below is the demonstration of such scenario.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //THIS IS WHERE THE ERROR WILL BE THROWN AS RECORD WITH ID IS ALREADY ATTACHED WITH modelFromDb OBJECT.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You will find many questions asking about error like "Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value" which can be caused by above snippet.
AsNoTracking() can prevent such exceptions. Suggested code will be like below.
public ActionResult Edit(Model model)
{
var modelFromDb = db.Model.AsNoTracking().Where(x => x.ID == model.ID);
if (modelFromDb.Count() > 0)
{
db.Entry(model).State = EntityState.Modified; //WORKS PERFECTLY.
db.SaveChanges();
return RedirectToAction("Index");
}
return View(model);
}
You can refer one such question asked here ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value
I have a small application where I am creating a customer
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateCustomer(GWCustomer customer)
{
if (string.IsNullOrEmpty(customer.CustomerName))
{
ModelState.AddModelError("CustomerName", "The name cannot be empty");
}
//...
if (ModelState.IsValid)
{
//insert in db
}
}
My problem is that the GWCustomer object has an Id, which is primary key and cannot be null. This makes the validation framework flag it as an error. But it's not an error, I haven't created the customer yet, and for now is should be null until it gets saved. How do I bypass this? Or fix it?
I never get to insert it in the DB because the ModelState is never valid.
Edit I am using Linq to SQL, and a repository pattern.
This will exclude value from binding, but not validation:
public ActionResult CreateCustomer([Bind(Exclude = "Id")]GWCustomer customer)
Even when validation occurs, you can still correct ModelState by calling:
ModelState.Remove("Id");
It will remove entries related to Id and change ModelState.Valid property to true if only Id was causing errors.
Using data layer objects in view layer is not recommended. You should definitely think about creating dedicated view model, without Id field.
Maybe you have this line in your view:
#Html.HiddenFor(model => model.Id)
Delete it and the view won't send that parameter with the model.
This is why I always say that the ViewModel objects (input and output) should be separated from the Domain Objects.
The input model should be validated in the way you are above; the domain object state should be validated before it gets written to the database (and exceptions thrown if it is somehow invalid).
I have a sql server table with 2 fields, ID (primary key) and Name (unique key).
I'm using linq to sql to produce model objects for asp.net MVC from this table.
To perform the model validation I've implemented IDateErrorInfo in a partial class
public partial class Company : IDataErrorInfo
{
private Dictionary<string, string> _errors = new Dictionary<string,string>();
partial void OnNameChanging(string value)
{
if (value.Trim().Length == 0)
{
_errors.Add("Name", "Name is required");
return;
}
}
}
This performs as expected with the Model.IsValid property and Html.ValidationSummary helper.
However, this code is just checking that the newly created Company has a Name that is not blank. I also need to check if the Name has been used by another Company in the table.
I could just call the AddCompany method on my repository and catch the SQLException, but this feels dirty.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="ID")] Company companyToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
//Add to the Database
try
{
_companyRepos.AddCompany(companyToCreate);
return RedirectToAction("Index");
}
catch(SQLException ex)
{
return View("do something to show the error inserting");
}
}
Ideally I want the OnNameChanging method in the partial class to perform the unique key check before I try to add the Company.
Any ideas on how I should be doing this? The only thought I've has so far is to create a fresh database connection in the partial class and query the table.
Thanks
One possibility is for the AddCompany method to return a boolean indicating success or failure of the operation.
However, it is customary to catch this type of error before you attempt to add the record. Put a
bool Exists(string companyName)
method in your Company Repository, and use this to catch the error before attempting to add the record.
The reason this is preferable is you know exactly why the failure occurred. Otherwise, you would either have to catch a custom exception or examine a returned error code.
However you slice it, you're going to obviously have to hit the database to get a list of names already in use. Therefore, I would suggest adding a method in your repository that basically just returns an IList<string> or IEnumerable<string> that simply contains all the distinct names in that table in the DB. Then, in your validating method, simply use that method on the repository to get all the unique names, and implement your check there.
I'm currenty manage to get some time to work on ASP.NET MVC. I'm doing the
tutorial Create a Movie Database in ASP.NET MVC, which still uses the ADO.NET Enity Model. I managed to create a List View from the LINQ Entity Model. So here is my problem.
The Bind Attribute doesn't work on my SQL Entity.
Original Code with Ado.NET
public ActionResult Create([Bind(Exclude="Id")] Movie movieToCreate)
{
if (!ModelState.IsValid)
return View();
_db.AddToMovieSet(movieToCreate);
_db.SaveChanges();
return RedirectToAction("Index");
}
My LINQ Code
public ActionResult Create([Bind(Exclude = "Id")] Movies movieToCreate)
{
if (!ModelState.IsValid)
{
return View();
}
_db_linq.Movies.InsertOnSubmit(movieToCreate);
_db_linq.SubmitChanges();
return RedirectToAction("Index");
}
But the Id Field isn't excluded. Any Ideas? Thanks!
Your ID property is probably an int and it's not a nullable type. And because of that, even though it's excluded when binding, it's got to have a value. In this case it has the default value of its type, which is zero.
Make sure you set up your database properly, having the ID field's IsIdentity property set to true and re-create your LINQ classes.
On the NerdDinner example of Professional ASP.NET MVC 1.0 there's a method to create a new dinner as copied bellow (page 89 of the free NerdDinner version).
There it checks ModelState.IsValid for true. It seems to check if the model is valid for the database (that is, it catches data type conversions, like dates with invalid format, but not business rules). Is that true?
When submitting the form, if you have an error in the date, ModelState.IsValid will be false and you'll get back an error, but only for the date because AddRuleViolations was never executed. If you remove the check for ModelState.IsValid completely, then you'll get all the errors (due to the exception), including a marking in the date when it is invalid. Then, why is the check for ModelState.IsValid there at all? Am I missing something?
//
// POST: /Dinners/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Dinner dinner) {
if (ModelState.IsValid) {
try {
dinner.HostedBy = "SomeUser";
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new {id = dinner.DinnerID });
} catch {
ModelState.AddRuleViolations(dinner.GetRuleViolations());
}
}
return View(dinner);
}
ModelState.IsValid tells you if any model errors have been added to ModelState.
The default model binder will add some errors for basic type conversion issues (for example, passing a non-number for something which is an "int"). You can populate ModelState more fully based on whatever validation system you're using.
The sample DataAnnotations model binder will fill model state with validation errors taken from the DataAnnotations attributes on your model.
From the Errata:
ModelState.AddRuleViolations(dinner.GetRuleViolations());
Should be:
ModelState.AddModelErrors(dinner.GetRuleViolations());
Reference: http://www.wrox.com/WileyCDA/WroxTitle/Professional-ASP-NET-MVC-1-0.productCd-0470384611,descCd-ERRATA.html
All the model fields which have definite types, those should be validated when returned to Controller. If any of the model fields are not matching with their defined type, then ModelState.IsValid will return false. Because, These errors will be added in ModelState.
Yes , Jared and Kelly Orr are right.
I use the following code like in edit exception.
foreach (var issue in dinner.GetRuleViolations())
{
ModelState.AddModelError(issue.PropertyName, issue.ErrorMessage);
}
in stead of
ModelState.AddRuleViolations(dinner.GetRuleViolations());