I have a question about ASP.NET MVC3 model binding. If I have a class I'm trying to use as a model, but I don't want the key put on the page, the model doesn't bind on a POST. Here is an example:
//Data Model
public class MyModel
{
[Key]
public string MyKey {get;set;} //Perhaps this is an ssn that I don't want on the form.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
//Conroller code.
public ViewResult Index()
{
MyModel model = new MyModel{ MyKey = "SecretInfo", MyValueToGet = "" };
return View(new model);
}
public ActionResult Edit(MyModel model)
{
repository.SaveChanges(model)
}
//View code.
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.MyValueToGet)
<input type="submit" value="Salve" />
}
So my problem is that model is null when the Edit method is called upon form submission. I can fix this by putting MyKey somewhere on the page (perhaps as a hidden field), but that is unacceptable if it is some sort of sensitive data. Is there a way to solve this problem? I am new to MVC, so I appreciate any help.
Create another unique but otherwise meaningless identifier like an (auto increment int) and use that to bind.
in other words modify your model to something like:
public class MyModel
{
[Key]
public int ID {get; set;}
public string MyKey {get;set;} //Now this can be sensitive, it doesn't matter because you no longer rely on it.
public string MyValueToGet {get;set;} //This is the value I want the user to enter.
}
EDIT
I believe your best choice would be to change the MyModel object, as it's design is flawed. The primary key in the majority of cases (and I think this is one of them) should be a simple auto incrementing integer, meaningless apart from it's role as the table's key.
While Luke's suggestion to use Session is a viable option and a solution that would work, I would personally do something similar to what I'll explain here, as it would seem to me to be more of the 'mvc way' of doing things.
Data model:
Either change your current model to something like what I suggest above, or, if that is not feasible for whatever reason (breaking dependancies or FK relationships), create a new table that can be used as a join, or proxy, if you will:
public class Proxy
{
public int ProxyId {get;set;}
public MyModel MyModel {get; set;}
}
Obviously, you'd have to do some work to populate this table, but you would then be able to use it to fetch records from MyModel without accessing the MyKey property directly.
It's not considered good practice to use your data models directly in your views, so you want to create a view model as well
public class MyModelViewModel
{
public int ModelId {get; set;}
public string ModelValueToGet {get; set;}
}
Notice we don't even need the key containing sensitive data in the view model.
Then type your view to the viewModel, not the data model, and include a hidden field for the ModelId
#using(Html.BeginForm("Edit", "Home", FormMethod.Post))
{
Enter a value: #Html.EditorFor(m => m.ModelValueToGet)
#Html.HiddenFor(m => m.ModelId)
<input type="submit" value="Save" />
}
Now in your controller you have your get method
public ViewResult Index()
{
//fetch the users record from the database
//if you're using the Proxy table, you'll want to write a LINQ query here
//instantiate a viewModel and populate it's properties using the fetched record
//remember, the viewModel.ModelId should be set to MyModel.ID or Proxy.ProxyId
//render the view
}
And the post method
public ViewResult Edit (MyModelViewModel viewModel)
{
//fetch the users record from the database using viewModel.ModelId
//If you're using the proxy table, you'll need to use that LINQ query again here
//update the record you fetched with the new data the user just entered
//you have complete control here of what gets updated and what stays the same
//pass the updated record to the repository to save the changes.
//redirect the user to be on their merry way
}
I think that's about as well as I can lay it out. Hope it makes sense.
An alternative is to encrypt the id before sending it to the client. Check this post for more information on how to accomplish this.
Asp MVC 3: Modifiy Values Sent to View
Related
I'm an ASP.NET MVC beginner and currently I use EF with a database-first approach to do my work. Everything goes well and every model have its controller and views files. I face problem when I want to save multi model data on a view.
In my case:
I have ASP.NET MVC form for teacher details data, and another for teacher employment history (this two are auto generate from scaffolding database approach)
I want a view Create.cshtml where user inputs teacher details and employment history and this can be saved into their own tables. Therefore, I use tuple and follow what (Multiple models in a view) and (Two models in one view in ASP MVC 3) did.
As a result, I successfully create teacher details and employment history on a view (Just interface).
But I have no idea how to save the input into the different tables (2 tables: teacher, employmentHistory). Now, when I save the input, nothing happens.
I guess I need to do something on controllers parts, please give me some ideas on how to do it.
Thanks in advance
First, welcome to StackOverflow!
You are correct, you should do something on the controller part.
I would create a ViewModel for the view. By doing this you can project the data from the database in any way that is needed in the view. Also, with this approach you don't return the full entity, maybe there is some sensitive information there. And you can get the benefit of the model validations also. (For example, what if at some point you need to add another field from another entity?)
I would also create partial views for the information in the view model and pass that model to the partial view, this way enabling the re-use of the views, if needed.
When passing the data to the controller you can pass the ViewModel and then save the data in the database.
Since you didn't give no model info of your classes, I give below an example. Either way (view model or tuple example you followed), you should change the controller code similar to what I'm writing below.
public class TeacherViewModel
{
//teacher details
[Required]
public int TeacherId {get; set;}
[Required]
public string TeacherName {get; set;}
// {...}
//teacher employment info
//here you can have a list of information or the last entry in the history,
//or what's required to add a new entry
public string LastWorkPlace {get; set;}
public DateTime From {get; set;}
public To {get; set; }
//{...}
}
public class TeacherController: Controller
{
//{...}
[HttpPost]
public ActionResult SaveTeacherInfo(TeacherViewModel model){
if (ModelState.IsValid) {
var teacher = new TeacherEntity
{
TeacherId = model.TeacherId, //if update otherwise leave blank
TeacherName = model.TeacherName
//{...}
};
//your context name here
_dbContext.Teachers.Add(teacher);
var teacherEmploymentInfo = new TeacherEmploymentHistory
{
TeacherId = teacher.TeacherId,
LastWorkPlace = model.LastWorkPlace,
From = model.From,
To = model.To
//{...}
};
_dbContext.TeachersEmploymentInfo.Add(teacherEmploymentInfo);
_dbContext.SaveChanges();
//_dbContext.SaveChangesAsync(); if using async await
}
return View(model); //return any validation errors from the view model
// to the user to fix.
}
}
If you are using the ErrorHandler as a global filter no need for try..catch block if you are not planing to return custom errors (BusinessException or DatabaseException, for example) from the controller, if not, you should wrap the code in a try..catch block.
I've been trying to get my head around how to solve this problem in the cleanest way.
How to get the model binder to NOT create empty objects when submitting empty strings for complex types?
Say I've got this model:
public class CreateCaseModel
{
[Required]
public UserModel Contact { get; set; }
public UserModel Assigned {get; set; }
}
and the user-model:
public class UserModel {
[Required]
public string Id {get;set;}
public string Name {get;set;
}
And in the view i have these inputs:
<input type="hidden" name="Assigned.Id" value="" />
<input type="hidden" name="Contact.Id" value="userId1" />
I post this to a controller something like:
public ActionResult Create(CreateCaseModel model){
if (!ModelState.IsValid)
{
//handle error
}else {
//create
}
}
In the controller now my model.Contact and model.Assigned will never be null.
Only the model.Contact will however contain anything (the Idwill be set)
At this point the modelstate will also be invalid due to the fact that the UserModel.Id field is marked as required.
What I want to happen is for the binder to not create the UserModel object at all if the input is empty.
The entire reason for me needing this is that I'm using the UserModel.Name field elsewhere in the view, and I'd like to group the two fields together. And I want to use the ModelState.IsValidcheck for serverside validation.
I could of course go with extracting the Id and Name fields of each userobjects and put them directly in the CreateCaseModel, but I want to explore if what I describe is even possible.
Grateful for any tips or pointers!
How to get the model binder to NOT create empty objects when submitting empty strings for complex types?
Write your own custom ModelBinder. The default model binder will always attempt to create complex types for recursive modelbinding.
What I am Attempting
I am trying to bind my ViewModel to my View which contains a Customer Name and a list of Contacts to reach that customer. Once the view is populated I also want the user to have the ability to add, delete, and edit contacts in the View.
What I've got so far
Currently I am able to bind to the ViewModel using the EditorTemplate and edit the data with no problems. The problem comes in when I want to add or remove rows in the list, if a user deletes row 5 from the middle of the ViewModel bound Editable List only rows 1 - 4 will end up being posted to the server.
The Code
Here is what my ViewModel looks like:
public class CustomerViewModel{
[Required]
public int CustomerUniqueID { get; set; }
[Required]
public string Name { get; set; }
public IEnumerable<CustomerContacts> Contacts { get; set; }
public CustomerViewModel() { }
public CustomerViewModel(Customer customer)
{
ICustomerContactRepository contacts = new LockShop.Domain.Concrete.EFCustomerContactRepository();
this.Contacts = contacts.FindContacts(customer.UniqueID);
this.Name = customer.Name;
}
}
I am passing my ViewModel to my view like this:
public ActionResult Edit(){
Customer customer = repository.Customers.FirstOrDefault(c => c.UniqueID == 23128);
return View(new CustomerViewModel(customer));
}
When it is time to render the list, I simply use the EditorFor helper:
#Html.EditorFor(model => model.Contacts)
What I love about this approach is that when I click the save button all of my changes are automatically posted to the controller!
My Question
What are the best practices of how to add the functionality of adding and removing rows to a list that is generated by an IEnumerable from my ViewModel?
What I Have Tried
I've read Steven Sanderson's blog post which talks about editing a variable length list in ASP.NET MVC. His post seems to only cover binding to a model and doesn't cover the possibility of binding to a list in a ViewModel. When I implemented his solution the post data from the Contacts list that got sent back to the controller was null.
Please feel free to ask any questions for clarification and thanks for your time.
I'm trying to figure out a 'best practice' here and hope I'm not too far off. Here's the scenario:
Let's say we have two Actions. One for sending out a Razor View that contains a form. On this form, there is a <select> that is loaded with values from our ViewModel (let's say it's an expiration date field or something). There is also fields for the user to fill in, such as Name.
The other Action takes in the POST of the view, with the model as the parameter:
[HttpPost]
[ActionName("MyForm")]
public virtual ActionResult MyForm(MyViewModel model) {
// logic here
}
Now, if there's some sort of issue with the model (let's say, we couldn't charge their credit card). We could do ModelState.AddModelError and then pass back the model:
return View(model);
However, is this a best practice? What about properties that are in our ViewModel, but we didn't "return back" from the page, such as our expiration date field values. These will now be NULL and we'll have an exception when loading the view.
Would best practice be to always "recreate" the view model before sending it back (somehow taking in the values they typed in, overlaying them onto the default viewmodel?) or should we have some method that always sets those "readonly" fields, and gets called in both Actions?
Hopefully this question makes sense. I don't know what to call the properties that are just "read only" on the view, and not passed back as part of the FORM POST.
Thanks.
You call read-only data, some call it system data, some buddy data (using enricher classes and Structuremap), often it is referred to as hydrating.
I usually approach it similar to this example ("a method which always sets those fields") or I'll create an action filter (OnActionExecuted) which injects buddy data depending on the type of the view model.
For example:
public class ContactFormData
{
// data which gets posted back
public string Name {get; set;}
public string CountryCode {get; set;}
// buddy data
public SelectList Countries {get; set;}
}
[HttpGet]
[ActionName("ContactForm")]
public virtual ActionResult ContactForm() {
var m = new ContactFormData();
return ShowContactForm(m);
}
[HttpPost]
[ActionName("ContactForm")]
public virtual ActionResult ContactForm(ContactFormData formdata) {
if (ModelState.IsValid)
{
// logic & redirect
return redirect;
}
return ShowContactForm(formdata);
}
private ActionResult ShowContactForm(ContactFormData formdata)
{
formData.Countries = GetCountriesSelectListSomewhere();
return View(m);
}
I am using MVC3, Razor, C#, .NET4, EF5.
I have a number of situations where I am editing the domain model directly, and yes I realise that I should be using View Models :) However for the short term I am hoping I can get a solution to my problem whereby I am getting nulls being saved to the DB, where those fields are not specified as Hidden Fields in the forms. I realise this is to do with the Model Binding behaviour.
My Edit Action:
public ActionResult Edit(int id)
{
StdOrg myOrg = db.StdOrg.Single(s => s.Id == id);
return View(myOrg);
}
Lets assume my View has 5 fields on it:
Name
Address1
Address2
Address3
Address4
Now for my Post Edit Action:
[HttpPost]
public ActionResult Edit(StdOrg myOrg)
{
if (ModelState.IsValid)
{
db.StdOrg.Attach(myOrg);
db.ObjectStateManager.ChangeObjectState(myOrg, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(myOrg);
}
Now my Table record has 10 columns in it, and columns 6-10 has values.
Name
Address1
Address2
Address3
Address4
Terms
EmployeeNo
Code
Active
SetUpDate
When I save the form, columns 6-10 ie Terms etc. get set to null. Now I realise this is due to the fact that I am not specifying them as hidden fields in my View and therefore MVC Model Binding is assuming these are null.
Is there a practical way around this, rather than specifying all columns in the form which I would rather not do for security reasons.
Many thanks in advance.
EDIT
My attempt which does not seem to work:
[HttpPost]
public ActionResult Edit([Bind(Include = "Id,Name,Name,Address1,Address2,Address3,Address4")] StdOrg myOrg)
Thoughts.... ???
IN CONCLUSION
For those interested I ended up using the "Value Injector" which is the other "Automapper" for which I wrote a simple matching routine to prevent nulls being set, and also to exclude navigation properties as well. Very useful Mapper.
One still really needs to implement ViewModels since these seem the only way to identify which of the classes properties are in the View, unless one can get Bind attribute working which I could not.
Otherwise even with a mapper one cannot reset a View field to null, since the mapper will ensure that it is ignored !! It cannot tell between View Properties and others in the domain class, as far as I can see.
Try using the BindAttribute, you can simply exclude or include the fields for that particular action (which is always good practice, someone could manipulate the ID field on the POST and modify a different value).
See this question for an example
Despite putting Exclude stuff:
[Bind(Exclude = "PropertyName,PropertyType")]
public class Filter {
public string PropertyName { get; set; }
public Type PropertyType { get; set; }
public string From { get; set; }
public string To { get; set; }
public string Match { get; set; }
}
those properties seem to be ovewritten with NULL, when I call this method from model (note: PropertyName and PropertyType are not and must not be present in markup! They are filled once in the code, before making markup)
[HttpPost]
public virtual PartialViewResult LoadFiltered(string entityType, IDictionary<string, Filter> filters)
And when no elements selected (all filters are disabled in interface) - I get two elements, having "controller" and "action" as keys. What the... ??