Merge dev-v7.6 into dev-v7-deploy

This commit is contained in:
Stephan
2016-11-15 13:59:19 +01:00
57 changed files with 2216 additions and 1396 deletions

View File

@@ -25,9 +25,6 @@
public const int DefaultContentListViewDataTypeId = -95;
public const int DefaultMediaListViewDataTypeId = -96;
public const int DefaultMembersListViewDataTypeId = -97;
// identifiers for lock objects
public const int ServersLock = -331;
}
public static class DatabaseProviders

View File

@@ -0,0 +1,29 @@
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;
namespace Umbraco.Core.Models.Rdbms
{
[TableName("umbracoLock")]
[PrimaryKey("id")]
[ExplicitColumns]
internal class LockDto
{
public LockDto()
{
Value = 1;
}
[Column("id")]
[PrimaryKeyColumn(Name = "PK_umbracoLock")]
public int Id { get; set; }
[Column("value")]
[NullSetting(NullSetting = NullSettings.NotNull)]
public int Value { get; set; }
[Column("name")]
[NullSetting(NullSetting = NullSettings.NotNull)]
[Length(64)]
public string Name { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
// ReSharper disable once CheckNamespace
namespace Umbraco.Core
{
static partial class Constants
{
public static class Locks
{
public const int Servers = -331;
}
}
}

View File

@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence
{
ValidateDatabase(database);
database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id",
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
new { @id = nodeId });
}
@@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence
{
ValidateDatabase(database);
database.ExecuteScalar<int>("SELECT sortOrder FROM umbracoNode WHERE id=@id",
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
new { @id = nodeId });
}
}

View File

@@ -105,7 +105,7 @@ namespace Umbraco.Core.Persistence
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
{
LogHelper.Debug<DefaultDatabaseFactory>("Create http [T" + Environment.CurrentManagedThreadId + "]");
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory),
HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory),
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
: new UmbracoDatabase(_connectionStringName, _logger));
@@ -124,7 +124,7 @@ namespace Umbraco.Core.Persistence
}
else
{
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
if (db != null) db.Dispose();
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
}
@@ -135,6 +135,8 @@ namespace Umbraco.Core.Persistence
ReleaseDatabase();
}
// during tests, the thread static var can leak between tests
// this method provides a way to force-reset the variable
internal void ResetForTests()
{
var value = NonContextValue;
@@ -164,7 +166,7 @@ namespace Umbraco.Core.Persistence
}
else
{
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
return db;
}

View File

@@ -8,6 +8,16 @@ namespace Umbraco.Core.Persistence.Mappers
{
public abstract class BaseMapper
{
private readonly ISqlSyntaxProvider _sqlSyntax;
protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider)
{
}
protected BaseMapper(ISqlSyntaxProvider sqlSyntax)
{
_sqlSyntax = sqlSyntax;
}
internal abstract ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache { get; }
@@ -58,8 +68,8 @@ namespace Umbraco.Core.Persistence.Mappers
string columnName = columnAttribute.Name;
string columnMap = string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(columnName));
_sqlSyntax.GetQuotedTableName(tableName),
_sqlSyntax.GetQuotedColumnName(columnName));
return columnMap;
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.Linq.Expressions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Mappers
{
@@ -16,6 +17,11 @@ namespace Umbraco.Core.Persistence.Mappers
{
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
{
}
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
// otherwise that would fail because there is no public constructor.
public ContentMapper()

View File

@@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Mappers
{
return byAttribute.Result;
}
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type");
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type " + type);
});
}

View File

@@ -33,7 +33,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
CreateUmbracNodeData();
}
if(tableName.Equals("cmsContentType"))
if (tableName.Equals("umbracoLock"))
{
CreateUmbracoLockData();
}
if (tableName.Equals("cmsContentType"))
{
CreateCmsContentTypeData();
}
@@ -141,9 +146,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
}
private void CreateUmbracoLockData()
{
// all lock objects
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.ServersLock, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1," + Constants.System.ServersLock, SortOrder = 1, UniqueId = new Guid("0AF5E610-A310-4B6F-925F-E928D5416AF7"), Text = "LOCK: Servers", NodeObjectType = Constants.ObjectTypes.LockObjectGuid, CreateDate = DateTime.Now });
_database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" });
}
private void CreateCmsContentTypeData()

View File

@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
{45, typeof (MigrationDto)},
{46, typeof (UmbracoDeployChecksumDto)},
{47, typeof (UmbracoDeployDependencyDto)},
{48, typeof (RedirectUrlDto) }
{48, typeof (RedirectUrlDto) },
{49, typeof (LockDto) }
};
#endregion

View File

@@ -0,0 +1,39 @@
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
{
[Migration("7.5.5", 101, GlobalSettings.UmbracoMigrationName)]
public class AddLockObjects : MigrationBase
{
public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
EnsureLockObject(Constants.Locks.Servers, "Servers");
}
public override void Down()
{
// not implemented
}
private void EnsureLockObject(int id, string name)
{
Execute.Code(db =>
{
var exists = db.Exists<LockDto>(id);
if (exists) return string.Empty;
// be safe: delete old umbracoNode lock objects if any
db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id });
// then create umbracoLock object
db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name });
return string.Empty;
});
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
{
[Migration("7.5.5", 100, GlobalSettings.UmbracoMigrationName)]
public class AddLockTable : MigrationBase
{
public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
: base(sqlSyntax, logger)
{ }
public override void Up()
{
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
if (tables.InvariantContains("umbracoLock") == false)
{
Create.Table("umbracoLock")
.WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock")
.WithColumn("value").AsInt32().NotNullable()
.WithColumn("name").AsString(64).NotNullable();
}
}
public override void Down()
{
// not implemented
}
}
}

View File

@@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer
// for some reason it was not, so it was created during migrations but not during
// new installs, so for ppl that upgrade, make sure they have it
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
}
public override void Down()

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0);
}
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
}
public override void Down()

View File

@@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence
public static Sql Where<T>(this Sql sql, Expression<Func<T, bool>> predicate)
{
var expresionist = new PocoToSqlExpressionHelper<T>();
var expresionist = new PocoToSqlExpressionVisitor<T>();
var whereExpression = expresionist.Visit(predicate);
return sql.Where(whereExpression, expresionist.GetSqlParameters());
}

View File

