I'm having trouble understanding how to render a collection as a drop down list.
If I have a model like:
public class AccountViewModel {
public string[] Country { get; set; }
}
I would like the string collection to render as a drop down list.
Using the html page helper InputFor doesn't seem to work. It simply render's a text box.
I've noticed that InputFor can reflect on the property type and render html accordingly. (Like a checkbox for a boolean field).
I also notice that FubuPageExtensions has methods for CheckBoxFor and TextBoxFor, but nothing equivalent to DropDownListFor.
I'm probably missing something quite fundamental in understanding html conventions in fubu.
Do I need to build the select tag myself? If so, what is the recommended approach to do it?
You are correct that (at the time I last looked) there is no FubuMVC.Core HTML extension method for generating select tags although you could use the HtmlTags library to generate a select tag via code.
As you touch upon in your question the correct way to attack this is likely with an HTML convention together with the HtmlTags library such as that demonstrated in the FubuMVC.Recipes example 'src/UI/HtmlConventionsWithPageExtensions'.
For example an enum generation example might be:
this.Editors
.If(e => e.Accessor.PropertyType.IsEnum)
.BuildBy(er =>
{
var tag = new HtmlTag("select");
var enumValues = Enum.GetValues(er.Accessor.PropertyType);
foreach (var enumValue in enumValues)
{
tag.Children.Add(new HtmlTag("option").Text(enumValue.ToString()));
}
return tag;
});
The FubuMVC.Recipes repository is quite new and still growing so there may be some better examples around but hope this gives you some ideas.
Related
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)
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.
This is a bit of a strange one with Sitecore... Basically I'm accessing an item from the Content API but it's not populating the Item.Fields hashtable with keys based on the text for the field (I guess I'd call this a field name) but rather with a GUID.
For example, here is some code I'm using to get an item:
var database = global::Sitecore.Configuration.Factory.GetDatabase("master");
var item = database.GetItem("/sitecore/content/Home");
item.Fields.ReadAll(); // edit, per recommendation... does not work
Sitecore.Data.Fields.Field f = item.Fields["SomeText"];
Assert.IsNotNull(f): // This fails
If I set a breakpoint and debug, I can see that there are values (indeed, the correct values) inside the Item.Fields hashtable, but the keys are all based on GUIDs rather than "field names" as most code samples regarding usage of this API suggest.
EDIT: Upon closer inspection, the DisplayName and Name fields are coming back as empty strings from the API (note these are clearly defined in Sitecore so still not sure what the issue is). It appears these might be used in conjunction with GUID as some sort of key for the hashtable.
Question: Is there something I'm doing wrong here? I've published the data template and the content item. Clearly the connection is being made because I'm getting results back from the API and even the correct values, just not the keys I'm expecting to use to reference the data values.
References:
http://sdn.sitecore.net/upload/sitecore6/content_api_cookbook-a4.pdf - checkout the example right at the top of page 28 where they access the "title" field. Also, check out the example directly below in 4.1.1 "How to Access System Fields" where they use static helpers with the GUIDs instantiated in a private static constructor. Is this the preferred method for accessing "user defined" fields?
Screenshot of sample data from Sitecore (notices the GUIDs as keys):
Code Samples from above linked document:
Accessing the "title" field:
Sitecore.Data.Database master = Sitecore.Configuration.Factory.GetDatabase("master");
Sitecore.Data.Items.Item home = master.GetItem("/sitecore/content/home");
Sitecore.Data.Fields.Field titleField = home.Fields["title"];
if(titleField!=null)
{
home.Editing.BeginEdit();
titleField.Value = "//TODO: replace with appropriate value";
home.Editing.EndEdit();
}
Accessing the system field "ArchiveDate":
Sitecore.Data.Database master = Sitecore.Configuration.Factory.GetDatabase("master");
Sitecore.Data.Items.Item sample = master.GetItem("/sitecore/content/home/sample");
Sitecore.Data.Fields.DateField archiveField =
sample.Fields[Sitecore.FieldIDs.ArchiveDate];
Decompiling the Sitecore.Kernel.dll we can see that:
public static class FieldIDs
{
// stripped down version
/// <summary>The ID of the 'Archive date' field.</summary>
public static ID ArchiveDate;
static FieldIDs()
{
FieldIDs.ArchiveDate = new ID("{56C15C6D-FD5A-40CA-BB37-64CEEC6A9BD5}");
}
}
If I understand correctly, you want the Fields collection to return all the fields available for that item, even if they do not have a value. By default, Sitecore will only return those fields that have a value.
You can solve this by calling the ReadAll() method before accessing the fields collection.
So in your example:
item.Fields.ReadAll();
Sitecore.Data.Fields.Field f = item.Fields["SomeText"];
Assert.IsNotNull(f): // This succeeds
I had a problem with identical symptoms. The root cause for me was a publishing issue. The folder containing my template was not published, though the template itself was. So I could see the fields in the debugger with the correct values and ids, but not the names. The solution was to ensure that all the parents of my template were also published.
So, I ended up going the route I mentioned in the question (which is what Sitecore uses internally and, #technophoria414 mentioned, a Sitecore developer best practice).
Basically:
namespace MyProject.Core.Data.Sitecore.Fields
{
public static class ContentItem
{
// stripped down version
public static ID DESCRIPTION_TEXT;
static ContentItem()
{
DESCRIPTION_TEXT= new ID("{56C15C6D-FD5A-40CA-BB37-64CEEC6A9BD5}"); // this will be some GUID out of Sitecore
}
}
}
Usage would be something like this:
var query = string.Format("fast:/sitecore/content/HomePageItems//*[#ContentSlug='{0}']", input);
var item = Database.SelectSingleItem(query);
var descriptionText = item.Fields[ContentItem.DESCRIPTION_TEXT].Value;
One of the key features of a project I'm working on is the ability for the user to configure Forms (as in "Forms" to fill-up) based on a pool of pre-existing field types (well known types, for instance "user name", "date of birth" etc. but also "generic types" like "string", "DateTime" etc.).
We used to have a static ViewModel that worked fine for the "well known" types and looked like this:
public class UserInputModel
{
[StringLength(200)]
public string Name { get; set; }
[Required(ErrorMessageResourceName = "BirthDateEmptyError", ErrorMessageResourceType = typeof(Resources.ErrorMessages))]
public DateTime BirthDate { get; set; }
//Here comes a lot of other properties
}
All the known properties were listed and we were showing or hiding them given the context.
But the last requirement came and changed all that. The user shall now be able to add as many generic type fields as he wants. In order to do this, we decided to make this InputModel entirely dynamic. It now looks like this:
public class UserInputModel
{
// Each ModelProperty has an "Id" and a "Value" property
public ICollection<ModelProperty> Properties { get; set; }
}
This works like a charm. The razor view only has to iterates over the collection, create the corresponding controls for each property of the collection in a more than standard way:
#Html.TextBoxFor(m => m.Properties[index].Value);
... and we nicely get the data back as a filled form.
=> This works fine, but we don't have any client-side validation. For this, we would need some Metadata... which we don't have via annotations anymore since we're dynamically creating the model.
In order to provide those MetaData, I created a CustomModelMetadataProvider that inherits from DataAnnotationsModelMetadataProvider and registered it as the new ModelMetadataProvider in the Global.asax. The CreateMetadata() function gets called upon creation of the ViewModel, and that for each of the properties of my ViewModel... sofar so good.
Where the problem starts: in order to add some metadata to the current property, I first need to identify which property I am currently looking at ("Name" has a maxlength of 200, "date of birth" hasn't so I cannot assign a maxlength to every property per default). And somewhow I didn't manage to do that yet since all the properties have the same name Value and the same container type ModelProperty.
I tried accessing the container of the property via reflection, but since the ModelAccessor's target is the ViewModel itself (because of the lambda expression m => m.Properties), the following construct gives me the ViewModel as a whole, not just the ModelProperty:
var container = modelAccessor.Target.GetType().GetField("container");
var containerObject = (UserInputModel)container.GetValue(modelAccessor.Target);
I've been flipping this over and over but cannot find a way to identify which ModelProperty I have in hand. Is there a way to do this?
Update: after flipping this in every possible direction for a while, we finally went another way. We are basically using unobstrusive javascript to use MVC's validation capabilities without touching attributes nor metadata. In short, we add HTML attributes like value-data="true" (and all other required attributes) to the #Html.TextBoxFor() statements. This works wonderfully for all the atomic validations (required, stringlength etc.).
Tim, you can leverage what appears to be client-side validation through Ajax with the Remote attribute on your properties.
Basically, you'll need to set up a validation controller and then write some smarts into that controller. But at least you'd be able to write some helper methods and keep it all in one place. You would have a series of validators, based on the meta data that you are presenting to the end users, and each validator method would work for a particular type with good re-use.
The one pitfall to this approach would be that you would need to write a validation method for each type and condition that you want to support. Sounds like you're having to go down that road anyways, though.
Hope this helps.
See if this article help you: Technique for carrying metadata to View Models with AutoMapper.
Also use this one for ideas (custom model metadata provider): changing viewmodel's MetadataType attribute at runtime
Fluent validation is probably the best option for you in my mind, but its obviously up to you to select the best match among those above.
Update
Try use ModelMetadata and override ModelMetadataProvider: Dive Deep Into MVC: ModelMetadata and ModelMetadataProvider. This way you completely customize your model metadata (this replaces data annotations) and you have complete control on what is happening, rather than relying on ASP.NET MVC.
Another good place to look at it is Creating your own ModelMetadataProvider to handle custom attributes.
Hope this all is of help to you.
I've been using T4MVC (FYI: v2.6.62) for quite some time, and I've been slowly moving over our code to this way of working (less reliance on magic strings).
But I've had to stop because, for some reason, T4MVC is unable to translate objects into urls, and only seems to be able to work on primitive types (int/string/etc).
Here is an example:
Route breakdown:
/MyController/MyAction/{Number}/{SomeText}
Class:
namespace MyNamespace
{
public class MyClass
{
public int Number { get; set; }
public string SomeText { get; set; }
}
}
Controller:
public class MyController
{
public virtual ActionResult MyAction(MyClass myClass)
{
return View();
}
}
View:
<%= Html.Action(
T4MVC.MyController.Actions.MyAction(
new MyClass()
{
Number = 1,
SomeText = "ABC"
}
) %>
The end result is this:
/MyController/MyAction?myClass=MyNamespace.MyClass
and not
/MyController/MyAction/1/ABC
Does anyone else have this problem? Are T4MVC urls like this available?
Question also asked at the ASP.NET Forum.
Update (10/11/2012): the recently added support for Model Unbinders (see section 3.1 in the doc) should hopefully cover a lot of these cases.
Original answer:
Copying my reply from the forum thread:
Hmmm, I don't think this has come up yet. Maybe in most cases that people have Action methods that take an object, the object's values come from posted form data, rather than being passed on the URL? In such scenario, the question doesn't arise.
I think in theory T4MVC could be changed to support this. It would just need to promote all the object's top level properties as route values rather than try to use the object itself (obviously, the current behavior is bogus, and is a result of just calling ToString() blindly).
Have others run into this and think it's worth addressing?
If I've understood the problem correctly then the following syntax should allow you to work around the problem.
<%= Html.ActionLink("test", MVC.MyController.MyAction().AddRouteValues(new MyClass() { Number = 5, SomeText = "Hello" })) %>
I think the answer to make the syntax nicer would be to wrap each non value type parameter in a RouteValueDictionary in each generated action result method
Edit: (Response to comment as not enough chars)
Ah ok I managed to recreate the simple example above using this method to give: /MyController/MyAction/5/Hello as the url.
I'm not quite sure how nested complex types would pan out in practice. You could use some recursion to dive down the into the top-level object and reflect over the values to add them but then you open up a new set of issues, such as how to cope with a child property name that is identical to the parent property name.
This seems like it could be a complex problem to solve, in a manner that would work for everyone.
Perhaps some kind of adapter pattern would be most useful to transform a complex object into route values. In the simplest case this might be to declare an extension method ToRouteDictionary that acts on your complex type and transforms it using your knowledge of how it should work. Just thinking out loud as I'm obviously not aware of your use cases