I have a "New user" form both for admins and for regular users. Both form use the RegisterModel
public class RegisterModel
{
[Required]
public string Name { get; set; }
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
The difference is that on my front end "New user" page I want users to provide their own password. But in back end, I want the system to generate the password.
Since I use the same RegisterModel for both forms, I get a validateion error in the back end saying Password is required..
I thought, I could solve this by adding this to my controller:
[HttpPost]
public ActionResult New(RegisterModel model)
{
model.Password = Membership.GeneratePassword(6, 1);
if (TryValidateModel(model))
{
// Do stuff
}
return View(model);
}
But I still get the error message Password is required.. Why is this the issue when I do call TryValidate in my controller?
What would be best practice for this issue, create a separate RegisterModelBackEnd or are there any other solutions to this?
When updating model manually, you do not need to use it as parameter in Action. Also, use this overload that lets you specify only the properties on which binding will occur.
protected internal bool TryUpdateModel<TModel>(
TModel model,
string[] includeProperties
)
where TModel : class
So, the working code will be
[HttpPost]
public ActionResult New()
{
RegisterModel model = new RegisterModel();
model.Password = Membership.GeneratePassword(6, 1);
if (TryValidateModel(model, new string[] {"Name", "Email"}))
{
// Do stuff
}
return View(model);
}
You can make this even simpler, using BindAttribute
[HttpPost]
public ActionResult New([Bind(Exlude="Password")]RegisterModel model)
{
if(ModelState.IsValid)
{
model.Password = Membership.GeneratePassword(6, 1);
// Do Stuff
}
return View(model);
}
And finally simplest and the best way
Define separate view models
Related
I am trying to understand what would be the best approach to my problem. Let's say I have a model like this:
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
[Required]
public int StoreId { get; set;}
[Required]
public DateTime UpdatedAt {get; set;}
}
and I have an API controller that will have a method like the following to insert a new customer in the database:
public IHttpActionResult Insert(Customer customer)
{
customer.StoreId = 5; //just for example
customer.UpdatedAt = DateTime.Now; //again, just as example
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Customers.Add(customer);
db.SaveChanges();
return Ok(customer.Id);
}
Now as you can see in the method, let's assume the StoreId and the UpdatedAt fields won't be included in the original http request posted to this method, because those values need to be calculated and assigned at the server side (let's say basically the client is not supposed to send those values to the server side method). In this case, the ModelState won't be valid anymore, as it is missing two required fields.
One way you can get around it, is to clear the errors on the model state one by one, by doing:
ModelState["Store.Id"].Errors.Clear();
ModelState["UpdatedBy"].Errors.Clear();
and then doing the validation, but it doesn't look a good way especially if you have many fields that need to be taken care of on the server side.
What are the better solutions?
The good way ? Create a view model specific to the view and have only properties which the view is supposed to provide.
public class CustomerVm
{
public int Id { get; set; }
public string Name { get; set; }
}
In your GET action, send an object of this to the view
public ActionResult Edit(int id) //Or even Create
{
var vm=new CustomerVm { Id=id, Name="Scott to be edited"};
return View(vm);
}
Now your view will be strongly typed to this
#model CustomerVm
#using(Html.BeginForm())
{
#Html.HiddenFor(s=>s.Id)
#Html.TextBoxFor(s=>s.Name)
<input type="submit" />
}
and in your HttpPost action, Use the same view model as your method parameter, read the property values and use that
[HttpPost]
public ActionResult Create(CustomerVm model)
{
var customerEntity = new Customer { Name= model.Name };
//Stuff server should set goes now
customerEntity.StoreId = 23;
customerEntity.UpdatedAt = DateTime.Now;
db.Customers.Add(customerEntity);
db.SaveChanges();
return Ok(customerEntity.Id);
}
This problem is best explained with code:
public async Task<ActionResult> Index()
{
if (TempData.ContainsKey("ModelState"))
{
ModelState.Merge((ModelStateDictionary)TempData["ModelState"]);
var viewModelA= new ViewModelA
{
FirstName = "John"
}
return View(viewModelA);
}
}
[HttpPost]
public async Task<ActionResult> IndexPostActionMethod(ViewModelB model)
{
if (ModelState.IsValid)
{
var result = await DoSomeStuff();
if (result.Succeeded)
{
DoSomeOtherStuff();
}
else
{
AddErrors(result);
}
}
TempData["ModelState"] = ModelState;
return RedirectToAction("Index");
}
The problem here is that when I post to IndexPostActionMethod and DoSomeStuff() returns false for some reason, the form/page is rediplayed by redirecting to Index and saving the ModelState in TempData during that redirect and then once the form/page is redisplayed, the value from ModelState (or where does it get the value from?) gets set as the value for a #Html.TextBoxFor(m => m.FirstName) and not the value that I set here: var viewModelA = new ViewModelA { FirstName = "John"}. Why can't I override the value and where does it get it from? It's like the framework is automatically updating viewModelA with whatever is in the ModelStatDictionary eventhough I create a new ViewModelA with a new value.
Don't do this. The way you should be handling a GET-POST-Redirect cycle is:
public async Task<ActionResult> Index()
{
var model = new ViewModel
{
FirstName = "John"
}
return View(model);
}
[HttpPost]
public async Task<ActionResult> Index(ViewModel model)
{
if (ModelState.IsValid)
{
var result = await DoSomeStuff();
if (result.Succeeded)
{
DoSomeOtherStuff();
}
else
{
AddErrors(result);
}
}
return View(model);
}
ModelState rules all. Regardless of what you pass to the view explicitly, if the value is present in ModelState it will take precedence. Using TempData to pass the ModelState to another view is such a code smell, "smell" doesn't even really cover it.
If your only goal is to use one view model for the GET action and another, different, view model for the POST action, then you should really just stop and ask yourself seriously why you need to do that. In all the years I've worked with MVC, I've never had to do that, never needed to do that, and I can't imagine a scenario where I would need to do that.
UPDATE
So, based on the scenario you laid out in the comments, I would design it like this:
public class IndexViewModel
{
public SomeTabViewModel SomeTab { get; set; }
public AnotherTabViewModel AnotherTab { get; set; }
public FooTabViewModle FooTab { get; set; }
}
public class SomeTabViewModel
{
[Required]
public string SomeRequiredField { get; set; }
}
public class AnotherTabViewModel
{
[Required]
public string AnotherRequiredField { get; set; }
}
public class FooTabViewModel
{
[Required]
public string FooRequiredField { get; set; }
}
Basically, you break up the individual POST pieces into view models that altogether make up one big view model. Then, model validation only follows actual internal view model instances of the container. So, for example if you postback to FooTab.FooRequiredField only, then only FooTab is instantiated. The other two view model properties will remain null and will not be validated internally (i.e. the fact that they have required fields doesn't matter).
I have read many times experts of MVC saying that if I were to use a SelectList, it's best to have a IEnumerable<SelectList> defined in my model.
For example, in this question.
Consider this simple example:
public class Car()
{
public string MyBrand { get; set; }
public IEnumerable<SelectListItem> CarBrands { get; set; } // Sorry, mistyped, it shoudl be SelectListItem rather than CarBrand
}
In Controller, people would do:
public ActionResult Index()
{
var c = new Car
{
CarBrands = new List<CarBrand>
{
// And here goes all the options..
}
}
return View(c);
}
However, from Pro ASP.NET MVC, I learned this way of Creating a new instance.
public ActionResult Create() // Get
{
return View()
}
[HttpPost]
public ActionResult Create(Car c)
{
if(ModelState.IsValid) // Then add it to database
}
My question is: How should I pass the SelectList to View? Since in the Get method there is no model existing, there seems to be no way that I could do this.
I could certainly do it using ViewBag, but I was told to avoid using ViewBag as it causes problems. I'm wondering what are my options.
You could create a ViewModel that has all the properties of Car which you want on your form then make your SelectList a property of that ViewModel class
public class AddCarViewModel
{
public int CarName { get; set; }
public string CarModel { get; set; }
... etc
public SelectList MyList
{
get;
set;
}
}
Your controller will look like
public ActionResult Create() // Get
{
AddCarViewModel model = new AddCarViewModel();
return View(model)
}
[HttpPost]
public ActionResult Create(AddCarViewModel c)
{
if(ModelState.IsValid) // Then add it to database
}
MarkUp
#Html.DropDownListFor(#model => model.ListProperty, Model.MyList, ....)
Easy way, this is a copy of my code
without model
In the controller
ViewBag.poste_id = new SelectList(db.Postes, "Id", "designation");
In the view
#Html.DropDownList("poste_id", null," -- ", htmlAttributes: new { #class = "form-control" })
I have a layered application that send commands to the business layer (actually, the application is based on ncqrs framework, but I don't think it's important here).
A command looks like this :
public class RegisterUserCommand : CommandBase
{
public string UserName { get; set; }
public string Email{ get; set; }
public DateTime RegistrationDate { get; set; }
public string ApiKey {get; set;} // edit
}
There is no logic in this class, only data.
I want to have the users type their user name, email and I want the system to use the current date to build the command.
What is best between :
create a strongly typed view based on the RegisterUserCommand, then inject the date and the APi Key just before sending it to the business layer ?
create a RegisterUserViewModel class, create the view with this class and create the command object based on the view input ?
I wrote the following code (for the solution n°2) :
public class RegisterController : Controller
{
//
// GET: /Register/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
RegistrationDate = DateTime.UtcNow,
Email= registrationData.Email,
UserName= registrationData.Name,
ApiKey = "KeyFromConfigSpecificToCaller" // edit
}
);
return View();
}
public class RegisterUserViewModel
{
[Required]
[StringLength(16)]
public string Name { get; set; }
[Required]
[StringLength(64)]
public string Email{ get; set; }
}
}
This code is working... but I wonder if I choose the correct way...
thanks for advises
[Edit] As the Datetime seems to cause misunderstanding, I added another property "ApiKey", that should also be set server side, from the web layer (not from the command layer)
[Edit 2] try the Erik suggestion and implement the 1st solution I imagined :
[HttpPost]
public ActionResult Index(RegisterUserCommand registrationCommand)
{
var service = NcqrsEnvironment.Get<ICommandService>();
registrationCommand.RegistrationDate = DateTime.UtcNow;
registrationCommand.ApiKey = "KeyFromConfigSpecificToCaller";
service.Execute(
registrationCommand
);
return View();
}
... Is it acceptable ?
I think you would be better off with option #2, where you would have a separate ViewModel and a Command. While it may seem redundant (to an extent), your commands are really messages from your web server to your command handler. Those messages may not be formatted the same as your ViewModel, nor should they be. And if you're using NCQRS as is, you would then have to map your commands to your AR methods and constructors.
While it may save you a little bit of time, I think you pigeon-hole yourself in to modeling your domain after your ViewModels, and that should not be the case. Your ViewModels should be a reflection of what your user experiences and sees; your domain should be a reflection of your business rules and knowledge, and are not always reflected in your view.
It may seem like a bit more work now, but do yourself a favor and keep your commands separate from your view models. You'll thank yourself later.
I hope this helps. Good luck!
I would recommend putting this into the constructor of the RegisterUserCommand class. That way the default behavior is always to set it to DateTime.UtcNow, and if you need to set it to something explicitly you can just add it to the object initializer. This will also help in scenarios where you're using this class in other parts of your project, and you forget to set the RegistrationDate explicitly.
public class RegisterUserCommand : CommandBase
{
public string UserName { get; set; }
public string Email{ get; set; }
public DateTime RegistrationDate { get; set; }
public RegisterUserCommand()
{
RegistrationDate = DateTime.UtcNow;
}
}
And the Controller
public class RegisterController : Controller
{
//
// GET: /Register/
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
Email= registrationData.Email,
OpenIdIdentifier = registrationData.OpenIdIdentifier
}
);
return View();
}
public class RegisterUserViewModel
{
[Required]
[StringLength(16)]
public string Name { get; set; }
[Required]
[StringLength(64)]
public string Email{ get; set; }
}
}
I would use number 1 and use the system.componentmodel.dataannotations.metadatatype for validation.
I created an example (answer) for another SO question Here.
This allows you to keep your model in another library, validate the fields and show the fields like you would internal/private classes with DataAnnotations. I'm not a big fan of creating a completely separate class for a view that has no additional value while having to ORM the data back to another class. (If you had additional values like dropdown list values, or default values then I think it would make sense).
Instead of
[HttpPost]
public ActionResult Index(RegisterUserViewModel registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
service.Execute(
new RegisterUserCommand
{
RegistrationDate = DateTime.UtcNow,
Email= registrationData.Email,
UserName= registrationData.Name,
ApiKey = "KeyFromConfigSpecificToCaller" // edit
}
);
return View();
}
You can have
[HttpPost]
public ActionResult Index(RegisterUserCommand registrationData)
{
var service = NcqrsEnvironment.Get<ICommandService>();
registrationData.ApiKey = "KeyFromConfigSpecificToCaller";
service.Execute(registrationData);
return View();
}
so according to Gu IValidatableObject.Validate() should get called when a controller validates it's model (i.e. before ModelState.IsValid) however simply making the model implement IValidatableObject doesn't seem to work, because Validate(..) doesn't get called.
Anyone know if there is something else I have to wire up to get this to work?
EDIT:
Here is the code as requested.
public class LoginModel : IValidatableObject
{
[Required]
[Description("Email Address")]
public string Email { get; set; }
[Required]
[Description("Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[DisplayName("Remember Me")]
public bool RememberMe { get; set; }
public int UserPk { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var result = DataContext.Fetch( db => {
var user = db.Users.FirstOrDefault(u => u.Email == Email);
if (user == null) return new ValidationResult("That email address doesn't exist.");
if (user.Password != User.CreateHash(Password, user.Salt)) return new ValidationResult("The password supplied is incorrect.");
UserPk = user.UserPk;
return null;
});
return new List<ValidationResult>(){ result };
}
}
The action. ( I don't do anything special in the Controller...)
[HttpPost]
public ActionResult Login(LoginModel model)
{
if (ModelState.IsValid)
{
FormsAuthentication.SetAuthCookie(model.Email, model.RememberMe);
return Redirect(Request.UrlReferrer.AbsolutePath);
}
if (ControllerContext.IsChildAction || Request.IsAjaxRequest())
return View("LoginForm", model);
return View(model);
}
I set a break point on the first line of LoginModel.Validate() and it doesn't seem to get hit.
There isn't anything more than that you just have to add it to the model you're validating. Here's an example of validation
public class User : IValidatableObject {
public Int32 UserID { get; set; }
public string Name { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
//do your validation
return new List<ValidationResult>();
}
}
And your controller would use this model
public ActionResult Edit(User user) {
if (ModelState.IsValid) {
}
}
Hope this helps. Other requirements are .net 4 and data annotations - which you obviously need jsut for ivalidatableobject. Post any issues and we'll see if we can't resolve them - like post your model and your controller...you might be missing something.
Validation using the DefaultModelBinder is a two stage process. First, Data Annotations are validated. Then (and only if the data annotations validation resulted in zero errors), IValidatableObject.Validate() is called. This all takes place automatically when your post action has a viewmodel parameter. ModelState.IsValid doesn't do anything as such. Rather it just reports whether any item in the ModelState collection has non-empty ModelErrorCollection.