@@ -2,7 +2,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
@@ -10,22 +9,117 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal abstract class BaseExpressionHelper<T> : BaseExpressionHelper
/// <summary>
/// Represents an expression which caches the visitor's result.
/// </summary>
internal class CachedExpression : Expression
{
protected abstract string VisitMemberAccess(MemberExpression m);
private string _visitResult;
protected internal virtual string Visit(Expression exp)
/// <summary>
/// Gets or sets the inner Expression.
/// </summary>
public Expression InnerExpression { get; private set; }
/// <summary>
/// Gets or sets the compiled SQL statement output.
/// </summary>
public string VisitResult
{
get { return _visitResult; }
set
{
if (Visited)
throw new InvalidOperationException("Cached expression has already been visited.");
_visitResult = value;
Visited = true;
}
}
if (exp == null) return string.Empty;
switch (exp.NodeType)
/// <summary>
/// Gets or sets a value indicating whether the cache Expression has been compiled already.
/// </summary>
public bool Visited { get; private set; }
/// <summary>
/// Replaces the inner expression.
/// </summary>
/// <param name="expression">expression.</param>
/// <remarks>The new expression is assumed to have different parameter but produce the same SQL statement.</remarks>
public void Wrap(Expression expression)
{
InnerExpression = expression;
}
}
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression.
/// </summary>
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal abstract class ExpressionVisitorBase
{
protected ExpressionVisitorBase(ISqlSyntaxProvider sqlSyntax)
{
SqlSyntax = sqlSyntax;
}
/// <summary>
/// Gets or sets a value indicating whether the visited expression has been visited already,
/// in which case visiting will just populate the SQL parameters.
/// </summary>
protected bool Visited { get; set; }
/// <summary>
/// Gets or sets the SQL syntax provider for the current database.
/// </summary>
protected ISqlSyntaxProvider SqlSyntax { get; private set; }
/// <summary>
/// Gets the list of SQL parameters.
/// </summary>
protected readonly List<object> SqlParameters = new List<object>();
/// <summary>
/// Gets the SQL parameters.
/// </summary>
/// <returns></returns>
public object[] GetSqlParameters()
{
return SqlParameters.ToArray();
}
/// <summary>
/// Visits the expression and produces the corresponding SQL statement.
/// </summary>
/// <param name="expression">The expression</param>
/// <returns>The SQL statement corresponding to the expression.</returns>
/// <remarks>Also populates the SQL parameters.</remarks>
public virtual string Visit(Expression expression)
{
// if the expression is a CachedExpression,
// visit the inner expression if not already visited
var cachedExpression = expression as CachedExpression;
if (cachedExpression != null)
{
Visited = cachedExpression.Visited;
expression = cachedExpression.InnerExpression;
}
if (expression == null) return string.Empty;
string result;
switch (expression.NodeType)
{
case ExpressionType.Lambda:
return VisitLambda(exp as LambdaExpression);
result = VisitLambda(expression as LambdaExpression);
break;
case ExpressionType.MemberAccess:
return VisitMemberAccess(exp as MemberExpression);
result = VisitMemberAccess(expression as MemberExpression);
break;
case ExpressionType.Constant:
return VisitConstant(exp as ConstantExpression);
result = VisitConstant(expression as ConstantExpression);
break;
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
@@ -49,7 +143,8 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return VisitBinary(exp as BinaryExpression);
result = VisitBinary(expression as BinaryExpression);
break;
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
@@ -58,35 +153,54 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return VisitUnary(exp as UnaryExpression);
result = VisitUnary(expression as UnaryExpression);
break;
case ExpressionType.Parameter:
return VisitParameter(exp as ParameterExpression);
result = VisitParameter(expression as ParameterExpression);
break;
case ExpressionType.Call:
return VisitMethodCall(exp as MethodCallExpression);
result = VisitMethodCall(expression as MethodCallExpression);
break;
case ExpressionType.New:
return VisitNew(exp as NewExpression);
result = VisitNew(expression as NewExpression);
break;
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return VisitNewArray(exp as NewArrayExpression);
result = VisitNewArray(expression as NewArrayExpression);
break;
default:
return exp.ToString();
result = expression.ToString();
break;
}
// if the expression is a CachedExpression,
// and is not already compiled, assign the result
if (cachedExpression != null)
{
if (cachedExpression.Visited == false)
cachedExpression.VisitResult = result;
result = cachedExpression.VisitResult;
}
return result;
}
protected abstract string VisitMemberAccess(MemberExpression m);
protected virtual string VisitLambda(LambdaExpression lambda)
{
if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
var m = lambda.Body as MemberExpression;
if (m.Expression != null)
if (m != null && m.Expression != null)
{
//This deals with members that are boolean (i.e. x => IsTrashed )
string r = VisitMemberAccess(m);
SqlParameters.Add(true);
return string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
var r = VisitMemberAccess(m);
//return string.Format("{0}={1}", r, GetQuotedTrueValue());
SqlParameters.Add(true);
return Visited ? string.Empty : string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
}
@@ -95,19 +209,24 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string VisitBinary(BinaryExpression b)
{
string left, right;
var left = string.Empty;
var right = string.Empty;
var operand = BindOperant(b.NodeType);
if (operand == "AND" || operand == "OR")
{
MemberExpression m = b.Left as MemberExpression;
var m = b.Left as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
SqlParameters.Add(1);
left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//left = string.Format("{0}={1}", r, GetQuotedTrueValue());
//don't execute if compiled
if (Visited == false)
{
left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
}
else
{
@@ -116,12 +235,15 @@ namespace Umbraco.Core.Persistence.Querying
m = b.Right as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
var r = VisitMemberAccess(m);
SqlParameters.Add(1);
right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//right = string.Format("{0}={1}", r, GetQuotedTrueValue());
//don't execute if compiled
if (Visited == false)
{
right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
}
}
else
{
@@ -132,14 +254,14 @@ namespace Umbraco.Core.Persistence.Querying
{
// deal with (x == true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof (bool))
return ((bool) constRight.Value) ? VisitNotNot(b.Left) : VisitNot(b.Left);
if (constRight != null && constRight.Type == typeof(bool))
return (bool)constRight.Value ? VisitNotNot(b.Left) : VisitNot(b.Left);
right = Visit(b.Right);
// deal with (true|false == x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof (bool))
return ((bool) constLeft.Value) ? VisitNotNot(b.Right) : VisitNot(b.Right);
if (constLeft != null && constLeft.Type == typeof(bool))
return (bool)constLeft.Value ? VisitNotNot(b.Right) : VisitNot(b.Right);
left = Visit(b.Left);
}
else if (operand == "<>")
@@ -147,13 +269,13 @@ namespace Umbraco.Core.Persistence.Querying
// deal with (x != true|false) - most common
var constRight = b.Right as ConstantExpression;
if (constRight != null && constRight.Type == typeof(bool))
return ((bool) constRight.Value) ? VisitNot(b.Left) : VisitNotNot(b.Left);
return (bool)constRight.Value ? VisitNot(b.Left) : VisitNotNot(b.Left);
right = Visit(b.Right);
// deal with (true|false != x) - why not
var constLeft = b.Left as ConstantExpression;
if (constLeft != null && constLeft.Type == typeof(bool))
return ((bool) constLeft.Value) ? VisitNot(b.Right) : VisitNotNot(b.Right);
return (bool)constLeft.Value ? VisitNot(b.Right) : VisitNotNot(b.Right);
left = Visit(b.Left);
}
else
@@ -178,26 +300,38 @@ namespace Umbraco.Core.Persistence.Querying
{
case "MOD":
case "COALESCE":
//don't execute if compiled
if (Visited == false)
{
return string.Format("{0}({1},{2})", operand, left, right);
}
//already compiled, return
return string.Empty;
default:
return "(" + left + " " + operand + " " + right + ")";
//don't execute if compiled
if (Visited == false)
{
return string.Concat("(", left, " ", operand, " ", right, ")");
}
//already compiled, return
return string.Empty;
}
}
protected virtual List<Object> VisitExpressionList(ReadOnlyCollection<Expression> original)
protected virtual List<object> VisitExpressionList(ReadOnlyCollection<Expression> original)
{
var list = new List<Object>();
var list = new List<object>();
for (int i = 0, n = original.Count; i < n; i++)
{
if (original[i].NodeType == ExpressionType.NewArrayInit ||
original[i].NodeType == ExpressionType.NewArrayBounds)
{
list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
}
else
{
list.Add(Visit(original[i]));
}
}
return list;
}
@@ -210,19 +344,27 @@ namespace Umbraco.Core.Persistence.Querying
try
{
var getter = lambda.Compile();
object o = getter();
var o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o.GetType());
//don't execute if compiled
if (Visited == false)
{
return string.Format("@{0}", SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
}
catch (InvalidOperationException)
{
//don't execute if compiled
if (Visited == false)
{
// FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
List<object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
foreach (var e in exprs)
{
r.AppendFormat("{0}{1}",
r.Length > 0 ? "," : "",
@@ -230,7 +372,9 @@ namespace Umbraco.Core.Persistence.Querying
}
return r.ToString();
}
//already compiled, return
return string.Empty;
}
}
protected virtual string VisitParameter(ParameterExpression p)
@@ -244,14 +388,14 @@ namespace Umbraco.Core.Persistence.Querying
return "null";
SqlParameters.Add(c.Value);
return string.Format("@{0}", SqlParameters.Count - 1);
//if (c.Value is bool)
//{
// object o = GetQuotedValue(c.Value, c.Value.GetType());
// return string.Format("({0}={1})", GetQuotedTrueValue(), o);
//}
//return GetQuotedValue(c.Value, c.Value.GetType());
//don't execute if compiled
if (Visited == false)
{
return string.Format("@{0}", SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
}
protected virtual string VisitUnary(UnaryExpression u)
@@ -277,10 +421,22 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.MemberAccess:
// false property , i.e. x => !Trashed
SqlParameters.Add(true);
//don't execute if compiled
if (Visited == false)
{
return string.Format("NOT ({0} = @{1})", o, SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
default:
//don't execute if compiled
if (Visited == false)
{
// could be anything else, such as: x => !x.Path.StartsWith("-20")
return "NOT (" + o + ")";
return string.Concat("NOT (", o, ")");
}
//already compiled, return
return string.Empty;
}
}
@@ -293,7 +449,14 @@ namespace Umbraco.Core.Persistence.Querying
case ExpressionType.MemberAccess:
// true property, i.e. x => Trashed
SqlParameters.Add(true);
//don't execute if compiled
if (Visited == false)
{
return string.Format("({0} = @{1})", o, SqlParameters.Count - 1);
}
//already compiled, return
return string.Empty;
default:
// could be anything else, such as: x => x.Path.StartsWith("-20")
return o;
@@ -302,27 +465,31 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string VisitNewArray(NewArrayExpression na)
{
var exprs = VisitExpressionList(na.Expressions);
List<Object> exprs = VisitExpressionList(na.Expressions);
//don't execute if compiled
if (Visited == false)
{
var r = new StringBuilder();
foreach (Object e in exprs)
foreach (var e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
}
//already compiled, return
return string.Empty;
}
protected virtual List<Object> VisitNewArrayFromExpressionList(NewArrayExpression na)
protected virtual List<object> VisitNewArrayFromExpressionList(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
var exprs = VisitExpressionList(na.Expressions);
return exprs;
}
protected virtual string BindOperant(ExpressionType e)
{
switch (e)
{
case ExpressionType.Equal:
@@ -383,11 +550,23 @@ namespace Umbraco.Core.Persistence.Querying
{
case "ToString":
SqlParameters.Add(objectForMethod.ToString());
//don't execute if compiled
if (Visited == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
case "ToUpper":
//don't execute if compiled
if (Visited == false)
return string.Format("upper({0})", visitedObjectForMethod);
//already compiled, return
return string.Empty;
case "ToLower":
//don't execute if compiled
if (Visited == false)
return string.Format("lower({0})", visitedObjectForMethod);
//already compiled, return
return string.Empty;
case "SqlWildcard":
case "StartsWith":
case "EndsWith":
@@ -488,7 +667,12 @@ namespace Umbraco.Core.Persistence.Querying
SqlParameters.Add(RemoveQuote(replaceValue));
//don't execute if compiled
if (Visited == false)
return string.Format("replace({0}, @{1}, @{2})", visitedObjectForMethod, SqlParameters.Count - 2, SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
//case "Substring":
// var startIndex = Int32.Parse(args[0].ToString()) + 1;
// if (args.Count == 2)
@@ -566,69 +750,17 @@ namespace Umbraco.Core.Persistence.Querying
public virtual string GetQuotedTableName(string tableName)
{
return string.Format("\"{0}\"", tableName);
return Visited ? tableName : string.Format("\"{0}\"", tableName);
}
public virtual string GetQuotedColumnName(string columnName)
{
return string.Format("\"{0}\"", columnName);
return Visited ? columnName : string.Format("\"{0}\"", columnName);
}
public virtual string GetQuotedName(string name)
{
return string.Format("\"{0}\"", name);
}
//private string GetQuotedTrueValue()
//{
// return GetQuotedValue(true, typeof(bool));
//}
//private string GetQuotedFalseValue()
//{
// return GetQuotedValue(false, typeof(bool));
//}
//public virtual string GetQuotedValue(object value, Type fieldType)
//{
// return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue);
//}
//private string GetTrueExpression()
//{
// object o = GetQuotedTrueValue();
// return string.Format("({0}={1})", o, o);
//}
//private string GetFalseExpression()
//{
// return string.Format("({0}={1})",
// GetQuotedTrueValue(),
// GetQuotedFalseValue());
//}
//private bool IsTrueExpression(string exp)
//{
// return (exp == GetTrueExpression());
//}
//private bool IsFalseExpression(string exp)
//{
// return (exp == GetFalseExpression());
//}
}
/// <summary>
/// Logic that is shared with the expression helpers
/// </summary>
internal class BaseExpressionHelper
{
protected List<object> SqlParameters = new List<object>();
public object[] GetSqlParameters()
{
return SqlParameters.ToArray();
return Visited ? name : string.Format("\"{0}\"", name);
}
protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType)
@@ -637,25 +769,45 @@ namespace Umbraco.Core.Persistence.Querying
{
case "SqlWildcard":
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (Visited == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "Equals":
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (Visited == false)
return SqlSyntax.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "StartsWith":
SqlParameters.Add(string.Format("{0}{1}",
RemoveQuote(val),
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder()));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
SqlSyntax.GetWildcardPlaceholder()));
//don't execute if compiled
if (Visited == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "EndsWith":
SqlParameters.Add(string.Format("{0}{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
SqlSyntax.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (Visited == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "Contains":
SqlParameters.Add(string.Format("{0}{1}{0}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
SqlSyntax.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//don't execute if compiled
if (Visited == false)
return SqlSyntax.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
//already compiled, return
return string.Empty;
case "InvariantEquals":
case "SqlEquals":
//recurse
@@ -728,9 +880,7 @@ namespace Umbraco.Core.Persistence.Querying
public virtual string EscapeParam(object paramValue)
{
return paramValue == null
? string.Empty
: SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString());
return paramValue == null ? string.Empty : SqlSyntax.EscapeString(paramValue.ToString());
}
public virtual bool ShouldQuoteValue(Type fieldType)
@@ -740,13 +890,6 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string RemoveQuote(string exp)
{
//if (exp.StartsWith("'") && exp.EndsWith("'"))
//{
// exp = exp.Remove(0, 1);
// exp = exp.Remove(exp.Length - 1, 1);
//}
//return exp;
if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
&&
(exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
@@ -757,17 +900,17 @@ namespace Umbraco.Core.Persistence.Querying
return exp;
}
//protected virtual string RemoveQuoteFromAlias(string exp)
//protected virtual string RemoveQuoteFromAlias(string expression)
//{
// if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
// if ((expression.StartsWith("\"") || expression.StartsWith("`") || expression.StartsWith("'"))
// &&
// (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
// (expression.EndsWith("\"") || expression.EndsWith("`") || expression.EndsWith("'")))
// {
// exp = exp.Remove(0, 1);
// exp = exp.Remove(exp.Length - 1, 1);
// expression = expression.Remove(0, 1);
// expression = expression.Remove(expression.Length - 1, 1);
// }
// return exp;
// return expression;
//}
}
}

View File

@@ -1,31 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
/// <summary>
/// Represents a query for building Linq translatable SQL queries
/// </summary>

View File

@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private readonly BaseMapper _mapper;
public ModelToSqlExpressionHelper()
{
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
//protected bool IsFieldName(string quotedExp)
//{
// //Not entirely sure this is reliable, but its better then simply returning true
// return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length;
//}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Linq.Expressions;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
/// based on Umbraco's business logic models.
/// </summary>
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal class ModelToSqlExpressionVisitor<T> : ExpressionVisitorBase
{
private readonly BaseMapper _mapper;
public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper)
: base(sqlSyntax)
{
_mapper = mapper;
}
public ModelToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
{ }
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
//don't execute if compiled
if (Visited == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
//don't execute if compiled
if (Visited == false)
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var o = getter();
SqlParameters.Add(o);
//don't execute if compiled
if (Visited == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionHelper()
{
_pd = new Database.PocoData(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
return string.Format("{0}.{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
}
//protected bool IsFieldName(string quotedExp)
//{
// return true;
//}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Linq;
using System.Linq.Expressions;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
/// based on Umbraco's DTOs.
/// </summary>
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal class PocoToSqlExpressionVisitor<T> : ExpressionVisitorBase
{
private readonly Database.PocoData _pd;
public PocoToSqlExpressionVisitor()
: base(SqlSyntaxContext.SqlSyntaxProvider)
{
_pd = new Database.PocoData(typeof(T));
}
protected override string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
//don't execute if compiled
if (Visited == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
{
//don't execute if compiled
if (Visited == false)
{
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
//already compiled, return
return string.Empty;
}
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var o = getter();
SqlParameters.Add(o);
//don't execute if compiled
if (Visited == false)
return string.Format("@{0}", SqlParameters.Count - 1);
//already compiled, return
return string.Empty;
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
return string.Format("{0}.{1}",
SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntax.GetQuotedColumnName(column.Value.ColumnName));
}
}
}

View File

@@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Querying
{
if (predicate != null)
{
var expressionHelper = new ModelToSqlExpressionHelper<T>();
//TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton
var expressionHelper = new ModelToSqlExpressionVisitor<T>();
string whereExpression = expressionHelper.Visit(predicate);
_wheres.Add(new Tuple<string, object[]>(whereExpression, expressionHelper.GetSqlParameters()));

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
namespace Umbraco.Core.Persistence.Querying
{
/// <summary>
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
/// We have to wait till v8 to make this change I suppose.
/// </summary>
internal static class QueryExtensions
{
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
{
var q = query as Query<T>;
if (q == null)
{
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
}
return q.GetWhereClauses();
}
}
}

View File

@@ -104,6 +104,12 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
#region Static Queries
private readonly IQuery<IContent> _publishedQuery = Query<IContent>.Builder.Where(x => x.Published == true);
#endregion
#region Overrides of PetaPocoRepositoryBase<IContent>
@@ -205,7 +211,6 @@ namespace Umbraco.Core.Persistence.Repositories
{
try
{
// InsertOrUpdate tries to update first, which is good since it is what
// should happen in most cases, then it tries to insert, and it should work
// unless the node has been deleted, and we just report the exception
Database.InsertOrUpdate(xmlItem);

View File

@@ -83,6 +83,12 @@ namespace Umbraco.Core.Persistence.Repositories
}
#region Static Queries
private IQuery<TEntity> _hasIdQuery;
#endregion
protected virtual TId GetEntityId(TEntity entity)
{
return (TId)(object)entity.Id;
@@ -111,9 +117,14 @@ namespace Umbraco.Core.Persistence.Repositories
RuntimeCache,
new RepositoryCachePolicyOptions(() =>
{
//create it once if it is needed (no need for locking here)
if (_hasIdQuery == null)
{
_hasIdQuery = Query<TEntity>.Builder.Where(x => x.Id != 0);
}
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
return PerformCount(query);
return PerformCount(_hasIdQuery);
})));
}
}

View File

@@ -56,6 +56,12 @@ namespace Umbraco.Core.Services
_userService = userService;
}
#region Static Queries
private readonly IQuery<IContent> _notTrashedQuery = Query<IContent>.Builder.Where(x => x.Trashed == false);
#endregion
public int CountPublished(string contentTypeAlias = null)
{
var uow = UowProvider.GetUnitOfWork();
@@ -789,8 +795,7 @@ namespace Umbraco.Core.Services
{
using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork()))
{
var query = Query<IContent>.Builder.Where(x => x.Trashed == false);
return repository.GetByPublishedVersion(query);
return repository.GetByPublishedVersion(_notTrashedQuery);
}
}

View File

@@ -62,6 +62,12 @@ namespace Umbraco.Core.Services
}
#region Static Queries
private readonly IQuery<IUmbracoEntity> _rootEntityQuery = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
#endregion
/// <summary>
/// Returns the integer id for a given GUID
/// </summary>
@@ -389,8 +395,7 @@ namespace Umbraco.Core.Services
var objectTypeId = umbracoObjectType.GetGuid();
using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork()))
{
var query = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
var entities = repository.GetByQuery(query, objectTypeId);
var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId);
return entities;
}

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Services
private readonly static string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER
+ "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT
private static readonly int[] LockingRepositoryIds = { Constants.System.ServersLock };
private static readonly int[] LockingRepositoryIds = { Constants.Locks.Servers };
private ServerRole _currentServerRole = ServerRole.Unknown;
private readonly LockingRepository<IServerRegistrationRepository> _lrepo;

View File

@@ -184,7 +184,7 @@ namespace Umbraco.Core
/// <param name="input"></param>
/// <param name="ignoreFromClean"></param>
/// <returns></returns>
internal static string CleanForXss(this string input, params char[] ignoreFromClean)
public static string CleanForXss(this string input, params char[] ignoreFromClean)
{
//remove any html
input = input.StripHtml();

View File

@@ -414,6 +414,7 @@
<Compile Include="Media\Exif\ExifTag.cs" />
<Compile Include="Media\Exif\ExifTagFactory.cs" />
<Compile Include="Models\Rdbms\AccessRuleDto.cs" />
<Compile Include="Models\Rdbms\LockDto.cs" />
<Compile Include="Models\Rdbms\RedirectUrlDto.cs" />
<Compile Include="Models\Rdbms\DocumentPublishedReadOnlyDto.cs" />
<Compile Include="Models\Rdbms\ExternalLoginDto.cs" />
@@ -427,6 +428,7 @@
<Compile Include="Models\IDomain.cs" />
<Compile Include="Persistence\BulkDataReader.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Persistence\Constants-Locks.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
<Compile Include="Persistence\Factories\MigrationEntryFactory.cs" />
@@ -440,6 +442,8 @@
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
<Compile Include="Persistence\Migrations\LocalMigrationContext.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionFourOneZero\AddPreviewXmlTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\AddLockObjects.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\AddLockTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\UpdateAllowedMediaTypesAtRoot.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveZero\EnsureServersLockObject.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveZero\AddRedirectUrlTable.cs" />
@@ -486,6 +490,7 @@
<Compile Include="Media\Exif\Utility.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeOne\UpdateUserLanguagesToIsoCode.cs" />
<Compile Include="Persistence\PocoDataDataReader.cs" />
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
@@ -518,6 +523,7 @@
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueConverter.cs" />
<Compile Include="Publishing\UnPublishedStatusType.cs" />
<Compile Include="Publishing\UnPublishStatus.cs" />
<Compile Include="SafeCallContext.cs" />
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />
@@ -1026,10 +1032,10 @@
<Compile Include="Persistence\PetaPocoConnectionExtensions.cs" />
<Compile Include="Persistence\PetaPocoExtensions.cs" />
<Compile Include="Persistence\PetaPocoSqlExtensions.cs" />
<Compile Include="Persistence\Querying\BaseExpressionHelper.cs" />
<Compile Include="Persistence\Querying\PocoToSqlExpressionHelper.cs" />
<Compile Include="Persistence\Querying\ExpressionVisitorBase.cs" />
<Compile Include="Persistence\Querying\PocoToSqlExpressionVisitor.cs" />
<Compile Include="Persistence\Querying\IQuery.cs" />
<Compile Include="Persistence\Querying\ModelToSqlExpressionHelper.cs" />
<Compile Include="Persistence\Querying\ModelToSqlExpressionVisitor.cs" />
<Compile Include="Persistence\Querying\Query.cs" />
<Compile Include="Persistence\Querying\SqlTranslator.cs" />
<Compile Include="Persistence\Relators\DictionaryLanguageTextRelator.cs" />

View File

@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Threading;
using System.Xml;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.Migrations.Initial;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Tests.TestHelpers;
using ILogger = Umbraco.Core.Logging.ILogger;
namespace Umbraco.Tests.Benchmarks
{
[Config(typeof(Config))]
public class ModelToSqlExpressionHelperBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
}
}
public ModelToSqlExpressionHelperBenchmarks()
{
_contentMapper = new ContentMapper(_syntaxProvider);
_contentMapper.BuildMap();
_cachedExpression = new CachedExpression();
}
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider();
private readonly BaseMapper _contentMapper;
private readonly CachedExpression _cachedExpression;
[Benchmark(Baseline = true)]
public void WithNonCached()
{
for (int i = 0; i < 100; i++)
{
var a = i;
var b = i*10;
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
var result = modelToSqlExpressionHelper.Visit(predicate);
}
}
[Benchmark()]
public void WithCachedExpression()
{
for (int i = 0; i < 100; i++)
{
var a = i;
var b = i * 10;
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
//wrap it!
_cachedExpression.Wrap(predicate);
var result = modelToSqlExpressionHelper.Visit(_cachedExpression);
}
}
}
}

View File

@@ -8,12 +8,7 @@ namespace Umbraco.Tests.Benchmarks
{
public static void Main(string[] args)
{
if (args.Length == 0)
{
var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
Console.ReadLine();
}
else if (args.Length == 1)
if (args.Length == 1)
{
var type = Assembly.GetExecutingAssembly().GetType("Umbraco.Tests.Benchmarks." +args[0]);
if (type == null)

View File

@@ -96,6 +96,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
<Compile Include="BulkInsertBenchmarks.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@@ -0,0 +1,304 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlServerCe;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Events;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Publishing;
using Umbraco.Core.Services;
using Umbraco.Tests.Services;
using Umbraco.Tests.TestHelpers;
using Ignore = NUnit.Framework.IgnoreAttribute;
namespace Umbraco.Tests.Persistence
{
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture]
[Ignore("Takes too much time.")]
public class LocksTests : BaseDatabaseFactoryTest
{
private ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider _uowProvider;
private ThreadSafetyServiceTest.PerThreadDatabaseFactory _dbFactory;
[SetUp]
public override void Initialize()
{
base.Initialize();
//we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that
//a Database instance is created per thread, whereas the default implementation which will work in an HttpContext
//threading environment, or a single apartment threading environment will not work for this test because
//it is multi-threaded.
_dbFactory = new ThreadSafetyServiceTest.PerThreadDatabaseFactory(Logger);
//overwrite the local object
ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
//disable cache
var cacheHelper = CacheHelper.CreateDisabledCacheHelper();
//here we are going to override the ServiceContext because normally with our test cases we use a
//global Database object but this is NOT how it should work in the web world or in any multi threaded scenario.
//we need a new Database object for each thread.
var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings());
_uowProvider = new ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider(_dbFactory);
var evtMsgs = new TransientMessagesFactory();
ApplicationContext.Services = new ServiceContext(
repositoryFactory,
_uowProvider,
new FileUnitOfWorkProvider(),
new PublishingStrategy(evtMsgs, Logger),
cacheHelper,
Logger,
evtMsgs);
// create a few lock objects
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.Execute("SET IDENTITY_INSERT umbracoLock ON");
database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" });
database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" });
database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" });
database.Execute("SET IDENTITY_INSERT umbracoLock OFF");
database.CompleteTransaction();
}
catch
{
database.AbortTransaction();
}
}
[TearDown]
public override void TearDown()
{
//dispose!
_dbFactory.Dispose();
_uowProvider.Dispose();
base.TearDown();
}
[Test]
public void Test()
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
}
finally
{
database.CompleteTransaction();
}
}
[Test]
public void ConcurrentReadersTest()
{
var threads = new List<Thread>();
var locker = new object();
var acquired = 0;
var maxAcquired = 0;
for (var i = 0; i < 5; i++)
{
threads.Add(new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
}
Thread.Sleep(500);
lock (locker)
{
if (maxAcquired < acquired) maxAcquired = acquired;
}
Thread.Sleep(500);
lock (locker)
{
acquired--;
}
}
finally
{
database.CompleteTransaction();
}
}));
}
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
Assert.AreEqual(5, maxAcquired);
}
[Test]
public void ConcurrentWritersTest()
{
var threads = new List<Thread>();
var locker = new object();
var acquired = 0;
var maxAcquired = 0;
for (var i = 0; i < 5; i++)
{
threads.Add(new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
}
Thread.Sleep(500);
lock (locker)
{
if (maxAcquired < acquired) maxAcquired = acquired;
}
Thread.Sleep(500);
lock (locker)
{
acquired--;
}
}
finally
{
database.CompleteTransaction();
}
}));
}
foreach (var thread in threads) thread.Start();
foreach (var thread in threads) thread.Join();
Assert.AreEqual(1, maxAcquired);
}
[Test]
public void DeadLockTest()
{
Exception e1 = null, e2 = null;
var thread1 = new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeWriteLock(1);
Thread.Sleep(1000);
database.AcquireLockNodeWriteLock(2);
Thread.Sleep(1000);
}
catch (Exception e)
{
e1 = e;
}
finally
{
database.CompleteTransaction();
}
});
var thread2 = new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeWriteLock(2);
Thread.Sleep(1000);
database.AcquireLockNodeWriteLock(1);
Thread.Sleep(1000);
}
catch (Exception e)
{
e2 = e;
}
finally
{
database.CompleteTransaction();
}
});
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Assert.IsNotNull(e1);
Assert.IsNotNull(e2);
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
}
[Test]
public void NoDeadLockTest()
{
Exception e1 = null, e2 = null;
var thread1 = new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
database.AcquireLockNodeWriteLock(1);
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
Console.WriteLine("LOCKS:");
foreach (var row in info)
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid,
row.resource_type, row.resource_description, row.request_mode, row.resource_table,
row.resource_table_id, row.request_status));
Thread.Sleep(6000);
}
catch (Exception e)
{
e1 = e;
}
finally
{
database.CompleteTransaction();
}
});
var thread2 = new Thread(() =>
{
var database = DatabaseContext.Database;
database.BeginTransaction(IsolationLevel.RepeatableRead);
try
{
Thread.Sleep(1000);
database.AcquireLockNodeWriteLock(2);
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
Console.WriteLine("LOCKS:");
foreach (var row in info)
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid,
row.resource_type, row.resource_description, row.request_mode, row.resource_table,
row.resource_table_id, row.request_status));
Thread.Sleep(1000);
}
catch (Exception e)
{
e2 = e;
}
finally
{
database.CompleteTransaction();
}
});
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Assert.IsNull(e1);
Assert.IsNull(e2);
}
}
}

