How to bind a model property with DefaultModelBinder - ASP.NET MVC2 - asp.net-mvc-2

I have the following scenario.
I have the Edit/Employee view populated with a model from an Entity Framework entity (Employee)
I post from Edit/Employee to the Save/Employee controller action. The Save/Employee action expect another type (EmployeeSave) which has Employee as property
This is the Edit/Employee method
public ActionResult Edit(EmployeesEdit command)
{
var employee = command.Execute();
if (employee != null)
{
return View(employee);
}
return View("Index");
}
This is the Save/Employee method
public ActionResult Save(EmployeesSave command)
{
var result = command.Execute();
if (result)
{
return View(command.Employee);
}
return View("Error");
}
This is the EmployeeSave class
public class EmployeesSave
{
public bool Execute()
{
// ... save the employee
return true;
}
//I want this prop populated by my model binder
public Employee Employee { get; set; }
}
The MVC DefaultModelBinder is able to resolve both Employee and EmployeeSave classes.

You might need to use BindAttribute here. If your view contains the properties of the EmployeeSaveViewModel and Employee named like this (I made up property names)
<input type="text" name="EmployeeSaveViewModel.Property1" />
<input type="text" name="EmployeeSaveViewModel.Employee.Name" />
<input type="text" name="EmployeeSaveViewModel.Employee.SomeProperty" />
Then, your action could look like this:
[HttpPost]
public ActionResult Save([Bind(Prefix="EmployeeSaveViewModel")]
EmployeeSaveViewModel vm)
{
if(ModelState.IsValid)
{
// do something fancy
}
// go back to Edit to correct errors
return View("Edit", vm);
}

You could resolve it by passing the edited data back to Edit action that handles HttpPost. Inside create EmployeeSave object and assign its Employee property the value of Employee returned to yout Edit action. Call Save action by passing EmployeeSave object.
[HttpGet]
public ActionResult Edit()
{
return View();
}
[HttpPost]
public ActionResult Edit(Employee employee)
{
EmployeeSave employeeSave = new EmployeeSave { Employee = employee };
return View("Save", employeeSave);
}
Another method would be to use EmployeeSave instead of Employee as your model.

Related

How and where to validate uniqueness of attribute/property in ASP.NET Core MVC using Entity Framework Core 6

I want to check, that users table records does not have specific email already stored.
If there is, then ModelState.IsValid returns false in controller action.
I understand need of unique constraint and I understand issues with race conditions. Those are not my concern right now.
At this point I just want to make ModelState.IsValid to return false after querying data in right place and making model invalid.
Should I implement such validation:
in DbContext?
in my entity classes (User / Company etc.)?
in SomeCustomValidation class?
directly in controller (since there I can already query the database...)
somewhere else...
And nice bonus would be create solution reusable across all entities :)
How should I do it?
You can custom validation attribute like below:
public class TestEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value,
ValidationContext validationContext)
{
var context = (YourDbContext)validationContext.GetService(typeof(YourDbContext));
if(!context.User.Any(a=>a.Email==value.ToString()))
{
return ValidationResult.Success;
}
return new ValidationResult("Email exists");
}
}
Model:
public class User
{
public int Id { get; set; }
[TestEmail]
public string Email { get; set; }
}
View(Test.cshtml):
#model User
<form method="post" asp-action="Test" asp-controller="Home">
<div class="form-group">
<input asp-for="Email" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<input type="submit" value="Post"/>
</form>
Controller:
//GET render the Test.cshtml
public async Task<IActionResult> Test()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Test(User user)
{
if(!ModelState.IsValid)
{
return View(user);
}
return RedirectToAction("Index");
}
better way is that you check it before every insert or update by :
if(db.Table.Any(x => x.UniqueCloumn == newValue))
error = "this record is already exist"
else
{
db.Table.Add(newObject);
db.Savechanges()
}
also there is some approach for reusable code that I do not recommend :
https://www.codeproject.com/Tips/1143215/Validate-a-Unique-Constraint-at-dbContext-Validate

Creating a record with values passed by query string

I am creating a record in a table that has foriegn key in it. The foriegn key is getting passed in the query string and I have set the value in the ViewBag. I have added this to the form but it will not work.
Here is the code from the controller:
public ActionResult Create(int propertyId)
{
ViewBag.storagePropertyId = propertyId;
return View();
}
Here is the code from the view.
#Html.HiddenFor(model => model.propertyId, new { value =ViewBag.propertyId })
Is this how I should be doing this? If so, is there a problem with that form
Why not just set the value of the Model.PropertyID in the controller, or better yet in a viewmodel.
ViewModel
public class MyViewModel()
{
public int PropertyID { get; set; }
public MyViewModel() { }
public MyViewModel(int propertyID)
{
this.PropertyID = propertyID;
}
}
Action Result
public ActionResult Create(int propertyId)
{
return View(new MyViewModel(propertyId));
}
View
#model MyViewModel
#Html.HiddenFor(m => m.PropertyID)
Then the value of the Hidden field will contain the value of the property ID.

