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`
This commit is contained in:
parent
a1a180d4d0
commit
0a62a581de
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
ViewBag.Anon = new
|
||||
{
|
||||
Name = "FirstName LastName",
|
||||
Address = new { Street = "123 Fake St.", City = "Redmond", State = "WA", Country = "USA", },
|
||||
};
|
||||
}
|
||||
|
|
@ -61,7 +62,7 @@
|
|||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div style="float: left; border: 5px solid blue;">
|
||||
<div style="float: left; border: 5px solid blue; padding-right: 10px;">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
|
|
@ -98,6 +99,76 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div style="float: left; border: thick solid lightskyblue; margin-left: 15px; padding-right: 10px">
|
||||
<form action="/Home/Hello" method="post">
|
||||
<table>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Model.Name</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Name")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Model.Address</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBoxFor(m => m.Address, htmlAttributes: new { @class = "form-control" })
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Anon.Name</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Anon.Name")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Anon.Address.Street</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Anon.Address.Street", (object)ViewBag.Anon.Address.Street)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Anon.Address.City</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Anon.Address.City", value: null, format: "{0} (3)")
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Anon.Address.State</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Anon.Address.State", value: null, htmlAttributes: new { @class = "form-control" })
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label class="control-label col-md-2">Anon.Address.Country</label>
|
||||
</td>
|
||||
<td>
|
||||
@Html.TextBox("Anon.Address.Country", value: null, format: "{0} (4)",
|
||||
htmlAttributes: new { @class = "form-control" })
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value="Save" class="btn btn-default" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div style="float: right; border: 5px solid red;">
|
||||
@await Component.InvokeAsync("Tags", 15)
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Reflection;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
|
|
@ -30,9 +31,10 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HtmlHelper"/> class.
|
||||
/// </summary>
|
||||
public HtmlHelper(IViewEngine viewEngine)
|
||||
public HtmlHelper([NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider)
|
||||
{
|
||||
_viewEngine = viewEngine;
|
||||
MetadataProvider = metadataProvider;
|
||||
|
||||
// Underscores are fine characters in id's.
|
||||
IdAttributeDotReplacement = "_";
|
||||
|
|
@ -75,6 +77,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
protected IModelMetadataProvider MetadataProvider { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dictionary of HTML attributes from the input object,
|
||||
/// translating underscores to dashes.
|
||||
|
|
@ -125,6 +129,12 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return value != null ? WebUtility.HtmlEncode(value.ToString()) : string.Empty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string FormatValue(object value, string format)
|
||||
{
|
||||
return ViewDataDictionary.FormatValue(value, format);
|
||||
}
|
||||
|
||||
public string GenerateIdFromName([NotNull] string name)
|
||||
{
|
||||
return TagBuilder.CreateSanitizedId(name, IdAttributeDotReplacement);
|
||||
|
|
@ -283,7 +293,108 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
}
|
||||
}
|
||||
|
||||
public static string GetInputTypeString(InputType inputType)
|
||||
/// <inheritdoc />
|
||||
public HtmlString TextBox(string name, object value, string format, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return GenerateTextBox(metadata: null, name: name, value: value, format: format,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
protected string EvalString(string key, string format)
|
||||
{
|
||||
return Convert.ToString(ViewData.Eval(key, format), CultureInfo.CurrentCulture);
|
||||
}
|
||||
|
||||
protected object GetModelStateValue(string key, Type destinationType)
|
||||
{
|
||||
ModelState modelState;
|
||||
if (ViewData.ModelState.TryGetValue(key, out modelState) && modelState.Value != null)
|
||||
{
|
||||
return modelState.Value.ConvertTo(destinationType, culture: null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected IDictionary<string, object> GetValidationAttributes(string name)
|
||||
{
|
||||
return GetValidationAttributes(name, metadata: null);
|
||||
}
|
||||
|
||||
// Only render attributes if unobtrusive client-side validation is enabled, and then only if we've
|
||||
// never rendered validation for a field with this name in this form. Also, if there's no form context,
|
||||
// then we can't render the attributes (we'd have no <form> to attach them to).
|
||||
protected IDictionary<string, object> GetValidationAttributes(string name, ModelMetadata metadata)
|
||||
{
|
||||
// TODO: Add validation attributes to input helpers.
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
protected virtual HtmlString GenerateTextBox(ModelMetadata metadata, string name, object value, string format,
|
||||
IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return GenerateInput(InputType.Text,
|
||||
metadata,
|
||||
name,
|
||||
value,
|
||||
useViewData: (metadata == null && value == null),
|
||||
isChecked: false,
|
||||
setId: true,
|
||||
isExplicitValue: true,
|
||||
format: format,
|
||||
htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
protected virtual HtmlString GenerateInput(InputType inputType, ModelMetadata metadata, string name,
|
||||
object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format,
|
||||
IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
// Not valid to use TextBoxForModel() in a top-level view; would end up with an unnamed input elements.
|
||||
// But we support the *ForModel() methods in any lower-level template, once HtmlFieldPrefix is non-empty.
|
||||
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
|
||||
if (string.IsNullOrEmpty(fullName))
|
||||
{
|
||||
throw new ArgumentException(Resources.ArgumentNullOrEmpty, "name");
|
||||
}
|
||||
|
||||
var tagBuilder = new TagBuilder("input");
|
||||
tagBuilder.MergeAttributes(htmlAttributes);
|
||||
tagBuilder.MergeAttribute("type", GetInputTypeString(inputType));
|
||||
tagBuilder.MergeAttribute("name", fullName, replaceExisting: true);
|
||||
|
||||
var valueParameter = FormatValue(value, format);
|
||||
switch (inputType)
|
||||
{
|
||||
case InputType.Text:
|
||||
default:
|
||||
var attributeValue = (string)GetModelStateValue(fullName, typeof(string));
|
||||
if (attributeValue == null)
|
||||
{
|
||||
attributeValue = useViewData ? EvalString(fullName, format) : valueParameter;
|
||||
}
|
||||
|
||||
tagBuilder.MergeAttribute("value", attributeValue, replaceExisting: isExplicitValue);
|
||||
break;
|
||||
}
|
||||
|
||||
if (setId)
|
||||
{
|
||||
tagBuilder.GenerateId(fullName, IdAttributeDotReplacement);
|
||||
}
|
||||
|
||||
// If there are any errors for a named field, we add the CSS attribute.
|
||||
ModelState modelState;
|
||||
if (ViewData.ModelState.TryGetValue(fullName, out modelState) && modelState.Errors.Count > 0)
|
||||
{
|
||||
tagBuilder.AddCssClass(ValidationInputCssClassName);
|
||||
}
|
||||
|
||||
tagBuilder.MergeAttributes(GetValidationAttributes(name, metadata));
|
||||
|
||||
return tagBuilder.ToHtmlString(TagRenderMode.SelfClosing);
|
||||
}
|
||||
|
||||
private static string GetInputTypeString(InputType inputType)
|
||||
{
|
||||
switch (inputType)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
using Microsoft.AspNet.Mvc.Rendering.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
|
|
@ -9,8 +12,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
|
||||
/// </summary>
|
||||
public HtmlHelper(IViewEngine viewEngine)
|
||||
: base(viewEngine)
|
||||
public HtmlHelper([NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider)
|
||||
: base(viewEngine, metadataProvider)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -45,9 +48,29 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return Name(expressionName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public HtmlString TextBoxFor<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression,
|
||||
string format, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
var metadata = GetModelMetadata(expression);
|
||||
return GenerateTextBox(metadata, GetExpressionName(expression), metadata.Model, format, htmlAttributes);
|
||||
}
|
||||
|
||||
protected string GetExpressionName<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression)
|
||||
{
|
||||
return ExpressionHelper.GetExpressionText(expression);
|
||||
}
|
||||
|
||||
protected ModelMetadata GetModelMetadata<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression)
|
||||
{
|
||||
var metadata = ExpressionMetadataProvider.FromLambdaExpression(expression, ViewData, MetadataProvider);
|
||||
if (metadata == null)
|
||||
{
|
||||
var expressionName = GetExpressionName(expression);
|
||||
throw new InvalidOperationException(Resources.FormatHtmlHelper_NullModelMetadata(expressionName));
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
public static class HtmlHelperInputExtensions
|
||||
{
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name)
|
||||
{
|
||||
return TextBox(htmlHelper, name, value: null);
|
||||
}
|
||||
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
object value)
|
||||
{
|
||||
return TextBox(htmlHelper, name, value, format: null);
|
||||
}
|
||||
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
object value, string format)
|
||||
{
|
||||
return TextBox(htmlHelper, name, value, format, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
object value, object htmlAttributes)
|
||||
{
|
||||
return TextBox(htmlHelper, name, value, format: null, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
object value, string format, object htmlAttributes)
|
||||
{
|
||||
return htmlHelper.TextBox(name, value, format,
|
||||
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
|
||||
}
|
||||
|
||||
public static HtmlString TextBox<TModel>([NotNull] this IHtmlHelper<TModel> htmlHelper, string name,
|
||||
object value, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return htmlHelper.TextBox(name, value, format: null, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression)
|
||||
{
|
||||
return TextBoxFor(htmlHelper, expression, format: null);
|
||||
}
|
||||
|
||||
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, string format)
|
||||
{
|
||||
return TextBoxFor(htmlHelper, expression, format, htmlAttributes: null);
|
||||
}
|
||||
|
||||
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
|
||||
{
|
||||
return TextBoxFor(htmlHelper, expression, format: null, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
|
||||
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes)
|
||||
{
|
||||
return htmlHelper.TextBoxFor(expression, format: format,
|
||||
htmlAttributes: HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
|
||||
}
|
||||
|
||||
public static HtmlString TextBoxFor<TModel, TProperty>([NotNull] this IHtmlHelper<TModel> htmlHelper,
|
||||
[NotNull] Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return htmlHelper.TextBoxFor(expression, format: null, htmlAttributes: htmlAttributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -101,6 +102,37 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <returns>A task that represents when rendering has completed.</returns>
|
||||
Task RenderPartialAsync([NotNull] string partialViewName, object model, ViewDataDictionary viewData);
|
||||
|
||||
/// <summary>
|
||||
/// Render an input element of type "text".
|
||||
/// </summary>
|
||||
/// <param name="name">
|
||||
/// Rendered element's name. Also use this name to find value in submitted data or view data. Use view data
|
||||
/// only if value is not in submitted data and <paramref name="value"/> is <c>null</c>.
|
||||
/// </param>
|
||||
/// <param name="value">
|
||||
/// If non-<c>null</c>, value to include in the element. Ignore if named value is found in submitted data.
|
||||
/// </param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="htmlAttributes">
|
||||
/// <see cref="IDictionary{string, object}"/> containing additional HTML attributes.
|
||||
/// </param>
|
||||
/// <returns>New <see cref="HtmlString"/> containing the rendered HTML.</returns>
|
||||
HtmlString TextBox(string name, object value, string format, IDictionary<string, object> htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Render an input element of type "text".
|
||||
/// </summary>
|
||||
/// <param name="expression">
|
||||
/// An expression that identifies the object that contains the properties to render.
|
||||
/// </param>
|
||||
/// <param name="format"></param>
|
||||
/// <param name="htmlAttributes">
|
||||
/// <see cref="IDictionary{string, object}"/> containing additional HTML attributes.
|
||||
/// </param>
|
||||
/// <returns>New <see cref="HtmlString"/> containing the rendered HTML.</returns>
|
||||
HtmlString TextBoxFor<TProperty>([NotNull] Expression<Func<TModel, TProperty>> expression, string format,
|
||||
IDictionary<string, object> htmlAttributes);
|
||||
|
||||
/// <summary>
|
||||
/// Returns an unordered list (ul element) of validation messages that are in the <see cref="ModelStateDictionary"/> object.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,20 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// <auto-generated />
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
|
|
@ -122,6 +139,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ExpressionHelper_InvalidIndexerExpression"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The IModelMetadataProvider was unable to provide metadata for expression '{0}'.
|
||||
/// </summary>
|
||||
internal static string HtmlHelper_NullModelMetadata
|
||||
{
|
||||
get { return GetString("HtmlHelper_NullModelMetadata"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The IModelMetadataProvider was unable to provide metadata for expression '{0}'.
|
||||
/// </summary>
|
||||
internal static string FormatHtmlHelper_NullModelMetadata(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlHelper_NullModelMetadata"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Must call 'Contextualize' method before using this HtmlHelper instance.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,9 @@
|
|||
<data name="ExpressionHelper_InvalidIndexerExpression" xml:space="preserve">
|
||||
<value>The expression compiler was unable to evaluate the indexer expression '{0}' because it references the model parameter '{1}' which is unavailable.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_NullModelMetadata" xml:space="preserve">
|
||||
<value>The IModelMetadataProvider was unable to provide metadata for expression '{0}'.</value>
|
||||
</data>
|
||||
<data name="HtmlHelper_NotContextualized" xml:space="preserve">
|
||||
<value>Must call 'Contextualize' method before using this HtmlHelper instance.</value>
|
||||
</data>
|
||||
|
|
|
|||
Loading…
Reference in New Issue