Skip to content

Commit 0a62a58

Browse files
committed
Add TextBox and TextBoxFor
- HtmlHelper service now needs an `IModelMetadataProvider` instance - make `GetInputTypeString()` private - use `TextBox()` and `TextBoxFor()` in MVC sample - throw if `ExpressionMetadataProvider.FromLambdaExpression()` returns `null`
1 parent a1a180d commit 0a62a58

File tree

7 files changed

+355
-5
lines changed

7 files changed

+355
-5
lines changed

samples/MvcSample.Web/Views/Shared/MyView.cshtml

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
ViewBag.Anon = new
99
{
10+
Name = "FirstName LastName",
1011
Address = new { Street = "123 Fake St.", City = "Redmond", State = "WA", Country = "USA", },
1112
};
1213
}
@@ -61,7 +62,7 @@
6162
</div>
6263

6364
<div class="row">
64-
<div style="float: left; border: 5px solid blue;">
65+
<div style="float: left; border: 5px solid blue; padding-right: 10px;">
6566
<table>
6667
<tr>
6768
<td>
@@ -98,6 +99,76 @@
9899
</table>
99100
</div>
100101

102+
<div style="float: left; border: thick solid lightskyblue; margin-left: 15px; padding-right: 10px">
103+
<form action="/Home/Hello" method="post">
104+
<table>
105+
<tr>
106+
<td>
107+
<label class="control-label col-md-2">Model.Name</label>
108+
</td>
109+
<td>
110+
@Html.TextBox("Name")
111+
</td>
112+
</tr>
113+
<tr>
114+
<td>
115+
<label class="control-label col-md-2">Model.Address</label>
116+
</td>
117+
<td>
118+
@Html.TextBoxFor(m => m.Address, htmlAttributes: new { @class = "form-control" })
119+
</td>
120+
</tr>
121+
<tr>
122+
<td>
123+
<label class="control-label col-md-2">Anon.Name</label>
124+
</td>
125+
<td>
126+
@Html.TextBox("Anon.Name")
127+
</td>
128+
</tr>
129+
<tr>
130+
<td>
131+
<label class="control-label col-md-2">Anon.Address.Street</label>
132+
</td>
133+
<td>
134+
@Html.TextBox("Anon.Address.Street", (object)ViewBag.Anon.Address.Street)
135+
</td>
136+
</tr>
137+
<tr>
138+
<td>
139+
<label class="control-label col-md-2">Anon.Address.City</label>
140+
</td>
141+
<td>
142+
@Html.TextBox("Anon.Address.City", value: null, format: "{0} (3)")
143+
</td>
144+
</tr>
145+
<tr>
146+
<td>
147+
<label class="control-label col-md-2">Anon.Address.State</label>
148+
</td>
149+
<td>
150+
@Html.TextBox("Anon.Address.State", value: null, htmlAttributes: new { @class = "form-control" })
151+
</td>
152+
</tr>
153+
<tr>
154+
<td>
155+
<label class="control-label col-md-2">Anon.Address.Country</label>
156+
</td>
157+
<td>
158+
@Html.TextBox("Anon.Address.Country", value: null, format: "{0} (4)",
159+
htmlAttributes: new { @class = "form-control" })
160+
</td>
161+
</tr>
162+
</table>
163+
164+
<div class="form-group">
165+
<div class="col-md-2">
166+
<input type="submit" value="Save" class="btn btn-default" />
167+
</div>
168+
</div>
169+
</form>
170+
</div>
171+
101172
<div style="float: right; border: 5px solid red;">
102173
@await Component.InvokeAsync("Tags", 15)
103174
</div>

src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelper.cs

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Text;
88
using System.Threading.Tasks;
99
using Microsoft.AspNet.Abstractions;
10+
using Microsoft.AspNet.Mvc.ModelBinding;
1011

