diff --git a/src/Microsoft.Restier.Core/ApiBase.cs b/src/Microsoft.Restier.Core/ApiBase.cs index 5c886877..c7c23307 100644 --- a/src/Microsoft.Restier.Core/ApiBase.cs +++ b/src/Microsoft.Restier.Core/ApiBase.cs @@ -3,7 +3,10 @@ using System; using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Restier.Core.Submit; namespace Microsoft.Restier.Core { @@ -12,15 +15,16 @@ namespace Microsoft.Restier.Core /// /// /// - /// An API configuration is intended to be long-lived, and can be - /// statically cached according to an API type specified when the - /// configuration is created. Additionally, the API model produced - /// as a result of a particular configuration is cached under the same + /// An API configuration is intended to be long-lived, and can be statically cached according to an API type specified when the + /// configuration is created. Additionally, the API model produced as a result of a particular configuration is cached under the same /// API type to avoid re-computing it on each invocation. /// /// public abstract class ApiBase : IDisposable { + + #region Private Members + private static ConcurrentDictionary> publisherServicesCallback = new ConcurrentDictionary>(); @@ -28,19 +32,21 @@ public abstract class ApiBase : IDisposable private ApiConfiguration apiConfiguration; - /// - /// Initializes a new instance of the class. - /// - /// - /// An containing all services of this . - /// - protected ApiBase(IServiceProvider serviceProvider) => ServiceProvider = serviceProvider; + private readonly DefaultSubmitHandler submitHandler; + + #endregion + + #region Public Properties /// /// Gets the which contains all services of this . /// public IServiceProvider ServiceProvider { get; private set; } + #endregion + + #region Internal Properties + /// /// Gets the API configuration for this API. /// @@ -57,6 +63,26 @@ internal ApiConfiguration Configuration } } + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// An containing all services of this . + /// + protected ApiBase(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + submitHandler = new DefaultSubmitHandler(serviceProvider); + } + + #endregion + + #region Static Methods + /// /// Configure services for this API. /// @@ -99,9 +125,7 @@ public static void AddPublisherServices(Type apiType, Action /// /// Get publisher registering service callback for specified Api. /// - /// - /// The Api type of which to get the publisher registering service callback. - /// + /// The Api type of which to get the publisher registering service callback. /// The service registering callback. //[CLSCompliant(false)] public static Action GetPublisherServiceCallback(Type apiType) @@ -114,6 +138,26 @@ public static Action GetPublisherServiceCallback(Type apiTyp return emptyConfig; } + #endregion + + #region Public Methods + + /// + /// Asynchronously submits changes made using an API context. + /// + /// A change set, or null to submit existing pending changes. + /// An optional cancellation token. + /// A task that represents the asynchronous operation whose result is a submit result. + public async Task SubmitAsync(ChangeSet changeSet = null, CancellationToken cancellationToken = default) + { + var submitContext = new SubmitContext(ServiceProvider, changeSet); + return await submitHandler.SubmitAsync(submitContext, cancellationToken).ConfigureAwait(false); + } + + #endregion + + #region IDisposable Pattern + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// @@ -140,6 +184,8 @@ protected virtual void Dispose(bool disposing) } } + #endregion + } } \ No newline at end of file diff --git a/src/Microsoft.Restier.Core/ApiConfiguration.cs b/src/Microsoft.Restier.Core/ApiConfiguration.cs index b7ea64f2..f7d5b984 100644 --- a/src/Microsoft.Restier.Core/ApiConfiguration.cs +++ b/src/Microsoft.Restier.Core/ApiConfiguration.cs @@ -26,7 +26,7 @@ internal class ApiConfiguration internal IEdmModel Model { get; private set; } - internal TaskCompletionSource CompeteModelGeneration(out Task running) + internal TaskCompletionSource CompleteModelGeneration(out Task running) { var source = new TaskCompletionSource(TaskCreationOptions.AttachedToParent); var runningTask = Interlocked.CompareExchange(ref modelTask, source.Task, null); diff --git a/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs b/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs index a5fa80c8..d6f126b2 100644 --- a/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs +++ b/src/Microsoft.Restier.Core/Exceptions/ChangeSetValidationException.cs @@ -14,6 +14,9 @@ public class ChangeSetValidationException : Exception { private IEnumerable errorValidationResults; + /// + /// + /// public ChangeSetValidationException() { } diff --git a/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs b/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs index c82b4cdf..4d37a0e4 100644 --- a/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs +++ b/src/Microsoft.Restier.Core/Extensions/ApiBaseExtensions.cs @@ -177,7 +177,7 @@ public static IEnumerable GetApiServices(this ApiBase api) where T : class throw new InvalidOperationException(Resources.ModelBuilderNotRegistered); } - var source = config.CompeteModelGeneration(out var running); + var source = config.CompleteModelGeneration(out var running); if (source == null) { return await running.ConfigureAwait(false); @@ -403,34 +403,6 @@ public static IQueryable GetQueryableSource(this ApiBase api #endregion - #region Submit - - /// - /// Asynchronously submits changes made using an API context. - /// - /// - /// An API. - /// - /// - /// A change set, or null to submit existing pending changes. - /// - /// - /// An optional cancellation token. - /// - /// - /// A task that represents the asynchronous - /// operation whose result is a submit result. - /// - public static async Task SubmitAsync(this ApiBase api, ChangeSet changeSet = null, CancellationToken cancellationToken = default(CancellationToken)) - { - Ensure.NotNull(api, nameof(api)); - - var submitContext = new SubmitContext(api.ServiceProvider, changeSet); - return await DefaultSubmitHandler.SubmitAsync(submitContext, cancellationToken).ConfigureAwait(false); - } - - #endregion - #region GetQueryableSource Private /// diff --git a/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs b/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs index 58d9acde..32b39fb6 100644 --- a/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs +++ b/src/Microsoft.Restier.Core/Extensions/ServiceCollectionExtensions.cs @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Restier.Core.Query; -using Microsoft.Restier.Core.Submit; using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Restier.Core.Query; +using Microsoft.Restier.Core.Submit; namespace Microsoft.Restier.Core { diff --git a/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs b/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs index 48b3d7d4..e3fd0248 100644 --- a/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs +++ b/src/Microsoft.Restier.Core/Submit/DefaultSubmitHandler.cs @@ -10,14 +10,47 @@ using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Restier.Core.Submit { /// - /// Represents the default submit handler. + /// The default handler for submitting changes through the . /// - internal static class DefaultSubmitHandler + internal class DefaultSubmitHandler { + + #region Private Members + + private readonly IChangeSetInitializer initializer; + private readonly IChangeSetItemAuthorizer authorizer; + private readonly IChangeSetItemValidator validator; + private readonly IChangeSetItemFilter filter; + private readonly ISubmitExecutor executor; + + #endregion + + #region Constructors + + /// + /// + /// + /// + public DefaultSubmitHandler(IServiceProvider serviceProvider) + { + //RWM: This stuff SHOULD be getting passed into a constructor. But the DI implementation is less than awesome. + // So we'll work around it for now and still save some allocations. + initializer = serviceProvider.GetService(); + executor = serviceProvider.GetService(); + authorizer = serviceProvider.GetService(); + validator = serviceProvider.GetService(); + filter = serviceProvider.GetService(); + } + + #endregion + + #region Public Methods + /// /// Asynchronously executes the submit flow. /// @@ -31,17 +64,16 @@ internal static class DefaultSubmitHandler /// A task that represents the asynchronous /// operation whose result is a submit result. /// - public static async Task SubmitAsync(SubmitContext context, CancellationToken cancellationToken) + public async Task SubmitAsync(SubmitContext context, CancellationToken cancellationToken) { Ensure.NotNull(context, nameof(context)); - var preparer = context.GetApiService(); - if (preparer == null) + if (initializer == null) { throw new NotSupportedException(Resources.ChangeSetPreparerMissing); } - await preparer.InitializeAsync(context, cancellationToken).ConfigureAwait(false); + await initializer.InitializeAsync(context, cancellationToken).ConfigureAwait(false); if (context.Result != null) { @@ -65,6 +97,10 @@ public static async Task SubmitAsync(SubmitContext context, Cancel return context.Result; } + #endregion + + #region Private Methods + private static string GetAuthorizeFailedMessage(ChangeSetItem item) { switch (item.Type) @@ -96,7 +132,7 @@ private static string GetAuthorizeFailedMessage(ChangeSetItem item) } } - private static async Task PerformValidate(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + private async Task PerformValidate(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) { await InvokeAuthorizers(context, changeSetItems, cancellationToken).ConfigureAwait(false); @@ -115,9 +151,8 @@ private static async Task PerformValidate(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + private async Task InvokeAuthorizers(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) { - var authorizer = context.GetApiService(); if (authorizer == null) { return; @@ -132,9 +167,8 @@ private static async Task InvokeAuthorizers(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + private async Task InvokeValidators(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) { - var validator = context.GetApiService(); if (validator == null) { return; @@ -159,11 +193,12 @@ private static async Task InvokeValidators(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + private async Task PerformPreEvent(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) { - - var processor = context.GetApiService(); - //TODO: Should we error out if there is no ChangeSetFilter? No consistent pattern to follow. + if (filter == null) + { + return; + } foreach (var item in changeSetItems) { @@ -172,9 +207,9 @@ private static async Task PerformPreEvent(SubmitContext context, IEnumerable(); if (executor == null) { throw new NotSupportedException(Resources.SubmitExecutorMissing); @@ -204,19 +238,21 @@ private static async Task PerformPersist(SubmitContext context, CancellationToke context.Result = await executor.ExecuteSubmitAsync(context, cancellationToken).ConfigureAwait(false); } - private static async Task PerformPostEvent(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) + private async Task PerformPostEvent(SubmitContext context, IEnumerable changeSetItems, CancellationToken cancellationToken) { - var processor = context.GetApiService(); - if (processor == null) + if (filter == null) { - //TODO: It feels like we should be following the same pattern as teh method above. return; } foreach (var item in changeSetItems) { - await processor.OnChangeSetItemProcessedAsync(context, item, cancellationToken).ConfigureAwait(false); + await filter.OnChangeSetItemProcessedAsync(context, item, cancellationToken).ConfigureAwait(false); } } + + #endregion + } -} + +} \ No newline at end of file