aspnetcore/src/Microsoft.AspNetCore.Mvc.Vi.../Internal/DefaultEditorTemplates.cs

438 lines
17 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public static class DefaultEditorTemplates
{
private const string HtmlAttributeKey = "htmlAttributes";
public static IHtmlContent BooleanTemplate(IHtmlHelper htmlHelper)
{
bool? value = null;
if (htmlHelper.ViewData.Model != null)
{
value = Convert.ToBoolean(htmlHelper.ViewData.Model, CultureInfo.InvariantCulture);
}
return htmlHelper.ViewData.ModelMetadata.IsNullableValueType ?
BooleanTemplateDropDownList(htmlHelper, value) :
BooleanTemplateCheckbox(htmlHelper, value ?? false);
}
private static IHtmlContent BooleanTemplateCheckbox(IHtmlHelper htmlHelper, bool value)
{
return htmlHelper.CheckBox(
expression: null,
isChecked: value,
htmlAttributes: CreateHtmlAttributes(htmlHelper, "check-box"));
}
private static IHtmlContent BooleanTemplateDropDownList(IHtmlHelper htmlHelper, bool? value)
{
return htmlHelper.DropDownList(
expression: null,
selectList: DefaultDisplayTemplates.TriStateValues(value),
optionLabel: null,
htmlAttributes: CreateHtmlAttributes(htmlHelper, "list-box tri-state"));
}
public static IHtmlContent CollectionTemplate(IHtmlHelper htmlHelper)
{
var viewData = htmlHelper.ViewData;
var model = viewData.Model;
if (model == null)
{
return HtmlString.Empty;
}
var collection = model as IEnumerable;
if (collection == null)
{
// Only way we could reach here is if user passed templateName: "Collection" to an Editor() overload.
throw new InvalidOperationException(Resources.FormatTemplates_TypeMustImplementIEnumerable(
"Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
}
var elementMetadata = htmlHelper.ViewData.ModelMetadata.ElementMetadata;
Debug.Assert(elementMetadata != null);
var typeInCollectionIsNullableValueType = elementMetadata.IsNullableValueType;
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
var metadataProvider = serviceProvider.GetRequiredService<IModelMetadataProvider>();
// Use typeof(string) instead of typeof(object) for IEnumerable collections. Neither type is Nullable<T>.
if (elementMetadata.ModelType == typeof(object))
{
elementMetadata = metadataProvider.GetMetadataForType(typeof(string));
}
var oldPrefix = viewData.TemplateInfo.HtmlFieldPrefix;
try
{
viewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
var fieldNameBase = oldPrefix;
var result = new HtmlContentBuilder();
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
var index = 0;
foreach (var item in collection)
{
var itemMetadata = elementMetadata;
if (item != null && !typeInCollectionIsNullableValueType)
{
itemMetadata = metadataProvider.GetMetadataForType(item.GetType());
}
var modelExplorer = new ModelExplorer(
metadataProvider,
container: htmlHelper.ViewData.ModelExplorer,
metadata: itemMetadata,
model: item);
var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
var templateBuilder = new TemplateBuilder(
viewEngine,
viewBufferScope,
htmlHelper.ViewContext,
htmlHelper.ViewData,
modelExplorer,
htmlFieldName: fieldName,
templateName: null,
readOnly: false,
additionalViewData: null);
result.AppendHtml(templateBuilder.Build());
}
return result;
}
finally
{
viewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
}
}
public static IHtmlContent DecimalTemplate(IHtmlHelper htmlHelper)
{
if (htmlHelper.ViewData.TemplateInfo.FormattedModelValue == htmlHelper.ViewData.Model)
{
htmlHelper.ViewData.TemplateInfo.FormattedModelValue =
string.Format(CultureInfo.CurrentCulture, "{0:0.00}", htmlHelper.ViewData.Model);
}
return StringTemplate(htmlHelper);
}
public static IHtmlContent HiddenInputTemplate(IHtmlHelper htmlHelper)
{
var viewData = htmlHelper.ViewData;
var model = viewData.Model;
var result = new HtmlContentBuilder();
if (!viewData.ModelMetadata.HideSurroundingHtml)
{
result.AppendHtml(DefaultDisplayTemplates.StringTemplate(htmlHelper));
}
// Special-case opaque values and arbitrary binary data.
var modelAsByteArray = model as byte[];
if (modelAsByteArray != null)
{
model = Convert.ToBase64String(modelAsByteArray);
}
var htmlAttributesObject = viewData[HtmlAttributeKey];
var hiddenResult = htmlHelper.Hidden(expression: null, value: model, htmlAttributes: htmlAttributesObject);
result.AppendHtml(hiddenResult);
return result;
}
private static IDictionary<string, object> CreateHtmlAttributes(
IHtmlHelper htmlHelper,
string className,
string inputType = null)
{
var htmlAttributesObject = htmlHelper.ViewData[HtmlAttributeKey];
if (htmlAttributesObject != null)
{
return MergeHtmlAttributes(htmlAttributesObject, className, inputType);
}
var htmlAttributes = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
{
{ "class", className }
};
if (inputType != null)
{
htmlAttributes.Add("type", inputType);
}
return htmlAttributes;
}
private static IDictionary<string, object> MergeHtmlAttributes(
object htmlAttributesObject,
string className,
string inputType)
{
var htmlAttributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributesObject);
object htmlClassObject;
if (htmlAttributes.TryGetValue("class", out htmlClassObject))
{
var htmlClassName = htmlClassObject.ToString() + " " + className;
htmlAttributes["class"] = htmlClassName;
}
else
{
htmlAttributes.Add("class", className);
}
// The input type from the provided htmlAttributes overrides the inputType parameter.
if (inputType != null && !htmlAttributes.ContainsKey("type"))
{
htmlAttributes.Add("type", inputType);
}
return htmlAttributes;
}
public static IHtmlContent MultilineTemplate(IHtmlHelper htmlHelper)
{
return htmlHelper.TextArea(
expression: string.Empty,
value: htmlHelper.ViewContext.ViewData.TemplateInfo.FormattedModelValue.ToString(),
rows: 0,
columns: 0,
htmlAttributes: CreateHtmlAttributes(htmlHelper, "text-box multi-line"));
}
public static IHtmlContent ObjectTemplate(IHtmlHelper htmlHelper)
{
var viewData = htmlHelper.ViewData;
var templateInfo = viewData.TemplateInfo;
var modelExplorer = viewData.ModelExplorer;
if (templateInfo.TemplateDepth > 1)
{
if (modelExplorer.Model == null)
{
return new HtmlString(modelExplorer.Metadata.NullDisplayText);
}
var text = modelExplorer.GetSimpleDisplayText();
if (modelExplorer.Metadata.HtmlEncode)
{
return new StringHtmlContent(text);
}
return new HtmlString(text);
}
var serviceProvider = htmlHelper.ViewContext.HttpContext.RequestServices;
var viewEngine = serviceProvider.GetRequiredService<ICompositeViewEngine>();
var viewBufferScope = serviceProvider.GetRequiredService<IViewBufferScope>();
var content = new HtmlContentBuilder();
foreach (var propertyExplorer in modelExplorer.Properties)
{
var propertyMetadata = propertyExplorer.Metadata;
if (!ShouldShow(propertyExplorer, templateInfo))
{
continue;
}
var templateBuilder = new TemplateBuilder(
viewEngine,
viewBufferScope,
htmlHelper.ViewContext,
htmlHelper.ViewData,
propertyExplorer,
htmlFieldName: propertyMetadata.PropertyName,
templateName: null,
readOnly: false,
additionalViewData: null);
var templateBuilderResult = templateBuilder.Build();
if (!propertyMetadata.HideSurroundingHtml)
{
var label = htmlHelper.Label(propertyMetadata.PropertyName, labelText: null, htmlAttributes: null);
if (!string.IsNullOrEmpty(label.ToString()))
{
var labelTag = new TagBuilder("div");
labelTag.AddCssClass("editor-label");
labelTag.InnerHtml.SetHtmlContent(label);
content.AppendLine(labelTag);
}
var valueDivTag = new TagBuilder("div");
valueDivTag.AddCssClass("editor-field");
valueDivTag.InnerHtml.AppendHtml(templateBuilderResult);
valueDivTag.InnerHtml.AppendHtml(" ");
valueDivTag.InnerHtml.AppendHtml(htmlHelper.ValidationMessage(
propertyMetadata.PropertyName,
message: null,
htmlAttributes: null,
tag: null));
content.AppendLine(valueDivTag);
}
else
{
content.AppendHtml(templateBuilderResult);
}
}
return content;
}
public static IHtmlContent PasswordTemplate(IHtmlHelper htmlHelper)
{
return htmlHelper.Password(
expression: null,
value: htmlHelper.ViewData.TemplateInfo.FormattedModelValue,
htmlAttributes: CreateHtmlAttributes(htmlHelper, "text-box single-line password"));
}
private static bool ShouldShow(ModelExplorer modelExplorer, TemplateInfo templateInfo)
{
return
modelExplorer.Metadata.ShowForEdit &&
!modelExplorer.Metadata.IsComplexType &&
!templateInfo.Visited(modelExplorer);
}
public static IHtmlContent StringTemplate(IHtmlHelper htmlHelper)
{
return GenerateTextBox(htmlHelper);
}
public static IHtmlContent PhoneNumberInputTemplate(IHtmlHelper htmlHelper)
{
return GenerateTextBox(htmlHelper, inputType: "tel");
}
public static IHtmlContent UrlInputTemplate(IHtmlHelper htmlHelper)
{
return GenerateTextBox(htmlHelper, inputType: "url");
}
public static IHtmlContent EmailAddressInputTemplate(IHtmlHelper htmlHelper)
{
return GenerateTextBox(htmlHelper, inputType: "email");
}
public static IHtmlContent DateTimeInputTemplate(IHtmlHelper htmlHelper)
{
ApplyRfc3339DateFormattingIfNeeded(htmlHelper, "{0:yyyy-MM-ddTHH:mm:ss.fffK}");
return GenerateTextBox(htmlHelper, inputType: "datetime");
}
public static IHtmlContent DateTimeLocalInputTemplate(IHtmlHelper htmlHelper)
{
ApplyRfc3339DateFormattingIfNeeded(htmlHelper, "{0:yyyy-MM-ddTHH:mm:ss.fff}");
return GenerateTextBox(htmlHelper, inputType: "datetime-local");
}
public static IHtmlContent DateInputTemplate(IHtmlHelper htmlHelper)
{
ApplyRfc3339DateFormattingIfNeeded(htmlHelper, "{0:yyyy-MM-dd}");
return GenerateTextBox(htmlHelper, inputType: "date");
}
public static IHtmlContent TimeInputTemplate(IHtmlHelper htmlHelper)
{
ApplyRfc3339DateFormattingIfNeeded(htmlHelper, "{0:HH:mm:ss.fff}");
return GenerateTextBox(htmlHelper, inputType: "time");
}
public static IHtmlContent NumberInputTemplate(IHtmlHelper htmlHelper)
{
return GenerateTextBox(htmlHelper, inputType: "number");
}
public static IHtmlContent FileInputTemplate(IHtmlHelper htmlHelper)
{
if (htmlHelper == null)
{
throw new ArgumentNullException(nameof(htmlHelper));
}
return GenerateTextBox(htmlHelper, inputType: "file");
}
public static IHtmlContent FileCollectionInputTemplate(IHtmlHelper htmlHelper)
{
if (htmlHelper == null)
{
throw new ArgumentNullException(nameof(htmlHelper));
}
var htmlAttributes =
CreateHtmlAttributes(htmlHelper, className: "text-box single-line", inputType: "file");
htmlAttributes["multiple"] = "multiple";
return GenerateTextBox(htmlHelper, htmlHelper.ViewData.TemplateInfo.FormattedModelValue, htmlAttributes);
}
private static void ApplyRfc3339DateFormattingIfNeeded(IHtmlHelper htmlHelper, string format)
{
if (htmlHelper.Html5DateRenderingMode != Html5DateRenderingMode.Rfc3339)
{
return;
}
var metadata = htmlHelper.ViewData.ModelMetadata;
var value = htmlHelper.ViewData.Model;
if (htmlHelper.ViewData.TemplateInfo.FormattedModelValue != value && metadata.HasNonDefaultEditFormat)
{
return;
}
if (value is DateTime || value is DateTimeOffset)
{
htmlHelper.ViewData.TemplateInfo.FormattedModelValue =
string.Format(CultureInfo.InvariantCulture, format, value);
}
}
private static IHtmlContent GenerateTextBox(IHtmlHelper htmlHelper, string inputType = null)
{
return GenerateTextBox(htmlHelper, inputType, htmlHelper.ViewData.TemplateInfo.FormattedModelValue);
}
private static IHtmlContent GenerateTextBox(IHtmlHelper htmlHelper, string inputType, object value)
{
var htmlAttributes =
CreateHtmlAttributes(htmlHelper, className: "text-box single-line", inputType: inputType);
return GenerateTextBox(htmlHelper, value, htmlAttributes);
}
private static IHtmlContent GenerateTextBox(IHtmlHelper htmlHelper, object value, object htmlAttributes)
{
return htmlHelper.TextBox(
current: null,
value: value,
format: null,
htmlAttributes: htmlAttributes);
}
}
}