I'm getting an error with TryUpdateModel when the field doesn't have a value and it's not nullable in the database. If I change the field in the database to nullable, it works. Anybody know a fix to get this to work with non nullable fields? I'm new to MVC, so I'm just playing with a tutorial to try to understand it....
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(FormCollection form)
{
var movieToAdd = new Movies();
if (Request != null && Request.Form != null && Request.Form.HasKeys() && ValueProvider == null)
{
ValueProvider = new FormCollection(Request.Form).ToValueProvider();
}
//Deserialize (Include white list!)
TryUpdateModel(movieToAdd, new string[] { "Title", "Director" }, form.ToValueProvider());
// Validate
if (string.IsNullOrEmpty(movieToAdd.Title))
ModelState.AddModelError("Title", "Title is required!");
if (string.IsNullOrEmpty(movieToAdd.Director))
ModelState.AddModelError("Director", "Director is required!");
// If valid, save movie to database
if (ModelState.IsValid)
{
_db.AddToMovies1(movieToAdd);
_db.SaveChanges();
return RedirectToAction("Index");
}
// Otherwise, reshow form
return View(movieToAdd);
}
There are a few things I would change.
Modify your method signature to accept the strongly typed model that your ActionResult is expecting. From there you can check if the Model is valid or not and continue with saving to the database
Use strongly typed models and data annotations for validating your model
EG:
Create a model which you would use for the form Get And Post actions:
public class YourModel
{
[Required]
public string Title {get; set;]
[Required]
public string Director {get; set;}
}
Your Post ActionResult then becomes:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Add(YourModel postedform)
{
// If valid, save movie to database
if (ModelState.IsValid)
{
_db.AddToMovies1(postedform);
_db.SaveChanges();
return RedirectToAction("Index");
}
// Otherwise, reshow form
return View(postedform);
}
Related
I have a simple form with a textbox (and a model editor I want to render in specific cases)
#using (Html.BeginForm("Import", "Flow"))
{
#Html.TextBoxFor(model => model.IsConfirmed)
#if (Model.IsConfirmed)
{
#Html.EditorFor(m => m.Preview)
}
}
The model used in this view is the following
public class ImportViewModel
{
public Boolean IsConfirmed { get; set; }
public PreviewViewModel Preview { get; set; }
public ImportViewModel()
{
this.IsConfirmed = false;
}
}
The form posts on the following controller
public class FlowController
{
[HttpPost]
public ActionResult Import(ImportViewModel model)
{
try
{
if (ModelState.IsValid)
{
if (model.IsConfirmed)
{
// do something else
}
else
{
model.Preview = Preview(model.strCA, model.SelectedAccount);
model.IsConfirmed = true;
return View(model);
}
}
}
catch (Exception ex)
{
throw new Exception("arf", ex);
}
return RedirectToAction("Index", "Home");
}
}
On first load, the textbox contains "false"
When posted, the property IsConfirmed of the model is set to "true" and this model is passed to the same view.
I expect the textbox to be "true" but it is still "false"... moreover the Preview property is correctly rendered, so it means Model.IsConfirmed is indeed true...
Am I missing something ?
Thanks
Make sure you remove the value from the ModelState if you intend to modify it:
ModelState.Remove("IsConfirmed");
model.IsConfirmed = true;
The reason you need to do that is because, by design, all Html helpers (such as TextBoxFor) will first look for a value in the ModelState when binding and only not found they will use the value on your model. And since there's a value with the same name in the ModelState (coming from the POST request), that's what's being used.
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">
}
I want to validate for a empty Id value in the url.
../Task/EditEmployee/afccb22a-7cfd-4be5-8f82-9bd353c13b16
I want that if the Id is empty
../Task/EditEmployee/
Than redirect the user to a certain page.
public ActionResult EditEmployee(Guid Id)
{
//Some code in here
}
It may not be the best solution but you can take id parameter as string and try to parse it like this:
public ActionResult EditEmployee(string id)
{
if(string.IsNullOrWhiteSpace(id))
{
// handle empty querystring
}
else
{
Guid guid;
if (Guid.TryParse(id, out guid))
{
//Some code in here
}
}
}
Or
You can also create a regex constraint on the route but that may be too complicated and hard to understand. Map this route before the default one.
routes.MapRoute(
"TastEditEmployee",
"Task/EditEmployee/{id}",
new { controller = "Task", action = "EditEmployee" },
new { id = #"^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$" }
);
Then you can use id parameter as Nullable Guid.
public ActionResult EditEmployee(Guid? id)
{
//do something
}
Since Guid is a struct, the value of Id will be Guid.Empty if it was omitted. You can check for that.
public ActionResult EditEmployee(Guid Id)
{
if (Id == Guid.Empty) throw new ArgumentException("Id not specified.");
}
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>
}
If I have the usual Edit actions, one for GET to retrieve an object by it's ID and to display it in an edit form. The next for POST to take the values in the ViewModel and update the object in the database.
public virtual ActionResult Edit(int id)
[HttpPost]
public ActionResult Edit(VehicleVariantEditSaveViewModel viewModel)
If an error occurs during model binding in the POST action, I understand I can RedirectToAction back to the GET action and preserve the ModelState validation errors by copying it to TempData and retrieving it after the redirect in the GET action.
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
How do I then convert that ViewData, which includes the previous invalid ModelState, into a new model to send to the view so the user sees their invalid input with validation warnings? Oddly enough if I pass in a new instance of my ViewModel retrieved from the database (with the original valid data) to the View() this is ignored and the (invalid) data in the ViewData is displayed!
Thanks
I had a similar problem and decided to use the following pattern:
public ActionResult PersonalRecord(Guid id)
{
if (TempData["Model"] == null)
{
var personalRecord = _context.PersonalRecords.Single(p => p.UserId == id);
var model = personalRecord.ToPersonalRecordModel();
return View(model);
}
else
{
ViewData = (ViewDataDictionary) TempData["ViewData"];
return View(TempData["Model"]);
}
}
[HttpPost]
public ActionResult PersonalRecord(PersonalRecordModel model)
{
try
{
if (ModelState.IsValid)
{
var personalRecord = _context.PersonalRecords.Single(u => u.UserId == model.UserId);
personalRecord.Email = model.Email;
personalRecord.DOB = model.DOB;
personalRecord.PrimaryPhone = model.PrimaryPhone;
_context.Update(personalRecord);
_context.SaveChanges();
return RedirectToAction("PersonalRecord");
}
}
catch (DbEntityValidationException ex)
{
var errors = ex.EntityValidationErrors.First();
foreach (var propertyError in errors.ValidationErrors)
{
ModelState.AddModelError(propertyError.PropertyName, propertyError.ErrorMessage);
}
}
TempData["Model"] = model;
TempData["ViewData"] = ViewData;
return RedirectToAction("PersonalRecord", new { id = model.UserId });
}
Hope this helps.
I noticed that the Model is included in ViewData so you don't need to pass it in addition to the ViewData, what I don't understand is how you get at it to then return it to the view.
public ViewResult Edit(int id)
{
// Check if we have ViewData in the session from a previous attempt which failed validation
if (TempData["ViewData"] != null)
{
ViewData = (ViewDataDictionary)TempData["ViewData"];
}
VehicleVariantEditViewModel viewModel = new VehicleVariantControllerViewModelBuilder()
.BuildForEdit(id);
return View(viewModel);
}
The above works but obviously it's making an unnecessary call to the database to build a new Model (which gets automagically overwritten with the invalid values from the Model in the passed ViewData)
Confusing.