Html.EditorFor nullable DateTime posting back as null from view - asp.net-mvc-3

Lets make the following assumptions; ASP.NET MVC 3 Razor C#, a strongly typed view bound to a view model (not entities etc.), using the Html.EditorFor method, to edit a nullable DateTime property in the view model. The two data annotation attributes I added seem to be causing model binding to fail.
Sample view code
#model MyApp.ViewModels.NullableDateTimeViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(m => m.DateOfBirth)
}
Sample ViewModel code
[DataType(DataType.Date,
ErrorMessage = "Please enter a valid date in the format dd MMM yyyy")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd MMM yyyy}")]
public class NullableDateTimeViewModel
{
public DateTime? DateOfBirth { get; set; }
}
Sample controller code
[HttpPost]
public ViewResult DoB(NullableDateTimeViewModel nullableDateTimeVM)
{
ContextDB db = new ContextDB();
Customer cust = new Customer();
// DateOfBirth is null so the update fails
cust.DateOfBirth = nullableDateTimeVM.DateOfBirth.Value;
db.Customers.Add(cust);
db.SaveChanges();
}
The data entered in the view is not posting back to the controller when the form in the view is submitted when the data annotation attributes are added. This means that model binding is failing when using EditorFor with those attributes. Model binding works fine with TextBoxFor, the value entered in the TextBoxFor input box is passed back to the view with the view model. What is the problem here with EditorFor and the data annotation validation attributes?
Can we please find a solution that does not involved reinventing the wheel by creating multiple additional classes, helpers, templates and writing a whole lot of additional code? I am looking for a one or two line solution.

Related

MVC dropdownlist , data not displaying when model is not Ienumerable

I am having a problem passing to the View a Model that contains the dropdown data and the model.
With this code my page loads, but the dropdownlist contains "System.Web.MVC.SelectList" when selected.
Here's my controller code.
public ActionResult Index(string productNameFilter, string productCategoryFilter, String productTypeFilter )
{
var ddl = new Items();
ddl.CategoryddList = itemsRepository.GetItemDdl("Item Categories").Select(c => new SelectListItem
{
Value = c.DropdownID.ToString(),
Text = c.DropdownText
});
ViewBag.CategoryDD = new SelectList(ddl.CategoryddList, "Value", "Text");
var model = itemsRepository.GetItemByName(productNameFilter);
return View(model);
}
Here's my view
#model Ienumerable<Models.items.items>
#Html.DropDownList("productCategoryFilter",
new SelectList(ViewBag.CategoryDD),
"---Select Category---")
Side note - if you use a ViewModel between the View and the Model instead of binding directly to the model, you can put your SelectList on the ViewModel and use #Html.DropdownFor() instead of #Html.Dropdown(). The ViewBag should really be used sparingly.
However back to your original question:
What is "Items()"? in your line
var ddl = new Items();
I'm not sure what good reason you would have NOT to make it enumerable.
I suspect it is not working because you are making a selectlist from a select list twice --
in your code behind you are defining ViewBag.CategoryDD as a SelectList(), and then in your Razor code you are creating a new SelectList() from the existing selectlist. You shouldn't have to do this.
The way I would do this is create a ProductViewModel class that contains your product category list AND your list of products (your current model), and a property for the selected filter.
public class ProductViewModel
{
public IEnumerable<Model.items.items> ProductList {get;set;}
public IEnumerable<SelectListItem> ProductCategoryList {get;set;} //SelectList is an IEnumerable<SelectListItem>
public string SelectedCategory {get;set;}
}
Then on your view the model would be
#model ProductViewModel
#Html.DisplayFor(model => model.SelectedCategory, "---Select Category---")
#Html.DropdownListFor(model => model.SelectedCategory, Model.ProductCatgoryList)

Required ModelValidation just for new objects ASP.NET MVC

