How do you put a ViewBag item into a Text Box? - c#

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)

Related

How to access properties in the model passed to the view?

I'm sending the following model to my view.
return View(new { url = returnUrl });
In the view, I'm don't want to specify any particular class for my object (since I wish to keep it flexible and elastic for now). So, the #Model is the apparently an object and as such, it's got no url property. Hence the error.
Additional information: 'object' does not contain a definition for 'url'
Now, I do know that the thing inside the object has url property. I have assigned it to it and I also see it when watching the variable as the exception's been thrown.
My question is how to access the field. Is my only option declaring a class and type the model using #model Something? I can't use as keyword to type it to var...
In "plain" C# we can do something like this.
var some = new {thing = "poof"};
string output = some.thing;
How do I do the equivalent of it in CSHTML file under Razor?
Strongly-typed view models are the way to go. Create a type that suits the needs of the view and treat reusability/duplication as a secondary concern.
However, let me explain why your attempt did not work.
It is legal to pass an anonymous type--even between assemblies[1]--as long as it is cast to object. In fact, the MVC framework assemblies consume anonymous types in many helper methods. Those anonymous types are then evaluated using reflection (optimized by caching).
1: I believe there are some caveats to this, and it certainly isn't good practice in most cases.
A view is compiled into a class so that it can be executed. Part of the class's contract is the type of model it expects, as indicated by #model in your view markup.
This presents a problem with anonymous types, as you cannot indicate their type in your view and type object doesn't expose the properties you set when declaring the type. Thus, you end up with a view that only knows that its model is an object.
Again, use strongly-typed models, or the ViewBag if you truly only need one or two values.
However, to prove that the anonymous type can be passed to the view, look at this (horrible) example:
Controller
return View( new { property1 = "hello world"} );
View
#model object
#{
var rvd = new RouteValueDictionary( Model );
}
#rvd["property1"]
We passed an anonymous type to the view as an object, and then read the object's properties using RouteValueDictionary.
You can use ViewData and ViewBag to send objects to the view page, in your case you can write in the controller something like this:
ViewData["url"] = url ; //Or whatever
return View();
Now in the view you can simply use your object example:<div>#ViewData["url"]</div>
But mainly in MVC it is more recommended to use strongly typed View Models
You may want to look into using the dynamic type in C#. See https://msdn.microsoft.com/en-us/library/dd264736.aspx for details.
While the standard would be to use a strongly-typed view model, there are some scenarios where you might want to use dynamic as your model type (or as a property of your strongly-typed view model), such as in a CMS where the properties are built dynamically by the CMS Provider.
Example view:
#model dynamic
<p>
Url: #Model.url
</p>

How do I use DisplayNameForModel?

I tried using this as a header for a view but it returns an empty string. In the Razor layout, I have something like:
#model IEnumerable<MVCApp.Models.Model>
<h2>#Html.DisplayNameForModel()</h2>
Do I need to set something on the model definition itself? I tried a data annotation [Display(Name="Model Name")] but it is a build error:
Attribute 'Display' is not valid on this declaration type. It is only valid on 'method, property, indexer, field, param' declarations.
The documentation DisplayNameExtensions.DisplayNameForModel Method is terse. The syntax calls for a parameter, but says:
No overload for method 'DisplayNameForModel' takes 1 arguments
As the Usage section says "When you use instance method syntax to call this method, omit the first parameter"
So, how do I use this method to return something?
I just used the default MVC 5 template project in VS2013 and have the #Html.DisplayNameForModel() working with no issues.
First, you are using the wrong data annotation on your view model. You want to use [DisplayName("My Model Name")] and not [Display()]
[DisplayName("Test View Model")]
public class TestViewModel
{
public string TestProperty { get; set; }
}
Second, the html parameter you are seeing on MSDN is a required parameter for any Html helpers in MVC. You do not have to pass anything for this value, the view engine does this for you. So, in your view, you would use the following to get the Display Name that you set on the model as so.
<h2>#Html.DisplayNameForModel()</h2>
Now, your result should output the display name attribute you set in your html. *Note the Test View Model above the Log In text.
try
#Html.DisplayNameFor(m => m.Name)

Extend HiddenFor Templates in ASP.NET MVC

I thought Html.HiddenFor could use Templates like Html.DisplayFor or Html.EditorFor. Unfortunately the method doesn't accept a TemplateName like the others.
I know, the workaround would be to use a DisplayFor/EditorFor Template which has HiddenFors. But I would like to find out how to extend the Html.HiddenFor method. Anyone?
Regards
Seems like you are mislead by wrong analogy. HiddenFor corresponds exactly to the <input type="hidden"/> tag. Just like TextBoxFor, CheckBoxFor etc. These methods are not designed to use templates. DisplayFor/EditorFor on the other side are specially created to be used with templates defined in the project. Thus what you are asking for is not possible out-of-the-box.
However you can always define your own overload for HiddenFor with whatever set of parameters and whatever logic you might require.
There is an overload which accept additional parameter - htmlAttributes. And you can use it for add some attributes to the result tag.
Also the second way is to create razor partial view in one of the folders
~/Areas/AreaName/Views/ControllerName/DisplayTemplates/TemplateName.cshtml
~/Areas/AreaName/Views/Shared/DisplayTemplates/TemplateName.cshtml
~/Views/ControllerName/DisplayTemplates/TemplateName.cshtml
~/Views/Shared/DisplayTemplates/TemplateName.cshtml
with name HiddenInput.cshtml
Here's what you do, you create it as an editor template, because as Andre pointed out, HiddenFor is equivalent to the helper methods like TextBoxFor and CheckboxFor.
It's likely that you'll want to have an actual editor too, so place your real editor under ~/Shared/EditorTemplates. We're going to put our "hidden editor" under the controller you wish to use it on.
~/Views/ControllerName/EditorTemplates/ModelName.cshtml
Lets say we have a Person model.
public class Person
{
public string First { get; set; }
public string Last { get; set; }
}
We'll create a partial view.
#Model Person
#Html.HiddenFor(p => p.First);
#Html.HiddenFor(p => p.Last);
And then we'll pretend we have a model that contains a Person as a property. From our main view, we call our "hidden editor" like so.
#Model Foo
#Html.EditorFor(f => f.Person)
Easy peasy lemon squeezy. A bit hacky, but it works like a charm.

Should View Models always contain the inputs for forms in that view?

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?

ASP.Net MVC - model with collection not populating on postback

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.

Categories