From 4e9d6433e2a8b47379c81a47edfd3a59a5978652 Mon Sep 17 00:00:00 2001 From: Vincent He Date: Fri, 8 Jul 2016 13:37:53 +0800 Subject: [PATCH] Clean some code and add IOperationProcessor --- src/GlobalSuppressions.cs | 1 + .../ConventionBasedChangeSetItemAuthorizer.cs | 11 +- .../ConventionBasedChangeSetItemProcessor.cs | 29 ++--- .../ConventionBasedOperationProcessor.cs | 106 ++++++++++++++++++ ...onventionBasedQueryExpressionProcessor.cs} | 10 +- src/Microsoft.Restier.Core/DataSourceStub.cs | 100 ----------------- .../Microsoft.Restier.Core.csproj | 4 +- .../Operation/IOperationProcessor.cs | 46 ++++++++ .../Operation/OperationContext.cs | 20 ++++ .../ServiceCollectionExtensions.cs | 3 +- .../Submit/ChangeSetItem.cs | 86 ++------------ .../Submit/DefaultSubmitHandler.cs | 13 +-- .../Submit/ChangeSetInitializer.cs | 8 +- .../Operation/OperationExecutor.cs | 55 +++++++-- .../RestierController.cs | 6 +- .../ChangeSetPreparerTests.cs | 2 +- .../PublicApi.bsl | 22 ++-- .../SaveTests.cs | 2 +- .../Extension/CustomizedSubmitProcessor.cs | 2 +- 19 files changed, 274 insertions(+), 252 deletions(-) create mode 100644 src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationProcessor.cs rename src/Microsoft.Restier.Core/Conventions/{ConventionBasedEntitySetProcessor.cs => ConventionBasedQueryExpressionProcessor.cs} (95%) create mode 100644 src/Microsoft.Restier.Core/Operation/IOperationProcessor.cs diff --git a/src/GlobalSuppressions.cs b/src/GlobalSuppressions.cs index e6b6ec17..b790cae3 100644 --- a/src/GlobalSuppressions.cs +++ b/src/GlobalSuppressions.cs @@ -158,6 +158,7 @@ [assembly: SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Scope = "member", Target = "Microsoft.Restier.Core.QueryableSource.#System.Linq.IQueryProvider.CreateQuery(System.Linq.Expressions.Expression)")] [assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntitySetTypeMap")] [assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Model.ModelContext.#EntityTypeKeyPropertiesMap")] +[assembly: SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Scope = "member", Target = "Microsoft.Restier.Core.Operation.OperationContext.#ParametersValue")] #endregion #region CA1801 Unused Parameters diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs index 294f3f11..f5b8ad10 100644 --- a/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemAuthorizer.cs @@ -80,26 +80,21 @@ private static string GetAuthorizeMethodName(ChangeSetItem item) case ChangeSetItemType.DataModification: DataModificationItem dataModification = (DataModificationItem)item; string operationName = null; - if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Insert) + if (dataModification.DataModificationItemAction == DataModificationItemAction.Insert) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationInsert; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Update) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Update) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationUpdate; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Remove) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Remove) { operationName = ConventionBasedChangeSetConstants.AuthorizeMethodDataModificationDelete; } return operationName + dataModification.EntitySetName; - case ChangeSetItemType.ActionInvocation: - ActionInvocationItem actionItem = (ActionInvocationItem)item; - return ConventionBasedChangeSetConstants.AuthorizeMethodActionInvocationExecute + - actionItem.ActionName; - default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemProcessor.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemProcessor.cs index 256d3d74..5f02171f 100644 --- a/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemProcessor.cs +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedChangeSetItemProcessor.cs @@ -14,7 +14,7 @@ namespace Microsoft.Restier.Core.Conventions { /// - /// A convention-based change set item filter. + /// A convention-based change set item processor which calls logic like OnInserting and OnInserted. /// internal class ConventionBasedChangeSetItemProcessor : IChangeSetItemProcessor { @@ -43,7 +43,7 @@ public Task OnProcessingChangeSetItemAsync( ChangeSetItem item, CancellationToken cancellationToken) { - return this.InvokeFilterMethodAsync( + return this.InvokeProcessorMethodAsync( context, item, ConventionBasedChangeSetConstants.FilterMethodNamePreFilterSuffix); } @@ -53,7 +53,7 @@ public Task OnProcessedChangeSetItemAsync( ChangeSetItem item, CancellationToken cancellationToken) { - return this.InvokeFilterMethodAsync( + return this.InvokeProcessorMethodAsync( context, item, ConventionBasedChangeSetConstants.FilterMethodNamePostFilterSuffix); } @@ -64,26 +64,21 @@ private static string GetMethodName(ChangeSetItem item, string suffix) case ChangeSetItemType.DataModification: DataModificationItem dataModification = (DataModificationItem)item; string operationName = null; - if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Insert) + if (dataModification.DataModificationItemAction == DataModificationItemAction.Insert) { operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationInsert; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Update) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Update) { operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationUpdate; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Remove) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Remove) { operationName = ConventionBasedChangeSetConstants.FilterMethodDataModificationDelete; } return operationName + suffix + dataModification.EntitySetName; - case ChangeSetItemType.ActionInvocation: - ActionInvocationItem actionItem = (ActionInvocationItem)item; - return ConventionBasedChangeSetConstants.FilterMethodActionInvocationExecute + - suffix + actionItem.ActionName; - default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); @@ -98,10 +93,6 @@ private static object[] GetParameters(ChangeSetItem item) DataModificationItem dataModification = (DataModificationItem)item; return new object[] { dataModification.Entity }; - case ChangeSetItemType.ActionInvocation: - ActionInvocationItem actionItem = (ActionInvocationItem)item; - return actionItem.GetArgumentArray(); - default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, Resources.InvalidChangeSetEntryType, item.Type)); @@ -114,13 +105,13 @@ private static bool ParametersMatch(ParameterInfo[] methodParameters, object[] p && !methodParameters.Where((mp, i) => !mp.ParameterType.IsInstanceOfType(parameters[i])).Any(); } - private Task InvokeFilterMethodAsync( + private Task InvokeProcessorMethodAsync( SubmitContext context, ChangeSetItem item, string methodNameSuffix) { - string methodName = ConventionBasedChangeSetItemProcessor.GetMethodName(item, methodNameSuffix); - object[] parameters = ConventionBasedChangeSetItemProcessor.GetParameters(item); + string methodName = GetMethodName(item, methodNameSuffix); + object[] parameters = GetParameters(item); MethodInfo method = this.targetType.GetQualifiedMethod(methodName); @@ -140,7 +131,7 @@ private Task InvokeFilterMethodAsync( } ParameterInfo[] methodParameters = method.GetParameters(); - if (ConventionBasedChangeSetItemProcessor.ParametersMatch(methodParameters, parameters)) + if (ParametersMatch(methodParameters, parameters)) { object result = method.Invoke(target, parameters); Task resultTask = result as Task; diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationProcessor.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationProcessor.cs new file mode 100644 index 00000000..155cbce1 --- /dev/null +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedOperationProcessor.cs @@ -0,0 +1,106 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Operation; + +namespace Microsoft.Restier.Core.Conventions +{ + /// + /// A convention-based change set item filter. + /// + internal class ConventionBasedOperationProcessor : IOperationProcessor + { + private Type targetType; + + private ConventionBasedOperationProcessor(Type targetType) + { + Ensure.NotNull(targetType, "targetType"); + this.targetType = targetType; + } + + /// + public static void ApplyTo( + IServiceCollection services, + Type targetType) + { + Ensure.NotNull(services, "services"); + Ensure.NotNull(targetType, "targetType"); + services.AddService( + (sp, next) => new ConventionBasedOperationProcessor(targetType)); + } + + /// + public Task OnExecutingOperationAsync( + OperationContext context, + CancellationToken cancellationToken) + { + return this.InvokeProcessorMethodAsync( + context, ConventionBasedChangeSetConstants.FilterMethodNamePreFilterSuffix); + } + + /// + public Task OnExecutedOperationAsync( + OperationContext context, + CancellationToken cancellationToken) + { + return this.InvokeProcessorMethodAsync( + context, ConventionBasedChangeSetConstants.FilterMethodNamePostFilterSuffix); + } + + private static bool ParametersMatch(ParameterInfo[] methodParameters, object[] parameters) + { + return methodParameters.Length == parameters.Length + && !methodParameters.Where((mp, i) => !mp.ParameterType.IsInstanceOfType(parameters[i])).Any(); + } + + private Task InvokeProcessorMethodAsync( + OperationContext context, + string methodNameSuffix) + { + string methodName = ConventionBasedChangeSetConstants.FilterMethodActionInvocationExecute + + methodNameSuffix + context.OperationName; + object[] parameters = null; + if (context.ParametersValue != null) + { + context.ParametersValue.ToArray(); + } + + MethodInfo method = this.targetType.GetQualifiedMethod(methodName); + + if (method != null && + (method.ReturnType == typeof(void) || + typeof(Task).IsAssignableFrom(method.ReturnType))) + { + object target = null; + if (!method.IsStatic) + { + target = context.GetApiService(); + if (target == null || + !this.targetType.IsInstanceOfType(target)) + { + return Task.WhenAll(); + } + } + + ParameterInfo[] methodParameters = method.GetParameters(); + if (ParametersMatch(methodParameters, parameters)) + { + object result = method.Invoke(target, parameters); + Task resultTask = result as Task; + if (resultTask != null) + { + return resultTask; + } + } + } + + return Task.WhenAll(); + } + } +} diff --git a/src/Microsoft.Restier.Core/Conventions/ConventionBasedEntitySetProcessor.cs b/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs similarity index 95% rename from src/Microsoft.Restier.Core/Conventions/ConventionBasedEntitySetProcessor.cs rename to src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs index 984a5e28..5bb5ada2 100644 --- a/src/Microsoft.Restier.Core/Conventions/ConventionBasedEntitySetProcessor.cs +++ b/src/Microsoft.Restier.Core/Conventions/ConventionBasedQueryExpressionProcessor.cs @@ -5,22 +5,20 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.OData.Edm; -using Microsoft.OData.Edm.Library; using Microsoft.Restier.Core.Query; namespace Microsoft.Restier.Core.Conventions { /// - /// A convention-based query expression filter on entity set. + /// A convention-based query expression processor which will apply OnFilter logic into query expression. /// - internal class ConventionBasedEntitySetProcessor : IQueryExpressionProcessor + internal class ConventionBasedQueryExpressionProcessor : IQueryExpressionProcessor { private Type targetType; - private ConventionBasedEntitySetProcessor(Type targetType) + private ConventionBasedQueryExpressionProcessor(Type targetType) { this.targetType = targetType; } @@ -36,7 +34,7 @@ public static void ApplyTo( Ensure.NotNull(services, "services"); Ensure.NotNull(targetType, "targetType"); services.AddService( - (sp, next) => new ConventionBasedEntitySetProcessor(targetType) + (sp, next) => new ConventionBasedQueryExpressionProcessor(targetType) { Inner = next, }); diff --git a/src/Microsoft.Restier.Core/DataSourceStub.cs b/src/Microsoft.Restier.Core/DataSourceStub.cs index 3c916e0c..ecf93175 100644 --- a/src/Microsoft.Restier.Core/DataSourceStub.cs +++ b/src/Microsoft.Restier.Core/DataSourceStub.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.Restier.Core.Properties; @@ -90,104 +89,5 @@ public static TResult GetPropertyValue( { throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); } - - /// - /// Identifies an entity set or results of a call to a function import. - /// TODO reserve for function/action supports - /// - /// - /// The type of the elements in the results. - /// - /// - /// The name of an entity set or function import. - /// - /// - /// If is a function import, - /// the arguments to be passed to the function import. - /// - /// - /// A representation of the entity set or - /// results of a call to the function import. - /// - internal static IEnumerable Results( - string name, params object[] arguments) - { - throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); - } - - /// - /// Identifies a singleton or result of a - /// call to a singular function import. - /// TODO reserve for function/action supports - /// - /// - /// The type of the result. - /// - /// - /// The name of a singleton or singular function import. - /// - /// - /// If is a singular function import, - /// the arguments to be passed to the singular function import. - /// - /// - /// A representation of the singleton or result - /// of a call to the singular function import. - /// - internal static TResult Result( - string name, params object[] arguments) - { - throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); - } - - /// - /// Identifies the results of a call to a function. - /// TODO reserve for function/action supports - /// - /// - /// The type of the elements in the results. - /// - /// - /// The name of a namespace containing the function. - /// - /// - /// The name of a function. - /// - /// - /// The arguments to be passed to the function. - /// - /// - /// A representation of the results of a call to the function. - /// - internal static IEnumerable Results( - string namespaceName, string name, params object[] arguments) - { - throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); - } - - /// - /// Identifies the result of a call to a singular function. - /// TODO reserve for function/action supports - /// - /// - /// The type of the result. - /// - /// - /// The name of a namespace containing the singular function. - /// - /// - /// The name of a singular function. - /// - /// - /// The arguments to be passed to the singular function. - /// - /// - /// A representation of the result of a call to the singular function. - /// - internal static TResult Result( - string namespaceName, string name, params object[] arguments) - { - throw new InvalidOperationException(Resources.DoNotCallDataSourceStubMethodDirectly); - } } } diff --git a/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj b/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj index 2bc5d512..c700ad04 100644 --- a/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj +++ b/src/Microsoft.Restier.Core/Microsoft.Restier.Core.csproj @@ -51,12 +51,13 @@ Shared\TypeExtensions.cs + - + @@ -72,6 +73,7 @@ + diff --git a/src/Microsoft.Restier.Core/Operation/IOperationProcessor.cs b/src/Microsoft.Restier.Core/Operation/IOperationProcessor.cs new file mode 100644 index 00000000..7a7030b5 --- /dev/null +++ b/src/Microsoft.Restier.Core/Operation/IOperationProcessor.cs @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Restier.Core.Operation +{ + /// + /// Represents a operation processor. + /// + public interface IOperationProcessor + { + /// + /// Asynchronously applies logic before a operation is executed. + /// + /// + /// The operation context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnExecutingOperationAsync( + OperationContext context, + CancellationToken cancellationToken); + + /// + /// Asynchronously applies logic after an operation is executed. + /// + /// + /// The submit context. + /// + /// + /// A cancellation token. + /// + /// + /// A task that represents the asynchronous operation. + /// + Task OnExecutedOperationAsync( + OperationContext context, + CancellationToken cancellationToken); + } +} diff --git a/src/Microsoft.Restier.Core/Operation/OperationContext.cs b/src/Microsoft.Restier.Core/Operation/OperationContext.cs index f69b2722..8d067bc6 100644 --- a/src/Microsoft.Restier.Core/Operation/OperationContext.cs +++ b/src/Microsoft.Restier.Core/Operation/OperationContext.cs @@ -2,12 +2,14 @@ // Licensed under the MIT License. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; namespace Microsoft.Restier.Core.Operation { /// /// Represents context under which a operation is executed. + /// One instance created for one execution of one operation. /// public class OperationContext : InvocationContext { @@ -15,6 +17,7 @@ public class OperationContext : InvocationContext private readonly Func getParameterValueFunc; private readonly bool isFunction; private readonly IQueryable bindingParameterValue; + private ICollection parametersValue; /// /// Initializes a new instance of the class. @@ -92,5 +95,22 @@ public IQueryable BindingParameterValue return this.bindingParameterValue; } } + + /// + /// Gets or sets the parameters value array used by method, + /// It is only set after parameters are prepared. + /// + public ICollection ParametersValue + { + get + { + return this.parametersValue; + } + + set + { + this.parametersValue = value; + } + } } } diff --git a/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs b/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs index cf63139b..463ccf08 100644 --- a/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Restier.Core/ServiceCollectionExtensions.cs @@ -260,8 +260,9 @@ public static IServiceCollection AddConventionBasedServices(this IServiceCollect ConventionBasedChangeSetItemAuthorizer.ApplyTo(services, apiType); ConventionBasedChangeSetItemProcessor.ApplyTo(services, apiType); services.AddService(); - ConventionBasedEntitySetProcessor.ApplyTo(services, apiType); + ConventionBasedQueryExpressionProcessor.ApplyTo(services, apiType); ConventionBasedOperationAuthorizer.ApplyTo(services, apiType); + ConventionBasedOperationProcessor.ApplyTo(services, apiType); return services; } diff --git a/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs b/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs index d5396e02..706773c5 100644 --- a/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs +++ b/src/Microsoft.Restier.Core/Submit/ChangeSetItem.cs @@ -17,7 +17,7 @@ namespace Microsoft.Restier.Core.Submit /// This is required because during the post-CUD events, the EntityState has been lost. /// This enum allows the API to remember which pre-CUD event was raised for the Entity. /// - public enum ChangeSetItemAction + public enum DataModificationItemAction { /// /// Specifies an undefined action. @@ -48,12 +48,7 @@ internal enum ChangeSetItemType /// /// Specifies a data modification item. /// - DataModification, - - /// - /// Specifies an action invocation item. - /// - ActionInvocation + DataModification } /// @@ -96,7 +91,6 @@ public abstract class ChangeSetItem internal ChangeSetItem(ChangeSetItemType type) { this.Type = type; - this.ChangeSetItemProcessingStage = ChangeSetItemProcessingStage.Initialized; } @@ -143,7 +137,7 @@ public class DataModificationItem : ChangeSetItem /// The type of the actual entity type in question. /// /// - /// The ChangeSetItemAction for the request. + /// The DataModificationItemAction for the request. /// /// /// The key of the entity being modified. @@ -158,7 +152,7 @@ public DataModificationItem( string entitySetName, Type expectedEntityType, Type actualEntityType, - ChangeSetItemAction action, + DataModificationItemAction action, IReadOnlyDictionary entityKey, IReadOnlyDictionary originalValues, IReadOnlyDictionary localValues) @@ -172,7 +166,7 @@ public DataModificationItem( this.EntityKey = entityKey; this.OriginalValues = originalValues; this.LocalValues = localValues; - this.ChangeSetItemAction = action; + this.DataModificationItemAction = action; } /// @@ -199,7 +193,7 @@ public DataModificationItem( /// /// Gets or sets the action to be taken. /// - public ChangeSetItemAction ChangeSetItemAction { get; set; } + public DataModificationItemAction DataModificationItemAction { get; set; } /// /// Gets or sets a value indicating whether the entity should be fully replaced by the modification. @@ -267,7 +261,7 @@ public IReadOnlyDictionary LocalValues public IQueryable ApplyTo(IQueryable query) { Ensure.NotNull(query, "query"); - if (this.ChangeSetItemAction == ChangeSetItemAction.Insert) + if (this.DataModificationItemAction == DataModificationItemAction.Insert) { throw new InvalidOperationException(Resources.DataModificationNotSupportCreateEntity); } @@ -371,7 +365,7 @@ public class DataModificationItem : DataModificationItem /// The type of the actual entity type in question. /// /// - /// The ChangeSetItemAction for the request. + /// The DataModificationItemAction for the request. /// /// /// The key of the entity being modified. @@ -386,7 +380,7 @@ public DataModificationItem( string entitySetName, Type expectedEntityType, Type actualEntityType, - ChangeSetItemAction action, + DataModificationItemAction action, IReadOnlyDictionary entityKey, IReadOnlyDictionary originalValues, IReadOnlyDictionary localValues) @@ -414,66 +408,4 @@ public DataModificationItem( } } } - - /// - /// Represents an action invocation item in a change set. - /// - public class ActionInvocationItem : ChangeSetItem - { - /// - /// Initializes a new instance of the class. - /// - /// - /// An action name. - /// - /// - /// A set of arguments to pass to the action. - /// - public ActionInvocationItem( - string actionName, - IDictionary arguments) - : base(ChangeSetItemType.ActionInvocation) - { - Ensure.NotNull(actionName, "actionName"); - this.ActionName = actionName; - this.Arguments = arguments; - } - - /// - /// Gets or sets the operation (action) request. - /// - public string ActionName { get; set; } - - /// - /// Gets the set of arguments to pass to the action. - /// - public IDictionary Arguments { get; private set; } - - /// - /// Gets or sets the result of the action. - /// - /// - /// Initially this will be null, however after the action - /// has been invoked it will contain the result. - /// - public object Result { get; set; } - - /// - /// Gets an array of the arguments to pass to the action. - /// - /// - /// An array of the arguments to pass to the action. - /// - internal object[] GetArgumentArray() - { - if (this.Arguments == null) - { - return new object[] { }; - } - else - { - return this.Arguments.Select(a => a.Value).ToArray(); - } - } - } } diff --git a/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs b/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs index 9f684518..f1772dd0 100644 --- a/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs +++ b/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs @@ -74,15 +74,15 @@ private static string GetAuthorizeFailedMessage(ChangeSetItem item) case ChangeSetItemType.DataModification: DataModificationItem dataModification = (DataModificationItem)item; string message = null; - if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Insert) + if (dataModification.DataModificationItemAction == DataModificationItemAction.Insert) { message = Resources.NoPermissionToInsertEntity; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Update) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Update) { message = Resources.NoPermissionToUpdateEntity; } - else if (dataModification.ChangeSetItemAction == ChangeSetItemAction.Remove) + else if (dataModification.DataModificationItemAction == DataModificationItemAction.Remove) { message = Resources.NoPermissionToDeleteEntity; } @@ -93,13 +93,6 @@ private static string GetAuthorizeFailedMessage(ChangeSetItem item) return string.Format(CultureInfo.InvariantCulture, message, dataModification.EntitySetName); - case ChangeSetItemType.ActionInvocation: - ActionInvocationItem actionInvocation = (ActionInvocationItem)item; - return string.Format( - CultureInfo.InvariantCulture, - Resources.NoPermissionToInvokeAction, - actionInvocation.ActionName); - default: throw new InvalidOperationException(string.Format( CultureInfo.InvariantCulture, diff --git a/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs b/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs index 8fd00f9f..dfdae6a6 100644 --- a/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs +++ b/src/Microsoft.Restier.Providers.EntityFramework/Submit/ChangeSetInitializer.cs @@ -52,7 +52,7 @@ public async Task InitializeAsync( object entity; - if (entry.ChangeSetItemAction == ChangeSetItemAction.Insert) + if (entry.DataModificationItemAction == DataModificationItemAction.Insert) { entity = set.Create(); @@ -60,12 +60,12 @@ public async Task InitializeAsync( set.Add(entity); } - else if (entry.ChangeSetItemAction == ChangeSetItemAction.Remove) + else if (entry.DataModificationItemAction == DataModificationItemAction.Remove) { entity = await FindEntity(context, entry, cancellationToken); set.Remove(entity); } - else if (entry.ChangeSetItemAction == ChangeSetItemAction.Update) + else if (entry.DataModificationItemAction == DataModificationItemAction.Update) { entity = await FindEntity(context, entry, cancellationToken); @@ -110,7 +110,7 @@ private static async Task FindEntity( var message = string.Format( CultureInfo.InvariantCulture, Resources.PreconditionCheckFailed, - new object[] { item.ChangeSetItemAction, entity }); + new object[] { item.DataModificationItemAction, entity }); throw new PreconditionFailedException(message); } diff --git a/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs b/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs index 5365d2ab..89e9f00e 100644 --- a/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs +++ b/src/Microsoft.Restier.Publishers.OData/Operation/OperationExecutor.cs @@ -15,6 +15,7 @@ using Microsoft.Restier.Core; using Microsoft.Restier.Core.Exceptions; using Microsoft.Restier.Core.Operation; +using Microsoft.Restier.Core.Submit; using Microsoft.Restier.Publishers.OData.Formatter.Deserialization; using Microsoft.Restier.Publishers.OData.Properties; @@ -26,13 +27,7 @@ public async Task ExecuteOperationAsync( object instanceImplementMethod, OperationContext context, CancellationToken cancellationToken) { // Authorization check - var authorizor = context.GetApiService(); - - if (!await authorizor.AuthorizeAsync(context, cancellationToken)) - { - throw new SecurityException(string.Format( - CultureInfo.InvariantCulture, Resources.OperationUnAuthorizationExecution, context.OperationName)); - } + await InvokeAuthorizers(context, cancellationToken); // model build does not support operation with same name // So method with same name but different signature is not considered. @@ -82,7 +77,16 @@ public async Task ExecuteOperationAsync( parameters[paraIndex] = convertedValue; } - return InvokeOperation(instanceImplementMethod, method, parameters, model); + context.ParametersValue = parameters; + + // Invoke preprocessing on the operation execution + PerformPreEvent(context, cancellationToken); + + var result = InvokeOperation(instanceImplementMethod, method, parameters, model); + + // Invoke preprocessing on the operation execution + PerformPostEvent(context, cancellationToken); + return result; } private static object PrepareBindingParameter(Type bindingType, IQueryable bindingParameterValue) @@ -167,5 +171,40 @@ private static IQueryable InvokeOperation( return typedQueryable; } + + private static async Task InvokeAuthorizers( + OperationContext context, + CancellationToken cancellationToken) + { + var authorizor = context.GetApiService(); + if (authorizor == null) + { + return; + } + + if (!await authorizor.AuthorizeAsync(context, cancellationToken)) + { + throw new SecurityException(string.Format( + CultureInfo.InvariantCulture, Resources.OperationUnAuthorizationExecution, context.OperationName)); + } + } + + private static void PerformPreEvent(OperationContext context, CancellationToken cancellationToken) + { + var processor = context.GetApiService(); + if (processor != null) + { + processor.OnExecutingOperationAsync(context, cancellationToken); + } + } + + private static void PerformPostEvent(OperationContext context, CancellationToken cancellationToken) + { + var processor = context.GetApiService(); + if (processor != null) + { + processor.OnExecutedOperationAsync(context, cancellationToken); + } + } } } diff --git a/src/Microsoft.Restier.Publishers.OData/RestierController.cs b/src/Microsoft.Restier.Publishers.OData/RestierController.cs index a3aa20ac..b61b861f 100644 --- a/src/Microsoft.Restier.Publishers.OData/RestierController.cs +++ b/src/Microsoft.Restier.Publishers.OData/RestierController.cs @@ -164,7 +164,7 @@ public async Task Post(EdmEntityObject edmEntityObject, Cance entitySet.Name, expectedEntityType.GetClrType(Api), actualEntityType.GetClrType(Api), - ChangeSetItemAction.Insert, + DataModificationItemAction.Insert, null, null, edmEntityObject.CreatePropertyDictionary()); @@ -243,7 +243,7 @@ public async Task Delete(CancellationToken cancellationToken) entitySet.Name, path.EdmType.GetClrType(Api), null, - ChangeSetItemAction.Remove, + DataModificationItemAction.Remove, RestierQueryBuilder.GetPathKeyValues(path), propertiesInEtag, null); @@ -405,7 +405,7 @@ private async Task Update( entitySet.Name, expectedEntityType.GetClrType(Api), actualEntityType.GetClrType(Api), - ChangeSetItemAction.Update, + DataModificationItemAction.Update, RestierQueryBuilder.GetPathKeyValues(path), propertiesInEtag, edmEntityObject.CreatePropertyDictionary()); diff --git a/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs b/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs index 0ab3ca21..44689ced 100644 --- a/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs +++ b/test/Microsoft.Restier.Providers.EntityFramework.Tests/ChangeSetPreparerTests.cs @@ -23,7 +23,7 @@ public async Task ComplexTypeUpdate() "Readers", typeof(Person), null, - ChangeSetItemAction.Update, + DataModificationItemAction.Update, new Dictionary { { "Id", new Guid("53162782-EA1B-4712-AF26-8AA1D2AC0461") } }, new Dictionary(), new Dictionary { { "Addr", new Dictionary { { "Zip", "332" } } } }); diff --git a/test/Microsoft.Restier.TestCommon/PublicApi.bsl b/test/Microsoft.Restier.TestCommon/PublicApi.bsl index 8d38166a..91553af4 100644 --- a/test/Microsoft.Restier.TestCommon/PublicApi.bsl +++ b/test/Microsoft.Restier.TestCommon/PublicApi.bsl @@ -338,6 +338,11 @@ public interface Microsoft.Restier.Core.Operation.IOperationExecutor { System.Threading.Tasks.Task`1[[System.Linq.IQueryable]] ExecuteOperationAsync (object instanceImplementMethod, Microsoft.Restier.Core.Operation.OperationContext context, System.Threading.CancellationToken cancellationToken) } +public interface Microsoft.Restier.Core.Operation.IOperationProcessor { + System.Threading.Tasks.Task OnExecutedOperationAsync (Microsoft.Restier.Core.Operation.OperationContext context, System.Threading.CancellationToken cancellationToken) + System.Threading.Tasks.Task OnExecutingOperationAsync (Microsoft.Restier.Core.Operation.OperationContext context, System.Threading.CancellationToken cancellationToken) +} + public class Microsoft.Restier.Core.Operation.OperationContext : Microsoft.Restier.Core.InvocationContext { public OperationContext (Microsoft.Restier.Core.ApiContext apiContext, System.Func`2[[System.String],[System.Object]] getParameterValueFunc, string operationName, bool isFunction, System.Linq.IQueryable bindingParameterValue) @@ -345,6 +350,7 @@ public class Microsoft.Restier.Core.Operation.OperationContext : Microsoft.Resti System.Func`2[[System.String],[System.Object]] GetParameterValueFunc { public get; } bool IsFunction { public get; } string OperationName { public get; } + System.Collections.Generic.ICollection`1[[System.Object]] ParametersValue { public get; public set; } } public interface Microsoft.Restier.Core.Query.IQueryExecutor { @@ -426,7 +432,7 @@ public class Microsoft.Restier.Core.Query.QueryResult { Microsoft.OData.Edm.IEdmEntitySet ResultsSource { public get; public set; } } -public enum Microsoft.Restier.Core.Submit.ChangeSetItemAction : int { +public enum Microsoft.Restier.Core.Submit.DataModificationItemAction : int { Insert = 2 Remove = 3 Undefined = 0 @@ -458,14 +464,6 @@ public abstract class Microsoft.Restier.Core.Submit.ChangeSetItem { public bool HasChanged () } -public class Microsoft.Restier.Core.Submit.ActionInvocationItem : Microsoft.Restier.Core.Submit.ChangeSetItem { - public ActionInvocationItem (string actionName, System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] arguments) - - string ActionName { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } - System.Collections.Generic.IDictionary`2[[System.String],[System.Object]] Arguments { [CompilerGeneratedAttribute(),]public get; } - object Result { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } -} - public class Microsoft.Restier.Core.Submit.ChangeSet { public ChangeSet () public ChangeSet (System.Collections.Generic.IEnumerable`1[[Microsoft.Restier.Core.Submit.ChangeSetItem]] entries) @@ -493,10 +491,10 @@ public class Microsoft.Restier.Core.Submit.ChangeSetValidationException : System } public class Microsoft.Restier.Core.Submit.DataModificationItem : Microsoft.Restier.Core.Submit.ChangeSetItem { - public DataModificationItem (string entitySetName, System.Type expectedEntityType, System.Type actualEntityType, Microsoft.Restier.Core.Submit.ChangeSetItemAction action, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] entityKey, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] originalValues, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] localValues) + public DataModificationItem (string entitySetName, System.Type expectedEntityType, System.Type actualEntityType, Microsoft.Restier.Core.Submit.DataModificationItemAction action, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] entityKey, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] originalValues, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] localValues) System.Type ActualEntityType { [CompilerGeneratedAttribute(),]public get; } - Microsoft.Restier.Core.Submit.ChangeSetItemAction ChangeSetItemAction { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } + Microsoft.Restier.Core.Submit.DataModificationItemAction DataModificationItemAction { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } object Entity { [CompilerGeneratedAttribute(),]public get; [CompilerGeneratedAttribute(),]public set; } System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] EntityKey { [CompilerGeneratedAttribute(),]public get; } string EntitySetName { [CompilerGeneratedAttribute(),]public get; } @@ -511,7 +509,7 @@ public class Microsoft.Restier.Core.Submit.DataModificationItem : Microsoft.Rest } public class Microsoft.Restier.Core.Submit.DataModificationItem`1 : Microsoft.Restier.Core.Submit.DataModificationItem { - public DataModificationItem`1 (string entitySetName, System.Type expectedEntityType, System.Type actualEntityType, Microsoft.Restier.Core.Submit.ChangeSetItemAction action, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] entityKey, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] originalValues, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] localValues) + public DataModificationItem`1 (string entitySetName, System.Type expectedEntityType, System.Type actualEntityType, Microsoft.Restier.Core.Submit.DataModificationItemAction action, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] entityKey, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] originalValues, System.Collections.Generic.IReadOnlyDictionary`2[[System.String],[System.Object]] localValues) T Entity { public get; public set; } } diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs index 6c30d344..fb0e6b5a 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Northwind.Tests/SaveTests.cs @@ -60,7 +60,7 @@ public async Task TestEntityFilterReturnsTask() "Customers", typeof(Customer), null, - ChangeSetItemAction.Insert, + DataModificationItemAction.Insert, null, null, new Dictionary() diff --git a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Extension/CustomizedSubmitProcessor.cs b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Extension/CustomizedSubmitProcessor.cs index 6717515a..da98e06b 100644 --- a/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Extension/CustomizedSubmitProcessor.cs +++ b/test/ODataEndToEnd/Microsoft.OData.Service.Sample.Trippin/Extension/CustomizedSubmitProcessor.cs @@ -24,7 +24,7 @@ public Task OnProcessedChangeSetItemAsync(SubmitContext context, ChangeSetItem i { object myEntity = dataModificationItem.Entity; string entitySetName = dataModificationItem.EntitySetName; - ChangeSetItemAction operation = dataModificationItem.ChangeSetItemAction; + DataModificationItemAction operation = dataModificationItem.DataModificationItemAction; // In case of insert, the request URL has no key, and request body may not have key neither as the key may be generated by database var keyAttrbiutes = new Dictionary();