I have a form where I want the user to put it's phone number. However, the form looks like so:
[FormField-1] - [FormField-2] - [FormField-3]
Where there's three textboxes to facilitate the user in putting it in the format xxx-xxx-xxxx. However, my model class is:
class Person
{
[Required(ErrorMessage="Phone Mandatory!")]
public string phone {get; set;}
}
My view looks like so:
#model MvcApplication1.Models.Person
#using ( Html.BeginForm("Create", "Home"))
{
#Html.TextBoxFor( model => model.phone )
#Html.ValidationMessageFor( model => model.phone )<br />
<input type="submit" value="submit" />
}
How do I get around this, so that I don't have to have three different class properties to match the 3 different form fields that represent the phone number?
Thanks!
You could use a custom model binder to achieve this.
Basically you register a type (which you create) to handle a specific model, and you can read in the posted form fields and populate your model manually, rather than getting the default model binder to do it. I think that's your only option in this case.
You can make it easier by inheriting from the default model binder, then just manually processing the more complex fields yourself.
Have a look at this for a bit more info on working with custom model binding: Custom Model Binder
Edit: You can also just accept a type of FormCollection in your action to read the raw posted data and process it in there, if you don't want to go the whole stretch with custom model binding, but it depends on how you want to expand your model in the future I guess.
A more simple solution could be to use some javascript on the form to save the three separate values into one hidden input and bind that single input to the model.
I'd go with a custom ViewModel in this situation.
Another solution might also be to just build the phone number value in the Action method with something like:
[HttpPost]
public ActionResult FormSubmit(Person person)
{
person.phone = Request.Form["FormField-1"] + Request.Form["FormField-2"] + Request.Form["FormField-3"];
...
}
Related
I've been able to successfully return a model to a view and display the results in a strongly-typed fashion.
I have never seen an example where multiple models get returned. How do I go about that?
I suppose the controller would have something like this:
return View(lemondb.Messages.Where(p => p.user == tmp_username).ToList(), lemondb.Lemons.Where(p => p.acidity >= 2).ToList());
Does MVC let you return multiple models like that?
And then in the view I have this line at the top of the file:
#model IEnumerable<ElkDogTrader.Models.Message>
And I frequently make calls to "model" in the view.
#foreach (var item in Model)
If there were 2 models, how would I refer to them separately?
Is this possible with multiple models, or is this why people use ViewBag and ViewData?
You can create a custom model representing the data needed for your view.
public class UserView
{
public User User{get;set;}
public List<Messages> Messages{get;set;}
}
And then,
return View(new UserView(){ User = user, Messages = message});
In the view:
Model.User;
Model.Messages;
The ViewBag is useful because it is dynamically typed, so you can reference members in it directly without casting. You do, however, then lose static type checking at compile time.
ViewData can be useful if you have a one-off on your view data types and know the type and will be doing a cast in the view anyway. Some people like to keep the actual typed view pure in a sense that it represents the primary model only, others like to take advantage of the type checking at compile time and therefore make custom models needed for the view.
I believe ViewModel should be the way to go. Within the customary ViewModel, you can reference other models or define all the related domain models in the viewModel itself.
Just to make it clear, my question is how do you CREATE multiple objects using a single view. My ViewModel works fine, I can display multiple objects no problem.
Its been a while between .NET coding (last time I was coding in 2.0). I have created an MVC 4 project and successfully created a ViewModel (displaying data on a single view from multiple objects).
However, I cheated. I populated the database directly. I now face the question in reverse, what is the best practice for creating multiple objects from a single view?
In my example, I have a User, who has a userId. The userId is a foreign key in UserDetails.
Just trying to get back into the swing of it and wondering what you guys do?
There are 6 ways to pass multiple object in MVC
ViewModel
Partial View
ViewBag
ViewData
TempData
Tuple
Each one have there own pros and cons.you have to decide base on your issue in hand.
For more information you can refer code project article on it: How to Choose the Best Way to Pass Multiple Models in ASP.NET MVC
Let me give advantage and disadvantage of View Model
ViewModel :
Advantages
ViewModel allows us to render multiple model types in a View as a
single model.
Great intellisense support and compile time error checking on View
page.
ViewModel is good for security purpose also as Views have only what
they exactly need. Core domain models are not exposed to user.
If there is any change in core domain model, you do not need to
change anywhere in View code, just you need to modify corresponding
ViewModel.
Disadvantages
ViewModels add another layer between Models and Views so it increases
the complexity a little bit. So for small and demo applications, we
can use tuple or other ways to keep the things simple for demo.
Option 1:
The best approach is to use strongly typed views with Model or ViewModel.
For Example, you have two classes User and Education, you want to display all education details of user in a view, you can create a custom view model and pass it to view, where you view is strongly typed view model:
public class User
{
public int UserId {get;set;}
public string UserName {get;set}
-------
------
}
public class Education
{
public int UserId {get;set;}
public int DegreeId {get;set;}
public long Marks {get;set;}
---------------
--------------
}
Now create a view Model like this:
public class EducationViewModel
{
public User user {get;set;}
public List<Education> educationList {get;set;}
}
now pass the ViewModel to View and do this in View:
#model AppNameSpace.ViewModels.EducationViewModel
Tip: Create a folder named ViewModels and put all the viewmodel classes in it.
Option2:
Option 2 is to user ViewBag and pass multiple object from control to your view, ViewBag is accessible when you set some value in it in controller, you can access in the view of that action, after that it is automatically washed out, and its null if you access again it.
you can use ViewBag like this:
ViewBag.Message = "Using ViewBag";
and read value like this in View:
string Message = ViewBag.Message as string;
Option 3:
Option 3 is to store data in TempData, its once read only, means you set value in it, and when you read it, its automatically removed, TempData internally uses Session Variables.You can use TempData like this:
TempData["Key"] = "value";
now you read it in view:
string val = TempData["Key"] as string;
after reading it, it will be automatically removed, but you can keep it if you need it further like this:
TempData.Keep("Key");
I have the following code and I get an error saying:
has no applicable method named 'TextBoxFor' but appears to have an extension method by that name.
My Code:
#Html.TextBoxFor(ViewBag.taglist)
Why don't you use strongly typed model in your view instead of ViewBag. This will make your life easier.
In fact, you must use a model to with TextBoxFor, otherwise it just won't work. See the definition of TextBoxFor - as a second parameter it takes a lambda expression that takes a property form a model.
If you want just a text box, two options:
#Html.TextBox("NameOfTheTextbox", (String)ViewBag.SomeValue)
or just go
<input type="text" value="#ViewBag.SomeValue" />
No complex solutions required.
I agree with other suggestions of using a strongly-typed model, because the compile-time error support is so much better than debugging exceptions. Having said that, in order to do what you want, you can use this:
#Html.TextBox("NameOfTextBox", (string)ViewBag.taglist)
Update: A Simple Example
Now that you've provided some details in your comments, I've taken a guess at what you might be doing, in order to provide a simple example.
I'm assuming you have a list of tags (like SO has per question) that you'd like to display neatly in a textbox, with each tag separated by a space. I'm going to assume your Tag domain model looks something like this:
public class Tag
{
public int Id { get; set; }
public string Description { get; set; }
}
Now, your view will need a list of the tags but will likely need some other information to be displayed as well. However, let's just focus on the tags. Below is a view model to represent all the tags, taking into account that you want to display them as a string inside a textbox:
public class SomeViewModel
{
public string Tags { get; set; }
// Other properties
}
In order to get the data you want you could grab all of the tags like this:
public ActionResult Index()
{
using (YourContext db = new YourContext())
{
var model = new SomeViewModel();
model.Tags = string.Join(" ", db.Tags.Select(t => t.Description).ToList());
return View(model);
}
}
Notice how I'm directly passing model to the view.
The view is now very simple:
#model SomeViewModel
#Html.EditorFor(m => m.Tags)
The model directive is what signifies that a view is strongly-typed. That means this view will expect to receive an instance of SomeViewModel. As you can see from my action code above, we will be providing this view the type that it wants. This now allows us to make use of the strongly-typed HtmlHelper (i.e. Html.XxxFor) methods.
In this particular case, I've used Html.EditorFor, as it will choose an appropriate input element to render the data with. (In this case, because Description is a string, it will render a textbox.)
You cannot use Html.TextBoxFor without explicitly setting a type for your model within the view. If you don't specify a type it defaults to dynamic. If you want to do model binding then you must use an explicit type rather than a dynamic type like ViewBag. To use Html.TextBoxFor you must define a model type that defines the property that you wish to bind. Otherwise you have to use Html.TextBox and set the value manually from ViewBag. As others have said, you will make your life much easier if you use a statically typed model and take advantage of the inbuilt MVC model binding.
You have to use a lambda expression to select the property, plus you will have to cast the ViewBag member to the correct type.
#Html.TextBoxFor(model => (string)ViewBag.taglist)
This question is related to this one, but I think in my example I have detail which may alter answers.
Say I have a User action on a Controller that renders a View for displaying data about a particular User, it might have a UserViewModel like so:
public class UserViewModel {
public string FirstName;
public string LastName;
etc...
}
However, in this View, as well as showing this user data, I want to have a search textbox for the user so they can look up another user on this page. This form would post into an action of FindUser, which accepts the following model:
public class FindUserInputViewModel {
[Required]
public string SearchQuery;
}
If this action finds the model to be invalid, it redirects back to the User action, maintaining the ModelState.
Now, currently to show the validation error, I cannot use a strongly-typed helper as that search query property isn't in UserViewModel, I'd have to do this:
#Html.TextBox("SearchQuery")
#Html.ValidationMessageFor("SearchQuery")
This works, and the error will be displayed, as well as the old value that was POSTed being shown (as it is persisted in the ModelState). However, I'd prefer to use a strongly-typed helper wherever possible.
From all the examples I have seen, the pattern here seems to be that the UserViewModel should contain the FindUserInputViewModel inside it, perhaps as an FindUserInput property. I could then do:
#Html.TextBoxFor(m => m.FindUserInput.SearchQuery)
This also works, as long as I make sure my FindUser action binds to the correct prefix, or I specify a name in the TextboxFor method call.
However, I don't really see why my UserViewModel should contain this other ViewModel simply for the case of binding the validation using this helper. Does it bring other benefits that I am not seeing? I understand the use of it if your View's model is needing to render out the same data you are posting, such as on a typical Edit action, but that isn't the case here.
It looks like to me that what would be handy here is a another generic helper that can reference a different type, something like this:
#Html.TextBoxForType<FindUserInput>(m => m.SearchQuery)
This doesn't exist, but I think I should be able to write that, and this is a good case for one. Does that sound like an appropriate solution, or am I missing something here?
Another option entirely is perhaps that the small-form for posting in that FindUserInputViewModel should have its own GET action as well as POST, and then the User View can just call into it using #Html.Action. It could then render a partial-view that is only strongly typed to FindUserInputViewModel.
Why not create a partial view for your search and simply pass this a new FindUserInputViewModel from your user view?
#Html.Partial("FindUser", new FindUserInputViewModel())
You can type your partial view to FindUserInputViewModel and use strongly-typed helpers in there. I'd say this is the simplest and neatest approach, unless there's something I'm missing?
I have an ASP.Net MVC application with a model which is several layers deep containing a collection.
I believe that the view to create the objects is all set up correctly, but it just does not populate the collection within the model when I post the form to the server.
I have a piece of data which is found in the class hierarchy thus:
person.PersonDetails.ContactInformation[0].Data;
This class structure is created by LinqToSQL, and ContactInformation is of type EntitySet<ContactData>. To create the view I pass the following:
return View(person);
and within the view I have a form which contains a single text box with a name associated to the above mentioned field:
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
The post method within my controller is then as follows:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create (Person person)
{
//Do stuff to validate and add to the database
}
It is at this point where I get lost as person.PersonDetails.ContactInformation.Count() ==0. So the ModelBinder has created a ContactInformation object but not populated it with the object which it should hold (i.e ContactData) at index 0.
My question is two fold:
1. Have I taken the correct approach.. i.e. should this work?
2. Any ideas as to why it might be failing to populate the ContactInformation object?
Many thanks,
Richard
I think that your model is too complex for the default model binder to work with. You could try using multiple parameters and binding them with prefixes:
public ActionResult Create(
Person person,
[Bind(Prefix="Person.PersonDetails")]
PersonDetails details,
[Bind(Prefix="Person.PersonDetails.ContactInformation")]
ContactInformation[] info )
{
person.PersonDetails = details;
person.PersonDetails.ContactInformation = info;
...
}
Or you could develop your own custom model binder that would understand how to derive your complex model from the form inputs.
If a property is null, then the model binder other could not find it or could not find values in the submitted form necessary to make an instance of the type of the property. For example, if the property has a non-nullable ID and your form does not contain any data for that ID , the model binder will leave the property as null since it cannot make a new instance of the type without knowing the ID.
In other words, to diagnose this problem you must carefully compare the data in the submitted form (this is easy to see with Firebug or Fiddler) with the structure of the object you are expecting the model binder to populate. If any required fields are missing, or if the values are submitted in such a way that they cannot be converted to the type of a required field, then the entire object will be left null.
I've been struggling with this same type of scenario and eventually came to realize that the underlying problem is that the MVC default model binder does not seem to work on EntitySet<T> fields, only List<T> fields. I did however find a simple workaround that seems acceptable. In my case, I have a Company entity that has one to many relationship to Contacts (my Linq-to-Sql EntitySet).
Since it seems that when I change my code from EntitySet<Contact> to List<Contact>, the MVC default model binder starts working as expected (even though the LTS isn't now), I figured I would provide an alternate, "aliased" property to MVC that is of type List<Contact>, and sure enough, this seems to work.
In my Company entity class:
// This is what LINQ-to-SQL will use:
private EntitySet<Contact> _Contacts = new EntitySet<Contact>();
[Association(Storage="_Contacts", OtherKey="CompanyID", ThisKey="ID")]
public EntitySet<Contact> Contacts
{
get { return _Contacts; }
set { _Contacts.Assign(value); }
}
// This is what MVC default model binder (and my View) will use:
public List<Contact> MvcContacts
{
get { return _Contacts.ToList<Contact>(); }
set { _Contacts.AddRange(value); }
}
So now, in my View, I have the following:
<label>First Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].FirstName") %>
</label>
<label>Last Name*
<%= Html.TextBox("Company.MvcContacts[" + i + "].LastName") %>
</label>
Seems to work like a charm!
Best of luck!
-Mike
Maybe lack of Bind attribute is the case:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create ([Bind] Person person)
{
// Do stuff to validate and add to the database
}
The first argument of Html.TextBox is the name of the textbox, the second would be the value.
"Wrong":
<%= Html.TextBox("person.PersonDetails.ContactInformation[0].Data")%>
"Right":
<%= Html.TextBox("nameoftextbox", person.PersonDetails.ContactInformation[0].Data)%>
Make sure your models (and all nested models) are using properties (getters/setters) instead of fields. Apparently the default binder needs properties to function properly. I had a very similar situation that was fixed by changing the necessary fields to properties.