View File

@@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying
// {
// //Arrange
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
// var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
// var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
// var result = modelToSqlExpressionHelper.Visit(predicate);
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
@@ -30,16 +30,40 @@ namespace Umbraco.Tests.Persistence.Querying
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
// }
[Test]
public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
{
//Arrange
//use a single cached expression for multiple expressions and ensure the correct output
// is done for both of them.
var cachedExpression = new CachedExpression();
Expression<Func<IContent, bool>> predicate1 = content => content.Path.StartsWith("-1");
cachedExpression.Wrap(predicate1);
var modelToSqlExpressionHelper1 = new ModelToSqlExpressionVisitor<IContent>();
var result1 = modelToSqlExpressionHelper1.Visit(cachedExpression);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result1);
Assert.AreEqual("-1%", modelToSqlExpressionHelper1.GetSqlParameters()[0]);
Expression<Func<IContent, bool>> predicate2 = content => content.Path.StartsWith("-1,123,97");
cachedExpression.Wrap(predicate2);
var modelToSqlExpressionHelper2 = new ModelToSqlExpressionVisitor<IContent>();
var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2);
Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]);
}
[Test]
public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
{
//Arrange
Expression<Func<IContent, bool>> predicate = content => content.Path.StartsWith("-1");
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result);
Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
@@ -49,7 +73,7 @@ namespace Umbraco.Tests.Persistence.Querying
{
//Arrange
Expression<Func<IContent, bool>> predicate = content => content.ParentId == -1;
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
@@ -62,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Querying
public void Equals_Operator_For_Value_Gets_Escaped()
{
Expression<Func<IUser, bool>> predicate = user => user.Username == "hello@world.com";
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
@@ -75,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Querying
public void Equals_Method_For_Value_Gets_Escaped()
{
Expression<Func<IUser, bool>> predicate = user => user.Username.Equals("hello@world.com");
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
@@ -91,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Querying
SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of<ILogger>());
Expression<Func<IUser, bool>> predicate = user => user.Username.Equals("mydomain\\myuser");
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
@@ -108,7 +132,7 @@ namespace Umbraco.Tests.Persistence.Querying
SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of<ILogger>());
Expression<Func<UserDto, bool>> predicate = user => user.Login.StartsWith("mydomain\\myuser");
var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper<UserDto>();
var modelToSqlExpressionHelper = new PocoToSqlExpressionVisitor<UserDto>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Poco to Sql ExpressionHelper: \n" + result);
@@ -121,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Querying
public void Sql_Replace_Mapped()
{
Expression<Func<IUser, bool>> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com";
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);

View File

@@ -180,6 +180,7 @@
<Compile Include="IO\ShadowFileSystemTests.cs" />
<Compile Include="Logging\ConsoleLogger.cs" />
<Compile Include="Migrations\MigrationIssuesTests.cs" />
<Compile Include="Persistence\LocksTests.cs" />
<Compile Include="Persistence\BulkDataReaderTests.cs" />
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
<Compile Include="Persistence\PetaPocoCachesTest.cs" />

View File

@@ -1973,6 +1973,8 @@
<Content Include="Views\Partials\Grid\Editors\Textstring.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap2.cshtml" />
<Content Include="Views\Partials\Grid\Editors\Base.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap3-Fluid.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap2-Fluid.cshtml" />
<None Include="Web.Debug.config.transformed" />
<None Include="web.Template.Debug.config">
<DependentUpon>Web.Template.config</DependentUpon>

View File

@@ -64,8 +64,10 @@
JObject cfg = contentItem.config;
if(cfg != null)
foreach (JProperty property in cfg.Properties()) {
attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
foreach (JProperty property in cfg.Properties())
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
JObject style = contentItem.styles;
@@ -73,7 +75,13 @@
if (style != null) {
var cssVals = new List<string>();
foreach (JProperty property in style.Properties())
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
if (string.IsNullOrWhiteSpace(propertyValue) == false)
{
cssVals.Add(property.Name + ":" + propertyValue + ";");
}
}
if (cssVals.Any())
attrs.Add("style='" + string.Join(" ", cssVals) + "'");

View File

@@ -64,8 +64,10 @@
JObject cfg = contentItem.config;
if(cfg != null)
foreach (JProperty property in cfg.Properties()) {
attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\"");
foreach (JProperty property in cfg.Properties())
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
JObject style = contentItem.styles;
@@ -73,7 +75,13 @@
if (style != null) {
var cssVals = new List<string>();
foreach (JProperty property in style.Properties())
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
if (string.IsNullOrWhiteSpace(propertyValue) == false)
{
cssVals.Add(property.Name + ":" + propertyValue + ";");
}
}
if (cssVals.Any())
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");

View File

@@ -5,6 +5,7 @@
@*
Razor helpers located at the bottom of this file
*@
@if (Model != null && Model.sections != null)
{
var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1;
@@ -59,8 +60,10 @@
JObject cfg = contentItem.config;
if(cfg != null)
foreach (JProperty property in cfg.Properties()) {
attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
foreach (JProperty property in cfg.Properties())
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
JObject style = contentItem.styles;
@@ -68,7 +71,13 @@
if (style != null) {
var cssVals = new List<string>();
foreach (JProperty property in style.Properties())
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
if (string.IsNullOrWhiteSpace(propertyValue) == false)
{
cssVals.Add(property.Name + ":" + propertyValue + ";");
}
}
if (cssVals.Any())
attrs.Add("style='" + string.Join(" ", cssVals) + "'");

View File

@@ -64,8 +64,10 @@
JObject cfg = contentItem.config;
if(cfg != null)
foreach (JProperty property in cfg.Properties()) {
attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\"");
foreach (JProperty property in cfg.Properties())
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
}
JObject style = contentItem.styles;
@@ -73,7 +75,13 @@
if (style != null) {
var cssVals = new List<string>();
foreach (JProperty property in style.Properties())
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
{
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
if (string.IsNullOrWhiteSpace(propertyValue) == false)
{
cssVals.Add(property.Name + ":" + propertyValue + ";");
}
}
if (cssVals.Any())
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");

View File

@@ -1,5 +1,4 @@
@model dynamic
@using Umbraco.Web.Templates
@functions {
public static string EditorView(dynamic contentItem)

View File

@@ -1,3 +1,2 @@
@model dynamic
@using Umbraco.Web.Templates
@Html.Raw(Model.value)

View File

@@ -1,6 +1,4 @@
@inherits UmbracoViewPage<dynamic>
@using Umbraco.Web.Templates
@if (Model.value != null)
{

View File

@@ -1,5 +1,4 @@
@model dynamic
@using Umbraco.Web.Templates
@if (Model.value != null)
{

View File

@@ -4,10 +4,9 @@
@if (Model.editor.config.markup != null)
{
string markup = Model.editor.config.markup.ToString();
var UmbracoHelper = new UmbracoHelper(UmbracoContext.Current);
markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(Model.value.ToString()));
markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(TemplateUtilities.CleanForXss(Model.value.ToString())));
markup = markup.Replace("#style#", Model.editor.config.style.ToString());
<text>

View File

@@ -32,7 +32,7 @@ namespace Umbraco.Web.Cache
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
private static JsonPayload[] DeserializeFromJsonPayload(string json)
internal static JsonPayload[] DeserializeFromJsonPayload(string json)
{
var serializer = new JavaScriptSerializer();
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
@@ -45,7 +45,7 @@ namespace Umbraco.Web.Cache
/// <param name="contentType"></param>
/// <param name="isDeleted">if the item was deleted</param>
/// <returns></returns>
private static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false)
internal static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false)
{
var payload = new JsonPayload
{
@@ -59,16 +59,14 @@ namespace Umbraco.Web.Cache
? typeof(IMediaType).Name
: typeof(IMemberType).Name,
DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(),
WasDeleted = isDeleted
WasDeleted = isDeleted,
PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"),
AliasChanged = contentType.WasPropertyDirty("Alias"),
PropertyTypeAliasChanged = contentType.PropertyTypes.Any(x => x.WasPropertyDirty("Alias")),
IsNew = contentType.WasPropertyDirty("HasIdentity")
};
//here we need to check if the alias of the content type changed or if one of the properties was removed.
var dirty = contentType as IRememberBeingDirty;
if (dirty != null)
{
payload.PropertyRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
payload.AliasChanged = dirty.WasPropertyDirty("Alias");
payload.IsNew = dirty.WasPropertyDirty("HasIdentity");
}
return payload;
}
@@ -90,7 +88,7 @@ namespace Umbraco.Web.Cache
#region Sub classes
private class JsonPayload
internal class JsonPayload
{
public JsonPayload()
{
@@ -103,6 +101,7 @@ namespace Umbraco.Web.Cache
public string Type { get; set; }
public bool AliasChanged { get; set; }
public bool PropertyRemoved { get; set; }
public bool PropertyTypeAliasChanged { get; set; }
public JsonPayload[] DescendantPayloads { get; set; }
public bool WasDeleted { get; set; }
public bool IsNew { get; set; }
@@ -190,7 +189,7 @@ namespace Umbraco.Web.Cache
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey);
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
payloads.ForEach(payload =>
foreach (var payload in payloads)
{
//clear the cache for each item
ClearContentTypeCache(payload);
@@ -199,12 +198,12 @@ namespace Umbraco.Web.Cache
//if the item was deleted or the alias changed or property removed then we need to refresh the content.
//and, don't refresh the cache if it is new.
if (payload.Type == typeof(IContentType).Name
&& !payload.IsNew
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved))
&& payload.IsNew == false
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged))
{
needsContentRefresh = true;
}
});
}
//need to refresh the xml content cache if required
if (needsContentRefresh)
@@ -237,7 +236,7 @@ namespace Umbraco.Web.Cache
//cache if only a media type has changed.
//we don't want to update the routes cache if all of the content types here are new.
if (payloads.Any(x => x.Type == typeof(IContentType).Name)
&& !payloads.All(x => x.IsNew)) //if they are all new then don't proceed
&& payloads.All(x => x.IsNew) == false) //if they are all new then don't proceed
{
// SD: we need to clear the routes cache here!
//

View File

@@ -1,159 +0,0 @@
using System.Threading;
using System.Web;
using StackExchange.Profiling;
using Umbraco.Core;
namespace Umbraco.Web.Profiling
{
/// <summary>
/// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows
/// us to profile items during app startup - before an HttpRequest is created
/// </summary>
/// <remarks>
/// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all
/// profiling data and this sub class no longer performs any logic.
/// </remarks>
internal class StartupWebProfilerProvider : WebRequestProfilerProvider
{
public StartupWebProfilerProvider()
{
_startupPhase = StartupPhase.Boot;
//create the startup profiler
_startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose)
{
Name = "StartupProfiler"
};
}
private MiniProfiler _startupProfiler;
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
/// <summary>
/// Used to determine which phase the boot process is in
/// </summary>
private enum StartupPhase
{
None = 0,
Boot = 1,
Request = 2
}
private volatile StartupPhase _startupPhase;
/// <summary>
/// Executed once the application boot process is complete and changes the phase to Request
/// </summary>
public void BootComplete()
{
using (new ReadLock(_locker))
{
if (_startupPhase != StartupPhase.Boot) return;
}
using (var l = new UpgradeableReadLock(_locker))
{
if (_startupPhase == StartupPhase.Boot)
{
l.UpgradeToWriteLock();
_startupPhase = StartupPhase.Request;
}
}
}
/// <summary>
/// Executed when a profiling operation is completed
/// </summary>
/// <param name="discardResults"></param>
/// <remarks>
/// This checks if the bootup phase is None, if so, it just calls the base class, otherwise it checks
/// if a profiler is active (i.e. in startup), then sets the phase to None so that the base class will be used
/// for all subsequent calls.
/// </remarks>
public override void Stop(bool discardResults)
{
using (new ReadLock(_locker))
{
if (_startupPhase == StartupPhase.None)
{
base.Stop(discardResults);
return;
}
}
using (var l = new UpgradeableReadLock(_locker))
{
if (_startupPhase > 0 && base.GetCurrentProfiler() == null)
{
l.UpgradeToWriteLock();
_startupPhase = StartupPhase.None;
//This is required to pass the mini profiling context from before a request
// to the current startup request.
if (HttpContext.Current != null)
{
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
base.Stop(discardResults);
_startupProfiler = null;
}
}
else
{
base.Stop(discardResults);
}
}
}
/// <summary>
/// Executed when a profiling operation is started
/// </summary>
/// <param name="level"></param>
/// <returns></returns>
/// <remarks>
/// This checks if the startup phase is not None, if this is the case and the current profiler is NULL
/// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method.
/// </remarks>
public override MiniProfiler Start(ProfileLevel level)
{
using (new ReadLock(_locker))
{
if (_startupPhase > 0 && base.GetCurrentProfiler() == null)
{
SetProfilerActive(_startupProfiler);
return _startupProfiler;
}
return base.Start(level);
}
}
/// <summary>
/// This returns the current profiler
/// </summary>
/// <returns></returns>
/// <remarks>
/// If the boot phase is not None, then this will return the startup profiler (this), otherwise
/// returns the base class
/// </remarks>
public override MiniProfiler GetCurrentProfiler()
{
using (new ReadLock(_locker))
{
if (_startupPhase > 0)
{
try
{
var current = base.GetCurrentProfiler();
if (current == null) return _startupProfiler;
}
catch
{
return _startupProfiler;
}
}
return base.GetCurrentProfiler();
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Web;
using StackExchange.Profiling;
using StackExchange.Profiling.SqlFormatters;
@@ -14,22 +15,23 @@ namespace Umbraco.Web.Profiling
/// </summary>
internal class WebProfiler : IProfiler
{
private StartupWebProfilerProvider _startupWebProfilerProvider;
private const string BootRequestItemKey = "Umbraco.Web.Profiling.WebProfiler__isBootRequest";
private WebProfilerProvider _provider;
private int _first;
/// <summary>
/// Constructor
/// </summary>
internal WebProfiler()
{
//setup some defaults
// create our own provider, which can provide a profiler even during boot
// MiniProfiler's default cannot because there's no HttpRequest in HttpContext
_provider = new WebProfilerProvider();
// settings
MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter();
MiniProfiler.Settings.StackMaxLength = 5000;
//At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext
// since it hasn't started yet. So we need to do some hacking to enable profiling during startup.
_startupWebProfilerProvider = new StartupWebProfilerProvider();
//this should always be the case during startup, we'll need to set a custom profiler provider
MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider;
MiniProfiler.Settings.ProfilerProvider = _provider;
//Binds to application events to enable the MiniProfiler with a real HttpRequest
UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit;
@@ -57,43 +59,33 @@ namespace Umbraco.Web.Profiling
}
}
/// <summary>
/// Handle the begin request event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void UmbracoApplicationEndRequest(object sender, EventArgs e)
{
if (_startupWebProfilerProvider != null)
{
Stop();
_startupWebProfilerProvider = null;
}
else if (CanPerformProfilingAction(sender))
{
Stop();
}
}
/// <summary>
/// Handle the end request event
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void UmbracoApplicationBeginRequest(object sender, EventArgs e)
{
if (_startupWebProfilerProvider != null)
// if this is the first request, notify our own provider that this request is the boot request
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first)
{
_startupWebProfilerProvider.BootComplete();
_provider.BeginBootRequest();
((HttpApplication)sender).Context.Items[BootRequestItemKey] = true;
// and no need to start anything, profiler is already there
}
if (CanPerformProfilingAction(sender))
{
// else start a profiler, the normal way
else if (ShouldProfile(sender))
Start();
}
void UmbracoApplicationEndRequest(object sender, EventArgs e)
{
// if this is the boot request, or if we should profile this request, stop
// (the boot request is always profiled, no matter what)
var isBootRequest = ((HttpApplication)sender).Context.Items[BootRequestItemKey] != null; // fixme perfs
if (isBootRequest)
_provider.EndBootRequest();
if (isBootRequest || ShouldProfile(sender))
Stop();
}
private bool CanPerformProfilingAction(object sender)
private bool ShouldProfile(object sender)
{
if (GlobalSettings.DebugMode == false)
return false;
@@ -108,10 +100,10 @@ namespace Umbraco.Web.Profiling
return false;
if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"]))
return true;
return false;
if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath))
return true;
return false;
return true;
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Threading;
using System.Web;
using StackExchange.Profiling;
namespace Umbraco.Web.Profiling
{
/// <summary>
/// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows
/// us to profile items during app startup - before an HttpRequest is created
/// </summary>
/// <remarks>
/// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all
/// profiling data and this sub class no longer performs any logic.
/// </remarks>
internal class WebProfilerProvider : WebRequestProfilerProvider
{
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
private MiniProfiler _startupProfiler;
private int _first;
private volatile BootPhase _bootPhase;
public WebProfilerProvider()
{
// booting...
_bootPhase = BootPhase.Boot;
}
/// <summary>
/// Indicates the boot phase.
/// </summary>
private enum BootPhase
{
Boot = 0, // boot phase (before the 1st request even begins)
BootRequest = 1, // request boot phase (during the 1st request)
Booted = 2 // done booting
}
public void BeginBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.Boot)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.BootRequest;
// assign the profiler to be the current MiniProfiler for the request
// is's already active, starting and all
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
}
finally
{
_locker.ExitWriteLock();
}
}
public void EndBootRequest()
{
_locker.EnterWriteLock();
try
{
if (_bootPhase != BootPhase.BootRequest)
throw new InvalidOperationException("Invalid boot phase.");
_bootPhase = BootPhase.Booted;
_startupProfiler = null;
}
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
/// Executed when a profiling operation is started
/// </summary>
/// <param name="level"></param>
/// <returns></returns>
/// <remarks>
/// This checks if the startup phase is not None, if this is the case and the current profiler is NULL
/// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method.
/// </remarks>
public override MiniProfiler Start(ProfileLevel level)
{
var first = Interlocked.Exchange(ref _first, 1) == 0;
if (first == false) return base.Start(level);
_startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" };
SetProfilerActive(_startupProfiler);
return _startupProfiler;
}
/// <summary>
/// This returns the current profiler
/// </summary>
/// <returns></returns>
/// <remarks>
/// If the boot phase is not None, then this will return the startup profiler (this), otherwise
/// returns the base class
/// </remarks>
public override MiniProfiler GetCurrentProfiler()
{
// if not booting then just use base (fast)
// no lock, _bootPhase is volatile
if (_bootPhase == BootPhase.Booted)
return base.GetCurrentProfiler();
// else
try
{
var current = base.GetCurrentProfiler();
return current ?? _startupProfiler;
}
catch
{
return _startupProfiler;
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml;
@@ -67,12 +68,12 @@ namespace Umbraco.Web.Search
/// <summary>
/// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since
/// properties may have been added/removed
/// properties may have been added/removed, then we need to re-index any required data if aliases have been changed
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
/// <remarks>
/// See: http://issues.umbraco.org/issue/U4-4798
/// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833
/// </remarks>
static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e)
{
@@ -81,6 +82,79 @@ namespace Umbraco.Web.Search
{
provider.RefreshIndexerDataFromDataService();
}
if (e.MessageType == MessageType.RefreshByJson)
{
var contentTypesChanged = new HashSet<string>();
var mediaTypesChanged = new HashSet<string>();
var memberTypesChanged = new HashSet<string>();
var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString());
foreach (var payload in payloads)
{
if (payload.IsNew == false
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged))
{
//if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated
if (payload.Type == typeof(IContentType).Name)
{
//if it is content
contentTypesChanged.Add(payload.Alias);
}
else if (payload.Type == typeof(IMediaType).Name)
{
//if it is media
mediaTypesChanged.Add(payload.Alias);
}
else if (payload.Type == typeof(IMemberType).Name)
{
//if it is members
memberTypesChanged.Add(payload.Alias);
}
}
}
//TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up
// the re-indexing process, we don't want to revert to rebuilding the whole thing!
if (contentTypesChanged.Count > 0)
{
foreach (var alias in contentTypesChanged)
{
var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias);
var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id);
foreach (var contentItem in contentItems)
{
ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false);
}
}
}
if (mediaTypesChanged.Count > 0)
{
foreach (var alias in mediaTypesChanged)
{
var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias);
var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id);
foreach (var mediaItem in mediaItems)
{
ReIndexForMedia(mediaItem, mediaItem.Trashed == false);
}
}
}
if (memberTypesChanged.Count > 0)
{
foreach (var alias in memberTypesChanged)
{
var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias);
var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id);
foreach (var memberItem in memberItems)
{
ReIndexForMember(memberItem);
}
}
}
}
}
static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e)

