From 56b7f8d98b41105f950dc851053f7cd3c2d5a965 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Wed, 5 Sep 2012 09:35:24 +0700 Subject: [PATCH] Got .Field method for MVC working and have macro's rendering inside of RTE in MVC. Added internal setting for global settings to return an MVC area string based on the umbraco path. Added a ton of extension methods from v5 that are used in much of the MVC engines. Added UmbracoHelper methods for MVC rendering including Field so that we can render the correct RTE field markup when the RTE contains a macro, will add extension methods for the @CurrentPage dynamic object to do the same to make it consistent. --- .../Configuration/GlobalSettings.cs | 23 ++ src/Umbraco.Core/DictionaryExtensions.cs | 191 +++++++++++ src/Umbraco.Core/ObjectExtensions.cs | 1 - src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 312 ++++++++++++++++++ src/Umbraco.Web/ModelStateExtensions.cs | 76 +++++ src/Umbraco.Web/RenderFieldCaseType.cs | 13 + src/Umbraco.Web/RenderFieldEncodingType.cs | 13 + src/Umbraco.Web/Umbraco.Web.csproj | 6 + src/Umbraco.Web/UmbracoHelper.cs | 135 +++++++- src/Umbraco.Web/ViewContextExtensions.cs | 34 ++ .../ViewDataContainerExtensions.cs | 47 +++ src/Umbraco.Web/umbraco.presentation/item.cs | 11 +- 13 files changed, 856 insertions(+), 8 deletions(-) create mode 100644 src/Umbraco.Core/DictionaryExtensions.cs create mode 100644 src/Umbraco.Web/HtmlHelperRenderExtensions.cs create mode 100644 src/Umbraco.Web/ModelStateExtensions.cs create mode 100644 src/Umbraco.Web/RenderFieldCaseType.cs create mode 100644 src/Umbraco.Web/RenderFieldEncodingType.cs create mode 100644 src/Umbraco.Web/ViewContextExtensions.cs create mode 100644 src/Umbraco.Web/ViewDataContainerExtensions.cs diff --git a/src/Umbraco.Core/Configuration/GlobalSettings.cs b/src/Umbraco.Core/Configuration/GlobalSettings.cs index eb20a92e74..ac4f7647ae 100644 --- a/src/Umbraco.Core/Configuration/GlobalSettings.cs +++ b/src/Umbraco.Core/Configuration/GlobalSettings.cs @@ -150,6 +150,29 @@ namespace Umbraco.Core.Configuration } } + /// + /// This returns the string of the MVC Area route. + /// + /// + /// THIS IS TEMPORARY AND SHOULD BE REMOVED WHEN WE MIGRATE/UPDATE THE CONFIG SETTINGS TO BE A REAL CONFIG SECTION + /// AND SHOULD PROBABLY BE HANDLED IN A MORE ROBUST WAY. + /// + /// This will return the MVC area that we will route all custom routes through like surface controllers, etc... + /// We will use the 'Path' (default ~/umbraco) to create it but since it cannot contain '/' and people may specify a path of ~/asdf/asdf/admin + /// we will convert the '/' to '-' and use that as the path. its a bit lame but will work. + /// + internal static string MvcArea + { + get + { + if (Path.IsNullOrWhiteSpace()) + { + throw new InvalidOperationException("Cannot create an MVC Area path without the umbracoPath specified"); + } + return Path.TrimStart('~').TrimStart('/').Replace('/', '-').Trim(); + } + } + /// /// Gets the path to umbraco's client directory (/umbraco_client by default). /// This is a relative path to the Umbraco Path as it always must exist beside the 'umbraco' diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs new file mode 100644 index 0000000000..7e09b519a8 --- /dev/null +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Text; +using System.Web.Mvc; + +namespace Umbraco.Core +{ + /// + /// Extension methods for dictionary + /// + internal static class DictionaryExtensions + { + /// + /// Converts a dictionary to another type by only using direct casting + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add((TKeyOut)v.Key, (TValOut)v.Value); + } + return result; + } + + /// + /// Converts a dictionary to another type using the specified converters + /// + /// + /// + /// + /// + /// + /// + public static IDictionary ConvertTo(this IDictionary d, Func keyConverter, Func valConverter) + { + var result = new Dictionary(); + foreach (DictionaryEntry v in d) + { + result.Add(keyConverter(v.Key), valConverter(v.Value)); + } + return result; + } + + /// + /// Converts a dictionary to a NameValueCollection + /// + /// + /// + public static NameValueCollection ToNameValueCollection(this IDictionary d) + { + var n = new NameValueCollection(); + foreach (var i in d) + { + n.Add(i.Key, i.Value); + } + return n; + } + + /// + /// Converts a dictionary to a FormCollection + /// + /// + /// + public static FormCollection ToFormCollection(this IDictionary d) + { + var n = new FormCollection(); + foreach (var i in d) + { + n.Add(i.Key, Convert.ToString(i.Value)); + } + return n; + } + + /// + /// Returns a new dictionary of this ... others merged leftward. + /// + /// + /// + /// + /// + /// + /// + /// + /// Reference: http://stackoverflow.com/questions/294138/merging-dictionaries-in-c + /// + public static T MergeLeft(this T me, params IDictionary[] others) + where T : IDictionary, new() + { + var newMap = new T(); + foreach (var p in (new List> { me }).Concat(others).SelectMany(src => src)) + { + newMap[p.Key] = p.Value; + } + return newMap; + } + + /// + /// Returns the value of the key value based on the key, if the key is not found, a null value is returned + /// + /// The type of the key. + /// The type of the val. + /// The d. + /// The key. + /// The default value. + /// + public static TVal GetValue(this IDictionary d, TKey key, TVal defaultValue = default(TVal)) + { + if (d.ContainsKey(key)) + { + return d[key]; + } + return defaultValue; + } + + /// + /// Returns the value of the key value based on the key as it's string value, if the key is not found, then an empty string is returned + /// + /// + /// + /// + public static string GetValueAsString(this IDictionary d, TKey key) + { + if (d.ContainsKey(key)) + { + return d[key].ToString(); + } + return String.Empty; + } + + /// contains key ignore case. + /// The dictionary. + /// The key. + /// Value Type + /// The contains key ignore case. + public static bool ContainsKeyIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.Keys.Any(i => i.Equals(key, StringComparison.CurrentCultureIgnoreCase)); + } + + /// + /// Converts a dictionary object to a query string representation such as: + /// firstname=shannon&lastname=deminick + /// + /// + /// + public static string ToQueryString(this IDictionary d) + { + if (!d.Any()) return ""; + + var builder = new StringBuilder(); + foreach (var i in d) + { + builder.Append(String.Format("{0}={1}&", i.Key, i.Value)); + } + return builder.ToString().TrimEnd('&'); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The type + /// The entry + public static TValue GetEntryIgnoreCase(this IDictionary dictionary, string key) + { + return dictionary.GetEntryIgnoreCase(key, default(TValue)); + } + + /// The get entry ignore case. + /// The dictionary. + /// The key. + /// The default value. + /// The type + /// The entry + public static TValue GetEntryIgnoreCase(this IDictionary dictionary, string key, TValue defaultValue) + { + key = dictionary.Keys.Where(i => i.Equals(key, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); + + return !key.IsNullOrWhiteSpace() + ? dictionary[key] + : defaultValue; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 01bafd2f8e..41a0992829 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -10,7 +10,6 @@ using System.Reflection; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Security; -using System.Text; using System.Xml; namespace Umbraco.Core diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9f125d8550..289b0c56b4 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -38,6 +38,7 @@ + @@ -55,6 +56,7 @@ + diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs new file mode 100644 index 0000000000..6590854ad1 --- /dev/null +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -0,0 +1,312 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Web.Mvc; +using System.Web.Mvc.Html; +using Umbraco.Core; +using umbraco; + +namespace Umbraco.Web +{ + /// + /// HtmlHelper extensions for use in templates + /// + public static class HtmlHelperRenderExtensions + { + /// + /// Used for rendering out the Form for BeginUmbracoForm + /// + internal class UmbracoForm : MvcForm + { + public UmbracoForm( + ViewContext viewContext, + string surfaceController, + string surfaceAction, + string area, + Guid? surfaceId, + object additionalRouteVals = null) + : base(viewContext) + { + //need to create a params string as Base64 to put into our hidden field to use during the routes + string surfaceRouteParams; + if (surfaceId.HasValue) + { + surfaceRouteParams = string.Format("c={0}&a={1}&i={2}&ar={3}", + viewContext.HttpContext.Server.UrlEncode(surfaceController), + viewContext.HttpContext.Server.UrlEncode(surfaceAction), + viewContext.HttpContext.Server.UrlEncode(surfaceId.Value.ToString("N")), + area); + } + else + { + surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", + viewContext.HttpContext.Server.UrlEncode(surfaceController), + viewContext.HttpContext.Server.UrlEncode(surfaceAction), + area); + } + + var additionalRouteValsAsQuery = additionalRouteVals.ToDictionary().ToQueryString(); + if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) + surfaceRouteParams = "&" + additionalRouteValsAsQuery; + + _base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(surfaceRouteParams)); + + _textWriter = viewContext.Writer; + } + + + private bool _disposed; + private readonly string _base64String; + private readonly TextWriter _textWriter; + + protected override void Dispose(bool disposing) + { + if (this._disposed) + return; + this._disposed = true; + + //write out the hidden surface form routes + _textWriter.Write(""); + + base.Dispose(disposing); + } + } + + public static MvcHtmlString EditorFor(this HtmlHelper htmlHelper, string templateName = "", string htmlFieldName = "", object additionalViewData = null) + where T : new() + { + var model = new T(); + var typedHelper = new HtmlHelper( + htmlHelper.ViewContext.CopyWithModel(model), + htmlHelper.ViewDataContainer.CopyWithModel(model)); + + return typedHelper.EditorFor(x => model, templateName, htmlFieldName, additionalViewData); + } + + /// + /// A validation summary that lets you pass in a prefix so that the summary only displays for elements + /// containing the prefix. This allows you to have more than on validation summary on a page. + /// + /// + /// + /// + /// + /// + /// + public static MvcHtmlString ValidationSummary(this HtmlHelper htmlHelper, + string prefix = "", + bool excludePropertyErrors = false, + string message = "", + IDictionary htmlAttributes = null) + { + if (prefix.IsNullOrWhiteSpace()) + { + return htmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + //if there's a prefix applied, we need to create a new html helper with a filtered ModelState collection so that it only looks for + //specific model state with the prefix. + var filteredHtmlHelper = new HtmlHelper(htmlHelper.ViewContext, htmlHelper.ViewDataContainer.FilterContainer(prefix)); + return filteredHtmlHelper.ValidationSummary(excludePropertyErrors, message, htmlAttributes); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes) + { + var settings = DependencyResolver.Current.GetService(); + var area = Umbraco.Core.Configuration.GlobalSettings.MvcArea; + var formAction = html.ViewContext.HttpContext.Request.Url.AbsolutePath; + + return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, null); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId) + { + return html.BeginUmbracoForm(action, surfaceId, null, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + object additionalRouteVals) + { + return html.BeginUmbracoForm(action, surfaceId, additionalRouteVals, new Dictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + object additionalRouteVals, + object htmlAttributes) + { + return html.BeginUmbracoForm(action, surfaceId, additionalRouteVals, htmlAttributes.ToDictionary()); + } + + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Guid surfaceId, + object additionalRouteVals, + IDictionary htmlAttributes) + { + //TODO: Make me work :) + + throw new NotImplementedException(); + + //var settings = DependencyResolver.Current.GetService(); + //var area = Umbraco.Core.Configuration.GlobalSettings.MvcArea; + //var formAction = html.ViewContext.HttpContext.Request.Url.AbsolutePath; + + //var surfaceMetadata = DependencyResolver.Current.GetService() + // .SurfaceControllers + // .Where(x => x.Metadata.Id == surfaceId) + // .SingleOrDefault(); + //if (surfaceMetadata == null) + // throw new InvalidOperationException("Could not find the surface controller with id " + surfaceId); + ////now, need to figure out what area this surface controller belongs too... + //var pluginDefition = surfaceMetadata.Metadata.PluginDefinition; + //if (pluginDefition.HasRoutablePackageArea()) + //{ + // //a plugin def CAN be null, if the plugin is actually in our Web DLL folder or if someone drops their + // //dll into the bin... though if they do that it still wont work since they wont get an area registered. + // //area = PluginManager.GetPackageFolderFromPluginDll(pluginDefition.OriginalAssemblyFile).Name; + // area = pluginDefition.PackageName; + //} + + ////render the form + //return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, surfaceMetadata.Metadata.ControllerName, action, area, surfaceId); + } + + /// + /// This renders out the form for us + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// This code is pretty much the same as the underlying MVC code that writes out the form + /// + private static MvcForm RenderForm(this HtmlHelper htmlHelper, + string formAction, + FormMethod method, + IDictionary htmlAttributes, + string surfaceController, + string surfaceAction, + string area, + Guid? surfaceId, + object additionalRouteVals = null) + { + + var tagBuilder = new TagBuilder("form"); + tagBuilder.MergeAttributes(htmlAttributes); + // action is implicitly generated, so htmlAttributes take precedence. + tagBuilder.MergeAttribute("action", formAction); + // method is an explicit parameter, so it takes precedence over the htmlAttributes. + tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled; + if (traditionalJavascriptEnabled) + { + // forms must have an ID for client validation + tagBuilder.GenerateId("form" + Guid.NewGuid().ToString("N")); + } + htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); + + //new UmbracoForm: + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, surfaceId, additionalRouteVals); + + if (traditionalJavascriptEnabled) + { + htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"]; + } + return theForm; + } + + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs new file mode 100644 index 0000000000..d2980c5aab --- /dev/null +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -0,0 +1,76 @@ +using System.Linq; +using System.Web.Mvc; + +namespace Umbraco.Web +{ + internal static class ModelStateExtensions + { + + /// + /// Merges ModelState that has names matching the prefix + /// + /// + /// + /// + public static void Merge(this ModelStateDictionary state, ModelStateDictionary dictionary, string prefix) + { + if (dictionary == null) + return; + foreach (var keyValuePair in dictionary.Where(keyValuePair => keyValuePair.Key.StartsWith(prefix + "."))) + { + state[keyValuePair.Key] = keyValuePair.Value; + } + } + + /// + /// Checks if there are any model errors on any fields containing the prefix + /// + /// + /// + /// + public static bool IsValid(this ModelStateDictionary state, string prefix) + { + return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); + } + + + //NOTE: we used this alot in v5 when we had editors in MVC, this was really handy for knockout editors using JS + + ///// + ///// Adds an error to the model state that has to do with data validation, this is generally used for JSON responses + ///// + ///// + ///// + //public static void AddDataValidationError(this ModelStateDictionary state, string errorMessage) + //{ + // state.AddModelError("DataValidation", errorMessage); + //} + + /// + /// Serializes the ModelState to JSON for JavaScript to interrogate the errors + /// + /// + /// + public static JsonResult ToJsonErrors(this ModelStateDictionary state) + { + return new JsonResult + { + Data = new + { + success = state.IsValid.ToString().ToLower(), + failureType = "ValidationError", + validationErrors = from e in state + where e.Value.Errors.Count > 0 + select new + { + name = e.Key, + errors = e.Value.Errors.Select(x => x.ErrorMessage) + .Concat( + e.Value.Errors.Where(x => x.Exception != null).Select(x => x.Exception.Message)) + } + } + }; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/RenderFieldCaseType.cs b/src/Umbraco.Web/RenderFieldCaseType.cs new file mode 100644 index 0000000000..ebb9b386c4 --- /dev/null +++ b/src/Umbraco.Web/RenderFieldCaseType.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Web +{ + /// + /// Used in the .Field method when rendering an Umbraco field to specify what case type it should be + /// + public enum RenderFieldCaseType + { + Upper, + Lower, + Title, + Unchanged + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/RenderFieldEncodingType.cs b/src/Umbraco.Web/RenderFieldEncodingType.cs new file mode 100644 index 0000000000..001fb2023d --- /dev/null +++ b/src/Umbraco.Web/RenderFieldEncodingType.cs @@ -0,0 +1,13 @@ +namespace Umbraco.Web +{ + + /// + /// Used in the .Field method to render an Umbraco field to specify what encoding to use + /// + public enum RenderFieldEncodingType + { + Url, + Html, + Unchanged + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 40b08cca4b..62384ef293 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -246,6 +246,7 @@ ASPXCodeBehind + @@ -255,6 +256,9 @@ + + + ASPXCodeBehind @@ -262,6 +266,8 @@ + + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 8262ddfe48..7962cab229 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -2,8 +2,13 @@ using System.Collections; using System.IO; using System.Web; +using System.Web.Configuration; +using System.Web.UI; using Umbraco.Core; +using Umbraco.Core.Models; using umbraco; +using System.Collections.Generic; +using umbraco.presentation.templateControls; namespace Umbraco.Web { @@ -13,10 +18,12 @@ namespace Umbraco.Web public class UmbracoHelper { private readonly UmbracoContext _umbracoContext; + private readonly IDocument _currentPage; internal UmbracoHelper(UmbracoContext umbracoContext) { _umbracoContext = umbracoContext; + _currentPage = _umbracoContext.DocumentRequest.Document; } @@ -62,11 +69,133 @@ namespace Umbraco.Web UmbracoContext.Current.DocumentRequest.UmbracoPage.Elements, _umbracoContext.PageId.Value); containerPage.Controls.Add(macroControl); - var output = new StringWriter(); - _umbracoContext.HttpContext.Server.Execute(containerPage, output, false); - return new HtmlString(output.ToString()); + using (var output = new StringWriter()) + { + _umbracoContext.HttpContext.Server.Execute(containerPage, output, false); + return new HtmlString(output.ToString()); + } } #endregion + + #region Field + + /// + /// Renders an field to the template + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IHtmlString Field(string fieldAlias, string valueAlias = "", + string altFieldAlias = "", string altValueAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", + bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, + RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + string formatString = "") + { + return Field(_currentPage, fieldAlias, valueAlias, altFieldAlias, altValueAlias, + altText, insertBefore, insertAfter, recursive, convertLineBreaks, removeParagraphTags, + casing, encoding, formatString); + } + + /// + /// Renders an field to the template + /// + /// + /// + /// + /// + /// + /// TODO: This currently doesn't do anything!! we should implement it... it is static text that is displayed if all else fails + /// + /// + /// + /// + /// + /// + /// + /// + /// + public IHtmlString Field(IDocument currentPage, string fieldAlias, string valueAlias = "", + string altFieldAlias = "", string altValueAlias = "", string altText = "", string insertBefore = "", string insertAfter = "", + bool recursive = false, bool convertLineBreaks = false, bool removeParagraphTags = false, + RenderFieldCaseType casing = RenderFieldCaseType.Unchanged, + RenderFieldEncodingType encoding = RenderFieldEncodingType.Unchanged, + string formatString = "") + { + //TODO: This is real nasty and we should re-write the 'item' and 'ItemRenderer' class but si fine for now + + var attributes = new Dictionary + { + {"field", fieldAlias}, + {"recursive", recursive.ToString().ToLowerInvariant()}, + {"useIfEmpty", altFieldAlias}, + {"textIfEmpty", altText}, + {"stripParagraph", removeParagraphTags.ToString().ToLowerInvariant()}, + { + "case", casing == RenderFieldCaseType.Lower ? "lower" + : casing == RenderFieldCaseType.Upper ? "upper" + : casing == RenderFieldCaseType.Title ? "title" + : string.Empty + }, + {"insertTextBefore", insertBefore}, + {"insertTextAfter", insertAfter}, + {"convertLineBreaks", convertLineBreaks.ToString().ToLowerInvariant()} + }; + switch (encoding) + { + case RenderFieldEncodingType.Url: + attributes.Add("urlEncode", "true"); + break; + case RenderFieldEncodingType.Html: + attributes.Add("htmlEncode", "true"); + break; + case RenderFieldEncodingType.Unchanged: + default: + break; + } + + //need to convert our dictionary over to this weird dictionary type + var attributesForItem = new AttributeCollectionAdapter( + new AttributeCollection( + new StateBag())); + foreach(var i in attributes) + { + attributesForItem.Add(i.Key, i.Value); + } + + var item = new Item() + { + Field = fieldAlias, + TextIfEmpty = altText, + LegacyAttributes = attributesForItem + }; + var containerPage = new FormlessPage(); + containerPage.Controls.Add(item); + + using (var output = new StringWriter()) + using (var htmlWriter = new HtmlTextWriter(output)) + { + ItemRenderer.Instance.Init(item); + ItemRenderer.Instance.Load(item); + ItemRenderer.Instance.Render(item, htmlWriter); + _umbracoContext.HttpContext.Server.Execute(containerPage, output, false); + return new HtmlString(output.ToString()); + } + } + + #endregion + } } diff --git a/src/Umbraco.Web/ViewContextExtensions.cs b/src/Umbraco.Web/ViewContextExtensions.cs new file mode 100644 index 0000000000..3d15b0d95d --- /dev/null +++ b/src/Umbraco.Web/ViewContextExtensions.cs @@ -0,0 +1,34 @@ +using System.Web.Mvc; + +namespace Umbraco.Web +{ + internal static class ViewContextExtensions + { + /// + /// Creates a new ViewContext from an existing one but specifies a new Model for the ViewData + /// + /// + /// + /// + public static ViewContext CopyWithModel(this ViewContext vc, object model) + { + return new ViewContext + { + Controller = vc.Controller, + HttpContext = vc.HttpContext, + RequestContext = vc.RequestContext, + RouteData = vc.RouteData, + TempData = vc.TempData, + View = vc.View, + ViewData = new ViewDataDictionary(vc) + { + Model = model + }, + FormContext = vc.FormContext, + ClientValidationEnabled = vc.ClientValidationEnabled, + UnobtrusiveJavaScriptEnabled = vc.UnobtrusiveJavaScriptEnabled, + Writer = vc.Writer + }; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/ViewDataContainerExtensions.cs b/src/Umbraco.Web/ViewDataContainerExtensions.cs new file mode 100644 index 0000000000..b5a9843c5e --- /dev/null +++ b/src/Umbraco.Web/ViewDataContainerExtensions.cs @@ -0,0 +1,47 @@ +using System.Web.Mvc; + +namespace Umbraco.Web +{ + internal static class ViewDataContainerExtensions + { + private class ViewDataContainer : IViewDataContainer + { + public ViewDataContainer() + { + ViewData = new ViewDataDictionary(); + } + public ViewDataDictionary ViewData { get; set; } + } + + /// + /// Creates a new IViewDataContainer but with a filtered ModelState + /// + /// + /// + /// + public static IViewDataContainer FilterContainer(this IViewDataContainer container, string prefix) + { + var newContainer = new ViewDataContainer(); + newContainer.ViewData.ModelState.Merge(container.ViewData.ModelState, prefix); + return newContainer; + } + + /// + /// Returns a new IViewContainer based on the current one but supplies a different model to the ViewData + /// + /// + /// + /// + public static IViewDataContainer CopyWithModel(this IViewDataContainer container, object model) + { + return new ViewDataContainer + { + ViewData = new ViewDataDictionary(container.ViewData) + { + Model = model + } + }; + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/item.cs b/src/Umbraco.Web/umbraco.presentation/item.cs index 5fed9e0738..30c5a2c217 100644 --- a/src/Umbraco.Web/umbraco.presentation/item.cs +++ b/src/Umbraco.Web/umbraco.presentation/item.cs @@ -2,6 +2,7 @@ using System; using System.Collections; using System.Web; using System.Xml; +using Umbraco.Core; namespace umbraco { @@ -121,10 +122,12 @@ namespace umbraco } // CASING - if (helper.FindAttribute(attributes, "case") == "lower") - _fieldContent = _fieldContent.ToLower(); - else if (helper.FindAttribute(attributes, "case") == "upper") - _fieldContent = _fieldContent.ToUpper(); + if (helper.FindAttribute(attributes, "case") == "lower") + _fieldContent = _fieldContent.ToLower(); + else if (helper.FindAttribute(attributes, "case") == "upper") + _fieldContent = _fieldContent.ToUpper(); + else if (helper.FindAttribute(attributes, "case") == "title") + _fieldContent = _fieldContent.ConvertCase(StringAliasCaseType.PascalCase); // OTHER FORMATTING FUNCTIONS // If we use masterpages, this is moved to the ItemRenderer to add support for before/after in inline XSLT