Implement MachineKey.Protect and MachineKey.Unprotect
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / Html / TemplateHelpers.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\r
5  * This software is subject to the Microsoft Public License (Ms-PL). \r
6  * A copy of the license can be found in the license.htm file included \r
7  * in this distribution.\r
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc.Html {\r
14     using System;\r
15     using System.Collections;\r
16     using System.Collections.Generic;\r
17     using System.Globalization;\r
18     using System.IO;\r
19     using System.Linq;\r
20     using System.Linq.Expressions;\r
21     using System.Web.Mvc.Resources;\r
22     using System.Web.Routing;\r
23     using System.Web.UI.WebControls;\r
24 \r
25     internal static class TemplateHelpers {\r
26         static readonly Dictionary<DataBoundControlMode, string> modeViewPaths =\r
27             new Dictionary<DataBoundControlMode, string> {\r
28                 { DataBoundControlMode.ReadOnly, "DisplayTemplates" },\r
29                 { DataBoundControlMode.Edit,     "EditorTemplates" }\r
30             };\r
31 \r
32         static readonly Dictionary<string, Func<HtmlHelper, string>> defaultDisplayActions =\r
33             new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase) {\r
34                 { "EmailAddress",       DefaultDisplayTemplates.EmailAddressTemplate },\r
35                 { "HiddenInput",        DefaultDisplayTemplates.HiddenInputTemplate },\r
36                 { "Html",               DefaultDisplayTemplates.HtmlTemplate },\r
37                 { "Text",               DefaultDisplayTemplates.StringTemplate },\r
38                 { "Url",                DefaultDisplayTemplates.UrlTemplate },\r
39                 { "Collection",         DefaultDisplayTemplates.CollectionTemplate },\r
40                 { typeof(bool).Name,    DefaultDisplayTemplates.BooleanTemplate },\r
41                 { typeof(decimal).Name, DefaultDisplayTemplates.DecimalTemplate },\r
42                 { typeof(string).Name,  DefaultDisplayTemplates.StringTemplate },\r
43                 { typeof(object).Name,  DefaultDisplayTemplates.ObjectTemplate },\r
44             };\r
45 \r
46         static readonly Dictionary<string, Func<HtmlHelper, string>> defaultEditorActions =\r
47             new Dictionary<string, Func<HtmlHelper, string>>(StringComparer.OrdinalIgnoreCase) {\r
48                 { "HiddenInput",        DefaultEditorTemplates.HiddenInputTemplate },\r
49                 { "MultilineText",      DefaultEditorTemplates.MultilineTextTemplate },\r
50                 { "Password",           DefaultEditorTemplates.PasswordTemplate },\r
51                 { "Text",               DefaultEditorTemplates.StringTemplate },\r
52                 { "Collection",         DefaultEditorTemplates.CollectionTemplate },\r
53                 { typeof(bool).Name,    DefaultEditorTemplates.BooleanTemplate },\r
54                 { typeof(decimal).Name, DefaultEditorTemplates.DecimalTemplate },\r
55                 { typeof(string).Name,  DefaultEditorTemplates.StringTemplate },\r
56                 { typeof(object).Name,  DefaultEditorTemplates.ObjectTemplate },\r
57             };\r
58 \r
59         internal static string cacheItemId = Guid.NewGuid().ToString();\r
60 \r
61         internal delegate string ExecuteTemplateDelegate(HtmlHelper html, ViewDataDictionary viewData, string templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames);\r
62 \r
63         internal static string ExecuteTemplate(HtmlHelper html, ViewDataDictionary viewData, string templateName, DataBoundControlMode mode, GetViewNamesDelegate getViewNames) {\r
64             Dictionary<string, ActionCacheItem> actionCache = GetActionCache(html);\r
65             Dictionary<string, Func<HtmlHelper, string>> defaultActions = mode == DataBoundControlMode.ReadOnly ? defaultDisplayActions : defaultEditorActions;\r
66             string modeViewPath = modeViewPaths[mode];\r
67 \r
68             foreach (string viewName in getViewNames(viewData.ModelMetadata, templateName, viewData.ModelMetadata.TemplateHint, viewData.ModelMetadata.DataTypeName)) {\r
69                 string fullViewName = modeViewPath + "/" + viewName;\r
70                 ActionCacheItem cacheItem;\r
71 \r
72                 if (actionCache.TryGetValue(fullViewName, out cacheItem)) {\r
73                     if (cacheItem != null) {\r
74                         return cacheItem.Execute(html, viewData);\r
75                     }\r
76                 }\r
77                 else {\r
78                     ViewEngineResult viewEngineResult = ViewEngines.Engines.FindPartialView(html.ViewContext, fullViewName);\r
79                     if (viewEngineResult.View != null) {\r
80                         actionCache[fullViewName] = new ActionCacheViewItem { ViewName = fullViewName };\r
81 \r
82                         StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);\r
83                         viewEngineResult.View.Render(new ViewContext(html.ViewContext, viewEngineResult.View, viewData, html.ViewContext.TempData, writer), writer);\r
84                         return writer.ToString();\r
85                     }\r
86 \r
87                     Func<HtmlHelper, string> defaultAction;\r
88                     if (defaultActions.TryGetValue(viewName, out defaultAction)) {\r
89                         actionCache[fullViewName] = new ActionCacheCodeItem { Action = defaultAction };\r
90                         return defaultAction(\r
91                             new HtmlHelper(\r
92                                 new ViewContext(html.ViewContext, html.ViewContext.View, viewData, html.ViewContext.TempData, html.ViewContext.Writer),\r
93                                 html.ViewDataContainer\r
94                             )\r
95                         );\r
96                     }\r
97 \r
98                     actionCache[fullViewName] = null;\r
99                 }\r
100             }\r
101 \r
102             throw new InvalidOperationException(\r
103                 String.Format(\r
104                     CultureInfo.CurrentCulture,\r
105                     MvcResources.TemplateHelpers_NoTemplate,\r
106                     viewData.ModelMetadata.RealModelType.FullName\r
107                 )\r
108             );\r
109         }\r
110 \r
111         internal static Dictionary<string, ActionCacheItem> GetActionCache(HtmlHelper html) {\r
112             HttpContextBase context = html.ViewContext.HttpContext;\r
113             Dictionary<string, ActionCacheItem> result;\r
114 \r
115             if (!context.Items.Contains(cacheItemId)) {\r
116                 result = new Dictionary<string, ActionCacheItem>();\r
117                 context.Items[cacheItemId] = result;\r
118             }\r
119             else {\r
120                 result = (Dictionary<string, ActionCacheItem>)context.Items[cacheItemId];\r
121             }\r
122 \r
123             return result;\r
124         }\r
125 \r
126         internal delegate IEnumerable<string> GetViewNamesDelegate(ModelMetadata metadata, params string[] templateHints);\r
127 \r
128         internal static IEnumerable<string> GetViewNames(ModelMetadata metadata, params string[] templateHints) {\r
129             foreach (string templateHint in templateHints.Where(s => !String.IsNullOrEmpty(s))) {\r
130                 yield return templateHint;\r
131             }\r
132 \r
133             // We don't want to search for Nullable<T>, we want to search for T (which should handle both T and Nullable<T>)\r
134             Type fieldType = Nullable.GetUnderlyingType(metadata.RealModelType) ?? metadata.RealModelType;\r
135 \r
136             // TODO: Make better string names for generic types\r
137             yield return fieldType.Name;\r
138 \r
139             if (!metadata.IsComplexType) {\r
140                 yield return "String";\r
141             }\r
142             else if (fieldType.IsInterface) {\r
143                 if (typeof(IEnumerable).IsAssignableFrom(fieldType)) {\r
144                     yield return "Collection";\r
145                 }\r
146 \r
147                 yield return "Object";\r
148             }\r
149             else {\r
150                 bool isEnumerable = typeof(IEnumerable).IsAssignableFrom(fieldType);\r
151 \r
152                 while (true) {\r
153                     fieldType = fieldType.BaseType;\r
154                     if (fieldType == null)\r
155                         break;\r
156 \r
157                     if (isEnumerable && fieldType == typeof(Object)) {\r
158                         yield return "Collection";\r
159                     }\r
160 \r
161                     yield return fieldType.Name;\r
162                 }\r
163             }\r
164         }\r
165 \r
166         internal static MvcHtmlString Template(HtmlHelper html, string expression, string templateName, string htmlFieldName, DataBoundControlMode mode, object additionalViewData) {\r
167             return MvcHtmlString.Create(Template(html, expression, templateName, htmlFieldName, mode, additionalViewData, TemplateHelper));\r
168         }\r
169 \r
170         // Unit testing version\r
171         internal static string Template(HtmlHelper html, string expression, string templateName, string htmlFieldName,\r
172                                         DataBoundControlMode mode, object additionalViewData, TemplateHelperDelegate templateHelper) {\r
173             return templateHelper(html,\r
174                                   ModelMetadata.FromStringExpression(expression, html.ViewData),\r
175                                   htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),\r
176                                   templateName,\r
177                                   mode,\r
178                                   additionalViewData);\r
179         }\r
180 \r
181         internal static MvcHtmlString TemplateFor<TContainer, TValue>(this HtmlHelper<TContainer> html, Expression<Func<TContainer, TValue>> expression,\r
182                                                                       string templateName, string htmlFieldName, DataBoundControlMode mode,\r
183                                                                       object additionalViewData) {\r
184             return MvcHtmlString.Create(TemplateFor(html, expression, templateName, htmlFieldName, mode, additionalViewData, TemplateHelper));\r
185         }\r
186 \r
187         // Unit testing version\r
188         internal static string TemplateFor<TContainer, TValue>(this HtmlHelper<TContainer> html, Expression<Func<TContainer, TValue>> expression,\r
189                                                                string templateName, string htmlFieldName, DataBoundControlMode mode,\r
190                                                                object additionalViewData, TemplateHelperDelegate templateHelper) {\r
191             return templateHelper(html,\r
192                                   ModelMetadata.FromLambdaExpression(expression, html.ViewData),\r
193                                   htmlFieldName ?? ExpressionHelper.GetExpressionText(expression),\r
194                                   templateName,\r
195                                   mode,\r
196                                   additionalViewData);\r
197         }\r
198 \r
199         internal delegate string TemplateHelperDelegate(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData);\r
200 \r
201         internal static string TemplateHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData) {\r
202             return TemplateHelper(html, metadata, htmlFieldName, templateName, mode, additionalViewData, ExecuteTemplate);\r
203         }\r
204 \r
205         internal static string TemplateHelper(HtmlHelper html, ModelMetadata metadata, string htmlFieldName, string templateName, DataBoundControlMode mode, object additionalViewData, ExecuteTemplateDelegate executeTemplate) {\r
206             // TODO: Convert Editor into Display if model.IsReadOnly is true? Need to be careful about this because\r
207             // the Model property on the ViewPage/ViewUserControl is get-only, so the type descriptor automatically\r
208             // decorates it with a [ReadOnly] attribute...\r
209 \r
210             if (metadata.ConvertEmptyStringToNull && String.Empty.Equals(metadata.Model)) {\r
211                 metadata.Model = null;\r
212             }\r
213 \r
214             object formattedModelValue = metadata.Model;\r
215             if (metadata.Model == null && mode == DataBoundControlMode.ReadOnly) {\r
216                 formattedModelValue = metadata.NullDisplayText;\r
217             }\r
218 \r
219             string formatString = mode == DataBoundControlMode.ReadOnly ? metadata.DisplayFormatString : metadata.EditFormatString;\r
220             if (metadata.Model != null && !String.IsNullOrEmpty(formatString)) {\r
221                 formattedModelValue = String.Format(CultureInfo.CurrentCulture, formatString, metadata.Model);\r
222             }\r
223 \r
224             // Normally this shouldn't happen, unless someone writes their own custom Object templates which\r
225             // don't check to make sure that the object hasn't already been displayed\r
226             object visitedObjectsKey = metadata.Model ?? metadata.RealModelType;\r
227             if (html.ViewDataContainer.ViewData.TemplateInfo.VisitedObjects.Contains(visitedObjectsKey)) {    // DDB #224750\r
228                 return String.Empty;\r
229             }\r
230 \r
231             ViewDataDictionary viewData = new ViewDataDictionary(html.ViewDataContainer.ViewData) {\r
232                 Model = metadata.Model,\r
233                 ModelMetadata = metadata,\r
234                 TemplateInfo = new TemplateInfo {\r
235                     FormattedModelValue = formattedModelValue,\r
236                     HtmlFieldPrefix = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName),\r
237                     VisitedObjects = new HashSet<object>(html.ViewContext.ViewData.TemplateInfo.VisitedObjects),    // DDB #224750\r
238                 }\r
239             };\r
240 \r
241             if (additionalViewData != null) {\r
242                 foreach (KeyValuePair<string, object> kvp in new RouteValueDictionary(additionalViewData)) {\r
243                     viewData[kvp.Key] = kvp.Value;\r
244                 }\r
245             }\r
246 \r
247             viewData.TemplateInfo.VisitedObjects.Add(visitedObjectsKey);    // DDB #224750\r
248 \r
249             return executeTemplate(html, viewData, templateName, mode, GetViewNames);\r
250         }\r
251 \r
252         internal abstract class ActionCacheItem {\r
253             public abstract string Execute(HtmlHelper html, ViewDataDictionary viewData);\r
254         }\r
255 \r
256         internal class ActionCacheCodeItem : ActionCacheItem {\r
257             public Func<HtmlHelper, string> Action { get; set; }\r
258 \r
259             public override string Execute(HtmlHelper html, ViewDataDictionary viewData) {\r
260                 ViewContext newViewContext = new ViewContext(html.ViewContext, html.ViewContext.View, viewData, html.ViewContext.TempData, html.ViewContext.Writer);\r
261                 HtmlHelper newHtmlHelper = new HtmlHelper(newViewContext, html.ViewDataContainer);\r
262                 return Action(newHtmlHelper);\r
263             }\r
264         }\r
265 \r
266         internal class ActionCacheViewItem : ActionCacheItem {\r
267             public string ViewName { get; set; }\r
268 \r
269             public override string Execute(HtmlHelper html, ViewDataDictionary viewData) {\r
270                 ViewEngineResult viewEngineResult = ViewEngines.Engines.FindPartialView(html.ViewContext, ViewName);\r
271                 StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);\r
272                 viewEngineResult.View.Render(new ViewContext(html.ViewContext, viewEngineResult.View, viewData, html.ViewContext.TempData, writer), writer);\r
273                 return writer.ToString();\r
274             }\r
275         }\r
276     }\r
277 }\r