asp.net mvc3 Bind Exclude on properties does not work - asp.net-mvc

I have a class, which has 8 properties / 8 columns in DB. In the Edit page, I want to exclude the AddedDate and UserID fields. When a user edits a voucher, he can't overwrite the AddedDate or UserID values in the DB.
public class Voucher
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime AddedDate { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
public Guid UserID { get; set; }
}
Here is what I have for Edit controller:
// POST: /Voucher/Edit/5
[HttpPost]
public ActionResult Edit([Bind(Exclude = "AddedDate")]Voucher voucher)
{
if (ModelState.IsValid)
{
db.Entry(voucher).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(voucher);
}
On Edit page, when I click on submit, I got the following error:
System.Data.SqlServerCe.SqlCeException: An overflow occurred while converting to datetime.
Seems like the AddedDate didn't get excluded from the voucher object and triggered the error.
Would you please let me know how to fix it? Thanks!
(it is an updated version of asp.net mvc3 UpdateModel exclude properties is not working, I will go with another approach)

Never use your domain entities as action arguments and never pass your domain entities to your views. I would recommend you to use view models. In the view model you will include only the properties that you want to be bound from the view. The view model is a class that's specifically tailored to the requirements of a given view.
public class VoucherViewModel
{
public int ID { get; set; }
public string Title { get; set; }
public string SiteName { get; set; }
public string DealURL { get; set; }
public DateTime? ExpirationDate { get; set; }
public string VoucherFileURL { get; set; }
}
and then:
[HttpPost]
public ActionResult Edit(VoucherViewModel model)
{
// TODO: if the view model is valid map it to a model
// and pass the model to your DAL
// To ease the mapping between your models and view models
// you could use a tool such as AutoMapper: http://automapper.org/
...
}
UPDATE:
In the comments section #Rick.Anderson-at-Microsoft.com points out that while I have answered your question I haven't explained where the problem comes from.
The thing is that DateTime is a value type meaning it will always have a value. The [Bind(Exclude = "AddedDate")] works perfectly fine and it does what it is supposed to do => it doesn't bind the AddedDate property from the request. As a consequence the property will have its default value which for a DateTime field is 1/1/0001 12:00:00 AM and when he attempts to save this in SQL Server it blows because SQL Server doesn't support such format.

Related

ASP.NET MVC 5 Model-Binding Edit View

I cannot come up with a solution to a problem that's best described verbally and with a little code. I am using VS 2013, MVC 5, and EF6 code-first; I am also using the MvcControllerWithContext scaffold, which generates a controller and views that support CRUD operations.
Simply, I have a simple model that contains a CreatedDate value:
public class WarrantyModel
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
DateTime CreatedDate { get; set; }
DateTime LastModifiedDate { get; set; }
}
The included MVC scaffold uses the same model for its index, create, delete, details, and edit views. I want the CreatedDate in the 'create' view; I do not want it in the 'edit' view because I do not want its value to change when the edit view is posted back to the server and I don't want anyone to be able to tamper with the value during a form-post.
Ideally, I don't want the CreatedDate to ever get to the Edit view. I have found a few attributes I can place on the model's CreatedDate property (for example, [ScaffoldColumn(false)]) that prevent it from appearing on the Edit view, but then I'm getting binding errors on postback because the CreatedDate ends up with a value of 1/1/0001 12:00:00 AM. That's because the Edit view is not passing a value back to the controller for the CreatedDate field.
I don't want to implement a solution that requires any SQL Server changes, such as adding a trigger on the table that holds the CreatedDate value. If I wanted to do a quick-fix, I would store the CreatedDate (server-side, of course) before the Edit view is presented and then restore the CreatedDate on postback--that would let me change the 1/1/0001 date to the CreatedDate EF6 pulled from the database before rendering the view. That way, I could send CreatedDate as a hidden form field and then overwrite its value in the controller after postback, but I don't have a good strategy for storing server-side values (I don't want to use Session variable or the ViewBag).
I looked at using [Bind(Exclude="CreatedDate")], but that doesn't help.
The code in my controller's Edit post-back function looks like this:
public ActionResult Edit([Bind(Include="Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
{
if (ModelState.IsValid)
{
db.Entry(warrantymodel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warrantymodel);
}
I thought I might be able to examine the db.Entry(warrantymodel) object within the if block above and examine at the OriginalValue for CreatedDate, but when I try to access that value (as shown next), I get an exception of type 'System.InvalidOperationException':
var originalCreatedDate = db.Entry(warrantymodel).Property("CreatedDate").OriginalValue;
If I could successfully examine the original CreatedDate value (i.e., the one that is already in the database) I could just overwrite whatever the CurrentValue is. But since the above line of code generates an exception, I don't know what else to do. (I thought about querying the database for the value but that's just silly since the database was already queried for the value before the Edit view was rendered).
Another idea I had was to change the IsModified value to false for the CreatedDate value but when I debug then I discover that it is already is set to false in my 'if' block shown earlier:
bool createdDateIsModified = db.Entry(warrantymodel).Property("CreatedDate").IsModified;
I am out of ideas on how to handle this seemingly simple problem. In summary, I do not want to pass a model field to an Edit view and I want that field (CreatedDate, in this example) to maintain its original value when the other Edit fields from the view are posted back and persisted to the database using db.SaveChanges().
Any help/thoughts would be most appreciated.
Thank you.
You should leverage ViewModels:
public class WarrantyModelCreateViewModel
{
public int Id { get; set; }
public string Description { get; set; }
DateTime CreatedDate { get; set; }
DateTime LastModifiedDate { get; set; }
}
public class WarrantyModelEditViewModel
{
public int Id { get; set; }
public string Description { get; set; }
DateTime LastModifiedDate { get; set; }
}
The intention of a ViewModel is a bit different than that of a domain model. It provides the view with just enough information it needs to render properly.
ViewModels can also retain information that doesn't pertain to your domain at all. It could hold a reference to the sorting property on a table, or a search filter. Those certainly wouldn't make sense to put on your domain model!
Now, in your controllers, you map properties from the ViewModels to your domain models and persist your changes:
public ActionResult Edit(WarrantyModelEditViewModel vm)
{
if (ModelState.IsValid)
{
var warrant = db.Warranties.Find(vm.Id);
warrant.Description = vm.Description;
warrant.LastModifiedDate = vm.LastModifiedDate;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warrantymodel);
}
Furthermore, ViewModels are great for amalgamating data from multiple models. What if you had a details view for your warranties, but you also wanted to see all servicing done under that warranty? You could simply use a ViewModel like this:
public class WarrantyModelDetailsViewModel
{
public int Id { get; set; }
public string Description { get; set; }
DateTime CreatedDate { get; set; }
DateTime LastModifiedDate { get; set; }
List<Services> Services { get; set; }
}
ViewModels are simple, flexible, and very popular to use. Here is a good explantion of them: http://lostechies.com/jimmybogard/2009/06/30/how-we-do-mvc-view-models/
You're going to end up writing a lot of mapping code. Automapper is awesome and will do most of the heavy lifting: http://automapper.codeplex.com/
This is not an answer for the questions, but it might be critical for those who is using Bind() and facing different problems. When I was searching "why Bind() clears out all pre-existing but not-bound values", I found this:
(in the HttpPost Edit()) The scaffolder generated a Bind attribute and added the entity created by the model binder to the entity set with a Modified flag. That code is no longer recommended because the Bind attribute clears out any pre-existing data in fields not listed in the Include parameter. In the future, the MVC controller scaffolder will be updated so that it doesn't generate Bind attributes for Edit methods.
from a official page (last updated in 2015, March):
http://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/implementing-basic-crud-functionality-with-the-entity-framework-in-asp-net-mvc-application#overpost
According to the topic:
Bind is not recommended and will be removed in the future from the auto-generated codes.
TryUpdateModel() is now the official solution.
You can search "TryUpdateModel" in the topic for details.
It may solve your problem
In Model:
Use ?
public class WarrantyModel
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
DateTime? CreatedDate { get; set; }
DateTime? LastModifiedDate { get; set; }
}
After form submit:
public ActionResult Edit([Bind(Include = "Id,Description,CreatedDate,LastModifiedDate")] WarrantyModel warrantymodel)
{
if (ModelState.IsValid)
{
db.Entry(warrantymodel).State = EntityState.Modified;
db.Entry(warrantymodel).Property("CreatedDate").IsModified=false
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warrantymodel);
}
+1 for cheny's answer. Use TryUpdateModel instead of Bind.
public ActionResult Edit(int id)
{
var warrantymodel = db.Warranties.Find(id);
if (TryUpdateModel(warrantymodel, "", new string[] { "Id", "Description", "LastModifiedDate" }))
{
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warrantymodel);
}
If you want to use View Model, you can use Automapper and configure it to skip null values so the existing data still exists in the domain model.
Example:
Model:
public class WarrantyModel
{
public int Id { get; set; }
public string Description { get; set; }
DateTime CreatedDate { get; set; }
DateTime? LastModifiedDate { get; set; }
}
ViewModel:
public class WarrantyViewModel
{
public int Id { get; set; }
public string Description { get; set; }
DateTime? CreatedDate { get; set; }
DateTime? LastModifiedDate { get; set; }
}
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include="Id,Description,LastModifiedDate")] WarrantyViewModel warrantyViewModel)
{
var warrantyModel = db.Warranties.Find(warrantyViewModel.Id);
Mapper.Map(warrantyViewModel, warrantyModel);
if (ModelState.IsValid)
{
db.Entry(warrantyModel).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(warrantyModel);
}
Automapper:
Mapper.CreateMap<WarrantyViewModel, WarrantyModel>()
.ForAllMembers(opt => opt.Condition(srs => !srs.IsSourceValueNull));
try to remove the Create date prompt text box in the Edit view. In my application, the scaffold generated Edit and Create Views contain the Primary key which is generated in the database.
Controller:
...
warrantymodel.CreatedDate = DateTime.Parse(Request.Form["CreatedDate"]);
...

Validation occurred client side with breeze entities for some properties which will be filled server side in the BeforeSaveEntity

I use the Durandal template in my asp.net mvc solution. The question here is related to Breeze which is also used in Durandal. Let's say I have the following entity in my model:
public class Driver
{
[Key]
public int Id { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
public string Comment { get; set; }
public int? CreatedById { get; set; }
public DateTime? CreatedTime { get; set; }
public int? UpdatedById { get; set; }
public DateTime? UpdatedTime { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User UpdatedBy { get; set; }
}
As you can see, I have some properties used to track creation/updates for time and userid (UpdatedById, UpdatedTime, ...). I would like to let the user edit/create my drivers in some data entry pages then fill in these properties (UpdatedById, UpdatedTime, ...) server side automatically in the BeforeSaveEntity method.
It works but as you noted I had to allow nullable on the properties like int? or DateTime? because in case of adding a new entity (everything is blank) the validation failed if I didn't proceed like that.
My question: is there another solution or something that could be done to avoid using nullable types on my model (int? - DateTime?) for these properties which track my creation/edition?
Thanks.
Make them nonnullable and fill in "dummy" values of the client, in a "registered" ctor for each type that will then get overwritten on the server.

MVC4: Modelstate is not valid when creating a new record

I try to add a new Country which has a link to continent. When I press the "Create" button, it doesn't add a new record. I debugged my project and I think it's because the ValidState is false. The reason because of this is that the property "Continent" is null, but the Continent_Id isn't.
I have the same problem when I try to edit an existing Country. (I have populated my database with an SQL script in SQL Management Studio)
Can someone help me please?
Continent class:
public class Continent
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
//Navigation
public virtual List<Country> Countries { get; set; }
}
Country class
public class Country
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
public int Continent_Id { get; set; }
//Navigation
[Required, ForeignKey("Continent_Id")]
public virtual Continent Continent { get; set; }
}
Controller class ( create function )
//
// GET: /Countries/Create
public ActionResult Create()
{
ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name");
return View();
}
//
// POST: /Countries/Create
[HttpPost]
public ActionResult Create(Country country)
{
var errors = ModelState.Values.SelectMany(v => v.Errors); //to check the errors
if (ModelState.IsValid)
{
db.Countries.Add(country);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.Continent_Id = new SelectList(db.Continents, "Id", "Name", country.Continent_Id);
return View(country);
Just before the line if (ModelState.IsValid) put this
ModelState.Remove("v_id");
Where v_id is your primarykey column name in your case
I fixed this issue by putting the Required validation off of Continent, and set it only at the Continent_Id. Now the ID property is required, but the Continent isn't.
public class Country
{
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
[Required] //added Required
public int Continent_Id { get; set; }
//Navigation
[ForeignKey("Continent_Id")] //removed Required
public virtual Continent Continent { get; set; }
}
Thanks for the responses !
I'm not sure, but I believe your issue is timing. Model validation happens automatically during binding; at that time, the Continent property is null. You set the property later but the model state is not re-evaluated when you check IsValid. I see three options:
Quick and dirty: Take the Required validation off of Continent and validate Continent_Id instead, adding a check in the controller to ensure a valid Continent is retrieved from Find().
Most work: Create a custom model binder to actually use the Continent_Id to retrieve and populate the Continent. You are almost there on this one since having both Continent_Id and Continent as properties of Country is redundant and an opportunity for inconsistencies.
Probably best option: Make your controller accept a view model that only has the data you expect to come back from the form and populate a Country object from it.
The reason the ModelState isn't valid is because you have marked the Continent property as required but in i guess in your view you don't have form fields the will bind to some properties of the Continent object.
So either don't mark the Continent object as required or provide a hidden field with a name of Continent.Id or Continent.Name so that the model binder will populate the Continent property:
#Html.HiddenFor(m => m.Continent.Id)
But that will lead to the next problem: You habe marked the Name property of the Continent class as required so you will have to provide a form field for that property too.
The base problem is, that you try to reuse your repository classes as viewmodel classes.
A better approach would be to use separate classes as viewmodels to pass your data between the controller and the view:
class CountryViewModel {
public int Id { get; set; }
[Required, MaxLength(25)]
public string Name { get; set; }
[MaxLength(5)]
public string Abbreviation { get; set; }
public int Continent_Id { get; set; }
}
To map between your Country and CountryViewModel object use a mapper like AutoMapper.

MVC parameter not binding to controller action (KENDO UI)

Hope someone can help - this has been bugging me for around 2 hours - its probably something simple :)
Kendo UI Grid sends a request to my controller
http://localhost:1418/user/update?UserID=1&UserName=Admin&RoleName=Admin&Email=c.j.hannon%40gmail.com&Active=true&Company%5BCompanyID%5D=1&Company%5BCompanyName%5D=asd
However, the controller class 'Company' isnt bound by the binder? Can any one help my view model and controller action signature are below:
[HttpGet]
public JsonResult Update(UserViewModel model)
{
svcUser.UpdateUser(new UpdateUserRequest() {
UserID=model.UserID,
RoleID = model.RoleName,
Email = model.Email,
Active = model.Active.GetValueOrDefault(false),
UserName = model.UserName
});
return Json("", JsonRequestBehavior.AllowGet);
}
public class UserViewModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public CompanyViewModel Company { get; set; }
}
Cheers
Craig
A few things. Your immediate problem is that Company is mapped to a complex object not a primitive type. Kendo Grid just does not do this (as of this writing). Just guessing, but you probably want to setup a foreign key binding on the Grid and just pass back the Id of the company from a listbox. This is not as bad as you think and it will immediatly fix your problem and look nice too.
Maybe personal taste but seems to be a convention. Use the suffix ViewModel for the model that is bound to your View and just the suffix Model for your business objects. So a Kendo Grid is always populated with a Model.
Ex.:
public class UserModel
{
public int UserID { get; set; }
public string UserName { get; set; }
public string RoleName { get; set; }
public string Email { get; set; }
public bool? Active { get; set; }
public int CompanyID { get; set; }
}
public class CompanyModel
{
public int ID { get; set; }
public string Name { get; set; }
}
public class UserViewModel
{
public UserModel UserModel { get; set; }
public IList<CompanyModel> Companies { get; set; }
}
public ActionResult UserEdit(string id)
{
var model = new UserViewModel();
model.UserModel = load...
model.Companies = load list...
return View(model);
}
#model UserViewModel
...
column.ForeignKey(fk => fk.CompanyId, Model.Companies, "ID", "Name")
(Razor Notation)
BUT! This is just an example, you are better off Ajax loading the Grid with the IList becuase I assume you have many Users in the Grid at once, though you could server bind off the ViewModel with a List too. But the list of Companies is probably the same every time, so map it to the View just liek this rather than Ajax load it every time you do a row edit. (not always true)

ASP MVC 3.0 Complex View

I am developing a application for Sales Order Management using ASP.NET MVC 3.0. I need to develop a page where Customer Details can be added.
Customer Details Include
public class Customer
{
public int ID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Alias { get; set; }
public int DefaultCreditPeriod { get; set; }
public Accounts Accounts { get; set; }
public IList<Address> Addresses { get; set; }
public IList<Contact> Contacts { get; set; }
}
public class Accounts
{
public int ID { get; set; }
public string VATNo { get; set; }
public string CSTNo { get; set; }
public string PANNo { get; set; }
public string TANNo { get; set; }
public string ECCNo { get; set; }
public string ExciseNo { get; set; }
public string ServiceTaxNo { get; set; }
public bool IsServiceTaxApplicable { get; set; }
public bool IsTDSDeductable { get; set; }
public bool IsTCSApplicable { get; set; }
}
public class Address
{
public int ID { get; set; }
public AddressType Type { get; set; }
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { get; set; }
public string Line4 { get; set; }
public string Country { get; set; }
public string PostCode { get; set; }
}
public class Contact
{
public int ID { get; set; }
public ContactType Type { get; set; }
public string Name { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Extension { get; set; }
public string MobileNumber { get; set; }
public string EmailId { get; set; }
public string FaxNumber { get; set; }
public string Website { get; set; }
}
Customer Requires a single page to fill all the customer details(General info, Account Info,Address Info and Contact Info). There will be multiple Addresses(Billing, Shipping, etc) and multiple Contacts (Sales, Purchase). I am new to MVC. How to Create the View for the above and Add multiple Address dynamically?
I often create wrapper models to handle this kind of situation e.g.
public class CustomerWrapperModel
{
public Customer Customer { get; set;}
public Accounts Accounts { get; set;}
public List<Address> AddressList { get; set}
//Add
public CustomerWrapperModel()
{
}
//Add/Edit
public CustomerWrapperModel(Customer customer, Accounts accounts, List<Address> addressList)
{
this.Customer = customer;
this.Accounts = accounts;
this.AddressList = addressList;
}
}
then declare the View to be of type CustomerWrapperModel and use editors like so:
#model MyNamespace.CustomerWrapperModel
#Html.EditorFor(model => model.Customer)
#Html.EditorFor(model => model.Accounts)
#Html.EditorFor(model => model.AddressList)
and have a controller to receive the post that looks like this:
[HttpPost]
public ActionResult(Customer customer, Accounts accounts, List<Address> addressList)
{
//Handle db stuff here
}
As far as adding addresses dynamically I found the best way to do this if you're using MVC validation and want to keep the list structured correctly with the right list indexes so that you can have the List parameter in your controller is to post the current Addresses to a helper controller like this:
[HttpPost]
public PartialResult AddAddress(List<Address> addressList)
{
addressList.Add(new Address);
return PartialView(addressList);
}
then have a partial view that just renders out the address fields again:
#model List<MyNamespace.Address>
#{
//Hack to get validation on form fields
ViewContext.FormContext = new FormContext();
}
#Html.EditorForModel()
make sure you address fields are all in one container and then you can just overwrite the existing ones with the returned data and your new address fields will be appended at the bottom. Once you have updated your container you can do something like this to rewire the validation:
var data = $("form").serialize();
$.post("/Customer/AddAddress", data, function (data) {
$("#address-container").html(data);
$("form").removeData("validator");
$("form").removeData("unobtrusiveValidation");
$.validator.unobtrusive.parse("form");
});
NB. I know some people with have an issue with doing it this way as it requires a server side hit to add fields to a page that could easily just be added client side (I always used to do it all client side but tried it once with this method and have never gone back). The reason I do it this way is because it's the easiest way to keep the indexes on the list items correct especially if you have inserts as well as add and your objects have a lot of properties. Also, by using the partial view to render the data you can ensure that the validation is generated on the new fields for you out of the box instead of having to hand carve the validation for the newly added client side fields. The trade off is in most cases a minor amount of data being transferred during the ajax request.
You may also choose to be more refined with the fields you send to the AddAddress controller, as you can see I just post the entire form to the controller and ignore everything but the Address fields, I am using fast servers and the additional (minor) overhead of the unwanted form fields is negligible compared to the time I could waste coding this type of functionality in a more bandwidth efficient manner.
You pass your root model object to the View call in your controller like this:
public ActionResult Index() {
var customer = GetCustomer(); // returns a Customer
return View(customer);
}
And then your view looks something like this:
#model Customer
<!DOCTYPE html>
<!-- etc., etc. -->
<h1>Customer #Model.Name</h1>
<ul>
#foreach (var address in Model.Addresses) {
<li>#address.Line1</li>
}
</ul>
One gets the picture.
The code above depends on the #model directive, which is new in ASP.NET MVC 3 (see this blog post).
Is a good question :D for normal navigation properties such as Accounts doing this is not to hard:
#Html.EditorFor(model => model.Accounts.ID)
#Html.EditorFor(model => model.Accounts.VATNo)
will do something you want. But for collection navigation properties (Addresses and Contacts) you can't do this in one place by default. I suggest you use a different page for Addresses (and one for Contacts). Because it is the easiest way. But if you want to do this in one place (and also with out AJAX requests), you can create view by Customer, use scaffolding for model and it's simple navigation properties, and for lists (Addresses, Contacts) you must add them with JavaScript to the input fields (for example for each Address added, put it in an Array) and post fields to server. At server you can get main model and simple properties by default model-binder and for lists, you can 1) create your own model binder 2) parse them from inputted strings by yourself. Good lock

Resources