MVC3 - Accessing contents of dropdownlist in Controller

Im new to MVC2/3 so keep that in mind. Also, using Ajax or jQuery is NOT an option.
I have a web page where the user must choose an item out of a dropdown list and then hit a "Filter" button. (Clicking this button will simply trigger the default POST action in my controller and return a filtered list of results.
I have everything working except I am running into one issue. When the Filter action is complete and returns control back to my view, the dropdown list contents are lost (ie, null). The results are returned no problem, it's just that my dropdown list is blank - thus preventing user from selecting another item out of the list.
Am I supposed to re-fill the dropdown list on the Filter action or is there a cleaner way to do this?
Here is a snapshot of my code:
My ViewModel
public class MyViewModel {
[DisplayName("Store")]
public IEnumerable<Store> StoreList { get; set; }
public string SelectedStore { get; set; }
}
My View (Index.cshtml)
#using (Html.BeginForm()) {
<h2>Search</h2>
#Html.LabelFor(m => m.StoreList)
#Html.DropDownListFor(m => m.SelectedStore, new SelectList(Model.StoreList, "StoreCode", "StoreCode"), "Select Store")
<input type="submit" value="Filter" />
}
My Controller:
public class MyController : Controller
{
public ActionResult Index() {
MyViewModel vm = new MyViewModel();
var storelist = new List<Store>();
storelist.Add(new Store { StoreCode = "XX" });
storelist.Add(new Store { StoreCode = "YY" });
storelist.Add(new Store { StoreCode = "ZZ" });
vm.StoreList = storelist;
return View(vm);
}
[HttpPost]
public ActionResult Index(MyViewModel model, string SelectedStore, FormCollection collection) {
if (ModelState.IsValid) {
/* this works, model state is valid */
/* BUT, the contents of model.StoreList is null */
}
return View( model);
}
}
Yes, you have to repopulate any models (including ViewData) that are passed to the view. Remember, it's a stateless system, your controller is re-instantiated with every call and starts effectively from scratch.
I would do it thus:
public class MyController : Controller
{
private List<Store> GetStoreList()
{
List<Store> StoreList = new List<Store>();
// ... Do work to populate store list
return StoreList;
}
public ActionResult Index() {
MyViewModel vm = new MyViewModel();
vm.StoreList = GetStoreList();
return View(vm);
}
[HttpPost]
public ActionResult Index(MyViewModel model, string SelectedStore, FormCollection collection) {
if (ModelState.IsValid) {
/* this works, model state is valid */
/* BUT, the contents of model.StoreList is null */
}
model.StoreList = GetStoreList();
return View( model);
}
}
Short answer is yes, you need to refill the drop-down list in the Filter action. ASP.NET MVC isn't WebForms - there is no ViewState to preserve the contents of your list.
Again fill the dropdown as mvc don't have View state
[HttpPost]
public ActionResult Index(MyViewModel model, string SelectedStore, FormCollection collection) {
if (ModelState.IsValid) {
/* this works, model state is valid */
/* BUT, the contents of model.StoreList is null */
}
model.StoreList = GetStoreList();
return View( model);
}

ModelState.IsValid works on a "new" form, but not on an "edit" form

