1 namespace System.Web.Mvc.Html {
2     using System;
3     using System.Collections.Generic;
4     using System.Diagnostics.CodeAnalysis;
5     using System.Globalization;
6     using System.Linq.Expressions;
7     using System.Web.Mvc.Resources;
8 
9     public static class TextAreaExtensions {
10         // These values are similar to the defaults used by WebForms
11         // when using <asp:TextBox TextMode="MultiLine"> without specifying
12         // the Rows and Columns attributes.
13         private const int TextAreaRows = 2;
14         private const int TextAreaColumns = 20;
15         private static Dictionary<string, object> implicitRowsAndColumns = new Dictionary<string, object> {
16             { "rows", TextAreaRows.ToString(CultureInfo.InvariantCulture) },
17             { "cols", TextAreaColumns.ToString(CultureInfo.InvariantCulture) },
18         };
19 
GetRowsAndColumnsDictionary(int rows, int columns)20         private static Dictionary<string, object> GetRowsAndColumnsDictionary(int rows, int columns) {
21             if (rows < 0) {
22                 throw new ArgumentOutOfRangeException("rows", MvcResources.HtmlHelper_TextAreaParameterOutOfRange);
23             }
24             if (columns < 0) {
25                 throw new ArgumentOutOfRangeException("columns", MvcResources.HtmlHelper_TextAreaParameterOutOfRange);
26             }
27 
28             Dictionary<string, object> result = new Dictionary<string, object>();
29             if (rows > 0) {
30                 result.Add("rows", rows.ToString(CultureInfo.InvariantCulture));
31             }
32             if (columns > 0) {
33                 result.Add("cols", columns.ToString(CultureInfo.InvariantCulture));
34             }
35 
36             return result;
37         }
38 
TextArea(this HtmlHelper htmlHelper, string name)39         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name) {
40             return TextArea(htmlHelper, name, null /* value */, null /* htmlAttributes */);
41         }
42 
TextArea(this HtmlHelper htmlHelper, string name, object htmlAttributes)43         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, object htmlAttributes) {
44             return TextArea(htmlHelper, name, null /* value */, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
45         }
46 
TextArea(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes)47         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes) {
48             return TextArea(htmlHelper, name, null /* value */, htmlAttributes);
49         }
50 
TextArea(this HtmlHelper htmlHelper, string name, string value)51         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value) {
52             return TextArea(htmlHelper, name, value, null /* htmlAttributes */);
53         }
54 
TextArea(this HtmlHelper htmlHelper, string name, string value, object htmlAttributes)55         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, object htmlAttributes) {
56             return TextArea(htmlHelper, name, value, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
57         }
58 
TextArea(this HtmlHelper htmlHelper, string name, string value, IDictionary<string, object> htmlAttributes)59         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, IDictionary<string, object> htmlAttributes) {
60             ModelMetadata metadata = ModelMetadata.FromStringExpression(name, htmlHelper.ViewContext.ViewData);
61             if (value != null) {
62                 metadata.Model = value;
63             }
64 
65             return TextAreaHelper(htmlHelper, metadata, name, implicitRowsAndColumns, htmlAttributes);
66         }
67 
TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, object htmlAttributes)68         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, object htmlAttributes) {
69             return TextArea(htmlHelper, name, value, rows, columns, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
70         }
71 
TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, IDictionary<string, object> htmlAttributes)72         public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, string value, int rows, int columns, IDictionary<string, object> htmlAttributes) {
73             ModelMetadata metadata = ModelMetadata.FromStringExpression(name, htmlHelper.ViewContext.ViewData);
74             if (value != null) {
75                 metadata.Model = value;
76             }
77 
78             return TextAreaHelper(htmlHelper, metadata, name, GetRowsAndColumnsDictionary(rows, columns), htmlAttributes);
79         }
80 
81         [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
TextAreaFor(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)82         public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) {
83             return TextAreaFor(htmlHelper, expression, (IDictionary<string, object>)null);
84         }
85 
86         [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
TextAreaFor(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)87         public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes) {
88             return TextAreaFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
89         }
90 
91         [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
TextAreaFor(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)92         public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes) {
93             if (expression == null) {
94                 throw new ArgumentNullException("expression");
95             }
96 
97             return TextAreaHelper(htmlHelper,
98                                   ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
99                                   ExpressionHelper.GetExpressionText(expression),
100                                   implicitRowsAndColumns,
101                                   htmlAttributes);
102         }
103 
104         [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
TextAreaFor(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, object htmlAttributes)105         public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, object htmlAttributes) {
106             return TextAreaFor(htmlHelper, expression, rows, columns, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
107         }
108 
109         [SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
TextAreaFor(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, IDictionary<string, object> htmlAttributes)110         public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, int rows, int columns, IDictionary<string, object> htmlAttributes) {
111             if (expression == null) {
112                 throw new ArgumentNullException("expression");
113             }
114 
115             return TextAreaHelper(htmlHelper,
116                                   ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData),
117                                   ExpressionHelper.GetExpressionText(expression),
118                                   GetRowsAndColumnsDictionary(rows, columns),
119                                   htmlAttributes);
120         }
121 
122         [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "If this fails, it is because the string-based version had an empty 'name' parameter")]
TextAreaHelper(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string name, IDictionary<string, object> rowsAndColumns, IDictionary<string, object> htmlAttributes)123         private static MvcHtmlString TextAreaHelper(HtmlHelper htmlHelper, ModelMetadata modelMetadata, string name, IDictionary<string, object> rowsAndColumns, IDictionary<string, object> htmlAttributes) {
124             string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
125             if (String.IsNullOrEmpty(fullName)) {
126                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "name");
127             }
128 
129             TagBuilder tagBuilder = new TagBuilder("textarea");
130             tagBuilder.GenerateId(fullName);
131             tagBuilder.MergeAttributes(htmlAttributes, true);
132             tagBuilder.MergeAttributes(rowsAndColumns, rowsAndColumns != implicitRowsAndColumns);  // Only force explicit rows/cols
133             tagBuilder.MergeAttribute("name", fullName, true);
134 
135             // If there are any errors for a named field, we add the CSS attribute.
136             ModelState modelState;
137             if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState) && modelState.Errors.Count > 0) {
138                 tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
139             }
140 
141             tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name));
142 
143             string value;
144             if (modelState != null && modelState.Value != null) {
145                 value = modelState.Value.AttemptedValue;
146             }
147             else if (modelMetadata.Model != null) {
148                 value = modelMetadata.Model.ToString();
149             }
150             else {
151                 value = String.Empty;
152             }
153 
154             // The first newline is always trimmed when a TextArea is rendered, so we add an extra one
155             // in case the value being rendered is something like "\r\nHello".
156             tagBuilder.SetInnerText(Environment.NewLine + value);
157 
158             return tagBuilder.ToMvcHtmlString(TagRenderMode.Normal);
159         }
160     }
161 }
162