1112
namespace Microsoft.AspNet.Mvc.Rendering
1213
{
@@ -30,9 +31,10 @@ public class HtmlHelper : ICanHasViewContext
3031
/// <summary>
3132
/// Initializes a new instance of the <see cref="HtmlHelper"/> class.
3233
/// </summary>
33-
public HtmlHelper(IViewEngine viewEngine)
34+
public HtmlHelper([NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider)
3435
{
3536
_viewEngine = viewEngine;
37+
MetadataProvider = metadataProvider;
3638

3739
// Underscores are fine characters in id's.
3840
IdAttributeDotReplacement = "_";
@@ -75,6 +77,8 @@ public ViewDataDictionary ViewData
7577
}
7678
}
7779

80+
protected IModelMetadataProvider MetadataProvider { get; private set; }
81+
7882
/// <summary>
7983
/// Creates a dictionary of HTML attributes from the input object,
8084
/// translating underscores to dashes.
@@ -125,6 +129,12 @@ public string Encode(object value)
125129
return value != null ? WebUtility.HtmlEncode(value.ToString()) : string.Empty;
126130
}
127131

132+
/// <inheritdoc />
133+
public string FormatValue(object value, string format)
134+
{
135+
return ViewDataDictionary.FormatValue(value, format);
136+
}
137+
128138
public string GenerateIdFromName([NotNull] string name)
129139
{
130140
return TagBuilder.CreateSanitizedId(name, IdAttributeDotReplacement);
@@ -283,7 +293,108 @@ public static string GetFormMethodString(FormMethod method)
283293
}
284294
}
285295