I have this issue since yesterday.
In my User model I have a [NotMapped] called "ConfirmPassword". I don´t save it on the database but I use it on my Create form as a always to validate the data input for new users.
Since than, it´s ok. The problem is on my [HttpPost] Edit action. I should be able to edit some user's data without type and confirm the password. I use both Password and ConfirmPassword as a way to confirm the old password and informe the new one, if I wanna change the password. But, if I don´t, I leave them blank.
I have used already the code below to be able to pass the ModelState.IsValid() condition and it worked:
ModelState["Password"].Errors.Clear();
ModelState["ConfirmPassword"].Errors.Clear();
But, just before the db.SaveChanges(), as User user view model is considered, it has both properties empty and I got:
Property: ConfirmPassword Error: The field ConfirmPassword is invalid.
The question is: How could I skip de Required model validation when I want to update an object?
I read already about custom ModelValidations with classes extending ValidationAttribute and
DataAnnotationsModelValidator but I am not doing it right.
Any idea? How could I create a custom model validation that checks if the UserId property is null or not. It´s a nice way to check if I'm in Create or Edit action.
Thanks,
Paulo
Using the domain objects as your ViewModel will leads you to a condition of less scalability. I would opt for seperate ViewModels specific for the Views. When i have to save the data i map the ViewModel to the Domain model and save that. In your speciific case, i would create 2 ViewModels
public class CustomerViewModel
{
public string FirstName { set;get;}
public string LastName { set;get;}
}
And i will Have another ViewModel which inherits from the above class, for the Create View
public class CustomerCreateViewModel :CustomerViewModel
{
[Required]
public string Password { set;get;}
[Required]
public string ConfirmPassword { set;get;}
}
Now in my Get actions, i use this ViewModel
public ActionResult Create()
{
var vm=new CustomerCreateViewModel();
return View(vm);
}
and of course my View(create.cshtml) is now binded to this ViewModel
#model CustomerCreateViewModel
<h2>Create Csustomer</h2/>
//Other form stuff
Similarly for My Edit Action,
public ActionResult Edit(int id)
{
var vm=new CustomerViewModel();
var domainCustomer=repo.GetCustomerFromID(id);
if(domainCustomer!=null)
{
//This manual mapping can be replaced by AutoMapper.
vm.FirstName=domainCustomer.FirstName;
vm.LastName=domainCustomer.LastName;
}
return View(vm);
}
This view is bounded to CustomerViewModel
#model CustomerViewModel
<h2>Edit Info of #Model.FirstName</h2>
//Other form stuff
In your POST Actions, Map it back to the Domain object and Save
[HttpPost]
public ActionResult Create(CustomerCreateViewModel model)
{
if(ModelState.IsValid)
{
var domainCust=new Customer();
domainCust.FirstName=model.FirstName;
repo.InsertCustomer(domainCust);
//Redirect if success (to follow PRG pattern)
}
return View(model);
}
Instead of writing the Mapping yourself, you may consider using AutoMapper library to do it for you.

Best way to bind the constant values into view (MVC3)

I have a constants values such as "Required","Optional", and "Hidden". I want this to bind in the dropdownlist. So far on what I've done is the below code, this is coded in the view. What is the best way to bind the constant values to the dropdownlist? I want to implement this in the controller and call it in the view.
#{
var dropdownList = new List<KeyValuePair<int, string>> { new KeyValuePair<int, string>(0, "Required"), new KeyValuePair<int, string>(1, "Optional"), new KeyValuePair<int, string>(2, "Hidden") };
var selectList = new SelectList(dropdownList, "key", "value", 0);
}
Bind the selectList in the Dropdownlist
#Html.DropDownListFor(model => model.EM_ReqTitle, selectList)
Judging by the property EM_RegTitle I'm guessing that the model you're using is auto-generated from a database in some way. Maybe Entity Framework? If this is the case, then you should be able to create a partial class in the same namespace as your ORM/Entity Framework entities and add extra properties. Something like:
public partial class MyModel
{
public SelectList MyConstantValues { get; set; }
}
You can then pass your SelectList with the rest of the model.
There are usually hangups from using ORM/EF entities through every layer in your MVC app and although it looks easy in code examples online, I would recommend creating your own View Model classes and using something like AutoMapper to fill these views. This way you're only passing the data that the views need and you avoid passing the DB row, which could contain other sensitive information that you do not want the user to view or change.
You can also move the logic to generate your static value Select Lists into your domain model, or into a service class to help keep reduce the amount of code and clutter in the controllers.
Hope this helps you in some way!
Example...
Your View Model (put this in your "Model" dir):
public class MyViewModel
{
public SelectList RegTitleSelectList { get; set; }
public int RegTitle { get; set; }
}
Your Controller (goes in the "Controllers" dir):
public class SimpleController : Controller
{
MyViewModel model = new MyViewModel();
model.RegTitle = myEfModelLoadedFromTheDb.EM_RegTitle;
model.RegTitleSelectList = // Code goes here to populate the select list.
return View(model);
}
Now right click the SimpleController class name in your editor and select "Add View...".
Create a new view, tick strongly typed and select your MyViewModel class as the model class.
Now edit the view and do something similar to what you were doing earlier in your code. You'll notice there should now be a #model line at the top of your view. This indicates that your view is a strongly typed view and uses the MyViewModel model.
If you get stuck, there are plenty of examples online to getting to basics with MVC and Strongly Typed Views.
You would prefer view model and populate it with data in controller.
class MyViewModel
{
public string ReqTitle { get; set; }
public SelectList SelectListItems { get; set; }
}
Then you can use:
#Html.DropDownListFor(model => model.EM_ReqTitle, model.SelectListItems)

DropDownList Client Side Validation is validating when it should not be. (MVC3, Razor)