I have a ASP.NET MVC 2.0 application using Entity Framework. All my views use view models, most of them complex. Meaning...the object to be edited is a property of the view model, and not the view model itself.
I am using partial classes with data annotations, and checking ModelState.IsValid inside the POST actions in the controller.
I have a "NEW" form and an "EDIT" form for a simple object with 3 fields!
The ModelState.IsValid check works on the NEW form, and shows the correct "required field" errors, if I try to submit a blank form.
But if I load an EDIT form, and clear the values from some textboxes that are required, and submit the form, I do NOT get validation errors, I just get an exception:
Error executing child request for handler 'System.Web.Mvc.HttpHandlerUtil+ServerExecuteHttpHandlerWrapper'.
So my question is, does ModelState.IsValid not work with an EDIT form, since perhaps it's looking at the values from the view model object that were loaded, instead of the FormCollection?
// this one does not validate
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int accountStatusKey, AccountStatusEditViewModel model, FormCollection values)
{
if (ModelState.IsValid)
{
db.UpdateAccountStatus(accountStatusKey, values);
return RedirectToAction("States");
}
else
{
return View("Edit", model);
}
}
// this one does validate
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(AccountStatusNewViewModel model, FormCollection values)
{
if (ModelState.IsValid)
{
db.AddAccountStatus(values);
return View("States", new AccountStatusStatesViewModel());
}
else
{
return View("New", model);
}
}
// how I arrive AT the edit form
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult Edit(int accountStatusKey)
{
return View("Edit", new AccountStatusEditViewModel(accountStatusKey));
}
// and finally, the view model code
public class AccountStatusEditViewModel : ViewModelBase
{
public AccountStatus AccountStatus { get; private set; }
public IEnumerable States { get; private set; }
public List StatusTypes { get; private set; }
public AccountStatusEditViewModel(int accountStatusKey)
{
AccountStatus = db.GetAccountStatusByKey(accountStatusKey);
States = db.GetAllStates();
StatusTypes = new List();
StatusTypes.Add("Primary Status");
StatusTypes.Add("Secondary Status");
StatusTypes.Add("External Status");
}
public AccountStatusEditViewModel()
{
}
}
// this action method does not work at all either - no db updating, no validation
// the page simply redirects to "States" view, which should only happen if the db
// was being updated, right? But nothing is changing in the DB, and the validation
// never happens.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(AccountStatusEditViewModel model)
{
if (ModelState.IsValid)
{
if (TryUpdateModel(model, "AccountStatus"))
{
return RedirectToAction("States");
}
else
{
return View("Edit", model);
}
}
else
{
return View("Edit", model);
}
}
Since the 2.0 version of MVC i'm not using the formcollection anymore.
I only use the viewmodel in the action's parameter when i have a post, like this:
[HttpPost]
public ActionResult Activatecard(ActivateCardViewModel model)
{
When 'it' can't create my model (entered blabla for a datetime field, or when the validations are not met (i use the validation attributes from the System.ComponentModel.DataAnnotations namespace) i get the ModelState.IsValid equals to false.
I created a blank asp.net mvc2 app, and this was the model that the standard template used for the logon action.
Please eliminate the FromCollection parameters. You can use the default ModelBinders for your view model. Asp.net MVC try to maps the values from your form into your model.
Can you please post the action method, which leads you to the edit form, too?

How to prevent validation of relationships in model binder?

An example, if I have a class named Order with a field referencing a Customer, and then an Order form with an drop down list (<%= Html.DropDownListFor(e => e.Customer.ID, new SelectList(...)) %>) for setting the Customer the model binder will create an empty Customer with only the ID set. This works fine with NHibernate but when validation is added to some of the fields of the Customer class, the model binder will say these fields are required. How could I prevent the model binder from validating these references?
Thanks!
Ages old question, but I figured I'd answer anyways for posterity. You need a custom model binder in this situation to intercept that property before it attempts to bind it. The default model binder will recursively attempt to bind properties using their custom binder, or the default one if not set.
The override you're looking for in DefaultModelBinder is GetPropertyValue. This is called over all properties in the model, and by default it calls back to DefaultModelBinder.BindModel - the entry point to the whole process.
Simplified model:
public class Organization
{
public int Id { get; set; }
[Required]
public OrganizationType Type { get; set; }
}
public class OrganizationType
{
public int Id { get; set; }
[Required, MaxLength(30)]
public string Name { get; set; }
}
View:
<div class="editor-label">
#Html.ErrorLabelFor(m => m.Organization.Type)
</div>
<div class="editor-field">
#Html.DropDownListFor(m => m.Organization.Type.Id, Model.OrganizationTypes, "-- Type")
</div>
Model Binder:
public class OrganizationModelBinder : DefaultModelBinder
{
protected override object GetPropertyValue(
ControllerContext controllerContext,
ModelBindingContext bindingContext,
System.ComponentModel.PropertyDescriptor propertyDescriptor,
IModelBinder propertyBinder)
{
if (propertyDescriptor.PropertyType == typeof(OrganizationType))
{
var idResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + ".Id");
if (idResult == null || string.IsNullOrEmpty(idResult.AttemptedValue))
{
return null;
}
var id = (int)idResult.ConvertTo(typeof(int));
// Can validate the id against your database at this point if needed...
// Here we just create a stub object, skipping the model binding and
// validation on OrganizationType
return new OrganizationType { Id = id };
}
return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder);
}
}
Note that in the View we create the DropDownListFor the model.foo.bar.Id. In the model binder, ensure that this is added to the model name as well. You can leave it off of both, but then DropDownListFor has a few issues finding the selected value without pre-selecting it in the SelectList you send it.
Finally, back in the controller, be sure to attach this property in your database context (if you're using Entity Framework, others might handle differently). Otherwise, it isn't tracked and the context will attempt to add it on save.
Controller:
public ActionResult Create(Organization organization)
{
if (ModelState.IsValid)
{
_context.OrganizationTypes.Attach(organization.Type);
_context.Organizations.Add(organization);
_context.SaveChanges();
return RedirectToAction("Index");
}
// Build up view model...
return View(viewModel);
}