From f10b35780cdd1a15a0030786eee69353e9558a47 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 23 Jan 2019 10:22:29 +0100 Subject: [PATCH] - Removed PostFilterParams - Handling of PostFilter using Expression trees --- .../src/common/resources/entity.resource.js | 15 +- src/Umbraco.Web/Editors/EntityController.cs | 131 ++++++++++++++---- .../Editors/TemplateQueryController.cs | 2 +- .../Models/TemplateQuery/OperatorFactory.cs | 32 +++++ .../Models/TemplateQuery/QueryCondition.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 147 insertions(+), 46 deletions(-) create mode 100644 src/Umbraco.Web/Models/TemplateQuery/OperatorFactory.cs diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index f864696873..753d180880 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -254,22 +254,13 @@ function entityResource($q, $http, umbRequestHelper) { * * @param {string} type Object type name * @param {string} postFilter optional filter expression which will execute a dynamic where clause on the server - * @param {string} postFilterParams optional parameters for the postFilter expression * @returns {Promise} resourcePromise object containing the entity. * */ - getAll: function (type, postFilter, postFilterParams) { - + getAll: function (type, postFilter) { //need to build the query string manually - var query = "type=" + type + "&postFilter=" + (postFilter ? postFilter : ""); - if (postFilter && postFilterParams) { - var counter = 0; - _.each(postFilterParams, function(val, key) { - query += "&postFilterParams[" + counter + "].key=" + key + "&postFilterParams[" + counter + "].value=" + val; - counter++; - }); - } - + var query = "type=" + type + "&postFilter=" + (postFilter ? encodeURIComponent(postFilter) : ""); + return umbRequestHelper.resourcePromise( $http.get( umbRequestHelper.getApiUrl( diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index b25f3f5af1..19f7158c5d 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Net; using System.Web.Http; using AutoMapper; @@ -11,11 +10,10 @@ using Umbraco.Web.Mvc; using System.Linq; using System.Net.Http; using System.Net.Http.Formatting; +using System.Reflection; using Umbraco.Core.Models; -using Constants = Umbraco.Core.Constants; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; -using Examine; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -24,12 +22,14 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Models.Mapping; +using Umbraco.Web.Models.TemplateQuery; using Umbraco.Web.Search; using Umbraco.Web.Services; using Umbraco.Web.Trees; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { /// @@ -242,7 +242,7 @@ namespace Umbraco.Web.Editors }; } - + /// /// Gets an entity by a xpath query /// @@ -863,9 +863,15 @@ namespace Umbraco.Web.Editors } } - public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter, [FromUri]IDictionary postFilterParams) + /// + /// + /// + /// The type of entity. + /// Optional filter - Format like: "BoolVariable==true&IntVariable>=6". Invalid filters are ignored. + /// + public IEnumerable GetAll(UmbracoEntityTypes type, string postFilter) { - return GetResultForAll(type, postFilter, postFilterParams); + return GetResultForAll(type, postFilter); } /// @@ -873,29 +879,28 @@ namespace Umbraco.Web.Editors /// /// /// A string where filter that will filter the results dynamically with linq - optional - /// the parameters to fill in the string where filter - optional /// - private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null, IDictionary postFilterParams = null) + private IEnumerable GetResultForAll(UmbracoEntityTypes entityType, string postFilter = null) { var objectType = ConvertToObjectType(entityType); if (objectType.HasValue) { //TODO: Should we order this by something ? var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); - return ExecutePostFilter(entities, postFilter, postFilterParams); + return ExecutePostFilter(entities, postFilter); } //now we need to convert the unknown ones switch (entityType) { case UmbracoEntityTypes.Template: var templates = Services.FileService.GetTemplates(); - var filteredTemplates = ExecutePostFilter(templates, postFilter, postFilterParams); + var filteredTemplates = ExecutePostFilter(templates, postFilter); return filteredTemplates.Select(Mapper.Map); case UmbracoEntityTypes.Macro: //Get all macros from the macro service var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); - var filteredMacros = ExecutePostFilter(macros, postFilter, postFilterParams); + var filteredMacros = ExecutePostFilter(macros, postFilter); return filteredMacros.Select(Mapper.Map); case UmbracoEntityTypes.PropertyType: @@ -906,7 +911,7 @@ namespace Umbraco.Web.Editors .ToArray() .SelectMany(x => x.PropertyTypes) .DistinctBy(composition => composition.Alias); - var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter, postFilterParams); + var filteredPropertyTypes = ExecutePostFilter(propertyTypes, postFilter); return Mapper.Map, IEnumerable>(filteredPropertyTypes); case UmbracoEntityTypes.PropertyGroup: @@ -917,32 +922,32 @@ namespace Umbraco.Web.Editors .ToArray() .SelectMany(x => x.PropertyGroups) .DistinctBy(composition => composition.Name); - var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter, postFilterParams); + var filteredpropertyGroups = ExecutePostFilter(propertyGroups, postFilter); return Mapper.Map, IEnumerable>(filteredpropertyGroups); case UmbracoEntityTypes.User: var users = Services.UserService.GetAll(0, int.MaxValue, out _); - var filteredUsers = ExecutePostFilter(users, postFilter, postFilterParams); + var filteredUsers = ExecutePostFilter(users, postFilter); return Mapper.Map, IEnumerable>(filteredUsers); case UmbracoEntityTypes.Stylesheet: - if (!postFilter.IsNullOrWhiteSpace() || (postFilterParams != null && postFilterParams.Count > 0)) + if (!postFilter.IsNullOrWhiteSpace()) throw new NotSupportedException("Filtering on stylesheets is not currently supported"); return Services.FileService.GetStylesheets().Select(Mapper.Map); - + case UmbracoEntityTypes.Language: - if (!postFilter.IsNullOrWhiteSpace() || (postFilterParams != null && postFilterParams.Count > 0)) + if (!postFilter.IsNullOrWhiteSpace() ) throw new NotSupportedException("Filtering on languages is not currently supported"); return Services.LocalizationService.GetAllLanguages().Select(Mapper.Map); case UmbracoEntityTypes.DictionaryItem: - if (!postFilter.IsNullOrWhiteSpace() || (postFilterParams != null && postFilterParams.Count > 0)) - throw new NotSupportedException("Filtering on languages is not currently supported"); + if (!postFilter.IsNullOrWhiteSpace()) + throw new NotSupportedException("Filtering on dictionary items is not currently supported"); return GetAllDictionaryItems(); @@ -952,20 +957,90 @@ namespace Umbraco.Web.Editors } } - private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter, IDictionary postFilterParams) + private IEnumerable ExecutePostFilter(IEnumerable entities, string postFilter) { - // if a post filter is assigned then try to execute it - if (postFilter.IsNullOrWhiteSpace() == false) + if (postFilter.IsNullOrWhiteSpace()) return entities; + + var postFilterConditions = postFilter.Split('&'); + + foreach (var postFilterCondition in postFilterConditions) { - // fixme/task/critical - trouble is, we've killed the dynamic Where thing! - throw new NotImplementedException("oops"); - //return postFilterParams == null - // ? entities.AsQueryable().Where(postFilter).ToArray() - // : entities.AsQueryable().Where(postFilter, postFilterParams).ToArray(); + var queryCondition = BuildQueryCondition(postFilterCondition); + + if (queryCondition != null) + { + var whereClauseExpression = queryCondition.BuildCondition("x"); + + entities = entities.Where(whereClauseExpression.Compile()); + } + } return entities; } + private static QueryCondition BuildQueryCondition(string postFilter) + { + var postFilterParts = postFilter.Split(new[] + { + "=", + "==", + "!=", + "<>", + ">", + "<", + ">=", + "<=" + }, 2, StringSplitOptions.RemoveEmptyEntries); + + if (postFilterParts.Length != 2) + { + return null; + } + + var propertyName = postFilterParts[0]; + var constraintValue = postFilterParts[1]; + var stringOperator = postFilter.Substring(propertyName.Length, + postFilter.Length - propertyName.Length - constraintValue.Length); + Operator binaryOperator; + + try + { + binaryOperator = OperatorFactory.FromString(stringOperator); + } + catch (ArgumentException) + { + // unsupported operators are ignored + return null; + } + + var type = typeof(T); + var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); + + if (property == null) + { + return null; + } + + var queryCondition = new QueryCondition() + { + Term = new OperatorTerm() + { + Operator = binaryOperator + }, + ConstraintValue = constraintValue, + Property = new PropertyModel() + { + Alias = propertyName, + Name = propertyName, + Type = property.PropertyType.Name + } + }; + + return queryCondition; + } + + + #region Methods to get all dictionary items private IEnumerable GetAllDictionaryItems() @@ -993,7 +1068,7 @@ namespace Umbraco.Web.Editors GetChildItemsForList(childItem, list); } - } + } #endregion } diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 73ebdf3bc5..2483446ec2 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -118,7 +118,7 @@ namespace Umbraco.Web.Editors foreach (var condition in model.Filters.Where(x => !x.ConstraintValue.IsNullOrWhiteSpace())) { //x is passed in as the parameter alias for the linq where statement clause - var operation = condition.BuildCondition("x", contents, Properties); + var operation = condition.BuildCondition("x"); contents = contents.Where(operation.Compile()); queryExpression.Append(indent); diff --git a/src/Umbraco.Web/Models/TemplateQuery/OperatorFactory.cs b/src/Umbraco.Web/Models/TemplateQuery/OperatorFactory.cs new file mode 100644 index 0000000000..0b595723f1 --- /dev/null +++ b/src/Umbraco.Web/Models/TemplateQuery/OperatorFactory.cs @@ -0,0 +1,32 @@ +using System; + +namespace Umbraco.Web.Models.TemplateQuery +{ + public static class OperatorFactory + { + public static Operator FromString(string stringOperator) + { + if (stringOperator == null) throw new ArgumentNullException(nameof(stringOperator)); + + switch (stringOperator) + { + case "=": + case "==": + return Operator.Equals; + case "!=": + case "<>": + return Operator.NotEquals; + case "<": + return Operator.LessThan; + case "<=": + return Operator.LessThanEqualTo; + case ">": + return Operator.GreaterThan; + case ">=": + return Operator.GreaterThanEqualTo; + default: + throw new ArgumentException($"A operator cannot be created from the specified string '{stringOperator}'", nameof(stringOperator)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 009b568556..a58badcf2c 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -18,11 +18,10 @@ namespace Umbraco.Web.Models.TemplateQuery private static Lazy StringContainsMethodInfo => new Lazy(() => typeof(string).GetMethod("Contains", new[] {typeof(string)})); - public static Expression> BuildCondition(this QueryCondition condition, - string parameterAlias, IEnumerable contents, IEnumerable properties) + public static Expression> BuildCondition(this QueryCondition condition, string parameterAlias) { object constraintValue; - switch (condition.Property.Type) + switch (condition.Property.Type.ToLowerInvariant()) { case "string": constraintValue = condition.ConstraintValue; @@ -30,12 +29,15 @@ namespace Umbraco.Web.Models.TemplateQuery case "datetime": constraintValue = DateTime.Parse(condition.ConstraintValue); break; + case "boolean": + constraintValue = Boolean.Parse(condition.ConstraintValue); + break; default: constraintValue = Convert.ChangeType(condition.ConstraintValue, typeof(int)); break; } - var parameterExpression = Expression.Parameter(typeof(IPublishedContent), parameterAlias); + var parameterExpression = Expression.Parameter(typeof(T), parameterAlias); var propertyExpression = Expression.Property(parameterExpression, condition.Property.Alias); var valueExpression = Expression.Constant(constraintValue); @@ -73,7 +75,7 @@ namespace Umbraco.Web.Models.TemplateQuery } var predicate = - Expression.Lambda>(bodyExpression.Reduce(), parameterExpression); + Expression.Lambda>(bodyExpression.Reduce(), parameterExpression); return predicate; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6736f7512b..d2913dd228 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -172,6 +172,7 @@ +