How to add a bind list to the TryUpdateModel in asp.net mvc3 - asp.net-mvc-3

I have the following action method:-
[HttpPost]
public ActionResult Edit(int id, FormCollection collection)
{
Assessment a = elearningrepository.GetAssessment(id);
try
{
if (TryUpdateModel(a))
{
elearningrepository.Save();
return RedirectToAction("Details", new { id = a.AssessmentID });
}
}
//code does here
but I can not write something like if (TryUpdateModel(a, "Assessment", new string { "Date"})) to specify that I only allow the Date property to be updated.
So how I can add a bind list to the above if (TryUpdateModel(a))?
BR

but I can not write something like
if (TryUpdateModel(a, "Assessment", new string { "Date"}))
That's because you should write it like this, since the allowed properties argument represents a string array:
if (TryUpdateModel(a, "Assessment", new[] { "Date" }))
{
}

I would suggest that you stay away from using TryUpdateModel in general.
The repository usually has an update method that sets the entityState to modified before Save() is called, i cannot see that in the code above.
If your goal is to display a record and only allow date to be saved, then create a view for that model, and render fields with:
This sets the model for the view:
#model YourNamespace.Models.Assessment
#Html.DisplayFor(model=>model.propertyToDisplay)
on the items you only want to display, and a
#Html.EditorFor(model=>model.Date)
In your action controller you take the properties you want to bind to as input parameters:
Edited
class Assessment
{
public int Id { get; set; }
public DateTime Date { get; set; }
//Other properties
}
public ActionResult Edit(int Id, DateTime Date)
{
var assessment = elearningrepository.GetAssessment(id);
assessment.Date = Date;
elearningrepository.UpdateAssessment(assessment);
elearningrepository.Save();
//Redirect to action Detail
}
In this case the model binder should just bind to Id, and Date, so even if someone tries to post other values (editing the html form is easy), parameters in ActionResult should be named exactly as in the Model and use that to fetch and update the entity.
You should validate that the user actually can access and edit that id, or as an alternative use MVC Security Codeplex to check that the Id parameter has not been tampered with. it is really easy and convenient to use, but that is another discussion.
As an alternative you can use an attribute like this, described in this blog, but I don't use that myself:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create( [Bind(Include="Id,Date")] Assessment assessment)

i tried this an it works fine
string[] allowedProperties = new[] { "Date" };
try
{
if (TryUpdateModel(a, allowedProperties))
{

Related

Single property not getting bound on HttpPost

I'm working on the first MVC3 project at our company, and I've hit a block. No one can seem to figure out what's going on.
I have a complex Model that I'm using on the page:
public class SpaceModels : List<SpaceModel> {
public bool HideValidation { get; set; }
[Required(ErrorMessage=Utilities.EffectiveDate + Utilities.NotBlank)]
public DateTime EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
}
In the Controller, I create a SpaceModels object with blank SpaceModels for when Spaces get combined (this would be the destination Space).
// Need a list of the models for the View.
SpaceModels models = new SpaceModels();
models.EffectiveDate = DateTime.Now.Date;
models.DisplayEffectiveDate = true;
models.Add(new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
return View("CombineSpaces", models);
Then in the View, I am using that SpaceModels object as the Model, and in the form making a TextBox for the Effective Date:
#model Data.SpaceModels
#using (Html.BeginForm("CombineSpaces", "Space")) {
<div class="EditLine">
<span class="EditLabel LongText">
New Space Open Date
</span>
#Html.TextBoxFor(m => m.EffectiveDate, new {
size = "20",
#class = "datecontrol",
// Make this as a nullable DateTime for Display purposes so we don't start the Calendar at 1/1/0000.
#Value = Utilities.ToStringOrDefault(Model.EffectiveDate == DateTime.MinValue ? null : (DateTime?)Model.EffectiveDate, "MM/dd/yyyy", string.Empty)
})
#Html.ValidationMessageFor(m => m.EffectiveDate)
</div>
<hr />
Html.RenderPartial("_SpaceEntry", Model);
}
The Partial View that gets rendered iterates through all SpaceModels, and creates a containing the Edit fields for the individual SpaceModel objects. (I'm using the List to use the same Views for when the Spaces get Subdivided as well.)
Then on the HttpPost, the EffectiveDate is still back at it's DateTime.MinValue default:
[HttpPost]
public ActionResult CombineSpaces(SpaceModels model, long siteID, long storeID, DateTime? effectiveDate) {
// processing code
}
I added that DateTime? effectiveDate parameter to prove that the value when it gets changed does in fact come back. I even tried moving the rendering of the TextBox into the _SpaceEntry Partial View, but nothing worked there either.
I did also try using the #Html.EditorFor(m => m.EffectiveDate) in place of the #Html.TextBoxFor(), but that still returned DateTime.MinValue. (My boss doesn't like giving up the control of rendering using the #Html.EditorForModel by the way.)
There has to be something simple that I'm missing. Please let me know if you need anything else.
Looking at the source code for DefaultModelBinder, specifically BindComplexModel(), if it detects a collection type it will bind the individual elements but will not attempt to bind properties of the list object itself.
What model binding does is attempt to match the names of things or elements in the view to properties in your model or parameters in your action method. You do not have to pass all of those parameters, all you have to do is add them to your view model, then call TryUpdateModel in your action method. I am not sure what you are trying to do with SpaceModel or List but I do not see the need to inherit from the List. Im sure you have a good reason for doing it. Here is how I would do it.
The view model
public class SpacesViewModel
{
public DateTime? EffectiveDate { get; set; }
public bool DisplayEffectiveDate { get; set; }
public List<SpaceModel> SpaceModels { get; set; }
}
The GET action method
[ActionName("_SpaceEntry")]
public PartialViewResult SpaceEntry()
{
var spaceModels = new List<SpaceModel>();
spaceModels.Add(
new SpaceModel { StoreID = storeID, SiteID = siteID, IsActive = true });
var spacesVm = new SpacesViewModel
{
EffectiveDate = DateTime.Now,
DisplayEffectiveDate = true,
SpaceModels = spaceModels
};
return PartialView("_SpaceEntry", spacesVm);
}
The POST action method
[HttpPost]
public ActionResult CombineSpaces()
{
var spacesVm = new SpacesViewModel();
// this forces model binding and calls ModelState.IsValid
// and returns true if the model is Valid
if (TryUpdateModel(spacesVm))
{
// process your data here
}
return RedirectToAction("Index", "Home");
}
And the view
<label>Effective date: </label>
#Html.TextBox("EffectiveDate", Model.EffectiveDate.HasValue ?
Model.EffectiveDate.Value.ToString("MM/dd/yyyy") : string.empty,
new { #class = "datecontrol" })
Sometimes you need to explicitly bind form data using hidden fields such as
#Html.HiddenField("EffectiveDate", Model.EfectiveDate.)
In order to bind the properties of the SpaceModel object you can add individual properties such as SiteID to the view model or add a SpaceModel property for a single SpaceModel. If you want to successfully bind a complex model, add it as a Dictionary populated with key-value pairs rather than a List. You should then add the dictionary to the view model. You can even add a dictionary of dictionaries for hierarchical data.
I hope this helps :)

context.SaveChanges() works, but database is never updated? (MVC 3)

I've written a form in ASP.NET MVC3, and I can't get the entry to save the changes I make in the database, but while debugging, I noticed that the changes were reflected in the data context. I am experiencing no errors running this code. Let me know if you need more. Thanks!
Controller
[HttpPost]
public ActionResult Edit(Tool tool, FormCollection collection)
{
if (collection["Tool.Person.PersonID"] != "")
{
tool.Person= context.People.Find(
Convert.ToInt32(collection["Tool.Person.PersonID"])
);
}
if (collection["Tool.Company.CompanyID"] != "")
{
tool.Company = context.Companies.Find(
Convert.ToInt32(collection["Tool.Company.CompanyID"])
);
}
if (ModelState.IsValid)
{
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
The first two if statements are checking to see if the user inputted a person or company, and the information is passed via the FormCollection. PersonID and CompanyID are primary keys for Person and Company, respectively. I went through the method line by line multiple times and achieve the same result - after context.SaveChanges();, the context reflects the changes, but the database entries remain null for both Person_PersonID and Company_CompanyID.
Try using a view model and accessing the database after the user submits the form.
This should get you well on your way.
ViewModel
using System.ComponentModel.DataAnnotations;
namespace Project.ViewModels
{
public class _tools
{
[Required(ErrorMessage="ToolID is required")]
public int32 ToolID{ get; set; } //whatever ID you use to retrieve the Tool from the database.
[Required(ErrorMessage="PersonID is required")]
public int32 PersonID{ get; set; }
[Required(ErrorMessage="CompanyID is required")]
public int32 CompanyID{ get; set; }
}
}
Controller Post
[HttpPost]
public ActionResult Edit(_tool viewModel)
{
if (ModelState.IsValid)
{
Tool tool = db.GetTool(viewModel.ToolID) //whatever method you use to get a current version of the row. You already do this before you send the data to the client, so just copy that code
tool.Person = viewModel.PersonID
tool.Company = viewModel.CompanyID
context.Entry(tool).State = EntityState.Modified;
context.SaveChanges();
return RedirectToAction("Index");
}
return View(tool);
}
View
#model = _tool
#using(Html.BeginForm("Edit", "ControllerNameHere", FormMethod.Post, null))
{
#Html.HiddenFor(model => model.ToolID)
#*Also add whatever inputs you use to get PersonID and CompanyID from the user.
Make sure to either use the #Html helpers or to give them names that correspond.
i.e. name one input PersonID and the other CompanyID*#
<input type="submit" value="Edit">
}

How to use different type of parameter without creating new view?

I would like to create a list with a string and an int value at the same time like follows:
#Html.ActionLink("Back to List", "IndexEvent", new { location = "location" })
and
#Html.ActionLink("Back to List", "IndexEvent", new { locationID = 1 })
It didn't work. I guess MVC controller didn't get the type difference of parameter. So, I had to make a new Action as "IndexEvenyByID" but it requires to have a new view. Since I wanted to keep it simple, is there any way to use same view with respect to different parameters?
Try adding two optional parameters to the IndexEvent action like this:
public ActionResult IndexEvent(string location = "", int? locationID = null)
This should not require a new view or view model. You should have two actions as you have described, but the code could be as follows:
Controller
public ActionResult GetEvents(string location){
var model = service.GetEventsByLocation(location);
return View("Events", model);
}
public ActionResult GetEventsById(int id){
var model = service.GetEventsById(id);
return View("Events", model);
}
Service
public MyViewModel GetEventsByLocation(string location){
//do stuff to populate a view model of type MyViewModel using a string
}
public MyViewModel GetEventsById(int id){
//do stuff to populate a view model of type MyViewModel using an id
}
Basically, if your View is going to use the same view model and the only thing that is changing is how you get that data, you can completely reuse the View.
If you really want to stick to a single action and multiple type, you could use a object parameter.
public ActionResult GetEvents(object location)
{
int locationID;
if(int.TryParse(location, out locationID))
var model = service.GetEventsByID(locationID);
else
var model = service.GetEventsByLocation(location as string);
return View("Events", model);
}
Something like that (Not completly right but it gives you an idea). This, however, wouldn't really be a "clean" way to do it IMO.
(Edit)
But the 2 actions method is still by far preferable (eg. What happens if we're able to parse a location name into a int?)

ASP.NET MVC 3 Viewmodel Pattern

I am trying to work out the best way of using a viewmodel in the case of creating a new object.
I have a very simple view model that contains a contact object and a select list of companies.
private ICompanyService _Service;
public SelectList ContactCompanyList { get; private set; }
public Contact contact { get; private set; }
public ContactCompanyViewModel(Contact _Contact)
{
_Service = new CompanyService();
contact = _Contact;
ContactCompanyList = GetCompanyList();
}
private SelectList GetCompanyList()
{
IEnumerable<Company> _CompanyList = _Service.GetAll();
return new SelectList(_CompanyList, "id", "name");
}
I then have contact controller that uses this viewmodel and enable me to select a related company for my contact.
[Authorize]
public ActionResult Create()
{
return View(new ContactCompanyViewModel(new Contact()));
}
My issue is with the create method on the controller.
[Authorize]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create(Contact _Contact)
{
try
{
_Service.Save(_Contact);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
The problem is that the view returns an empty contact object, but! the company id is populated, this is because the dropdown list explicitly declares its field name.
#Html.DropDownList("parent_company_id",Model.ContactCompanyList)
The standard html form fields pass the objects values back in the format of contact.forename when using the HTML.EditorFor helper...
#Html.EditorFor(model => model.contact.forename)
I can access them if I use a FormCollection as my create action method paremeter and then explicitly search for contact.value but I cannot use a Contact object as a parameter to keep my code nice and clean and not have to build a new contact object each time.
I tried passing the actual view model object back as a parameter but that simply blows up with a constructor error (Which is confusing seeing as the view is bound to the view model not the contact object).
Is there a way that I can define the name of the Html.EditFor field so that the value maps correctly back to the contact object when passed back to the create action method on my controller? Or Have I made some FUBAR mistake somewhere (that is the most likely explanation seeing as this is a learning exercise!).
Your view model seems wrong. View models should not reference any services. View models should not reference any domain models. View models should have parameterless constructors so that they could be used as POST action parameters.
So here's a more realistic view model for your scenario:
public class ContactCompanyViewModel
{
public string SelectedCompanyId { get; set; }
public IEnumerable<SelectListItem> CompanyList { get; set; }
... other properties that the view requires
}
and then you could have a GET action that will prepare and populate this view model:
public ActionResult Create()
{
var model = new ContactCompanyViewModel();
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
and a POST action:
[HttpPost]
public ActionResult Create(ContactCompanyViewModel model)
{
try
{
// TODO: to avoid this manual mapping you could use a mapper tool
// such as AutoMapper
var contact = new Contact
{
... map the contact domain model properties from the view model
};
_Service.Save(contact);
return RedirectToAction("Index");
}
catch
{
model.CompanyList = _Service.GetAll().ToList().Select(x => new SelectListItem
{
Value = x.id.ToString(),
Text = x.name
});
return View(model);
}
}
and now in your view you work with your view model:
#model ContactCompanyViewModel
#using (Html.BeginForm())
{
#Html.DropDownListFor(x => x.SelectedCompanyId, Model.CompanyList)
... other input fields for other properties
<button type="submit">Create</button>
}

Can I reuse a remote validation action in MVC3

I am using a Remote validation attribute on my view model to validate a Bank Account that is specified for my Company:
ViewModel:
[Remote("CheckDefaultBank", "Company")]
public string DefaultBank
{
This in the controller I have:
[HttpGet]
public JsonResult CheckDefaultBank(string defaultBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
That all works well. But, I have two other banks related to my company as well. However, when the remote validation js calls the action it uses a parameter mactching the field name of "DefaultBank"... so I use that as a parameter in my action.
Is there some attribute I can add in the view so that it will use a parameter of say "bankId" on the ajax get so I don't need an action for each field which are basically exactly the same?
The goal here is to eliminate now having to have this in my controller:
[HttpGet]
public JsonResult CheckRefundBank(string refundBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult CheckPayrollBank(string payrollBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
I was hoping I could do something like this in the view:
#Html.EditorFor(model => model.DefaultBank, new { data-validate-parameter: bankId })
This way I could just use the same action for all of the Bank entries like:
[HttpGet]
public JsonResult CheckValidBank(string bankId)
{
bool result = BankExists(bankId);
return Json(result, JsonRequestBehavior.AllowGet);
}
Possible?
For just such a situation, I wrote a RemoteReusableAttribute, which may be helpful to you. Here is a link to it: Custom remote Validation in MVC 3
Since MVC uses the default model binder for this, just like a normal action method. You could take a FormsCollection as your parameter and lookup the value. However, I personally would find it much easier to just use several parameters to the function, unless you start having dozens of different parameters.
You could also write a custom model binder, that would translate the passed parameter to a generic one.
Consider encapsulating the logic, "BankExists" in this case into a ValidationAttribute (Data Annotations Validator). This allows other scenarios as well.
Then use a wrapper ActionResult like the one below, which lets you pass in any validator.
[HttpGet]
public ActionResult CheckRefundBank(string refundBank)
{
var validation = BankExistsAttribute();
return new RemoteValidationResult(validation, defaultBank);
}
Here is the code for the ActionResult that works generically with Validators.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class RemoteValidationResult : ActionResult
{
public RemoteValidationResult(ValidationAttribute validation, object value)
{
this.Validation = validation;
this.Value = value;
}
public ValidationAttribute Validation { get; set; }
public object Value { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var json = new JsonResult();
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
if (Validation.IsValid(Value))
{
json.Data = true;
}
else
{
json.Data = Validation.FormatErrorMessage(Value.ToString());
}
json.ExecuteResult(context);
}
}
As an extra enhancement consider creating a Controller Extension method to dry up your return call even more.

Resources