286-
public static string GetInputTypeString(InputType inputType)
296+
/// <inheritdoc />
297+
public HtmlString TextBox(string name, object value, string format, IDictionary<string, object> htmlAttributes)
298+
{
299+
return GenerateTextBox(metadata: null, name: name, value: value, format: format,
300+
htmlAttributes: htmlAttributes);
301+
}
302+
303+
protected string EvalString(string key, string format)
304+
{
305+
return Convert.ToString(ViewData.Eval(key, format), CultureInfo.CurrentCulture);
306+
}
307+
308+
protected object GetModelStateValue(string key, Type destinationType)
309+
{
310+
ModelState modelState;
311+
if (ViewData.ModelState.TryGetValue(key, out modelState) && modelState.Value != null)
312+
{
313+
return modelState.Value.ConvertTo(destinationType, culture: null);
314+
}
315+
316+
return null;
317+
}
318+
319+
protected IDictionary<string, object> GetValidationAttributes(string name)
320+
{
321+
return GetValidationAttributes(name, metadata: null);
322+
}
323+
324+
// Only render attributes if unobtrusive client-side validation is enabled, and then only if we've
325+
// never rendered validation for a field with this name in this form. Also, if there's no form context,
326+
// then we can't render the attributes (we'd have no <form> to attach them to).
327+
protected IDictionary<string, object> GetValidationAttributes(string name, ModelMetadata metadata)
328+
{
329+
// TODO: Add validation attributes to input helpers.
330+
return new Dictionary<string, object>();
331+
}
332+
333+
protected virtual HtmlString GenerateTextBox(ModelMetadata metadata, string name, object value, string format,
334+
IDictionary<string, object> htmlAttributes)
335+
{
336+
return GenerateInput(InputType.Text,
337+
metadata,
338+
name,
339+
value,
340+
useViewData: (metadata == null && value == null),
341+
isChecked: false,
342+
setId: true,
343+
isExplicitValue: true,
344+
format: format,
345+
htmlAttributes: htmlAttributes);
346+
}
347+
348+
protected virtual HtmlString GenerateInput(InputType inputType, ModelMetadata metadata, string name,
349+
object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format,
350+
IDictionary<string, object> htmlAttributes)
351+
{
352+
// Not valid to use TextBoxForModel() in a top-level view; would end up with an unnamed input elements.
353+
// But we support the *ForModel() methods in any lower-level template, once HtmlFieldPrefix is non-empty.
354+
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
355+
if (string.IsNullOrEmpty(fullName))
356+
{
357+
throw new ArgumentException(Resources.ArgumentNullOrEmpty, "name");
358+
}
359+
360+
var tagBuilder = new TagBuilder("input");
361+
tagBuilder.MergeAttributes(htmlAttributes);
362+
tagBuilder.MergeAttribute("type", GetInputTypeString(inputType));
363+
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
364+
365+
var valueParameter = FormatValue(value, format);
366+
switch (inputType)
367+
{
368+
case InputType.Text:
369+
default:
370+
var attributeValue = (string)GetModelStateValue(fullName, typeof(string));
371+
if (attributeValue == null)
372+
{
373+
attributeValue = useViewData ? EvalString(fullName, format) : valueParameter;
374+
}
375+
376+
tagBuilder.MergeAttribute("value", attributeValue, replaceExisting: isExplicitValue);
377+
break;
378+
}
379+
380+
if (setId)
381+
{
382+
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
383+
}
384+
385+
// If there are any errors for a named field, we add the CSS attribute.
386+
ModelState modelState;
387+
if (ViewData.ModelState.TryGetValue(fullName, out modelState) && modelState.Errors.Count > 0)
388+
{
389+
tagBuilder.AddCssClass(ValidationInputCssClassName);
390+
}
391+
392+
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
393+
394+
return tagBuilder.ToHtmlString(TagRenderMode.SelfClosing);
395+
}
396+
397+
private static string GetInputTypeString(InputType inputType)
287398
{
288399
switch (inputType)
289400
{

src/Microsoft.AspNet.Mvc.Rendering/Html/HtmlHelperOfT.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.Contracts;
24
using System.Linq.Expressions;
5+
using Microsoft.AspNet.Mvc.ModelBinding;
36
using Microsoft.AspNet.Mvc.Rendering.Expressions;
47

58
namespace Microsoft.AspNet.Mvc.Rendering
@@ -9,8 +12,8 @@ public class HtmlHelper<TModel> : HtmlHelper, IHtmlHelper<TModel>
912
/// <summary>
1013
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
1114
/// </summary>
12-
public HtmlHelper(IViewEngine viewEngine)
13-
: base(viewEngine)
15+
public HtmlHelper([NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider)
16+
: base(viewEngine, metadataProvider)
1417
{
1518
}
1619

@@ -45,9 +48,29 @@ public HtmlString NameFor<TProperty>([NotNull] Expression<Func<TModel, TProperty
4548
return Name(expressionName);
4649
}
4750

51+
/// <inheritdoc />
52+
public HtmlString TextBoxFor<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression,
53+
string format, IDictionary<string, object> htmlAttributes)
54+
{
55+
var metadata = GetModelMetadata(expression);
56+
return GenerateTextBox(metadata, GetExpressionName(expression), metadata.Model, format, htmlAttributes);
57+
}
58+
4859
protected string GetExpressionName<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression)
4960
{
5061
return ExpressionHelper.GetExpressionText(expression);
5162
}
63+
64+
protected ModelMetadata GetModelMetadata<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression)
65+
{
66+
var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
67+
if (metadata == null)
68+
{
69+
var expressionName = GetExpressionName(expression);
70+
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionName));
71+
}
72+
73+
return metadata;
74+
}
5275
}
5376
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq.Expressions;
5+
6+
namespace Microsoft.AspNet.Mvc.Rendering
7+
{
8+
public static class HtmlHelperInputExtensions
9+
{
10+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name)
11+
{
12+
return TextBox(htmlHelper, name, value: null);
13+
}
14+
15+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
16+
object value)
17+
{
18+
return TextBox(htmlHelper, name, value, format: null);
19+
}
20+
21+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
22+
object value, string format)
23+
{
24+
return TextBox(htmlHelper, name, value, format, htmlAttributes: null);
25+
}
26+
27+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
28+
object value, object htmlAttributes)
29+
{
30+
return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
31+
}
32+
33+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
34+
object value, string format, object htmlAttributes)
35+
{
36+
return htmlHelper.TextBox(name, value, format,
37+
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
38+
}
39+
40+
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
41+
object value, IDictionary<string, object> htmlAttributes)
42+
{
43+
return htmlHelper.TextBox(name, value, format: null, htmlAttributes: htmlAttributes);
44+
}
45+
46+
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
47+
[NotNull] Expression<Func<TModel, TProperty>> expression)
48+
{
49+
return TextBoxFor(htmlHelper, expression, format: null);
50+
}
51+
52+
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
53+
[NotNull] Expression<Func<TModel, TProperty>> expression, string format)
54+
{
55+
return TextBoxFor(htmlHelper, expression, format, htmlAttributes: null);
56+
}
57+
58+
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
59+
[NotNull] Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
60+
{
61+
return TextBoxFor(htmlHelper, expression, format: null, htmlAttributes: htmlAttributes);
62+
}
63+
64+
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
65+
[NotNull] Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes)
66+
{
67+
return htmlHelper.TextBoxFor(expression, format: format,
68+
htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
69+
}
70+
71+
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
72+
[NotNull] Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
73+
{
74+
return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)