I am still learning MVC3 and Razor, so this is perhaps a simple question.
On a view I have a DropDownList whose sole purpose is to help filter (via AJAX) a second drop down list:
#Html.DropDownList("Segments", "-- select segment --")
There is a Segments property of the ViewModel that is defined as:
public IEnumerable<SelectListItem> Segments { get; set; }
There is JavaScript that handles the change event for this DropDownList and populates another DropDownList with appropriate values. That other DropDownList is defined like this:
#Html.DropDownListFor(m => m.fafhProdRecId, Enumerable.Empty<SelectListItem>(), "-- select product recommendation --")
This all works fine until I submit. When I submit, I get a validation error on the Segments drop down list!
Now -- there should be absolutely NO validation on the segments DropDownList -- there shouldn't be any client side validation on EITHER drop down list, for that matter.
But when I try to submit, I get the validation error message back:
The value '1' is invalid.
I have no idea why this is happening.
I have no idea how to decorate the Segments property to say that it is NOT required.
I have no idea how to tell the unobtrusive javascript validator that it is, in fact, being quite obtrusive.
In your ViewModel class add [Bind(Exclude = "Segments")]
From: Using Data Annotations for Model Validation
make sure that your Model has fafhProdRecId as nullable, I imagine it's declared as:
public int fafhProdRecId { get; set; }
change this to:
public int? fafhProdRecId { get; set; }
hopefully, that should resolve the issue as this effectively makes the model field nullable (assuming the db field IS nullable too of course).

A `ViewModel` for each page (`Create.cshtml` and `Edit.cshtml`)?

Questions
There are actually two related questions:
Should I create a ViewModel for each page?
If you do not have problems in creating a single ViewModel class for the two pages (Create.cshtml and Edit.cshtml) how can I validate the ViewModel in different ways (depending on the page that is being used)
Source
ViewModel
public class ProjectViewModel
{
public string Name { get; set; }
public string Url { get; set; }
public string Description { get; set; }
}
Edit.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = Model.Name;
}
#Html.EditorForModel()
Create.cshtml
#using BindSolution.ViewModel.Project
#model ProjectViewModel
#{
ViewBag.Title = "New Project";
}
#Html.EditorForModel()
ProjectValidator.cs
public class ProjectValidator : AbstractValidator<ProjectViewModel>
{
private readonly IProjectService _projectService;
public ProjectValidator(IProjectService projectService)
{
_projectService = projectService;
RuleFor(p => p.Name)
.NotEmpty().WithMessage("required field")
/*The validation should be made only if the page is Create.cshtml. That is, if you are creating a new project.*/
.When(p => p.??) //Problem Here!!
.Must(n => !_projectService.Exist(n)).WithMessage("name already exists");
RuleFor(p => p.Url)
.NotEmpty().WithMessage("required field");
}
}
Note that if the user is editing an existing project, validation of the property name should not be done again.
ProjectController.cs > Edit method
[HttpPost]
public ActionResult Edit(Guid projectID, ProjectViewModel model)
{
var project = _projectService.Repository.Get(projectID);
if (ModelState.IsValid && TryUpdateModel(project))
{
_projectService.Repository.Attach(project);
if (_projectImageWrap.Create(project) && _projectService.Repository.Save() > 0)
return AjaxRedirect("Index");
}
return View(model);
}
Notes
If I create a ViewModel for each page, there is a duplication of code since pages have the same properties.
Add a property on the ViewModel indicating what page it is being displayed does not solve my problem as to instantiate the ViewModel, I use AutoMapper.
To validate the data, I use FluentValidator.
Thank you all for your help!
My understanding is that there isn't a 1:1 correlation between ViewModels and Views. Oftentimes you will have a View that will not require a ViewModel to go alongside with it.
You will want to create a ViewModel if and only if you need a Model absolutely paralleled and tailored to a specific View. This will not be the case 100% of the time.
When the functionality / use case /validation is different between the pages I use different models. If its the exact same besides the presence of an ID or something similar I use the same model, and its also possible to just use the same view if the differences are pretty minor.
Since your validation is different, if I were doing it I would create two different models so that I could use the out of the box DataAnnotations, with your validation though it may not be required. You could also on the edit model have a readonly property for name since its not editable any longer.
For me the same object must have the same validation on every time, in main to ensure the consistence of the object, independently if it was created or edited.
i think that you should create only one validation, and edit your "exists" method to pass to verify if it is a new object or the current object in repository.
Personally, I don't have a problem with 2 view models, especially if (as Paul Tyng suggested) you use a base class for the fields that are common to edit and create scenarios.
However, if you really only want a single view model then you would either need to:
add a flag to the view model and use the When() method in your validator. Note though that this will not generate the appropriate client-side only validation
define a second validator and invoke the appropriate one from the controller (i.e. instead of the "automatic" validation)
Provide another view Edit.cshtml which will allow the user to edit the data for a selected item.
Create another view Query.cshtml which based on the ItemName will allow the users to query the Inventory table.
Perform the calculation for the total profit (numbersold times (saleprice-purchasecost). Display the total profit.
(BONUS) Create another view Sell.cshtml that will indicate the sale of an item. Adding one to NumberSold and subtract one from NumberInventory for the selected record.

Resources