using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.Rendering.Expressions;
namespace Microsoft.AspNet.Mvc.Rendering
{
///
/// Default implementation of non-generic portions of .
///
public class HtmlHelper : IHtmlHelper, ICanHasViewContext
{
public static readonly string ValidationInputCssClassName = "input-validation-error";
public static readonly string ValidationInputValidCssClassName = "input-validation-valid";
public static readonly string ValidationMessageCssClassName = "field-validation-error";
public static readonly string ValidationMessageValidCssClassName = "field-validation-valid";
public static readonly string ValidationSummaryCssClassName = "validation-summary-errors";
public static readonly string ValidationSummaryValidCssClassName = "validation-summary-valid";
private const string HiddenListItem = @"
";
private readonly IUrlHelper _urlHelper;
private readonly IViewEngine _viewEngine;
private readonly AntiForgery _antiForgeryInstance;
private ViewContext _viewContext;
///
/// Initializes a new instance of the class.
///
public HtmlHelper(
[NotNull] IViewEngine viewEngine,
[NotNull] IModelMetadataProvider metadataProvider,
[NotNull] IUrlHelper urlHelper,
[NotNull] AntiForgery antiForgeryInstance)
{
_viewEngine = viewEngine;
MetadataProvider = metadataProvider;
_urlHelper = urlHelper;
_antiForgeryInstance = antiForgeryInstance;
// Underscores are fine characters in id's.
IdAttributeDotReplacement = "_";
}
///
public Html5DateRenderingMode Html5DateRenderingMode { get; set; }
///
public string IdAttributeDotReplacement { get; set; }
///
public ViewContext ViewContext
{
get
{
if (_viewContext == null)
{
throw new InvalidOperationException(Resources.HtmlHelper_NotContextualized);
}
return _viewContext;
}
private set
{
_viewContext = value;
}
}
///
public dynamic ViewBag
{
get
{
return ViewContext.ViewBag;
}
}
///
public ViewDataDictionary ViewData
{
get
{
return ViewContext.ViewData;
}
}
protected IModelMetadataProvider MetadataProvider { get; private set; }
///
public HtmlString ActionLink(
[NotNull] string linkText,
string actionName,
string controllerName,
string protocol,
string hostname,
string fragment,
object routeValues,
object htmlAttributes)
{
var url = _urlHelper.Action(actionName, controllerName, routeValues);
return GenerateLink(linkText, url, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
///
/// 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.
///
/// If the object is already an instance, then it is
/// returned as-is.
///
///
/// new { property_name = "value" } will translate to the entry { "property_name" , "value" }
/// in the resulting dictionary.
///
/// The object to be converted.
/// The created dictionary of property names and property values.
public static IDictionary ObjectToDictionary(object obj)
{
return TypeHelper.ObjectToDictionary(obj);
}
///
/// Creates a dictionary of HTML attributes from the input object,
/// translating underscores to dashes in each public instance property.
///
/// If the object is already an instance, then it is
/// returned as-is.
///
/// new { data_name="value" } will translate to the entry { "data-name" , "value" }
/// in the resulting dictionary.
///
///
/// Anonymous object describing HTML attributes.
/// A dictionary that represents HTML attributes.
public static IDictionary AnonymousObjectToHtmlAttributes(object htmlAttributes)
{
var dictionary = htmlAttributes as IDictionary;
if (dictionary != null)
{
return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase);
}
dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase);
if (htmlAttributes != null)
{
foreach (var helper in HtmlAttributePropertyHelper.GetProperties(htmlAttributes))
{
dictionary.Add(helper.Name, helper.GetValue(htmlAttributes));
}
}
return dictionary;
}
public virtual void Contextualize([NotNull] ViewContext viewContext)
{
ViewContext = viewContext;
}
///
public HtmlString AntiForgeryToken()
{
return _antiForgeryInstance.GetHtml(ViewContext.HttpContext);
}
///
public MvcForm BeginForm(string actionName, string controllerName, object routeValues, FormMethod method,
object htmlAttributes)
{
// Only need a dictionary if htmlAttributes is non-null. TagBuilder.MergeAttributes() is fine with null.
IDictionary htmlAttributeDictionary = null;
if (htmlAttributes != null)
{
htmlAttributeDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
}
return GenerateForm(actionName, controllerName, routeValues, method, htmlAttributeDictionary);
}
///
public void EndForm()
{
var mvcForm = CreateForm();
mvcForm.EndForm();
}
///
public HtmlString CheckBox(string name, bool? isChecked, object htmlAttributes)
{
return GenerateCheckBox(metadata: null, name: name, isChecked: isChecked, htmlAttributes: htmlAttributes);
}
///
public string Encode(string value)
{
return (!string.IsNullOrEmpty(value)) ? WebUtility.HtmlEncode(value) : string.Empty;
}
///
public string Encode(object value)
{
return value != null ? WebUtility.HtmlEncode(value.ToString()) : string.Empty;
}
///
public string FormatValue(object value, string format)
{
return ViewDataDictionary.FormatValue(value, format);
}
///
public string GenerateIdFromName([NotNull] string name)
{
return TagBuilder.CreateSanitizedId(name, IdAttributeDotReplacement);
}
///
public HtmlString Display(string expression,
string templateName,
string htmlFieldName,
object additionalViewData)
{
var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider);
return GenerateDisplay(metadata,
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
templateName,
additionalViewData);
}
///
public HtmlString DisplayForModel(string templateName,
string htmlFieldName,
object additionalViewData)
{
return GenerateDisplay(ViewData.ModelMetadata,
htmlFieldName,
templateName,
additionalViewData);
}
///
public HtmlString DisplayName(string expression)
{
var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider);
return GenerateDisplayName(metadata, expression);
}
///
public HtmlString DropDownList(string name, IEnumerable selectList, string optionLabel,
object htmlAttributes)
{
return GenerateDropDown(
metadata: null,
expression: name,
selectList: selectList,
optionLabel: optionLabel,
htmlAttributes: htmlAttributes);
}
///
public HtmlString Editor(string expression, string templateName, string htmlFieldName,
object additionalViewData)
{
var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider);
return GenerateEditor(
metadata,
htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),
templateName,
additionalViewData);
}
///
public HtmlString Hidden(string name, object value, object htmlAttributes)
{
return GenerateHidden(metadata: null, name: name, value: value, useViewData: (value == null),
htmlAttributes: htmlAttributes);
}
///
public HtmlString Label(string expression, string labelText, object htmlAttributes)
{
var metadata = ExpressionMetadataProvider.FromStringExpression(expression, ViewData, MetadataProvider);
return GenerateLabel(
metadata,
expression,
labelText,
htmlAttributes);
}
///
public virtual HtmlString Name(string name)
{
var fullName = ViewData.TemplateInfo.GetFullHtmlFieldName(name);
return new HtmlString(Encode(fullName));
}
///
public async Task PartialAsync([NotNull] string partialViewName, object model,
ViewDataDictionary viewData)
{
using (var writer = new StringWriter(CultureInfo.CurrentCulture))
{
await RenderPartialCoreAsync(partialViewName, model, viewData, writer);
return new HtmlString(writer.ToString());
}
}
///
public Task RenderPartialAsync([NotNull] string partialViewName, object model, ViewDataDictionary viewData)
{
return RenderPartialCoreAsync(partialViewName, model, viewData, ViewContext.Writer);
}
protected virtual HtmlString GenerateDisplay(ModelMetadata metadata,
string htmlFieldName,
string templateName,
object additionalViewData)
{
var templateBuilder = new TemplateBuilder(_viewEngine,
ViewContext,
ViewData,
metadata,
htmlFieldName,
templateName,
readOnly: true,
additionalViewData: additionalViewData);
var templateResult = templateBuilder.Build();
return new HtmlString(templateResult);
}
protected virtual async Task RenderPartialCoreAsync([NotNull] string partialViewName,
object model,
ViewDataDictionary viewData,
TextWriter writer)
{
// Determine which ViewData we should use to construct a new ViewData
var baseViewData = viewData ?? ViewData;
var newViewData = new ViewDataDictionary(baseViewData, model);
var viewEngineResult = _viewEngine.FindPartialView(ViewContext.RouteValues, partialViewName);
if (!viewEngineResult.Success)
{
var locations = string.Empty;
if (viewEngineResult.SearchedLocations != null)
{
locations = Environment.NewLine +
string.Join(Environment.NewLine, viewEngineResult.SearchedLocations);
}
throw new InvalidOperationException(
Resources.FormatViewEngine_PartialViewNotFound(partialViewName, locations));
}
var view = viewEngineResult.View;
using (view as IDisposable)
{
var viewContext = new ViewContext(ViewContext, view, newViewData, writer);
await viewEngineResult.View.RenderAsync(viewContext);
}
}
///
public HtmlString Password(string name, object value, object htmlAttributes)
{
return GeneratePassword(metadata: null, name: name, value: value, htmlAttributes: htmlAttributes);
}
///
public HtmlString RadioButton(string name, object value, bool? isChecked, object htmlAttributes)
{
return GenerateRadioButton(metadata: null, name: name, value: value, isChecked: isChecked,
htmlAttributes: htmlAttributes);
}
///
public HtmlString Raw(string value)
{
return new HtmlString(value);
}
///
public HtmlString Raw(object value)
{
return new HtmlString(value == null ? null : value.ToString());
}
///
public virtual HtmlString ValidationSummary(bool excludePropertyErrors, string message, IDictionary htmlAttributes)
{
var formContext = ViewContext.ClientValidationEnabled ? ViewContext.FormContext : null;
if (ViewData.ModelState.IsValid == true)
{
if (formContext == null ||
ViewContext.UnobtrusiveJavaScriptEnabled &&
excludePropertyErrors)
{
// No client side validation/updates
return HtmlString.Empty;
}
}
string messageSpan;
if (!string.IsNullOrEmpty(message))
{
var spanTag = new TagBuilder("span");
spanTag.SetInnerText(message);
messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
}
else
{
messageSpan = null;
}
var htmlSummary = new StringBuilder();
var modelStates = ValidationHelpers.GetModelStateList(ViewData, excludePropertyErrors);
foreach (var modelState in modelStates)
{
foreach (var modelError in modelState.Errors)
{
string errorText = ValidationHelpers.GetUserErrorMessageOrDefault(modelError, modelState: null);
if (!string.IsNullOrEmpty(errorText))
{
var listItem = new TagBuilder("li");
listItem.SetInnerText(errorText);
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
}
}
}
if (htmlSummary.Length == 0)
{
htmlSummary.AppendLine(HiddenListItem);
}
var unorderedList = new TagBuilder("ul")
{
InnerHtml = htmlSummary.ToString()
};
var divBuilder = new TagBuilder("div");
divBuilder.MergeAttributes(htmlAttributes);
if (ViewData.ModelState.IsValid == true)
{
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryValidCssClassName);
}
else
{
divBuilder.AddCssClass(HtmlHelper.ValidationSummaryCssClassName);
}
divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);
if (formContext != null)
{
if (ViewContext.UnobtrusiveJavaScriptEnabled)
{
if (!excludePropertyErrors)
{
// Only put errors in the validation summary if they're supposed to be included there
divBuilder.MergeAttribute("data-valmsg-summary", "true");
}
}
else
{
// client validation summaries need an ID
divBuilder.GenerateId("validationSummary", IdAttributeDotReplacement);
formContext.ValidationSummaryId = divBuilder.Attributes["id"];
formContext.ReplaceValidationSummary = !excludePropertyErrors;
}
}
return divBuilder.ToHtmlString(TagRenderMode.Normal);
}
///
/// Returns the HTTP method that handles form input (GET or POST) as a string.
///
/// The HTTP method that handles the form.
/// The form method string, either "get" or "post".
public static string GetFormMethodString(FormMethod method)
{
switch (method)
{
case FormMethod.Get:
return "get";
case FormMethod.Post:
return "post";
default:
return "post";
}
}
///
public virtual HtmlString TextArea(string name, string value, int rows, int columns, object htmlAttributes)
{
var metadata = ExpressionMetadataProvider.FromStringExpression(name, ViewData, MetadataProvider);
if (value != null)
{
metadata.Model = value;
}
return GenerateTextArea(metadata, name, rows, columns, htmlAttributes);
}
///
public HtmlString TextBox(string name, object value, string format, IDictionary htmlAttributes)
{
return GenerateTextBox(metadata: null, name: name, value: value, format: format,
htmlAttributes: htmlAttributes);
}
///
public HtmlString Value([NotNull] string name, string format)
{
return GenerateValue(name, value: null, format: format, useViewData: true);
}
///
/// Override this method to return an subclass. That subclass may change
/// behavior.
///
/// A new instance.
protected virtual MvcForm CreateForm()
{
return new MvcForm(ViewContext);
}
protected bool EvalBoolean(string key)
{
return Convert.ToBoolean(ViewData.Eval(key), CultureInfo.InvariantCulture);
}
protected string EvalString(string key)
{
return Convert.ToString(ViewData.Eval(key), CultureInfo.CurrentCulture);
}
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 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