+
-
+
@Html.DisplayForModel()
diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
index 0488dfdae9..5c6d047837 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
+++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj
@@ -152,6 +152,7 @@
+
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 7674c4ac67..90da94d467 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -362,6 +362,54 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("Common_ValueNotValidForProperty"), p0);
}
+ ///
+ /// False
+ ///
+ internal static string Common_TriState_False
+ {
+ get { return GetString("Common_TriState_False"); }
+ }
+
+ ///
+ /// False
+ ///
+ internal static string FormatCommon_TriState_False()
+ {
+ return GetString("Common_TriState_False");
+ }
+
+ ///
+ /// Not Set
+ ///
+ internal static string Common_TriState_NotSet
+ {
+ get { return GetString("Common_TriState_NotSet"); }
+ }
+
+ ///
+ /// Not Set
+ ///
+ internal static string FormatCommon_TriState_NotSet()
+ {
+ return GetString("Common_TriState_NotSet");
+ }
+
+ ///
+ /// True
+ ///
+ internal static string Common_TriState_True
+ {
+ get { return GetString("Common_TriState_True"); }
+ }
+
+ ///
+ /// True
+ ///
+ internal static string FormatCommon_TriState_True()
+ {
+ return GetString("Common_TriState_True");
+ }
+
///
/// ViewData value must not be null.
///
@@ -474,6 +522,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("HtmlHelper_WrongSelectDataType"), p0, p1, p2);
}
+ ///
+ /// The '{0}' template was used with an object of type '{1}', which does not implement '{2}'.
+ ///
+ internal static string Templates_TypeMustImplementIEnumerable
+ {
+ get { return GetString("Templates_TypeMustImplementIEnumerable"); }
+ }
+
+ ///
+ /// The '{0}' template was used with an object of type '{1}', which does not implement '{2}'.
+ ///
+ internal static string FormatTemplates_TypeMustImplementIEnumerable(object p0, object p1, object p2)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("Templates_TypeMustImplementIEnumerable"), p0, p1, p2);
+ }
+
///
/// Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
///
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs
new file mode 100644
index 0000000000..4d86784918
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultDisplayTemplates.cs
@@ -0,0 +1,289 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Microsoft.AspNet.DependencyInjection;
+using Microsoft.AspNet.Mvc.Core;
+using Microsoft.AspNet.Mvc.ModelBinding;
+
+namespace Microsoft.AspNet.Mvc.Rendering
+{
+ public static class DefaultDisplayTemplates
+ {
+ public static string BooleanTemplate(IHtmlHelper
html)
+ {
+ bool? value = null;
+ if (html.ViewData.Model != null)
+ {
+ value = Convert.ToBoolean(html.ViewData.Model, CultureInfo.InvariantCulture);
+ }
+
+ return html.ViewData.ModelMetadata.IsNullableValueType ?
+ BooleanTemplateDropDownList(html, value) :
+ BooleanTemplateCheckbox(value ?? false);
+ }
+
+ private static string BooleanTemplateCheckbox(bool value)
+ {
+ var inputTag = new TagBuilder("input");
+ inputTag.AddCssClass("check-box");
+ inputTag.Attributes["disabled"] = "disabled";
+ inputTag.Attributes["type"] = "checkbox";
+ if (value)
+ {
+ inputTag.Attributes["checked"] = "checked";
+ }
+
+ return inputTag.ToString(TagRenderMode.SelfClosing);
+ }
+
+ private static string BooleanTemplateDropDownList(IHtmlHelper html, bool? value)
+ {
+ var selectTag = new TagBuilder("select");
+ selectTag.AddCssClass("list-box");
+ selectTag.AddCssClass("tri-state");
+ selectTag.Attributes["disabled"] = "disabled";
+
+ var builder = new StringBuilder();
+ builder.Append(selectTag.ToString(TagRenderMode.StartTag));
+
+ foreach (var item in TriStateValues(value))
+ {
+ var encodedText = html.Encode(item.Text);
+ var option = HtmlHelper.GenerateOption(item, encodedText);
+ builder.Append(option);
+ }
+
+ builder.Append(selectTag.ToString(TagRenderMode.EndTag));
+ return builder.ToString();
+ }
+
+ // Will soon need to be shared with the default editor templates implementations.
+ internal static List TriStateValues(bool? value)
+ {
+ return new List
+ {
+ new SelectListItem
+ {
+ Text = Resources.Common_TriState_NotSet,
+ Value = string.Empty,
+ Selected = !value.HasValue
+ },
+ new SelectListItem
+ {
+ Text = Resources.Common_TriState_True,
+ Value = "true",
+ Selected = (value == true),
+ },
+ new SelectListItem
+ {
+ Text = Resources.Common_TriState_False,
+ Value = "false",
+ Selected = (value == false),
+ },
+ };
+ }
+
+ public static string CollectionTemplate(IHtmlHelper html)
+ {
+ var model = html.ViewData.ModelMetadata.Model;
+ if (model == null)
+ {
+ return string.Empty;
+ }
+
+ var collection = model as IEnumerable;
+ if (collection == null)
+ {
+ // Only way we could reach here is if user passed templateName: "Collection" to a Display() overload.
+ throw new InvalidOperationException(Resources.FormatTemplates_TypeMustImplementIEnumerable(
+ "Collection", model.GetType().FullName, typeof(IEnumerable).FullName));
+ }
+
+ var typeInCollection = typeof(string);
+ var genericEnumerableType = collection.GetType().ExtractGenericInterface(typeof(IEnumerable<>));
+ if (genericEnumerableType != null)
+ {
+ typeInCollection = genericEnumerableType.GetGenericArguments()[0];
+ }
+
+ var typeInCollectionIsNullableValueType = typeInCollection.IsNullableValueType();
+
+ var oldPrefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
+
+ try
+ {
+ html.ViewData.TemplateInfo.HtmlFieldPrefix = string.Empty;
+
+ var fieldNameBase = oldPrefix;
+ var result = new StringBuilder();
+
+ var serviceProvider = html.ViewContext.HttpContext.RequestServices;
+ var metadataProvider = serviceProvider.GetService();
+ var viewEngine = serviceProvider.GetService();
+
+ var index = 0;
+ foreach (var item in collection)
+ {
+ var itemType = typeInCollection;
+ if (item != null && !typeInCollectionIsNullableValueType)
+ {
+ itemType = item.GetType();
+ }
+
+ var metadata = metadataProvider.GetMetadataForType(() => item, itemType);
+ var fieldName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", fieldNameBase, index++);
+
+ var templateBuilder = new TemplateBuilder(
+ viewEngine,
+ html.ViewContext,
+ html.ViewData,
+ metadata,
+ htmlFieldName: fieldName,
+ templateName: null,
+ readOnly: true,
+ additionalViewData: null);
+
+ var output = templateBuilder.Build();
+ result.Append(output);
+ }
+
+ return result.ToString();
+ }
+ finally
+ {
+ html.ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;
+ }
+ }
+
+ public static string DecimalTemplate(IHtmlHelper html)
+ {
+ if (html.ViewData.TemplateInfo.FormattedModelValue == html.ViewData.ModelMetadata.Model)
+ {
+ html.ViewData.TemplateInfo.FormattedModelValue =
+ string.Format(CultureInfo.CurrentCulture, "{0:0.00}", html.ViewData.ModelMetadata.Model);
+ }
+
+ return StringTemplate(html);
+ }
+
+ public static string EmailAddressTemplate(IHtmlHelper html)
+ {
+ var uriString = "mailto:" + ((html.ViewData.Model == null) ? string.Empty : html.ViewData.Model.ToString());
+ var linkedText = (html.ViewData.TemplateInfo.FormattedModelValue == null) ?
+ string.Empty :
+ html.ViewData.TemplateInfo.FormattedModelValue.ToString();
+
+ return HyperlinkTemplate(uriString, linkedText);
+ }
+
+ public static string HiddenInputTemplate(IHtmlHelper html)
+ {
+ // TODO: add ModelMetadata.HideSurroundingHtml and use here (return string.Empty)
+ return StringTemplate(html);
+ }
+
+ public static string HtmlTemplate(IHtmlHelper html)
+ {
+ return html.ViewData.TemplateInfo.FormattedModelValue.ToString();
+ }
+
+ public static string ObjectTemplate(IHtmlHelper html)
+ {
+ var viewData = html.ViewData;
+ var templateInfo = viewData.TemplateInfo;
+ var modelMetadata = viewData.ModelMetadata;
+ var builder = new StringBuilder();
+
+ if (modelMetadata.Model == null)
+ {
+ return modelMetadata.NullDisplayText;
+ }
+
+ if (templateInfo.TemplateDepth > 1)
+ {
+ // TODO: add ModelMetadata.SimpleDisplayText and use here (return SimpleDisplayText)
+ return modelMetadata.Model.ToString();
+ }
+
+ var serviceProvider = html.ViewContext.HttpContext.RequestServices;
+ var viewEngine = serviceProvider.GetService();
+
+ foreach (var propertyMetadata in modelMetadata.Properties.Where(pm => ShouldShow(pm, templateInfo)))
+ {
+ var divTag = new TagBuilder("div");
+
+ // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block)
+ {
+ var label = propertyMetadata.GetDisplayName();
+ if (!string.IsNullOrEmpty(label))
+ {
+ divTag.SetInnerText(label);
+ divTag.AddCssClass("display-label");
+ builder.AppendLine(divTag.ToString(TagRenderMode.Normal));
+
+ // Reset divTag for reuse.
+ divTag.Attributes.Clear();
+ }
+
+ divTag.AddCssClass("display-field");
+ builder.Append(divTag.ToString(TagRenderMode.StartTag));
+ }
+
+ var templateBuilder = new TemplateBuilder(
+ viewEngine,
+ html.ViewContext,
+ html.ViewData,
+ propertyMetadata,
+ htmlFieldName: propertyMetadata.PropertyName,
+ templateName: null,
+ readOnly: true,
+ additionalViewData: null);
+
+ builder.Append(templateBuilder.Build());
+
+ // TODO: add ModelMetadata.HideSurroundingHtml and use here (skip this block)
+ {
+ builder.AppendLine(divTag.ToString(TagRenderMode.EndTag));
+ }
+ }
+
+ return builder.ToString();
+ }
+
+ private static bool ShouldShow(ModelMetadata metadata, TemplateInfo templateInfo)
+ {
+ // TODO: add ModelMetadata.ShowForDisplay and include in this calculation (first)
+ return
+ !metadata.IsComplexType &&
+ !templateInfo.Visited(metadata);
+ }
+
+ public static string StringTemplate(IHtmlHelper html)
+ {
+ return html.Encode(html.ViewData.TemplateInfo.FormattedModelValue);
+ }
+
+ public static string UrlTemplate(IHtmlHelper html)
+ {
+ var uriString = (html.ViewData.Model == null) ? string.Empty : html.ViewData.Model.ToString();
+ var linkedText = (html.ViewData.TemplateInfo.FormattedModelValue == null) ?
+ string.Empty :
+ html.ViewData.TemplateInfo.FormattedModelValue.ToString();
+
+ return HyperlinkTemplate(uriString, linkedText);
+ }
+
+ // Neither uriString nor linkedText need be encoded prior to calling this method.
+ private static string HyperlinkTemplate(string uriString, string linkedText)
+ {
+ var hyperlinkTag = new TagBuilder("a");
+ hyperlinkTag.MergeAttribute("href", uriString);
+ hyperlinkTag.SetInnerText(linkedText);
+
+ return hyperlinkTag.ToString(TagRenderMode.Normal);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
index b8646ad9b6..9dd50c5bdf 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs
@@ -301,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
ViewContext,
ViewData,
metadata,
- templateName,
+ htmlFieldName,
templateName,
readOnly: true,
additionalViewData: additionalViewData);
@@ -1134,10 +1134,16 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
private string GenerateOption(SelectListItem item)
+ {
+ var encodedText = Encode(item.Text);
+ return GenerateOption(item, encodedText);
+ }
+
+ internal static string GenerateOption(SelectListItem item, string encodedText)
{
var builder = new TagBuilder("option")
{
- InnerHtml = Encode(item.Text)
+ InnerHtml = encodedText,
};
if (item.Value != null)
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs
index ab1605cb2e..d534a72ad9 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateBuilder.cs
@@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
return string.Empty;
}
- var viewData = new ViewDataDictionary(_viewData)
+ var viewData = new ViewDataDictionary(_viewData)
{
Model = _metadata.Model,
ModelMetadata = _metadata
@@ -86,7 +86,5 @@ namespace Microsoft.AspNet.Mvc.Rendering
return templateRenderer.Render();
}
-
-
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs
index 6091dbc860..1b966928ea 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TemplateRenderer.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Threading.Tasks;
using Microsoft.AspNet.DependencyInjection;
using Microsoft.AspNet.Mvc.Core;
@@ -15,15 +14,30 @@ namespace Microsoft.AspNet.Mvc.Rendering
private static readonly string DisplayTemplateViewPath = "DisplayTemplates";
private static readonly string EditorTemplateViewPath = "EditorTemplates";
+ private static readonly Dictionary, string>> _defaultDisplayActions =
+ new Dictionary, string>>(StringComparer.OrdinalIgnoreCase)
+ {
+ { "EmailAddress", DefaultDisplayTemplates.EmailAddressTemplate },
+ { "HiddenInput", DefaultDisplayTemplates.HiddenInputTemplate },
+ { "Html", DefaultDisplayTemplates.HtmlTemplate },
+ { "Text", DefaultDisplayTemplates.StringTemplate },
+ { "Url", DefaultDisplayTemplates.UrlTemplate },
+ { "Collection", DefaultDisplayTemplates.CollectionTemplate },
+ { typeof(bool).Name, DefaultDisplayTemplates.BooleanTemplate },
+ { typeof(decimal).Name, DefaultDisplayTemplates.DecimalTemplate },
+ { typeof(string).Name, DefaultDisplayTemplates.StringTemplate },
+ { typeof(object).Name, DefaultDisplayTemplates.ObjectTemplate },
+ };
+
private ViewContext _viewContext;
- private ViewDataDictionary _viewData;
+ private ViewDataDictionary _viewData;
private IViewEngine _viewEngine;
private string _templateName;
private bool _readOnly;
public TemplateRenderer([NotNull] IViewEngine viewEngine,
[NotNull] ViewContext viewContext,
- [NotNull] ViewDataDictionary viewData,
+ [NotNull] ViewDataDictionary viewData,
string templateName,
bool readOnly)
{
@@ -61,22 +75,29 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
}
- Func, Task> defaultAction;
+ Func, string> defaultAction;
if (defaultActions.TryGetValue(viewName, out defaultAction))
{
- // Right now there's no IhtmlHelper pass in or default templates so this will be
- // changed once a decision has been reached.
- return defaultAction(null).Result;
+ return defaultAction(MakeHtmlHelper(_viewContext, _viewData));
}
}
- throw new InvalidOperationException(Resources.FormatTemplateHelpers_NoTemplate(_viewData.ModelMetadata.RealModelType.FullName));
+ throw new InvalidOperationException(
+ Resources.FormatTemplateHelpers_NoTemplate(_viewData.ModelMetadata.RealModelType.FullName));
}
- private Dictionary, Task>> GetDefaultActions()
+ private Dictionary, string>> GetDefaultActions()
{
- // TODO: Implement default templates
- return new Dictionary, Task>>(StringComparer.OrdinalIgnoreCase);
+ if (_readOnly)
+ {
+ return _defaultDisplayActions;
+ }
+ else
+ {
+ // TODO: Support Editor() and its default templates.
+ // (No resource for this message because this line _must_ be very short-lived.)
+ throw new NotImplementedException("No default editor templates yet");
+ }
}
private IEnumerable GetViewNames()
@@ -148,5 +169,19 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
}
}
+
+ private static IHtmlHelper MakeHtmlHelper(ViewContext viewContext, ViewDataDictionary viewData)
+ {
+ var newHelper = viewContext.HttpContext.RequestServices.GetService>();
+
+ var contextable = newHelper as ICanHasViewContext;
+ if (contextable != null)
+ {
+ var newViewContext = new ViewContext(viewContext, viewContext.View, viewData, viewContext.Writer);
+ contextable.Contextualize(newViewContext);
+ }
+
+ return newHelper;
+ }
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 791f8551f2..5a44abefac 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -183,6 +183,15 @@
The value '{0}' is invalid.
+
+ False
+
+
+ Not Set
+
+
+ True
+
ViewData value must not be null.
@@ -204,6 +213,9 @@
The ViewData item that has the key '{0}' is of type '{1}' but must be of type '{2}'.
+
+ The '{0}' template was used with an object of type '{1}', which does not implement '{2}'.
+
Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.
@@ -231,4 +243,4 @@
The view '{0}' was not found. The following locations were searched:{1}.
-
\ No newline at end of file
+