View File

@@ -66,7 +66,8 @@ namespace Umbraco.Web.Templates
}
// static compiled regex for faster performance
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?",
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
/// <summary>
/// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
@@ -107,5 +108,9 @@ namespace Umbraco.Web.Templates
return text;
}
public static string CleanForXss(string text, params char[] ignoreFromClean)
{
return text.CleanForXss(ignoreFromClean);
}
}
}

View File

@@ -390,7 +390,7 @@
<Compile Include="Mvc\UmbracoRequireHttpsAttribute.cs" />
<Compile Include="Mvc\ValidateMvcAngularAntiForgeryTokenAttribute.cs" />
<Compile Include="OwinMiddlewareConfiguredEventArgs.cs" />
<Compile Include="Profiling\StartupWebProfilerProvider.cs" />
<Compile Include="Profiling\WebProfilerProvider.cs" />
<Compile Include="Profiling\WebProfiler.cs" />
<Compile Include="PropertyEditors\DatePreValueEditor.cs" />
<Compile Include="PropertyEditors\DateTimePreValueEditor.cs" />

View File

@@ -362,6 +362,11 @@ namespace UmbracoExamine
#region Protected
/// <summary>
/// This is a static query, it's parameters don't change so store statically
/// </summary>
private static readonly IQuery<IContent> PublishedQuery = Query<IContent>.Builder.Where(x => x.Published == true);
protected override void PerformIndexAll(string type)
{
const int pageSize = 10000;
@@ -389,9 +394,7 @@ namespace UmbracoExamine
else
{
//add the published filter
var qry = Query<IContent>.Builder.Where(x => x.Published == true);
descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, qry);
descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, PublishedQuery);
}
//if specific types are declared we need to post filter them