diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index 61aa7029d6..6c259b9f86 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -98,7 +98,7 @@ namespace Umbraco.Core // public bool IsConfigured { - // fixme - let's do this for the time being + // fixme - we should not do this - ok for now get { return Configured; diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index 6054056645..ca2bbe35fe 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -2,135 +2,165 @@ using System; namespace Umbraco.Core { - public struct AttemptOutcome + /// + /// Provides ways to create attempts. + /// + public static class Attempt { - private readonly bool _success; - - public AttemptOutcome(bool success) + /// + /// Creates a successful attempt with a result. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(T result) { - _success = success; + return Attempt.Succeed(result); } - public AttemptOutcome IfFailed(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - if (_success == false) - { - return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); - } - - //return a successful outcome since the last one was successful, this allows the next AttemptOutcome chained to - // continue properly. - return new AttemptOutcome(true); - } - - public AttemptOutcome IfSuccessful(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - if (_success) - { - return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); - } - //return a failed outcome since the last one was not successful, this allows the next AttemptOutcome chained to - // continue properly. - return new AttemptOutcome(false); - } - - private AttemptOutcome ExecuteNextAttempt(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - var attempt = nextAttempt(); - if (attempt.Success) - { - onSuccess(attempt.Result); - return new AttemptOutcome(true); - } - - if (onFail != null) - { - onFail(attempt.Error); - } - return new AttemptOutcome(false); - } - } - - /// - /// Represents the result of an operation attempt - /// - /// - /// - [Serializable] - public struct Attempt - { - private readonly bool _success; - private readonly T _result; - private readonly Exception _error; - - /// - /// Gets a value indicating whether this represents a successful operation. - /// - /// - public bool Success - { - get { return _success; } - } - - /// - /// Gets the error associated with an unsuccessful attempt. - /// - /// The error. - public Exception Error { get { return _error; } } - - /// - /// Gets the parse result. - /// - /// - public T Result - { - get { return _result; } - } - /// - /// Perform the attempt with callbacks + /// Creates a failed attempt with a result. /// - /// - /// - /// - public static AttemptOutcome Try(Attempt attempt, Action onSuccess, Action onFail = null) + /// The type of the attempted operation result. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(T result) + { + return Attempt.Fail(result); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(T result, Exception exception) + { + return Attempt.Fail(result, exception); + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool success, T result) + { + return Attempt.SucceedIf(success, result); + } + + + /// + /// Executes an attempt function, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt returned by the attempt function. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// The outcome of the attempt. + /// Runs or depending on the + /// whether the attempt function reports a success or a failure. + public static Outcome Try(Attempt attempt, Action onSuccess, Action onFail = null) { if (attempt.Success) { onSuccess(attempt.Result); - return new AttemptOutcome(true); + return Outcome.Success; } if (onFail != null) { - onFail(attempt.Error); + onFail(attempt.Exception); } - return new AttemptOutcome(false); + + return Outcome.Failure; } - /// - /// Represents an unsuccessful parse operation - /// - public static readonly Attempt False = new Attempt(false, default(T)); + /// + /// Represents the outcome of an attempt. + /// + /// Can be a success or a failure, and allows for attempts chaining. + public struct Outcome + { + private readonly bool _success; - /// - /// Initializes a new instance of the struct. - /// - /// If set to true this tuple represents a successful parse result. - /// The parse result. - /// - public Attempt(bool success, T result) - { - _success = success; - _result = result; - _error = null; - } + /// + /// Gets an outcome representing a success. + /// + public static readonly Outcome Success = new Outcome(true); - public Attempt(Exception error) - { - _success = false; - _result = default(T); - _error = error; - } - } + /// + /// Gets an outcome representing a failure. + /// + public static readonly Outcome Failure = new Outcome(false); + + private Outcome(bool success) + { + _success = success; + } + + /// + /// Executes another attempt function, if the previous one failed, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt function to execute, returning an attempt. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// If it executes, returns the outcome of the attempt, else returns a success outcome. + /// + /// Executes only if the previous attempt failed, else does not execute and return a success outcome. + /// If it executes, then runs or depending on the + /// whether the attempt function reports a success or a failure. + /// + public Outcome OnFailure(Func> nextFunction, Action onSuccess, Action onFail = null) + { + return _success + ? Success + : ExecuteNextFunction(nextFunction, onSuccess, onFail); + } + + /// + /// Executes another attempt function, if the previous one succeeded, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt function to execute, returning an attempt. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// If it executes, returns the outcome of the attempt, else returns a failed outcome. + /// + /// Executes only if the previous attempt succeeded, else does not execute and return a success outcome. + /// If it executes, then runs or depending on the + /// whether the attempt function reports a success or a failure. + /// + public Outcome OnSuccess(Func> nextFunction, Action onSuccess, Action onFail = null) + { + return _success + ? ExecuteNextFunction(nextFunction, onSuccess, onFail) + : Failure; + } + + private static Outcome ExecuteNextFunction(Func> nextFunction, Action onSuccess, Action onFail = null) + { + var attempt = nextFunction(); + + if (attempt.Success) + { + onSuccess(attempt.Result); + return Success; + } + + if (onFail != null) + { + onFail(attempt.Exception); + } + + return Failure; + } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Attempt{T}.cs b/src/Umbraco.Core/Attempt{T}.cs new file mode 100644 index 0000000000..4a348247d4 --- /dev/null +++ b/src/Umbraco.Core/Attempt{T}.cs @@ -0,0 +1,164 @@ +using System; +using Umbraco.Core.Dynamics; + +namespace Umbraco.Core +{ + /// + /// Represents the result of an operation attempt. + /// + /// The type of the attempted operation result. + [Serializable] + public struct Attempt + { + private readonly bool _success; + private readonly T _result; + private readonly Exception _exception; + + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success + { + get { return _success; } + } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception Exception { get { return _exception; } } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + /// Keep it for backward compatibility sake. + [Obsolete(".Error is obsolete, you should use .Exception instead.", false)] + public Exception Error { get { return _exception; } } + + /// + /// Gets the attempt result. + /// + public T Result + { + get { return _result; } + } + + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new Attempt(false, default(T), null); + + /// + /// Represents an unsuccessful attempt. + /// + /// Keep it for backward compatibility sake. + [Obsolete(".Failed is obsolete, you should use Attempt.Fail() instead.", false)] + public static readonly Attempt False = Failed; + + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, T result, Exception exception) + { + _success = success; + _result = result; + _exception = exception; + } + + /// + /// Initialize a new instance of the struct with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// Keep it for backward compatibility sake. + [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] + public Attempt(bool success, T result) + : this(success, result, null) + { } + + /// + /// Initialize a new instance of the struct representing a failed attempt, with an exception. + /// + /// The exception causing the failure of the attempt. + /// Keep it for backward compatibility sake. + [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] + public Attempt(Exception exception) + : this(false, default(T), exception) + { } + + /// + /// Creates a successful attempt. + /// + /// The successful attempt. + public static Attempt Succeed() + { + return new Attempt(true, default(T), null); + } + + /// + /// Creates a successful attempt with a result. + /// + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(T result) + { + return new Attempt(true, result, null); + } + + /// + /// Creates a failed attempt. + /// + /// The failed attempt. + public static Attempt Fail() + { + return Failed; + } + + /// + /// Creates a failed attempt with an exception. + /// + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(Exception exception) + { + return new Attempt(false, default(T), exception); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(T result) + { + return new Attempt(false, result, null); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(T result, Exception exception) + { + return new Attempt(false, result, exception); + } + + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The attempt. + public static Attempt SucceedIf(bool condition) + { + return condition ? new Attempt(true, default(T), null) : Failed; + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt SucceedIf(bool condition, T result) + { + return new Attempt(condition, result, null); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/LegacyUmbracoSettings.cs b/src/Umbraco.Core/Configuration/LegacyUmbracoSettings.cs index c9b1c2347c..53ef109112 100644 --- a/src/Umbraco.Core/Configuration/LegacyUmbracoSettings.cs +++ b/src/Umbraco.Core/Configuration/LegacyUmbracoSettings.cs @@ -514,12 +514,10 @@ namespace Umbraco.Core.Configuration { try { - List items = new List(); - XmlNode root = GetKeyAsNode("/settings/scripting/razor/notDynamicXmlDocumentElements"); - foreach (XmlNode element in root.SelectNodes(".//element")) - { - items.Add(element.InnerText); - } + var items = new List(); + var root = GetKeyAsNode("/settings/scripting/razor/notDynamicXmlDocumentElements"); + if (root != null) + items.AddRange(root.SelectNodes(".//element").Cast().Select(n => n.InnerText)); return items; } catch diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs index a7bb20ef5b..1338ee03e2 100644 --- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs +++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs @@ -82,7 +82,7 @@ namespace Umbraco.Core.Dynamics null, thisObject, args); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); } catch (MissingMethodException) { @@ -97,7 +97,7 @@ namespace Umbraco.Core.Dynamics null, thisObject, args); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); } catch (MissingMethodException) { @@ -106,13 +106,13 @@ namespace Umbraco.Core.Dynamics try { result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); } catch (TargetInvocationException ext) { //don't log here, we return this exception because the caller may need to do something specific when //this exception occurs. - return new Attempt(ext); + return Attempt.Fail(ext); } catch (Exception ex) { @@ -124,16 +124,16 @@ namespace Umbraco.Core.Dynamics sb.Append(t + ","); } LogHelper.Error(sb.ToString(), ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } - return Attempt.False; + return Attempt.Fail(); } } catch (Exception ex) { LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index 0e0d14d9c9..a84389d2e5 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -201,7 +201,7 @@ namespace Umbraco.Core.Dynamics //this is the result of an extension method execution gone wrong so we return dynamic null if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Error != null && attempt.Error is TargetInvocationException) + && attempt.Exception != null && attempt.Exception is TargetInvocationException) { result = new DynamicNull(); return true; @@ -261,7 +261,7 @@ namespace Umbraco.Core.Dynamics var attributes = xmlElement.Attributes(name).Select(attr => attr.Value).ToArray(); if (attributes.Any()) { - return new Attempt>(true, attributes); + return Attempt>.Succeed(attributes); } if (!attributes.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) @@ -271,12 +271,12 @@ namespace Umbraco.Core.Dynamics if (childElements.Any()) { //we've found a match by the first child of an element called 'root' (strange, but sure) - return new Attempt>(true, childElements); + return Attempt>.Succeed(childElements); } } //no deal - return Attempt>.False; + return Attempt>.Fail(); } /// @@ -293,7 +293,7 @@ namespace Umbraco.Core.Dynamics //Check if we've got any matches, if so then return true if (elements.Any()) { - return new Attempt>(true, elements); + return Attempt>.Succeed(elements); } if (!elements.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) @@ -303,12 +303,12 @@ namespace Umbraco.Core.Dynamics if (childElements.Any()) { //we've found a match by the first child of an element called 'root' (strange, but sure) - return new Attempt>(true, childElements); + return Attempt>.Succeed(childElements); } } //no deal - return Attempt>.False; + return Attempt>.Fail(); } private bool HandleIEnumerableXElement(IEnumerable elements, out object result) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 879c1a60e8..96e402a9b2 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -58,14 +58,14 @@ namespace Umbraco.Core try { var converted = (T) input; - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return !result.Success ? Attempt.False : new Attempt(true, (T)result.Result); + return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result); } /// @@ -80,15 +80,18 @@ namespace Umbraco.Core //if it is null and it is nullable, then return success with null if (input == null && destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof (Nullable<>)) { - return new Attempt(true, null); + return Attempt.Succeed(null); } //if its not nullable then return false - if (input == null) return Attempt.False; + if (input == null) return Attempt.Fail(); - if (destinationType == typeof(object)) return new Attempt(true, input); + if (destinationType == typeof(object)) return Attempt.Succeed(input); - if (input.GetType() == destinationType) return new Attempt(true, input); + if (input.GetType() == destinationType) return Attempt.Succeed(input); + + //check for string so that overloaders of ToString() can take advantage of the conversion. + if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); if (!destinationType.IsGenericType || destinationType.GetGenericTypeDefinition() != typeof(Nullable<>)) { @@ -101,11 +104,11 @@ namespace Umbraco.Core try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -116,11 +119,11 @@ namespace Umbraco.Core try { var converted = inputConverter.ConvertTo(input, destinationType); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -132,11 +135,11 @@ namespace Umbraco.Core try { var converted = boolConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -147,11 +150,11 @@ namespace Umbraco.Core try { var converted = outputConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -161,15 +164,15 @@ namespace Umbraco.Core try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return Attempt.False; + return Attempt.Fail(); } internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) @@ -358,11 +361,11 @@ namespace Umbraco.Core try { var output = value.ToXmlString(type); - return new Attempt(true, output); + return Attempt.Succeed(output); } catch (NotSupportedException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index cb5d7c95a1..4b6b513bf2 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -67,19 +67,19 @@ namespace Umbraco.Core.Persistence.Mappers if (mapper == null) { - return Attempt.False; + return Attempt.Fail(); } try { var instance = Activator.CreateInstance(mapper) as BaseMapper; return instance != null - ? new Attempt(true, instance) - : Attempt.False; + ? Attempt.Succeed(instance) + : Attempt.Fail(); } catch (Exception ex) { LogHelper.Error(typeof(MappingResolver), "Could not instantiate mapper of type " + mapper, ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs index db58b7f918..1041fdb367 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs @@ -21,9 +21,9 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions public override string ToString() { - return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), - SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name)); + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, + SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), + SqlSyntaxContext.SqlSyntaxProvider.Format(Column)); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 06fa303123..6425c9f566 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -118,9 +118,9 @@ namespace Umbraco.Core.Persistence.Repositories var rEntity = _cache.GetById(typeof(TEntity), key); if (rEntity != null) { - return new Attempt(true, (TEntity) rEntity); + return Attempt.Succeed((TEntity) rEntity); } - return Attempt.False; + return Attempt.Fail(); } protected abstract IEnumerable PerformGetAll(params TId[] ids); diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 840bb733f1..c48e353284 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -242,7 +242,7 @@ namespace Umbraco.Core { var filePath = GetPluginListFilePath(); if (!File.Exists(filePath)) - return Attempt>.False; + return Attempt>.Fail(); try { @@ -262,7 +262,7 @@ namespace Umbraco.Core if (xml.Root == null) - return Attempt>.False; + return Attempt>.Fail(); var typeElement = xml.Root.Elements() .SingleOrDefault(x => @@ -272,18 +272,16 @@ namespace Umbraco.Core //return false but specify this exception type so we can detect it if (typeElement == null) - return new Attempt>(new CachedPluginNotFoundInFile()); + return Attempt>.Fail(new CachedPluginNotFoundInFile()); //return success - return new Attempt>( - true, - typeElement.Elements("add") + return Attempt.Succeed(typeElement.Elements("add") .Select(x => (string)x.Attribute("type"))); } catch (Exception ex) { //if the file is corrupted, etc... return false - return new Attempt>(ex); + return Attempt>.Fail(ex); } } @@ -634,7 +632,7 @@ namespace Umbraco.Core //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed //so in this instance there will never be a result. - if (fileCacheResult.Error != null && fileCacheResult.Error is CachedPluginNotFoundInFile) + if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFile) { //we don't have a cache for this so proceed to look them up by scanning LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 3a1974279e..d3fd0daae0 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -143,16 +143,16 @@ namespace Umbraco.Core.Profiling private Attempt TryGetRequest(object sender) { var app = sender as HttpApplication; - if (app == null) return Attempt.False; + if (app == null) return Attempt.Fail(); try { var req = app.Request; - return new Attempt(true, new HttpRequestWrapper(req)); + return Attempt.Succeed(new HttpRequestWrapper(req)); } catch (HttpException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } } } diff --git a/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs b/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs index fec80ddc85..8480d91260 100644 --- a/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/TinyMcePropertyEditorValueConverter.cs @@ -20,7 +20,7 @@ namespace Umbraco.Core.PropertyEditors /// public virtual Attempt ConvertPropertyValue(object value) { - return new Attempt(true, new HtmlString(value.ToString())); + return Attempt.Succeed(new HtmlString(value.ToString())); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PublishedContentHelper.cs b/src/Umbraco.Core/PublishedContentHelper.cs index beb2bf1d14..9267ea6e77 100644 --- a/src/Umbraco.Core/PublishedContentHelper.cs +++ b/src/Umbraco.Core/PublishedContentHelper.cs @@ -93,7 +93,7 @@ namespace Umbraco.Core /// internal static Attempt ConvertPropertyValue(object currentValue, Guid dataType, string docTypeAlias, string propertyTypeAlias) { - if (currentValue == null) return Attempt.False; + if (currentValue == null) return Attempt.Fail(); //First lets check all registered converters for this data type. var converters = PropertyEditorValueConvertersResolver.Current.Converters @@ -105,7 +105,7 @@ namespace Umbraco.Core .Select(p => p.ConvertPropertyValue(currentValue)) .Where(converted => converted.Success)) { - return new Attempt(true, converted.Result); + return Attempt.Succeed(converted.Result); } //if none of the converters worked, then we'll process this from what we know @@ -118,17 +118,17 @@ namespace Umbraco.Core decimal dResult; if (decimal.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out dResult)) { - return new Attempt(true, dResult); + return Attempt.Succeed(dResult); } } //process string booleans as booleans if (sResult.InvariantEquals("true")) { - return new Attempt(true, true); + return Attempt.Succeed(true); } if (sResult.InvariantEquals("false")) { - return new Attempt(true, false); + return Attempt.Succeed(false); } //a really rough check to see if this may be valid xml @@ -147,16 +147,16 @@ namespace Umbraco.Core if (!LegacyUmbracoSettings.NotDynamicXmlDocumentElements.Any( tag => string.Equals(tag, documentElement, StringComparison.CurrentCultureIgnoreCase))) { - return new Attempt(true, new DynamicXml(e)); + return Attempt.Succeed(new DynamicXml(e)); } - return Attempt.False; + return Attempt.Fail(); } catch (Exception) { - return Attempt.False; + return Attempt.Fail(); } } - return Attempt.False; + return Attempt.Fail(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index 5f874a00b0..899567bf8d 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Publishing { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); } @@ -35,7 +35,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedHasExpired)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedHasExpired)); } //Check if the Content is Awaiting Release to verify that it can in fact be published @@ -44,7 +44,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedAwaitingRelease)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedAwaitingRelease)); } //Check if the Content is Trashed to verify that it can in fact be published @@ -53,7 +53,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedIsTrashed)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedIsTrashed)); } content.ChangePublishedState(PublishedState.Published); @@ -62,7 +62,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been published.", content.Name, content.Id)); - return new Attempt(true, new PublishStatus(content)); + return Attempt.Succeed(new PublishStatus(content)); } /// @@ -125,7 +125,7 @@ namespace Umbraco.Core.Publishing //We're going to populate the statuses with all content that is already published because below we are only going to iterate over // content that is not published. We'll set the status to "AlreadyPublished" statuses.AddRange(fetchedContent.Where(x => x.Published) - .Select(x => new Attempt(true, new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); + .Select(x => Attempt.Succeed(new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); int? firstLevel = null; @@ -169,7 +169,7 @@ namespace Umbraco.Core.Publishing //the publishing has been cancelled. LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -183,7 +183,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedContentInvalid))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -197,7 +197,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedHasExpired))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedHasExpired))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -211,7 +211,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedAwaitingRelease))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedAwaitingRelease))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -225,7 +225,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedIsTrashed))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedIsTrashed))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -239,7 +239,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been published.", item.Name, item.Id)); - statuses.Add(new Attempt(true, new PublishStatus(item))); + statuses.Add(Attempt.Succeed(new PublishStatus(item))); } } @@ -349,7 +349,7 @@ namespace Umbraco.Core.Publishing { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - result.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); + result.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); continue; } @@ -370,7 +370,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been unpublished.", item.Name, item.Id)); - result.Add(new Attempt(true, new PublishStatus(item))); + result.Add(Attempt.Succeed(new PublishStatus(item))); } return result; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 225e523493..4c63033cc3 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1483,7 +1483,7 @@ namespace Umbraco.Core.Services string.Format( "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", content.Name, content.Id)); - result.Add(new Attempt(false, new PublishStatus(content, PublishStatusType.FailedPathNotPublished))); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished))); return result; } @@ -1494,7 +1494,7 @@ namespace Umbraco.Core.Services string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", content.Name, content.Id)); result.Add( - new Attempt(false, + Attempt.Fail( new PublishStatus(content, PublishStatusType.FailedContentInvalid) { InvalidProperties = ((ContentBase) content).LastInvalidProperties @@ -1600,7 +1600,7 @@ namespace Umbraco.Core.Services { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) { - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); } } @@ -1670,7 +1670,7 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Publish, "Save and Publish performed by user", userId, content.Id); - return new Attempt(publishStatus.StatusType == PublishStatusType.Success, publishStatus); + return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); } } diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index 731a8de1f4..9e19b46c31 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -95,11 +95,11 @@ namespace Umbraco.Core { if (types.Length == 0) { - return Attempt.False; + return Attempt.Fail(); } if (types.Length == 1) { - return new Attempt(true, types[0]); + return Attempt.Succeed(types[0]); } foreach (var curr in types) @@ -112,11 +112,11 @@ namespace Umbraco.Core //if this type is the base for all others if (isBase) { - return new Attempt(true, curr); + return Attempt.Succeed(curr); } } - return Attempt.False; + return Attempt.Fail(); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index a0f2a94da5..e1a41fe2e5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -114,7 +114,7 @@ - + @@ -205,6 +205,7 @@ + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 5de4285469..2fda214e01 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -1,8 +1,6 @@ using System; using System.IO; using System.Linq; -using System.Text; -using System.Web; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -75,6 +73,7 @@ namespace Umbraco.Core /// internal static bool IsClientSideRequest(this Uri url) { + // fixme - but really, is this OK? we should accept either no url, or .aspx, and everything else is out var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".html", ".svg" }; return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x)); } @@ -88,7 +87,7 @@ namespace Umbraco.Core /// Everything else remains unchanged, except for the fragment which is removed. public static Uri Rewrite(this Uri uri, string path) { - if (!path.StartsWith("/")) + if (path.StartsWith("/") == false) throw new ArgumentException("Path must start with a slash.", "path"); return uri.IsAbsoluteUri @@ -106,9 +105,9 @@ namespace Umbraco.Core /// Everything else remains unchanged, except for the fragment which is removed. public static Uri Rewrite(this Uri uri, string path, string query) { - if (!path.StartsWith("/")) + if (path.StartsWith("/") == false) throw new ArgumentException("Path must start with a slash.", "path"); - if (query.Length > 0 && !query.StartsWith("?")) + if (query.Length > 0 && query.StartsWith("?") == false) throw new ArgumentException("Query must start with a question mark.", "query"); if (query == "?") query = ""; @@ -171,15 +170,14 @@ namespace Umbraco.Core var path = uri.GetSafeAbsolutePath(); if (uri.IsAbsoluteUri) { - if (path != "/" && !path.EndsWith("/")) + if (path != "/" && path.EndsWith("/") == false) uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query); return uri; } - else - { - if (path != "/" && !path.EndsWith("/")) - uri = new Uri(path + "/" + uri.Query, UriKind.Relative); - } + + if (path != "/" && path.EndsWith("/") == false) + uri = new Uri(path + "/" + uri.Query, UriKind.Relative); + return uri; } diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index cf76c4a032..b120eed8b8 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core { try { - doc = new XPathDocument(new XmlTextReader(new StringReader(xml))); + doc = CreateXPathDocument(xml); return true; } catch (Exception) @@ -63,23 +63,68 @@ namespace Umbraco.Core /// /// Tries to create a new XPathDocument from a property value. /// - /// The alias of the property. /// The value of the property. /// The XPath document. /// A value indicating whether it has been possible to create the document. - public static bool TryCreateXPathDocumentFromPropertyValue(string alias, object value, out XPathDocument doc) + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument doc) { - // In addition, DynamicNode strips dashes in elements or attributes - // names but really, this is ugly enough, and using dashes should be - // illegal in content type or property aliases anyway. + // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling + // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is + // to be returned as a DynamicXml and element names such as "value-item" are + // invalid and must be converted to "valueitem". But we don't have that sort of + // problem here - and we don't need to bother with dashes nor dots, etc. doc = null; var xml = value as string; - if (xml == null) return false; - xml = xml.Trim(); - if (xml.StartsWith("<") == false || xml.EndsWith(">") == false || xml.Contains('/') == false) return false; - if (LegacyUmbracoSettings.NotDynamicXmlDocumentElements.Any(x => x.InvariantEquals(alias))) return false; - return TryCreateXPathDocument(xml, out doc); + if (xml == null) return false; // no a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml + + var nav = doc.CreateNavigator(); + if (nav.MoveToFirstChild()) + { + var name = nav.LocalName; // must not match an excluded tag + if (LegacyUmbracoSettings.NotDynamicXmlDocumentElements.All(x => x.InvariantEquals(name) == false)) return true; + } + + doc = null; + return false; + } + + /// + /// Tries to create a new XElement from a property value. + /// + /// The value of the property. + /// The Xml element. + /// A value indicating whether it has been possible to create the element. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXElementFromPropertyValue(object value, out XElement elt) + { + // see note above in TryCreateXPathDocumentFromPropertyValue... + + elt = null; + var xml = value as string; + if (xml == null) return false; // not a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + + try + { + elt = XElement.Parse(xml, LoadOptions.None); + } + catch + { + elt = null; + return false; // string can't be parsed into xml + } + + var name = elt.Name.LocalName; // must not match an excluded tag + if (LegacyUmbracoSettings.NotDynamicXmlDocumentElements.All(x => x.InvariantEquals(name) == false)) return true; + + elt = null; + return false; } /// @@ -137,8 +182,8 @@ namespace Umbraco.Core } } } - + // used by DynamicNode only, see note in TryCreateXPathDocumentFromPropertyValue public static string StripDashesInElementOrAttributeNames(string xml) { using (var outputms = new MemoryStream()) @@ -289,17 +334,10 @@ namespace Umbraco.Core /// public static bool CouldItBeXml(string xml) { - if (string.IsNullOrEmpty(xml) == false) - { - xml = xml.Trim(); + if (string.IsNullOrEmpty(xml)) return false; - if (xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains("/")) - { - return true; - } - } - - return false; + xml = xml.Trim(); + return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); } /// diff --git a/src/Umbraco.Tests/AttemptTests.cs b/src/Umbraco.Tests/AttemptTests.cs index 9887dc1ea1..5fd4da7f8e 100644 --- a/src/Umbraco.Tests/AttemptTests.cs +++ b/src/Umbraco.Tests/AttemptTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using Umbraco.Core; @@ -7,26 +7,35 @@ namespace Umbraco.Tests [TestFixture] public class AttemptTests { - [Test] public void Chained_Attempts() { - Attempt.Try(new Attempt(true, "success!"), - s => Assert.AreEqual("success!", s), - exception => Assert.Fail("It was successful")) - .IfFailed(() => new Attempt(true, 123), - i => Assert.Fail("The previous attempt was successful!"), - exception => Assert.Fail("The previous attempt was successful!")) - .IfSuccessful(() => new Attempt(new Exception("Failed!")), - d => Assert.Fail("An exception was thrown"), - exception => Assert.AreEqual("Failed!", exception.Message)) - .IfSuccessful(() => new Attempt(true, 987), - i => Assert.Fail("The previous attempt failed!"), - exception => Assert.Fail("The previous attempt failed!")) - .IfFailed(() => new Attempt(true, "finished"), - i => Assert.AreEqual("finished", i), - exception => Assert.Fail("It was successful")); - } + Attempt.Try(Attempt.Succeed("success!"), + s => Assert.AreEqual("success!", s), + exception => Assert.Fail("Should have been successful.")) + // previous one was a success so that one SHOULD NOT run + // and report success + .OnFailure(() => Attempt.Succeed(123), + i => Assert.Fail("The previous attempt was successful!"), + exception => Assert.Fail("The previous attempt was successful!")) + + // previous one did not run, last run was a success so that one SHOULD run + // and report failure + .OnSuccess(() => Attempt.Fail(new Exception("Failed!")), + d => Assert.Fail("Should have failed."), + exception => Assert.AreEqual("Failed!", exception.Message)) + + // previous one did run and was a failure so that one SHOULD NOT run + .OnSuccess(() => Attempt.Succeed(987), + i => Assert.Fail("The previous attempt failed!"), + exception => Assert.Fail("The previous attempt failed!")) + + // previous one did not run, last run was a failure so that one SHOULD run + // then why does it run? + .OnFailure(() => Attempt.Succeed("finished"), + i => Assert.AreEqual("finished", i), + exception => Assert.Fail("Should have been successful.")); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/ObjectExtensionsTests.cs b/src/Umbraco.Tests/ObjectExtensionsTests.cs index 788a28c4f4..6829e61a4f 100644 --- a/src/Umbraco.Tests/ObjectExtensionsTests.cs +++ b/src/Umbraco.Tests/ObjectExtensionsTests.cs @@ -115,7 +115,16 @@ namespace Umbraco.Tests } } - /// + [Test] + public virtual void CanConvertObjectToString_Using_ToString_Overload() + { + var result = new MyTestObject().TryConvertTo(); + + Assert.IsTrue(result.Success); + Assert.AreEqual("Hello world", result.Result); + } + + /// /// Run once before each test in derived test fixtures. /// public override void TestSetup() @@ -130,5 +139,13 @@ namespace Umbraco.Tests { return; } + + private class MyTestObject + { + public override string ToString() + { + return "Hello world"; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs new file mode 100644 index 0000000000..57d1ee3492 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Subpage which acts as the strongly typed model for a Doc Type + /// with alias "Subpage" and "Subpage" as the allowed child type. + /// + /// Similar to the Textpage this model could also be generated, but it could also + /// act as a Code-First model by using the attributes shown on the various properties, + /// which decorate the model with information about the Document Type, its + /// Property Groups and Property Types. + /// + public class Subpage : TypedModelBase + { + public Subpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs new file mode 100644 index 0000000000..fff1ae9107 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Textpage which acts as the strongly typed model for a Doc Type + /// with alias "Textpage" and "Subpage" as the allowed child type. + /// + /// The basic properties are resolved by convention using the Resolve-Type-PropertyTypeAlias + /// convention available through the base class' protected Resolve-method and Property-delegate. + /// + /// The Textpage allows the use of Subpage and Textpage as child doc types, which are exposed as a + /// collection using the Children-Type-ContentTypeAlias convention available through the + /// base class' protected Children-method and ContentTypeAlias-delegate. + /// + /// + /// This code can easily be generated using simple conventions for the types and names + /// of the properties that this type of strongly typed model exposes. + /// + public class Textpage : TypedModelBase + { + public Textpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public string AuthorName { get { return Resolve(Property()); } } + + public DateTime Date { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Textpages + { + get + { + return Children(ContentTypeAlias()); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs deleted file mode 100644 index 47f2f817f1..0000000000 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Umbraco.Core.Models; - -namespace Umbraco.Tests.PublishedContent.StronglyTypedModels -{ - public class TextpageModel : TypedModelBase - { - public TextpageModel(IPublishedContent publishedContent) : base(publishedContent) - { - } - - public string Title { get { return Resolve(Property(), DefaultString); } } - - public string BodyText { get { return Resolve(Property(), DefaultString); } } - - public string AuthorName { get { return Resolve(Property()); } } - - public DateTime Date { get { return Resolve(Property(), DefaultDateTime); } } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index 4cc6e2ebf2..d606be3c5b 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -1,12 +1,34 @@ using System; +using System.Collections.Generic; +using System.Data.Entity.Design.PluralizationServices; +using System.Globalization; using System.Reflection; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { - public abstract class TypedModelBase + /// + /// Represents the abstract base class for a 'TypedModel', which basically wraps IPublishedContent + /// underneath a strongly typed model like "Textpage" and "Subpage". + /// Because IPublishedContent is used under the hood there is no need for additional mapping, so the + /// only added cost should be the creation of the objects, which the IPublishedContent instance is + /// passed into. + /// + /// This base class exposes a simple way to write property getters by convention without + /// using the string alias of a PropertyType (this is resolved by the use of the Property delegate). + /// + /// This base class also exposes query options like Parent, Children, Ancestors and Descendants, + /// which can be used for collections of strongly typed child models/objects. These types of collections + /// typically corresponds to 'allowed child content types' on a Doc Type (at different levels). + /// + /// The IPublishedContent properties are also exposed through this base class, but only + /// by casting the typed model to IPublishedContent, so the properties doesn't show up by default: + /// ie. ((IPublishedContent)textpage).Url + /// + public abstract class TypedModelBase : IPublishedContent { private readonly IPublishedContent _publishedContent; @@ -15,56 +37,168 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels _publishedContent = publishedContent; } - public readonly Func Property = MethodBase.GetCurrentMethod; - public static string DefaultString = default(string); - public static int DefaultInteger = default(int); - public static bool DefaultBool = default(bool); - public static DateTime DefaultDateTime = default(DateTime); + protected readonly Func Property = MethodBase.GetCurrentMethod; + protected readonly Func ContentTypeAlias = MethodBase.GetCurrentMethod; - public T Resolve(MethodBase methodBase) + #region Properties + + protected T Resolve(MethodBase methodBase) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias); + } + + protected T Resolve(string propertyTypeAlias) + { return _publishedContent.GetPropertyValue(propertyTypeAlias); } - public T Resolve(MethodBase methodBase, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); } - public T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, recursive, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, bool recursive, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); } + #endregion - public string ResolveString(MethodBase methodBase) + #region Querying + protected T Parent() where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + return (T) constructorInfo.Invoke(new object[] {_publishedContent.Parent}); } - public int ResolveInt(MethodBase methodBase) + protected IEnumerable Children(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Children(docTypeAlias); } - public bool ResolveBool(MethodBase methodBase) + protected IEnumerable Children(string docTypeAlias) where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if(constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } - public DateTime ResolveDate(MethodBase methodBase) + protected IEnumerable Ancestors(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Ancestors(docTypeAlias); } + + protected IEnumerable Ancestors(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + + protected IEnumerable Descendants(MethodBase methodBase) where T : TypedModelBase + { + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Descendants(docTypeAlias); + } + + protected IEnumerable Descendants(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + #endregion + + #region IPublishedContent + int IPublishedContent.Id { get { return _publishedContent.Id; } } + int IPublishedContent.TemplateId { get { return _publishedContent.TemplateId; } } + int IPublishedContent.SortOrder { get { return _publishedContent.SortOrder; } } + string IPublishedContent.Name { get { return _publishedContent.Name; } } + string IPublishedContent.UrlName { get { return _publishedContent.UrlName; } } + string IPublishedContent.DocumentTypeAlias { get { return _publishedContent.DocumentTypeAlias; } } + int IPublishedContent.DocumentTypeId { get { return _publishedContent.DocumentTypeId; } } + string IPublishedContent.WriterName { get { return _publishedContent.WriterName; } } + string IPublishedContent.CreatorName { get { return _publishedContent.CreatorName; } } + int IPublishedContent.WriterId { get { return _publishedContent.WriterId; } } + int IPublishedContent.CreatorId { get { return _publishedContent.CreatorId; } } + string IPublishedContent.Path { get { return _publishedContent.Path; } } + DateTime IPublishedContent.CreateDate { get { return _publishedContent.CreateDate; } } + DateTime IPublishedContent.UpdateDate { get { return _publishedContent.UpdateDate; } } + Guid IPublishedContent.Version { get { return _publishedContent.Version; } } + int IPublishedContent.Level { get { return _publishedContent.Level; } } + string IPublishedContent.Url { get { return _publishedContent.Url; } } + PublishedItemType IPublishedContent.ItemType { get { return _publishedContent.ItemType; } } + IPublishedContent IPublishedContent.Parent { get { return _publishedContent.Parent; } } + IEnumerable IPublishedContent.Children { get { return _publishedContent.Children; } } + + ICollection IPublishedContent.Properties { get { return _publishedContent.Properties; } } + + object IPublishedContent.this[string propertyAlias] { get { return _publishedContent[propertyAlias]; } } + + IPublishedContentProperty IPublishedContent.GetProperty(string alias) + { + return _publishedContent.GetProperty(alias); + } + #endregion } + /// + /// Extension methods for MethodBase, which are used to clean the name of the calling method "get_BodyText" + /// to "BodyText" and then make it camel case according to the UmbracoAlias convention "bodyText". + /// There is also a string extension for making plural words singular, which is used when going from + /// something like "Subpages" to "Subpage" for Children/Ancestors/Descendants Doc Type aliases. + /// public static class TypeExtensions { + public static string CleanCallingMethodName(this MethodBase methodBase) + { + return methodBase.Name.Replace("get_", ""); + } + public static string ToUmbracoAlias(this MethodBase methodBase) { - return methodBase.Name.Replace("get_", "").ToUmbracoAlias(); + return methodBase.CleanCallingMethodName().ToUmbracoAlias(); + } + + public static string ToSingular(this string pluralWord) + { + var service = PluralizationService.CreateService(new CultureInfo("en-US")); + if (service.IsPlural(pluralWord)) + return service.Singularize(pluralWord); + + return pluralWord; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs index 6e27886b83..ae0a3c062e 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -3,6 +3,12 @@ using Umbraco.Web.Mvc; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { + /// + /// Represents a basic extension of the UmbracoTemplatePage, which allows you to specify + /// the type of a strongly typed model that inherits from TypedModelBase. + /// The model is exposed as TypedModel. + /// + /// Type of the model to create/expose public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase { protected override void InitializePage() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 92720fe76c..0e91521f59 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -90,6 +90,7 @@ + True ..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll @@ -253,7 +254,8 @@ - + + diff --git a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs index e454a99f17..507b9fbb6e 100644 --- a/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs +++ b/src/Umbraco.Web/Macros/PartialViewMacroEngine.cs @@ -104,12 +104,6 @@ namespace Umbraco.Web.Macros if (currentPage == null) throw new ArgumentNullException("currentPage"); if (macro.ScriptName.IsNullOrWhiteSpace()) throw new ArgumentException("The ScriptName property of the macro object cannot be null or empty"); - if (!macro.ScriptName.StartsWith(SystemDirectories.MvcViews + "/MacroPartials/") - && (!Regex.IsMatch(macro.ScriptName, "~/App_Plugins/.+?/Views/MacroPartials", RegexOptions.Compiled))) - { - throw new InvalidOperationException("Cannot render the Partial View Macro with file: " + macro.ScriptName + ". All Partial View Macros must exist in the " + SystemDirectories.MvcViews + "/MacroPartials/ folder"); - } - var http = _getHttpContext(); var umbCtx = _getUmbracoContext(); var routeVals = new RouteData(); diff --git a/src/Umbraco.Web/Models/DynamicPublishedContent.cs b/src/Umbraco.Web/Models/DynamicPublishedContent.cs index 07a8f42c96..24896b356a 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContent.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContent.cs @@ -131,7 +131,7 @@ namespace Umbraco.Web.Models //this is the result of an extension method execution gone wrong so we return dynamic null if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Error != null && attempt.Error is TargetInvocationException) + && attempt.Exception != null && attempt.Exception is TargetInvocationException) { result = new DynamicNull(); return true; @@ -150,7 +150,7 @@ namespace Umbraco.Web.Models { if (binder.Name.InvariantEquals("ChildrenAsList") || binder.Name.InvariantEquals("Children")) { - return new Attempt(true, Children); + return Attempt.Succeed(Children); } if (binder.Name.InvariantEquals("parentId")) @@ -160,9 +160,9 @@ namespace Umbraco.Web.Models { throw new InvalidOperationException(string.Format("The node {0} does not have a parent", Id)); } - return new Attempt(true, parent.Id); + return Attempt.Succeed(parent.Id); } - return Attempt.False; + return Attempt.Fail(); } /// @@ -182,10 +182,10 @@ namespace Umbraco.Web.Models .ToArray(); if (filteredTypeChildren.Any()) { - return new Attempt(true, + return Attempt.Succeed( new DynamicPublishedContentList(filteredTypeChildren.Select(x => new DynamicPublishedContent(x)))); } - return Attempt.False; + return Attempt.Fail(); } /// @@ -201,9 +201,7 @@ namespace Umbraco.Web.Models ? reflectedProperty.Value : null; - return result == null - ? Attempt.False - : new Attempt(true, result); + return Attempt.If(result != null, result); } /// @@ -225,7 +223,7 @@ namespace Umbraco.Web.Models if (userProperty == null) { - return Attempt.False; + return Attempt.Fail(); } var result = userProperty.Value; @@ -249,7 +247,7 @@ namespace Umbraco.Web.Models result = converted.Result; } - return new Attempt(true, result); + return Attempt.Succeed(result); } @@ -384,7 +382,7 @@ namespace Umbraco.Web.Models { try { - return new Attempt(true, + return Attempt.Succeed( content.GetType().InvokeMember(memberAlias, System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance | @@ -395,7 +393,7 @@ namespace Umbraco.Web.Models } catch (MissingMethodException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } }; diff --git a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs index 3b323b004a..d937a31bbe 100644 --- a/src/Umbraco.Web/Models/DynamicPublishedContentList.cs +++ b/src/Umbraco.Web/Models/DynamicPublishedContentList.cs @@ -273,7 +273,7 @@ namespace Umbraco.Web.Models //this is the result of an extension method execution gone wrong so we return dynamic null if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Error != null && attempt.Error is TargetInvocationException) + && attempt.Exception != null && attempt.Exception is TargetInvocationException) { result = new DynamicNull(); return true; diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 1ebfebf1e4..56c4059f95 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -305,8 +305,9 @@ namespace Umbraco.Web.Mvc () => controllerType.FullName, () => typeof(IRenderMvcController).FullName, () => typeof(ControllerBase).FullName); - //exit as we cannnot route to the custom controller, just route to the standard one. - return def; + + //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults + // that have already been set above. } } diff --git a/src/Umbraco.Web/Mvc/SurfaceController.cs b/src/Umbraco.Web/Mvc/SurfaceController.cs index 23caa16619..4aa2052519 100644 --- a/src/Umbraco.Web/Mvc/SurfaceController.cs +++ b/src/Umbraco.Web/Mvc/SurfaceController.cs @@ -81,7 +81,7 @@ namespace Umbraco.Web.Mvc var routeDefAttempt = TryGetRouteDefinitionFromAncestorViewContexts(); if (!routeDefAttempt.Success) { - throw routeDefAttempt.Error; + throw routeDefAttempt.Exception; } var routeDef = routeDefAttempt.Result; @@ -105,7 +105,7 @@ namespace Umbraco.Web.Mvc var currentRouteData = currentContext.RouteData; if (currentRouteData.DataTokens.ContainsKey("umbraco-route-def")) { - return new Attempt(true, (RouteDefinition) currentRouteData.DataTokens["umbraco-route-def"]); + return Attempt.Succeed((RouteDefinition) currentRouteData.DataTokens["umbraco-route-def"]); } if (currentContext.IsChildAction) { @@ -118,7 +118,7 @@ namespace Umbraco.Web.Mvc currentContext = null; } } - return new Attempt( + return Attempt.Fail( new InvalidOperationException("Cannot find the Umbraco route definition in the route values, the request must be made in the context of an Umbraco request")); } diff --git a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs index 6f435c3d19..ee59ea6e94 100644 --- a/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/RteMacroRenderingPropertyEditorValueConverter.cs @@ -35,7 +35,7 @@ namespace Umbraco.Web.PropertyEditors //needs to be explicitly casted to Dictionary macroAttributes.ConvertTo(x => (string)x, x => (object)x)).ToString())); - return new Attempt(true, new HtmlString(sb.ToString())); + return Attempt.Succeed(new HtmlString(sb.ToString())); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs index 8d1d88be2e..1775d37fe0 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/RoutesCache.cs @@ -45,11 +45,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // document - whenever a document is updated in, or removed from, the XML cache // we must clear the cache - at the moment, we clear the entire cache - // TODO could we do partial updates instead of clearing the whole cache? global::umbraco.content.AfterUpdateDocumentCache += (sender, e) => Clear(); global::umbraco.content.AfterClearDocumentCache += (sender, e) => Clear(); - // FIXME + // fixme - refactor when content events are refactored // the content class needs to be refactored - at the moment // content.XmlContentInternal setter does not trigger any event // content.UpdateDocumentCache(List Documents) does not trigger any event diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 0744ff6fe8..b15db0a83e 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -110,13 +110,6 @@ namespace Umbraco.Web.Routing : UrlProviderMode.Auto; } - if (mode == UrlProviderMode.AutoLegacy) - { - mode = Core.Configuration.LegacyUmbracoSettings.UseDomainPrefixes - ? UrlProviderMode.Absolute - : UrlProviderMode.Auto; - } - if (domainUri == null) // no domain was found { if (current == null) diff --git a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs b/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs index b6373d410b..9baec728f1 100644 --- a/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs +++ b/src/Umbraco.Web/Routing/LegacyRequestInitializer.cs @@ -27,7 +27,7 @@ namespace Umbraco.Web.Routing // legacy - umbOriginalUrl used by presentation/umbraco/urlRewriter/UrlRewriterFormWriter which handles
internal class StandaloneBootManager : CoreBootManager { - // fixme - could we inherit from WebBootManager? - // fixme - highly experimental, probably not complete! + // TODO + // this is highly experimental and probably not complete - not for production usage! + // also, could we inherit from WebBootManager? private readonly IEnumerable _handlersToAdd; private readonly IEnumerable _handlersToRemove; @@ -36,7 +37,7 @@ namespace Umbraco.Web.Standalone // the DataTypesResolver otherwise they won't be loaded into the AppDomain. var interfacesAssemblyName = typeof(IDataType).Assembly.FullName; - // fixme - there's also that one... but we don't use it in standalone? + // TODO there's also that one... but we don't use it in standalone? //using umbraco.editorControls; //var editorControlsAssemblyName = typeof(uploadField).Assembly.FullName; } @@ -71,7 +72,7 @@ namespace Umbraco.Web.Standalone typeof (ContentFinderByNotFoundHandlers) ); - // fixme - what else? + // TODO what else? } // can't create context before resolution is frozen! diff --git a/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs b/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs index 55339e97b6..ac34e405e5 100644 --- a/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs +++ b/src/Umbraco.Web/Standalone/StandaloneHttpContext.cs @@ -22,7 +22,6 @@ namespace Umbraco.Web.Standalone public StandaloneHttpContext() { - //create a custom response with a custom writer. _response = new HttpResponseWrapper(new HttpResponse(_writer)); } @@ -35,7 +34,7 @@ namespace Umbraco.Web.Standalone } - // fixme - what shall we implement here? + // what else should we implement here? public override IDictionary Items { diff --git a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs index 8d165b9374..a85ead4746 100644 --- a/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs +++ b/src/Umbraco.Web/Trees/LegacyTreeDataConverter.cs @@ -153,10 +153,10 @@ namespace Umbraco.Web.Trees //First try to get a URL/title from the legacy action, // if that doesn't work, try to get the legacy confirm view - Attempt + Attempt .Try(GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection), action => menuItem.LaunchDialogUrl(action.Url, action.DialogTitle)) - .IfFailed(() => GetLegacyConfirmView(currentAction, currentSection), + .OnFailure(() => GetLegacyConfirmView(currentAction, currentSection), view => menuItem.LaunchDialogView( view, ui.GetText("defaultdialogs", "confirmdelete") + " '" + xmlTreeNode.Text + "' ?")); diff --git a/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs b/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs index 21700bf883..88b62463ca 100644 --- a/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs +++ b/src/Umbraco.Web/Trees/Menu/MenuItemExtensions.cs @@ -65,10 +65,10 @@ namespace Umbraco.Web.Trees.Menu { //First try to get a URL/title from the legacy action, // if that doesn't work, try to get the legacy confirm view - Attempt + Attempt .Try(LegacyTreeDataConverter.GetUrlAndTitleFromLegacyAction(menuItem.Action, item.Id.ToInvariantString(), nodeType, item.Name, currentSection), action => menuItem.LaunchDialogUrl(action.Url, action.DialogTitle)) - .IfFailed(() => LegacyTreeDataConverter.GetLegacyConfirmView(menuItem.Action, currentSection), + .OnFailure(() => LegacyTreeDataConverter.GetLegacyConfirmView(menuItem.Action, currentSection), view => menuItem.LaunchDialogView( view, ui.GetText("defaultdialogs", "confirmdelete") + " '" + item.Name + "' ?")); diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index a9d15981db..067860ad6b 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -47,6 +47,7 @@ namespace Umbraco.Web //see: http://issues.umbraco.org/issue/U4-2059 if (ApplicationContext.Current.OriginalRequestUrl.IsNullOrWhiteSpace()) { + // the keepalive service will use that url ApplicationContext.Current.OriginalRequestUrl = string.Format("{0}:{1}{2}", httpContext.Request.ServerVariables["SERVER_NAME"], httpContext.Request.ServerVariables["SERVER_PORT"], IOHelper.ResolveUrl(SystemDirectories.Umbraco)); } @@ -253,7 +254,7 @@ namespace Umbraco.Web reason = EnsureRoutableOutcome.NoContent; } - return new Attempt(reason == EnsureRoutableOutcome.IsRoutable, reason); + return Attempt.If(reason == EnsureRoutableOutcome.IsRoutable, reason); } /// diff --git a/src/Umbraco.Web/UriUtility.cs b/src/Umbraco.Web/UriUtility.cs index ee58adf327..24f2798b58 100644 --- a/src/Umbraco.Web/UriUtility.cs +++ b/src/Umbraco.Web/UriUtility.cs @@ -39,7 +39,6 @@ namespace Umbraco.Web // adds the virtual directory if any // see also VirtualPathUtility.ToAbsolute - // FIXME public static string ToAbsolute(string url) { //return ResolveUrl(url); diff --git a/src/Umbraco.Web/WebApi/UmbracoApiController.cs b/src/Umbraco.Web/WebApi/UmbracoApiController.cs index 457b106d45..189acc24f3 100644 --- a/src/Umbraco.Web/WebApi/UmbracoApiController.cs +++ b/src/Umbraco.Web/WebApi/UmbracoApiController.cs @@ -41,15 +41,15 @@ namespace Umbraco.Web.WebApi var httpContext = context as HttpContextBase; if (httpContext != null) { - return new Attempt(true, httpContext); + return Attempt.Succeed(httpContext); } } if (HttpContext.Current != null) { - return new Attempt(true, new HttpContextWrapper(HttpContext.Current)); + return Attempt.Succeed(new HttpContextWrapper(HttpContext.Current)); } - return Attempt.False; + return Attempt.Fail(); } /// diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index 55255f1a52..4abedc7394 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -17,6 +17,7 @@ using System.Xml.XPath; using System.Xml.Xsl; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Events; using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Macros; @@ -222,8 +223,32 @@ namespace umbraco return renderMacro(pageElements, pageId); } + /// + /// An event that is raised just before the macro is rendered allowing developers to modify the macro before it executes. + /// + public static event TypedEventHandler MacroRendering; + + /// + /// Raises the MacroRendering event + /// + /// + protected void OnMacroRendering(MacroRenderingEventArgs e) + { + if (MacroRendering != null) + MacroRendering(this, e); + } + + /// + /// Renders the macro + /// + /// + /// + /// public Control renderMacro(Hashtable pageElements, int pageId) { + // Event to allow manipulation of Macro Model + OnMacroRendering(new MacroRenderingEventArgs(pageElements, pageId)); + var macroInfo = (Model.MacroType == MacroTypes.Script && Model.Name.IsNullOrWhiteSpace()) ? string.Format("Render Inline Macro, Cache: {0})", Model.CacheDuration) : string.Format("Render Macro: {0}, type: {1}, cache: {2})", Name, Model.MacroType, Model.CacheDuration); @@ -1798,4 +1823,20 @@ namespace umbraco #endregion } + + /// + /// Event arguments used for the MacroRendering event + /// + public class MacroRenderingEventArgs : EventArgs + { + public Hashtable PageElements { get; private set; } + public int PageId { get; private set; } + + public MacroRenderingEventArgs(Hashtable pageElements, int pageId) + { + PageElements = pageElements; + PageId = pageId; + } + } + } \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Search/QuickSearchHandler.ashx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Search/QuickSearchHandler.ashx.cs index 2950f072bc..7d939e5809 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Search/QuickSearchHandler.ashx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/Search/QuickSearchHandler.ashx.cs @@ -60,7 +60,11 @@ namespace umbraco.presentation.umbraco.Search } else { - var operation = criteria.Field("__nodeName", txt.MultipleCharacterWildcard()); + var words = txt.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(w => w.ToLower().MultipleCharacterWildcard()).ToList(); + var operation = criteria.GroupedOr(new[] { "__nodeName" }, new[] { words[0] }); + words.RemoveAt(0); + foreach (var word in words) + operation = operation.And().GroupedOr(new[] { "__nodeName" }, new[] { word }); // ensure the user can only find nodes they are allowed to see if (UmbracoContext.Current.UmbracoUser.StartNodeId > 0) diff --git a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs index d804f97899..a2ac541247 100644 --- a/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs +++ b/src/umbraco.MacroEngines/RazorDynamicNode/DynamicNode.cs @@ -649,7 +649,7 @@ namespace umbraco.MacroEngines { try { - return new Attempt(true, + return Attempt.Succeed( n.GetType().InvokeMember(memberAlias, System.Reflection.BindingFlags.GetProperty | System.Reflection.BindingFlags.Instance | @@ -660,7 +660,7 @@ namespace umbraco.MacroEngines } catch (MissingMethodException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } }; diff --git a/src/umbraco.cms/businesslogic/web/Document.cs b/src/umbraco.cms/businesslogic/web/Document.cs index 66e6db5080..072d830e27 100644 --- a/src/umbraco.cms/businesslogic/web/Document.cs +++ b/src/umbraco.cms/businesslogic/web/Document.cs @@ -900,10 +900,7 @@ namespace umbraco.cms.businesslogic.web [Obsolete("Don't use! Only used internally to support the legacy events", false)] internal Attempt SaveAndPublish(int userId) { - var result = new Attempt(false, - new PublishStatus(Content, - PublishStatusType - .FailedCancelledByEvent)); + var result = Attempt.Fail(new PublishStatus(Content, PublishStatusType.FailedCancelledByEvent)); foreach (var property in GenericProperties) { Content.SetValue(property.PropertyType.Alias, property.Value); @@ -1028,10 +1025,10 @@ namespace umbraco.cms.businesslogic.web return result; } - return Attempt.False; + return Attempt.Fail(); } - return Attempt.False; + return Attempt.Fail(); } ///