Add infrastructure for templated display helpers.
I changed code paths for how we render templates from the old world to better separate code.
This commit is contained in:
parent
fc01cf6eea
commit
5da827b58f
|
|
@ -46,8 +46,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
set { _convertEmptyStringToNull = value; }
|
||||
}
|
||||
|
||||
public virtual string DataTypeName { get; set; }
|
||||
|
||||
public virtual string Description { get; set; }
|
||||
|
||||
public virtual string DisplayFormatString { get; set; }
|
||||
|
||||
public virtual string EditFormatString { get; set; }
|
||||
|
||||
public virtual bool IsComplexType
|
||||
{
|
||||
get { return !ModelType.HasStringConverter(); }
|
||||
|
|
@ -91,6 +97,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
get { return _modelType; }
|
||||
}
|
||||
|
||||
public virtual string NullDisplayText { get; set; }
|
||||
|
||||
public virtual IEnumerable<ModelMetadata> Properties
|
||||
{
|
||||
get
|
||||
|
|
@ -111,9 +119,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
protected IModelMetadataProvider Provider { get; set; }
|
||||
|
||||
/// <returns>
|
||||
/// Gets TModel if ModelType is Nullable(TModel), ModelType otherwise.
|
||||
/// Gets TModel if ModelType is Nullable{TModel}, ModelType otherwise.
|
||||
/// </returns>
|
||||
internal Type RealModelType
|
||||
public Type RealModelType
|
||||
{
|
||||
get
|
||||
{
|
||||
|
|
@ -133,6 +141,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
|
|||
}
|
||||
}
|
||||
|
||||
public virtual string TemplateHint { get; set; }
|
||||
|
||||
internal EfficientTypePropertyKey<Type, string> CacheKey
|
||||
{
|
||||
get
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
IdAttributeDotReplacement = "_";
|
||||
}
|
||||
|
||||
public IModelMetadataProvider MetadataProvider { get; private set; }
|
||||
|
||||
public string IdAttributeDotReplacement { get; set; }
|
||||
|
||||
public HttpContext HttpContext { get; private set; }
|
||||
|
|
@ -81,7 +83,43 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
protected IModelMetadataProvider MetadataProvider { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dictionary of HTML attributes from the input object,
|
||||
/// Creates a dictionary from an object, by adding each public instance property as a key with its associated
|
||||
/// value to the dictionary. It will expose public properties from derived types as well. This is typically used
|
||||
/// with objects of an anonymous type.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <c>new { property_name = "value" }</c> will translate to the entry <c>{ "property_name" , "value" }</c>
|
||||
/// in the resulting dictionary.
|
||||
/// </example>
|
||||
/// <param name="obj">The object to be converted.</param>
|
||||
/// <returns>The created dictionary of property names and property values.</returns>
|
||||
public static IDictionary<string, object> ObjectToDictionary(object obj)
|
||||
{
|
||||
IDictionary<string, object> result;
|
||||
var valuesAsDictionary = obj as IDictionary<string, object>;
|
||||
if (valuesAsDictionary != null)
|
||||
{
|
||||
result = new Dictionary<string, object>(valuesAsDictionary, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
foreach (var prop in obj.GetType().GetRuntimeProperties())
|
||||
{
|
||||
var value = prop.GetValue(obj);
|
||||
result.Add(prop.Name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a dictionary of HTML attributes from the input object,
|
||||
/// translating underscores to dashes.
|
||||
/// <example>
|
||||
/// new { data_name="value" } will translate to the entry { "data-name" , "value" }
|
||||
|
|
@ -92,27 +130,9 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// <returns>A dictionary that represents HTML attributes.</returns>
|
||||
public static IDictionary<string, object> AnonymousObjectToHtmlAttributes(object htmlAttributes)
|
||||
{
|
||||
Dictionary<string, object> result;
|
||||
var valuesAsDictionary = htmlAttributes as IDictionary<string, object>;
|
||||
if (valuesAsDictionary != null)
|
||||
{
|
||||
result = new Dictionary<string, object>(valuesAsDictionary, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
if (htmlAttributes != null)
|
||||
{
|
||||
foreach (var prop in htmlAttributes.GetType().GetRuntimeProperties())
|
||||
{
|
||||
var value = prop.GetValue(htmlAttributes);
|
||||
result.Add(prop.Name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
// NOTE: This should be doing more than just returning a generic conversion from obj -> dict
|
||||
// Once GitHub #80 has been completed this will do more than be a call through.
|
||||
return ObjectToDictionary(htmlAttributes);
|
||||
}
|
||||
|
||||
public virtual void Contextualize([NotNull] ViewContext viewContext)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
/// Initializes a new instance of the <see cref="HtmlHelper{TModel}"/> class.
|
||||
/// </summary>
|
||||
public HtmlHelper([NotNull] IViewEngine viewEngine, [NotNull] IModelMetadataProvider metadataProvider)
|
||||
: base(viewEngine, metadataProvider)
|
||||
: base(viewEngine, metadataProvider)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNet.Mvc.ModelBinding;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
internal class TemplateBuilder
|
||||
{
|
||||
private IViewEngine _viewEngine;
|
||||
private ViewContext _viewContext;
|
||||
private ViewDataDictionary _viewData;
|
||||
private ModelMetadata _metadata;
|
||||
private string _htmlFieldName;
|
||||
private string _templateName;
|
||||
private bool _readOnly;
|
||||
private object _additionalViewData;
|
||||
|
||||
public TemplateBuilder([NotNull] IViewEngine viewEngine,
|
||||
[NotNull] ViewContext viewContext,
|
||||
[NotNull] ViewDataDictionary viewData,
|
||||
[NotNull] ModelMetadata metadata,
|
||||
string htmlFieldName,
|
||||
string templateName,
|
||||
bool readOnly,
|
||||
object additionalViewData)
|
||||
{
|
||||
_viewEngine = viewEngine;
|
||||
_viewContext = viewContext;
|
||||
_viewData = viewData;
|
||||
_metadata = metadata;
|
||||
_htmlFieldName = htmlFieldName;
|
||||
_templateName = templateName;
|
||||
_readOnly = readOnly;
|
||||
_additionalViewData = additionalViewData;
|
||||
}
|
||||
|
||||
public string Build()
|
||||
{
|
||||
if (_metadata.ConvertEmptyStringToNull && string.Empty.Equals(_metadata.Model))
|
||||
{
|
||||
_metadata.Model = null;
|
||||
}
|
||||
|
||||
var formattedModelValue = _metadata.Model;
|
||||
if (_metadata.Model == null && _readOnly)
|
||||
{
|
||||
formattedModelValue = _metadata.NullDisplayText;
|
||||
}
|
||||
|
||||
var formatString = _readOnly ? _metadata.DisplayFormatString : _metadata.EditFormatString;
|
||||
|
||||
if (_metadata.Model != null && !string.IsNullOrEmpty(formatString))
|
||||
{
|
||||
formattedModelValue = string.Format(CultureInfo.CurrentCulture, formatString, _metadata.Model);
|
||||
}
|
||||
|
||||
// Normally this shouldn't happen, unless someone writes their own custom Object templates which
|
||||
// don't check to make sure that the object hasn't already been displayed
|
||||
if (_viewData.TemplateInfo.Visited(_metadata))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var viewData = new ViewDataDictionary(_viewData)
|
||||
{
|
||||
Model = _metadata.Model,
|
||||
ModelMetadata = _metadata
|
||||
};
|
||||
|
||||
viewData.TemplateInfo.FormattedModelValue = formattedModelValue;
|
||||
viewData.TemplateInfo.HtmlFieldPrefix = _viewData.TemplateInfo.GetFullHtmlFieldName(_htmlFieldName);
|
||||
|
||||
if (_additionalViewData != null)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> kvp in HtmlHelper.ObjectToDictionary(_additionalViewData))
|
||||
{
|
||||
viewData[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
object visitedObjectsKey = _metadata.Model ?? _metadata.RealModelType;
|
||||
viewData.TemplateInfo.AddVisited(visitedObjectsKey);
|
||||
|
||||
var templateRenderer = new TemplateRenderer(_viewEngine, _viewContext, viewData, _templateName, _readOnly);
|
||||
|
||||
return templateRenderer.Render();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Rendering
|
||||
{
|
||||
internal class TemplateRenderer
|
||||
{
|
||||
private static readonly string DisplayTemplateViewPath = "DisplayTemplates";
|
||||
private static readonly string EditorTemplateViewPath = "EditorTemplates";
|
||||
|
||||
private ViewContext _viewContext;
|
||||
private ViewDataDictionary _viewData;
|
||||
private IViewEngine _viewEngine;
|
||||
private string _templateName;
|
||||
private bool _readOnly;
|
||||
|
||||
public TemplateRenderer([NotNull] IViewEngine viewEngine,
|
||||
[NotNull] ViewContext viewContext,
|
||||
[NotNull] ViewDataDictionary viewData,
|
||||
string templateName,
|
||||
bool readOnly)
|
||||
{
|
||||
_viewEngine = viewEngine;
|
||||
_viewContext = viewContext;
|
||||
_viewData = viewData;
|
||||
_templateName = templateName;
|
||||
_readOnly = readOnly;
|
||||
}
|
||||
|
||||
public string Render()
|
||||
{
|
||||
var defaultActions = GetDefaultActions();
|
||||
var modeViewPath = _readOnly ? DisplayTemplateViewPath : EditorTemplateViewPath;
|
||||
|
||||
foreach (string viewName in GetViewNames())
|
||||
{
|
||||
var fullViewName = modeViewPath + "/" + viewName;
|
||||
|
||||
// Forcing synchronous behavior so users don't have to await templates.
|
||||
var viewEngineResult = _viewEngine.FindPartialView(_viewContext.ViewEngineContext, fullViewName).Result;
|
||||
if (viewEngineResult.Success)
|
||||
{
|
||||
using (var writer = new StringWriter(CultureInfo.InvariantCulture))
|
||||
{
|
||||
// Forcing synchronous behavior so users don't have to await templates.
|
||||
// TODO: Pass through TempData once implemented.
|
||||
viewEngineResult.View.RenderAsync(new ViewContext(_viewContext)
|
||||
{
|
||||
ViewData = _viewData,
|
||||
Writer = writer,
|
||||
}).Wait();
|
||||
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
Func<IHtmlHelper<object>, Task<string>> defaultAction;
|
||||
if (defaultActions.TryGetValue(viewName, out defaultAction))
|
||||
{
|
||||
// Right now there's no IhtmlHelper<object> pass in or default templates so this will be
|
||||
// changed once a decision has been reached.
|
||||
return defaultAction(null).Result;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(Resources.FormatTemplateHelpers_NoTemplate(_viewData.ModelMetadata.RealModelType.FullName));
|
||||
}
|
||||
|
||||
private Dictionary<string, Func<IHtmlHelper<object>, Task<string>>> GetDefaultActions()
|
||||
{
|
||||
// TODO: Implement default templates
|
||||
return new Dictionary<string, Func<IHtmlHelper<object>, Task<string>>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetViewNames()
|
||||
{
|
||||
var metadata = _viewData.ModelMetadata;
|
||||
var templateHints = new string[] {
|
||||
_templateName,
|
||||
metadata.TemplateHint,
|
||||
metadata.DataTypeName
|
||||
};
|
||||
|
||||
foreach (string templateHint in templateHints.Where(s => !string.IsNullOrEmpty(s)))
|
||||
{
|
||||
yield return templateHint;
|
||||
}
|
||||
|
||||
// We don't want to search for Nullable<T>, we want to search for T (which should handle both T and Nullable<T>)
|
||||
var fieldType = Nullable.GetUnderlyingType(metadata.RealModelType) ?? metadata.RealModelType;
|
||||
|
||||
yield return fieldType.Name;
|
||||
|
||||
if (fieldType == typeof(string))
|
||||
{
|
||||
// Nothing more to provide
|
||||
yield break;
|
||||
}
|
||||
else if (!metadata.IsComplexType)
|
||||
{
|
||||
// IsEnum is false for the Enum class itself
|
||||
if (fieldType.IsEnum)
|
||||
{
|
||||
// Same as fieldType.BaseType.Name in this case
|
||||
yield return "Enum";
|
||||
}
|
||||
else if (fieldType == typeof(DateTimeOffset))
|
||||
{
|
||||
yield return "DateTime";
|
||||
}
|
||||
|
||||
yield return "String";
|
||||
}
|
||||
else if (fieldType.IsInterface)
|
||||
{
|
||||
if (typeof(IEnumerable).IsAssignableFrom(fieldType))
|
||||
{
|
||||
yield return "Collection";
|
||||
}
|
||||
|
||||
yield return "Object";
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType);
|
||||
|
||||
while (true)
|
||||
{
|
||||
fieldType = fieldType.BaseType;
|
||||
if (fieldType == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (isEnumerable && fieldType == typeof(Object))
|
||||
{
|
||||
yield return "Collection";
|
||||
}
|
||||
|
||||
yield return fieldType.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,6 +187,22 @@ namespace Microsoft.AspNet.Mvc.Rendering
|
|||
return GetString("TemplateHelpers_TemplateLimitations");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to locate an appropriate template for type {0}.
|
||||
/// </summary>
|
||||
internal static string TemplateHelpers_NoTemplate
|
||||
{
|
||||
get { return GetString("TemplateHelpers_NoTemplate"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unable to locate an appropriate template for type {0}.
|
||||
/// </summary>
|
||||
internal static string FormatTemplateHelpers_NoTemplate(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("TemplateHelpers_NoTemplate"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -147,6 +147,9 @@
|
|||
<data name="TemplateHelpers_TemplateLimitations" xml:space="preserve">
|
||||
<value>Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.</value>
|
||||
</data>
|
||||
<data name="TemplateHelpers_NoTemplate" xml:space="preserve">
|
||||
<value>Unable to locate an appropriate template for type {0}.</value>
|
||||
</data>
|
||||
<data name="ViewData_ModelCannotBeNull" xml:space="preserve">
|
||||
<value>The model item passed is null, but this ViewDataDictionary instance requires a non-null model item of type '{0}'.</value>
|
||||
</data>
|
||||
|
|
|
|||
Loading…
Reference in New Issue