From c0cc848df28d7619552d7c2ef7f6186334a60bb7 Mon Sep 17 00:00:00 2001 From: alistair Date: Sun, 28 Jul 2019 13:58:42 +0100 Subject: [PATCH 1/4] Switch Action Filters to entirely execute in the WebAPI filter continuation flow The action filter wrapper has been replaced, and a new autofac filter interface defined with a registered adapter around the old style. --- .../ActionFilterWrapper.cs | 169 ---------------- .../Autofac.Integration.WebApi.csproj | 6 +- .../AutofacActionFilterAdapter.cs | 136 +++++++++++++ .../AutofacFilterCategory.cs | 10 + .../AutofacWebApiFilterProvider.cs | 8 +- ...ContinuationActionFilterOverrideWrapper.cs | 29 +++ .../ContinuationActionFilterWrapper.cs | 95 +++++++++ .../IAutofacContinuationActionFilter.cs | 55 ++++++ .../RegistrationExtensions.cs | 182 +++++++++++++++++- .../ActionFilterFixture.cs | 4 +- .../Autofac.Integration.WebApi.Test.csproj | 11 +- .../AutofacFilterBaseFixture.cs | 35 +++- .../AutofacWebApiFilterProviderFixture.cs | 27 +-- ...tionActionFilterOverrideWrapperFixture.cs} | 4 +- ...ContinuationActionFilterWrapperFixture.cs} | 105 ++++++++-- .../FilterOrderingFixture.cs | 33 +++- .../RegistrationExtensionsFixture.cs | 38 +++- .../TestTypes/TestAsyncContext.cs | 40 ++++ .../TestTypes/TestContinuationActionFilter.cs | 41 ++-- .../TestContinuationActionFilter2.cs | 54 ++++++ .../TestOldActionFilterAsyncContextAccess.cs | 60 ++++++ .../TestOldActionFilterLocalAsync.cs | 33 ++++ 22 files changed, 913 insertions(+), 262 deletions(-) delete mode 100644 src/Autofac.Integration.WebApi/ActionFilterWrapper.cs create mode 100644 src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs create mode 100644 src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs create mode 100644 src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs create mode 100644 src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs rename test/Autofac.Integration.WebApi.Test/{ActionFilterOverrideWrapperFixture.cs => ContinuationActionFilterOverrideWrapperFixture.cs} (67%) rename test/Autofac.Integration.WebApi.Test/{ActionFilterWrapperFixture.cs => ContinuationActionFilterWrapperFixture.cs} (61%) create mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs rename src/Autofac.Integration.WebApi/ActionFilterOverrideWrapper.cs => test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs (55%) create mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs create mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs create mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs diff --git a/src/Autofac.Integration.WebApi/ActionFilterWrapper.cs b/src/Autofac.Integration.WebApi/ActionFilterWrapper.cs deleted file mode 100644 index 6faa8dd..0000000 --- a/src/Autofac.Integration.WebApi/ActionFilterWrapper.cs +++ /dev/null @@ -1,169 +0,0 @@ -// This software is part of the Autofac IoC container -// Copyright (c) 2012 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; -using Autofac.Features.Metadata; - -namespace Autofac.Integration.WebApi -{ - /// - /// Resolves a filter for the specified metadata for each controller request. - /// - [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "Derived attribute adds filter override support")] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] - internal class ActionFilterWrapper : ActionFilterAttribute, IAutofacActionFilter - { - private readonly HashSet _allFilters; - - /// - /// Initializes a new instance of the class. - /// - /// The collection of filter metadata blocks that this wrapper should run. - public ActionFilterWrapper(HashSet filterMetadata) - { - if (filterMetadata == null) - { - throw new ArgumentNullException(nameof(filterMetadata)); - } - - _allFilters = filterMetadata; - } - - /// - /// Occurs after the action method is invoked. - /// - /// The context for the action. - /// A cancellation token for signaling task ending. - /// - /// Thrown if is . - /// - public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) - { - if (actionExecutedContext == null) - { - throw new ArgumentNullException(nameof(actionExecutedContext)); - } - - var dependencyScope = actionExecutedContext.Request.GetDependencyScope(); - var lifetimeScope = dependencyScope.GetRequestLifetimeScope(); - - var filters = lifetimeScope.Resolve>>>(); - - // Issue #16: OnActionExecuted needs to happen in the opposite order of OnActionExecuting. - foreach (var filter in filters.Where(this.FilterMatchesMetadata).Reverse()) - { - await filter.Value.Value.OnActionExecutedAsync(actionExecutedContext, cancellationToken); - } - } - - /// - /// Occurs before the action method is invoked. - /// - /// The context for the action. - /// A cancellation token for signaling task ending. - /// - /// Thrown if is . - /// - public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) - { - if (actionContext == null) - { - throw new ArgumentNullException(nameof(actionContext)); - } - - var dependencyScope = actionContext.Request.GetDependencyScope(); - var lifetimeScope = dependencyScope.GetRequestLifetimeScope(); - - var filters = lifetimeScope.Resolve>>>(); - - // Need to know the set of filters that we've executed so far, so - // we can go back through them. - var executedFilters = new List(); - - // Issue #16: OnActionExecuted needs to happen in the opposite order of OnActionExecuting. - foreach (var filter in filters.Where(this.FilterMatchesMetadata)) - { - await filter.Value.Value.OnActionExecutingAsync(actionContext, cancellationToken); - - // Issue #30: If an actionfilter sets a response, it should prevent the others from running - // their own OnActionExecuting methods, and call OnActionExecuted on already-run - // filters. - // The OnActionExecutedAsync method of the wrapper will never fire if there - // is a response set, so we must call the OnActionExecutedAsync of prior filters - // ourselves. - if (actionContext.Response != null) - { - await ExecuteManualOnActionExecutedAsync(executedFilters.AsEnumerable().Reverse(), actionContext, cancellationToken); - break; - } - - executedFilters.Add(filter.Value.Value); - } - } - - /// - /// Method to manually invoke OnActionExecutedAsync outside of the filter wrapper's own - /// OnActionExecuted async method. - /// - private async Task ExecuteManualOnActionExecutedAsync( - IEnumerable filters, - HttpActionContext actionContext, - CancellationToken cancellationToken) - { - HttpActionExecutedContext localExecutedContext = null; - - foreach (var alreadyRanFilter in filters) - { - if (localExecutedContext == null) - { - localExecutedContext = new HttpActionExecutedContext - { - ActionContext = actionContext, - Response = actionContext.Response - }; - } - - await alreadyRanFilter.OnActionExecutedAsync(localExecutedContext, cancellationToken); - } - } - - private bool FilterMatchesMetadata(Meta> filter) - { - var metadata = filter.Metadata.TryGetValue(AutofacWebApiFilterProvider.FilterMetadataKey, out var metadataAsObject) - ? metadataAsObject as FilterMetadata - : null; - - return _allFilters.Contains(metadata); - } - } -} \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/Autofac.Integration.WebApi.csproj b/src/Autofac.Integration.WebApi/Autofac.Integration.WebApi.csproj index d12d4a3..f3b875f 100644 --- a/src/Autofac.Integration.WebApi/Autofac.Integration.WebApi.csproj +++ b/src/Autofac.Integration.WebApi/Autofac.Integration.WebApi.csproj @@ -51,9 +51,10 @@ CodeAnalysisDictionary.xml - + + + - @@ -68,6 +69,7 @@ + diff --git a/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs b/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs new file mode 100644 index 0000000..1b8301c --- /dev/null +++ b/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs @@ -0,0 +1,136 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Runtime.ExceptionServices; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Autofac.Integration.WebApi +{ + /// + /// This is an adapter responsible for wrapping the old style + /// and converting it to a . + /// + /// + /// The adapter from old -> new is registered in . + /// + internal class AutofacActionFilterAdapter : IAutofacContinuationActionFilter + { + private readonly IAutofacActionFilter _legacyFilter; + + public AutofacActionFilterAdapter(IAutofacActionFilter legacyFilter) + { + _legacyFilter = legacyFilter; + } + + public async Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) + { + await _legacyFilter.OnActionExecutingAsync(actionContext, cancellationToken); + + if (actionContext.Response != null) + { + return actionContext.Response; + } + + return await CallOnActionExecutedAsync(actionContext, cancellationToken, continuation); + } + + /// + /// The content of this method is taken from the ActionFilterAttribute code in the ASP.NET source, since + /// that is basically the reference implementation for invoking an async filter's OnActionExecuted correctly. + /// + [SuppressMessage("Microsoft.CodeQuality", "CA1068", Justification = "Matching parameter order in original implementtion.")] + private async Task CallOnActionExecutedAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) + { + cancellationToken.ThrowIfCancellationRequested(); + + HttpResponseMessage response = null; + ExceptionDispatchInfo exceptionInfo = null; + + try + { + response = await continuation(); + } + catch (Exception e) + { + exceptionInfo = ExceptionDispatchInfo.Capture(e); + } + + Exception exception; + + if (exceptionInfo == null) + { + exception = null; + } + else + { + exception = exceptionInfo.SourceException; + } + + HttpActionExecutedContext executedContext = new HttpActionExecutedContext(actionContext, exception) + { + Response = response + }; + + try + { + await _legacyFilter.OnActionExecutedAsync(executedContext, cancellationToken); + } + catch + { + // Catch is running because OnActionExecuted threw an exception, so we just want to re-throw. + // We also need to reset the response to forget about it since a filter threw an exception. + actionContext.Response = null; + throw; + } + + if (executedContext.Response != null) + { + return executedContext.Response; + } + + Exception newException = executedContext.Exception; + + if (newException != null) + { + if (newException == exception) + { + exceptionInfo.Throw(); + } + else + { + throw newException; + } + } + + throw new InvalidOperationException(); + } + } +} diff --git a/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs b/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs index 74608d4..50585f8 100644 --- a/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs +++ b/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs @@ -50,6 +50,16 @@ internal enum AutofacFilterCategory /// AuthenticationFilterOverride, + /// + /// Action filters using the continuation style. + /// + ContinuationActionFilter, + + /// + /// Action filter overrides using the continuation style. + /// + ContinuationActionFilterOverride, + /// /// Action filters /// diff --git a/src/Autofac.Integration.WebApi/AutofacWebApiFilterProvider.cs b/src/Autofac.Integration.WebApi/AutofacWebApiFilterProvider.cs index cbc8b67..1889c68 100644 --- a/src/Autofac.Integration.WebApi/AutofacWebApiFilterProvider.cs +++ b/src/Autofac.Integration.WebApi/AutofacWebApiFilterProvider.cs @@ -150,8 +150,8 @@ private static void ResolveAllScopedFilterOverrides( ILifetimeScope lifeTimeScope, HttpActionDescriptor descriptor) { - ResolveScopedFilter( - filterContext, scope, lifeTimeScope, descriptor, hs => new ActionFilterOverrideWrapper(hs), AutofacFilterCategory.ActionFilterOverride); + ResolveScopedFilter( + filterContext, scope, lifeTimeScope, descriptor, hs => new ContinuationActionFilterOverrideWrapper(hs), AutofacFilterCategory.ActionFilterOverride); ResolveScopedFilter( filterContext, scope, lifeTimeScope, descriptor, hs => new AuthenticationFilterOverrideWrapper(hs), AutofacFilterCategory.AuthenticationFilterOverride); ResolveScopedFilter( @@ -162,8 +162,8 @@ private static void ResolveAllScopedFilterOverrides( private static void ResolveAllScopedFilters(FilterContext filterContext, FilterScope scope, ILifetimeScope lifeTimeScope, HttpActionDescriptor descriptor) { - ResolveScopedFilter( - filterContext, scope, lifeTimeScope, descriptor, hs => new ActionFilterWrapper(hs), AutofacFilterCategory.ActionFilter); + ResolveScopedFilter( + filterContext, scope, lifeTimeScope, descriptor, hs => new ContinuationActionFilterWrapper(hs), AutofacFilterCategory.ActionFilter); ResolveScopedFilter( filterContext, scope, lifeTimeScope, descriptor, hs => new AuthenticationFilterWrapper(hs), AutofacFilterCategory.AuthenticationFilter); ResolveScopedFilter( diff --git a/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs b/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs new file mode 100644 index 0000000..139f3b8 --- /dev/null +++ b/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Web.Http.Filters; + +namespace Autofac.Integration.WebApi +{ + /// + /// Resolves a filter override for the specified metadata for each controller request. + /// + internal sealed class ContinuationActionFilterOverrideWrapper : ContinuationActionFilterWrapper, IOverrideFilter + { + /// + /// Initializes a new instance of the class. + /// + /// The filter metadata. + public ContinuationActionFilterOverrideWrapper(HashSet filterMetadata) + : base(filterMetadata) + { + } + + /// + /// Gets the filters to override. + /// + public Type FiltersToOverride + { + get { return typeof(IActionFilter); } + } + } +} \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs b/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs new file mode 100644 index 0000000..c81817a --- /dev/null +++ b/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs @@ -0,0 +1,95 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; +using Autofac.Features.Metadata; + +namespace Autofac.Integration.WebApi +{ + /// + /// Resolves a filter for the specified metadata for each controller request. + /// + internal class ContinuationActionFilterWrapper : IActionFilter, IAutofacContinuationActionFilter + { + private readonly HashSet _allFilters; + + /// + /// Initializes a new instance of the class. + /// + /// The collection of filter metadata blocks that this wrapper should run. + public ContinuationActionFilterWrapper(HashSet filterMetadata) + { + if (filterMetadata == null) + { + throw new ArgumentNullException(nameof(filterMetadata)); + } + + _allFilters = filterMetadata; + } + + private bool FilterMatchesMetadata(Meta> filter) + { + var metadata = filter.Metadata.TryGetValue(AutofacWebApiFilterProvider.FilterMetadataKey, out var metadataAsObject) + ? metadataAsObject as FilterMetadata + : null; + + return _allFilters.Contains(metadata); + } + + public bool AllowMultiple { get; } = true; + + public Task ExecuteActionFilterAsync( + HttpActionContext actionContext, + CancellationToken cancellationToken, + Func> continuation) + { + var dependencyScope = actionContext.Request.GetDependencyScope(); + var lifetimeScope = dependencyScope.GetRequestLifetimeScope(); + + var filters = lifetimeScope.Resolve>>>(); + + Func> result = continuation; + + Func> ChainContinuation(Func> next, IAutofacContinuationActionFilter innerFilter) + { + return () => innerFilter.ExecuteActionFilterAsync(actionContext, cancellationToken, next); + } + + foreach (var filterStage in filters.Reverse().Where(FilterMatchesMetadata)) + { + result = ChainContinuation(result, filterStage.Value.Value); + } + + return result(); + } + } +} \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs b/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs new file mode 100644 index 0000000..5a35733 --- /dev/null +++ b/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs @@ -0,0 +1,55 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Autofac.Integration.WebApi +{ + /// + /// An action filter that will be created for each controller request, and + /// executes using continuations, so the async context is preserved. + /// + public interface IAutofacContinuationActionFilter + { + /// + /// The method called when the filter executes. The filter should call 'continuation' to + /// continue processing the request. + /// + /// The context of the current action. + /// A cancellation token for the request. + /// The function to call that invokes the next filter in the chain. + [SuppressMessage("Microsoft.CodeQuality", "CA1068", Justification = "Matching parameter order in IActionFilter.")] + Task ExecuteActionFilterAsync( + HttpActionContext actionContext, + CancellationToken cancellationToken, + Func> next); + } +} \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs index 2b74cbf..e26b80f 100644 --- a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs +++ b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs @@ -36,6 +36,7 @@ using System.Web.Http.ModelBinding; using Autofac.Builder; using Autofac.Core; +using Autofac.Features.Metadata; using Autofac.Features.Scanning; namespace Autofac.Integration.WebApi @@ -242,6 +243,10 @@ public static void RegisterWebApiFilterProvider(this ContainerBuilder builder, H builder.Register(c => new AutofacWebApiFilterProvider(c.Resolve())) .As() .SingleInstance(); // It would be nice to scope this per request. + + // Register the adapter to turn the old IAutofacActionFilters into the new style. + builder.RegisterAdapter( + legacy => new AutofacActionFilterAdapter(legacy)); } /// @@ -257,7 +262,7 @@ public static IRegistrationBuilder> actionSelector) where TController : IHttpController { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilter, actionSelector); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, actionSelector); } /// @@ -270,7 +275,7 @@ public static IRegistrationBuilder(this IRegistrationBuilder registration) where TController : IHttpController { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilter); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter); } /// @@ -281,7 +286,7 @@ public static IRegistrationBuilder AsWebApiActionFilterForAllControllers(this IRegistrationBuilder registration) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilter, descriptor => true, FilterScope.Controller); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, (scope, descriptor) => true, FilterScope.Controller); } /// @@ -297,7 +302,7 @@ public static IRegistrationBuilder predicate, FilterScope filterScope = FilterScope.Action) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilter, predicate, filterScope); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, predicate, filterScope); } /// @@ -313,7 +318,7 @@ public static IRegistrationBuilder predicate, FilterScope filterScope = FilterScope.Action) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilter, predicate, filterScope); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, predicate, filterScope); } /// @@ -329,7 +334,7 @@ public static IRegistrationBuilder> actionSelector) where TController : IHttpController { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, actionSelector); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, actionSelector); } /// @@ -342,7 +347,7 @@ public static IRegistrationBuilder(this IRegistrationBuilder registration) where TController : IHttpController { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilterOverride); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilterOverride); } /// @@ -353,7 +358,7 @@ public static IRegistrationBuilder AsWebApiActionFilterOverrideForAllControllers(this IRegistrationBuilder registration) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, descriptor => true, FilterScope.Controller); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, (scope, descriptor) => true, FilterScope.Controller); } /// @@ -369,7 +374,7 @@ public static IRegistrationBuilder predicate, FilterScope filterScope = FilterScope.Action) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, predicate, filterScope); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, predicate, filterScope); } /// @@ -385,7 +390,7 @@ public static IRegistrationBuilder predicate, FilterScope filterScope = FilterScope.Action) { - return AsFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, predicate, filterScope); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilterOverride, predicate, filterScope); } /// @@ -907,6 +912,17 @@ private static IRegistrationBuilder(registration, filterCategory, (lifetime, action) => predicate(action), filterScope); } + private static IRegistrationBuilder + AsActionFilterFor( + IRegistrationBuilder registration, + AutofacFilterCategory filterCategory, + Func predicate, + FilterScope filterScope) + { + return AsActionFilterFor(registration, filterCategory, (lifetime, action) => predicate(action), filterScope); + } + private static IRegistrationBuilder AsFilterFor( IRegistrationBuilder(); } + private static IRegistrationBuilder + AsActionFilterFor( + IRegistrationBuilder registration, + AutofacFilterCategory filterCategory, + Func predicate, + FilterScope filterScope) + { + if (registration == null) throw new ArgumentNullException(nameof(registration)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); + if (filterScope != FilterScope.Action && filterScope != FilterScope.Controller) + throw new InvalidEnumArgumentException(nameof(filterScope), (int)filterScope, typeof(FilterScope)); + + var limitType = registration.ActivatorData.Activator.LimitType; + + bool isLegacyFilterType = false; + + if (limitType.IsAssignableTo()) + { + isLegacyFilterType = true; + } + else if (!limitType.IsAssignableTo()) + { + var message = string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.MustBeAssignableToFilterType, + limitType.FullName, + typeof(IAutofacContinuationActionFilter).FullName); + throw new ArgumentException(message, nameof(registration)); + } + + // Get the filter metadata set. + registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); + + var registrationMetadata = new FilterPredicateMetadata + { + Scope = filterScope, + FilterCategory = filterCategory, + Predicate = predicate + }; + + filterMeta.PredicateSet.Add(registrationMetadata); + + if (isLegacyFilterType) + { + return registration.As(); + } + + return registration.As(); + } + private static IRegistrationBuilder AsFilterFor(IRegistrationBuilder registration, AutofacFilterCategory filterCategory) where TController : IHttpController @@ -980,6 +1047,50 @@ private static IRegistrationBuilder(); } + private static IRegistrationBuilder + AsActionFilterFor(IRegistrationBuilder registration, AutofacFilterCategory filterCategory) + where TController : IHttpController + { + if (registration == null) throw new ArgumentNullException(nameof(registration)); + + var limitType = registration.ActivatorData.Activator.LimitType; + + bool isLegacyFilterType = false; + + if (limitType.IsAssignableTo()) + { + isLegacyFilterType = true; + } + else if (!limitType.IsAssignableTo()) + { + var message = string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.MustBeAssignableToFilterType, + limitType.FullName, + typeof(IAutofacContinuationActionFilter).FullName); + throw new ArgumentException(message, nameof(registration)); + } + + // Get the filter metadata set. + registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); + + var registrationMetadata = new FilterPredicateMetadata + { + Scope = FilterScope.Controller, + FilterCategory = filterCategory, + Predicate = (scope, descriptor) => descriptor.ControllerDescriptor.ControllerType == typeof(TController) + }; + + filterMeta.PredicateSet.Add(registrationMetadata); + + if (isLegacyFilterType) + { + return registration.As(); + } + + return registration.As(); + } + private static IRegistrationBuilder AsFilterFor( IRegistrationBuilder registration, @@ -1020,6 +1131,57 @@ private static IRegistrationBuilder(); } + private static IRegistrationBuilder + AsActionFilterFor( + IRegistrationBuilder registration, + AutofacFilterCategory filterCategory, + Expression> actionSelector) + where TController : IHttpController + { + if (registration == null) throw new ArgumentNullException(nameof(registration)); + if (actionSelector == null) throw new ArgumentNullException(nameof(actionSelector)); + + var limitType = registration.ActivatorData.Activator.LimitType; + + bool isLegacyFilterType = false; + + if (limitType.IsAssignableTo()) + { + isLegacyFilterType = true; + } + else if (!limitType.IsAssignableTo()) + { + var message = string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.MustBeAssignableToFilterType, + limitType.FullName, + typeof(IAutofacContinuationActionFilter).FullName); + throw new ArgumentException(message, nameof(registration)); + } + + // Get the filter metadata set. + registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); + + var method = GetMethodInfo(actionSelector); + + var registrationMetadata = new FilterPredicateMetadata + { + Scope = FilterScope.Action, + FilterCategory = filterCategory, + Predicate = (scope, descriptor) => typeof(TController).IsAssignableFrom(descriptor.ControllerDescriptor.ControllerType) && + ActionMethodMatches(descriptor, method) + }; + + filterMeta.PredicateSet.Add(registrationMetadata); + + if (isLegacyFilterType) + { + return registration.As(); + } + + return registration.As(); + } + private static void AsOverrideFor(ContainerBuilder builder, AutofacFilterCategory filterCategory) { builder.RegisterInstance(new AutofacOverrideFilter(typeof(TFilter))) diff --git a/test/Autofac.Integration.WebApi.Test/ActionFilterFixture.cs b/test/Autofac.Integration.WebApi.Test/ActionFilterFixture.cs index 3d54327..7d76461 100644 --- a/test/Autofac.Integration.WebApi.Test/ActionFilterFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/ActionFilterFixture.cs @@ -73,12 +73,12 @@ protected override Action ConfigureControllerFilterOverride() diff --git a/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj b/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj index ba40634..7bf19af 100644 --- a/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj +++ b/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj @@ -9,7 +9,7 @@ Properties Autofac.Integration.WebApi.Test Autofac.Integration.WebApi.Test - v4.5.2 + v4.6.1 512 $(NoWarn);CS1591;SA1602;SA1611;CA1812 ..\..\..\ @@ -60,7 +60,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -96,11 +96,14 @@ + + + @@ -110,6 +113,8 @@ + + diff --git a/test/Autofac.Integration.WebApi.Test/AutofacFilterBaseFixture.cs b/test/Autofac.Integration.WebApi.Test/AutofacFilterBaseFixture.cs index fba1bc2..80dc235 100644 --- a/test/Autofac.Integration.WebApi.Test/AutofacFilterBaseFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/AutofacFilterBaseFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Configuration; using System.Linq; using System.Web.Http; using System.Web.Http.Controllers; @@ -249,13 +250,17 @@ private void AssertSingleFilter( Action> configure) { var builder = new ContainerBuilder(); + var configuration = new HttpConfiguration(); builder.Register(c => new Logger()).InstancePerDependency(); configure(builder.Register(registration).InstancePerRequest()); + + // Need to do this so our adapter gets registered + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var actionDescriptor = BuildActionDescriptorForGetMethod(typeof(TController)); - var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); var wrapperType = GetWrapperType(); @@ -270,9 +275,12 @@ private void AssertNoFilter( var builder = new ContainerBuilder(); builder.Register(c => new Logger()).InstancePerDependency(); configure(builder.Register(registration).InstancePerRequest()); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var actionDescriptor = BuildActionDescriptorForGetMethod(typeof(TController)); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); @@ -292,9 +300,12 @@ private void AssertMultipleFilters( builder.Register(c => new Logger()).InstancePerDependency(); configure1(builder.Register(registration1).InstancePerRequest()); configure2(builder.Register(registration2).InstancePerRequest()); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); + var actionDescriptor = BuildActionDescriptorForGetMethod(); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); @@ -309,9 +320,11 @@ private static void AssertOverrideFilter(Action r { var builder = new ContainerBuilder(); registration(builder); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var actionDescriptor = BuildActionDescriptorForGetMethod(typeof(TController)); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); @@ -329,9 +342,11 @@ private void AssertOverrideFilter( var builder = new ContainerBuilder(); builder.Register(c => new Logger()).InstancePerDependency(); configure(builder.Register(registration).InstancePerRequest()); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var actionDescriptor = BuildActionDescriptorForGetMethod(typeof(TController)); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); @@ -348,10 +363,12 @@ private void AssertMultiControllerRegistration( { var builder = new ContainerBuilder(); builder.Register(c => new Logger()).InstancePerDependency(); + var configuration = new HttpConfiguration(); configure(builder.Register(registration).InstancePerRequest()); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var wrapperType = GetWrapperType(); @@ -382,9 +399,11 @@ private void AssertMultiControllerRegistration( builder.Register(c => new Logger()).InstancePerDependency(); configure1(builder.Register(registration1).InstancePerRequest()); configure2(builder.Register(registration2).InstancePerRequest()); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var wrapperType = GetWrapperType(); diff --git a/test/Autofac.Integration.WebApi.Test/AutofacWebApiFilterProviderFixture.cs b/test/Autofac.Integration.WebApi.Test/AutofacWebApiFilterProviderFixture.cs index 2ba1cf9..3dd77ec 100644 --- a/test/Autofac.Integration.WebApi.Test/AutofacWebApiFilterProviderFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/AutofacWebApiFilterProviderFixture.cs @@ -55,23 +55,6 @@ public void ReturnsFiltersWithoutPropertyInjectionForUnregisteredDependencies() Assert.Null(filter.Logger); } - [Fact] - public void CanRegisterMultipleFilterTypesAgainstSingleService() - { - var builder = new ContainerBuilder(); - builder.RegisterInstance(new TestCombinationFilter()) - .AsWebApiActionFilterFor() - .AsWebApiAuthenticationFilterFor() - .AsWebApiAuthorizationFilterFor() - .AsWebApiExceptionFilterFor(); - var container = builder.Build(); - - Assert.NotNull(container.Resolve()); - Assert.NotNull(container.Resolve()); - Assert.NotNull(container.Resolve()); - Assert.NotNull(container.Resolve()); - } - [Fact] public void ResolvesMultipleFiltersOfDifferentTypes() { @@ -94,12 +77,12 @@ public void ResolvesMultipleFiltersOfDifferentTypes() .AsWebApiActionFilterFor() .InstancePerRequest(); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var container = builder.Build(); var provider = new AutofacWebApiFilterProvider(container); - var configuration = new HttpConfiguration - { - DependencyResolver = new AutofacWebApiDependencyResolver(container) - }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); + var actionDescriptor = BuildActionDescriptorForGetMethod(); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); @@ -108,7 +91,7 @@ public void ResolvesMultipleFiltersOfDifferentTypes() Assert.Single(filters.OfType()); Assert.Single(filters.OfType()); Assert.Single(filters.OfType()); - Assert.Single(filters.OfType()); + Assert.Single(filters.OfType()); } private static ReflectedHttpActionDescriptor BuildActionDescriptorForGetMethod() diff --git a/test/Autofac.Integration.WebApi.Test/ActionFilterOverrideWrapperFixture.cs b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterOverrideWrapperFixture.cs similarity index 67% rename from test/Autofac.Integration.WebApi.Test/ActionFilterOverrideWrapperFixture.cs rename to test/Autofac.Integration.WebApi.Test/ContinuationActionFilterOverrideWrapperFixture.cs index d2083db..68d05ce 100644 --- a/test/Autofac.Integration.WebApi.Test/ActionFilterOverrideWrapperFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterOverrideWrapperFixture.cs @@ -5,12 +5,12 @@ namespace Autofac.Integration.WebApi.Test { - public class ActionFilterOverrideWrapperFixture + public class ContinuationActionFilterOverrideWrapperFixture { [Fact] public void FiltersToOverrideReturnsCorrectType() { - var wrapper = new ActionFilterOverrideWrapper(new HashSet()); + var wrapper = new ContinuationActionFilterOverrideWrapper(new HashSet()); Assert.Equal(typeof(IActionFilter), wrapper.FiltersToOverride); } } diff --git a/test/Autofac.Integration.WebApi.Test/ActionFilterWrapperFixture.cs b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs similarity index 61% rename from test/Autofac.Integration.WebApi.Test/ActionFilterWrapperFixture.cs rename to test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs index 5eb162c..a7751e4 100644 --- a/test/Autofac.Integration.WebApi.Test/ActionFilterWrapperFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Net; using System.Net.Http; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; @@ -12,12 +14,12 @@ namespace Autofac.Integration.WebApi.Test { - public class ActionFilterWrapperFixture + public class ContinuationActionFilterWrapperFixture { [Fact] public void RequiresFilterMetadata() { - var exception = Assert.Throws(() => new ActionFilterWrapper(null)); + var exception = Assert.Throws(() => new ContinuationActionFilterWrapper(null)); Assert.Equal("filterMetadata", exception.ParamName); } @@ -26,6 +28,8 @@ public async void WrapperResolvesActionFilterFromDependencyScope() { var builder = new ContainerBuilder(); builder.Register(c => new Logger()).InstancePerDependency(); + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); var activationCount = 0; builder.Register(c => new TestActionFilter(c.Resolve())) .AsWebApiActionFilterFor(c => c.Get()) @@ -40,14 +44,10 @@ public async void WrapperResolvesActionFilterFromDependencyScope() var methodInfo = typeof(TestController).GetMethod("Get"); var actionDescriptor = CreateActionDescriptor(methodInfo); var actionContext = new HttpActionContext(controllerContext, actionDescriptor); - var httpActionExecutedContext = new HttpActionExecutedContext(actionContext, null); - var wrapper = new ActionFilterWrapper(filterMetadata.ToSingleFilterHashSet()); + var wrapper = new ContinuationActionFilterWrapper(filterMetadata.ToSingleFilterHashSet()); - await wrapper.OnActionExecutingAsync(actionContext, CancellationToken.None); - Assert.Equal(1, activationCount); - - await wrapper.OnActionExecutedAsync(httpActionExecutedContext, CancellationToken.None); + await wrapper.ExecuteActionFilterAsync(actionContext, CancellationToken.None, () => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))); Assert.Equal(1, activationCount); } @@ -70,6 +70,10 @@ public async void RunsFiltersInCorrectOrder() .InstancePerRequest() .GetMetadata(out var testFilter2Meta); + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); var resolver = new AutofacWebApiDependencyResolver(container); @@ -77,15 +81,17 @@ public async void RunsFiltersInCorrectOrder() var methodInfo = typeof(TestController).GetMethod("Get"); var actionDescriptor = CreateActionDescriptor(methodInfo); var actionContext = new HttpActionContext(controllerContext, actionDescriptor); - var httpActionExecutedContext = new HttpActionExecutedContext(actionContext, null); - var wrapper = new ActionFilterWrapper(new HashSet + var wrapper = new ContinuationActionFilterWrapper(new HashSet { testFilter1Meta, testFilter2Meta }); - await wrapper.OnActionExecutingAsync(actionContext, CancellationToken.None); - await wrapper.OnActionExecutedAsync(httpActionExecutedContext, CancellationToken.None); + await wrapper.ExecuteActionFilterAsync( + actionContext, + CancellationToken.None, + () => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))); + Assert.Equal("TestActionFilter2.OnActionExecutingAsync", order[0]); Assert.Equal("TestActionFilter.OnActionExecutingAsync", order[1]); Assert.Equal("TestActionFilter.OnActionExecutedAsync", order[2]); @@ -126,6 +132,10 @@ public async void StopsIfFilterOnExecutingSetsResponse() .InstancePerRequest() .GetMetadata(out var testActionFilter3Metadata); + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); var resolver = new AutofacWebApiDependencyResolver(container); @@ -134,7 +144,7 @@ public async void StopsIfFilterOnExecutingSetsResponse() var actionDescriptor = CreateActionDescriptor(methodInfo); var actionContext = new HttpActionContext(controllerContext, actionDescriptor); - var wrapper = new ActionFilterWrapper(new HashSet + var wrapper = new ContinuationActionFilterWrapper(new HashSet { testActionFilterMetadata, testActionFilterWithResponseMetadata, @@ -142,7 +152,10 @@ public async void StopsIfFilterOnExecutingSetsResponse() testActionFilter3Metadata }); - await wrapper.OnActionExecutingAsync(actionContext, CancellationToken.None); + await wrapper.ExecuteActionFilterAsync( + actionContext, + CancellationToken.None, + () => throw new Exception("Should never reach here because a filter set the response.")); Assert.Equal("TestActionFilter3.OnActionExecutingAsync", order[0]); Assert.Equal("TestActionFilter2.OnActionExecutingAsync", order[1]); @@ -152,6 +165,70 @@ public async void StopsIfFilterOnExecutingSetsResponse() Assert.Equal(5, order.Count); } + + [Fact] + public void AsyncStatePreservedBetweenFilters() + { + // Issue #34 - Async/await context lost between filters. + var builder = new ContainerBuilder(); + var order = new List(); + + builder.Register(ctx => new DelegatingLogger(s => order.Add(s))) + .As() + .SingleInstance(); + // Autofac filters will resolve in reverse order. + builder.RegisterType() + .AsWebApiActionFilterFor(c => c.Get()) + .InstancePerRequest() + .GetMetadata(out var testFilter1Meta); + builder.RegisterType() + .AsWebApiActionFilterFor(c => c.Get()) + .InstancePerRequest() + .GetMetadata(out var testFilter2Meta); + + // Old filters always run after new ones, because they're + // adapted. + builder.RegisterType() + .AsWebApiActionFilterFor(c => c.Get()) + .InstancePerRequest() + .GetMetadata(out var testFilter3Meta); + builder.RegisterType() + .AsWebApiActionFilterFor(c => c.Get()) + .InstancePerRequest() + .GetMetadata(out var testFilter4Meta); + + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + + var container = builder.Build(); + + var resolver = new AutofacWebApiDependencyResolver(container); + var controllerContext = CreateControllerContext(resolver); + var methodInfo = typeof(TestController).GetMethod("Get"); + var actionDescriptor = CreateActionDescriptor(methodInfo); + var actionContext = new HttpActionContext(controllerContext, actionDescriptor); + var wrapper = new ContinuationActionFilterWrapper(new HashSet + { + testFilter1Meta, + testFilter2Meta, + testFilter3Meta, + testFilter4Meta + }); + + wrapper.ExecuteActionFilterAsync( + actionContext, + CancellationToken.None, + () => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))).Wait(); + + Assert.Equal("TestContinuationActionFilter ExecuteActionFilterAsync Before: 123", order[0]); + Assert.Equal("TestContinuationActionFilter2 ExecuteActionFilterAsync Before: 123", order[1]); + Assert.Equal("TestOldActionFilterAsyncAccess OnActionExecuting: 456", order[2]); + Assert.Equal("TestOldActionFilterAsyncAccess2 OnActionExecuted Local Value: localAsync", order[3]); + Assert.Equal("TestContinuationActionFilter2 ExecuteActionFilterAsync After: 456", order[4]); + Assert.Equal("TestContinuationActionFilter ExecuteActionFilterAsync After: 456", order[5]); + } + private static HttpActionDescriptor CreateActionDescriptor(MethodInfo methodInfo) { var controllerDescriptor = new HttpControllerDescriptor { ControllerType = methodInfo.DeclaringType }; diff --git a/test/Autofac.Integration.WebApi.Test/FilterOrderingFixture.cs b/test/Autofac.Integration.WebApi.Test/FilterOrderingFixture.cs index 280b443..15e4643 100644 --- a/test/Autofac.Integration.WebApi.Test/FilterOrderingFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/FilterOrderingFixture.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; +using Autofac.Features.Metadata; using Autofac.Integration.WebApi.Test.TestTypes; using Xunit; @@ -63,6 +65,11 @@ public async void FilterOrderForAllFilterTypes() builder.RegisterType>().AsWebApiExceptionFilterOverrideFor(c => c.Get()); builder.RegisterType>().AsWebApiExceptionFilterFor(c => c.Get()); builder.RegisterType>().AsWebApiExceptionFilterOverrideFor(c => c.Get()); + + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); // Set up the filter provider so we can resolve the set of filters @@ -79,7 +86,7 @@ public async void FilterOrderForAllFilterTypes() // resolution of the actual filters internally, so you won't // actually see them in this list. We have to fake an execution // pipeline to see what's really there. - var configuration = new HttpConfiguration { DependencyResolver = new AutofacWebApiDependencyResolver(container) }; + configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container); var filterInfos = provider.GetFilters(configuration, actionDescriptor).ToArray(); // Fake execution of the filters to force the lazy initialization. @@ -106,10 +113,8 @@ public async void FilterOrderForAllFilterTypes() await fi.OnAuthorizationAsync(actionContext, token); } - foreach (var fi in filterInfos.Select(f => f.Instance).OfType()) - { - await fi.OnActionExecutingAsync(actionContext, token); - } + // Emulate the action filter execution pipeline. + await ExecuteContinuationFilters(actionContext, filterInfos.Select(f => f.Instance).OfType(), token); foreach (var fi in filterInfos.Select(f => f.Instance).OfType()) { @@ -167,6 +172,24 @@ public async void FilterOrderForAllFilterTypes() } } + private static async Task ExecuteContinuationFilters(HttpActionContext actionContext, IEnumerable filters, CancellationToken cancellationToken) + { + // Terminate the pipeline last. + Func> result = () => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + + Func> ChainContinuation(Func> next, IActionFilter innerFilter) + { + return () => innerFilter.ExecuteActionFilterAsync(actionContext, cancellationToken, next); + } + + foreach (var filterStage in filters.Reverse()) + { + result = ChainContinuation(result, filterStage); + } + + await result(); + } + private class A { } diff --git a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs index bf916c1..81fa2aa 100644 --- a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs @@ -127,7 +127,7 @@ public void AsModelBinderForTypesThrowsExceptionWhenAllTypesNullInList() [Fact] public void AsModelBinderForTypesThrowsExceptionForEmptyTypeList() { - var types = new Type[0]; + var types = Array.Empty(); var builder = new ContainerBuilder(); var registration = builder.RegisterType(); Assert.Throws(() => registration.AsModelBinderForTypes(types)); @@ -294,6 +294,42 @@ public void AsActionFilterWhereRequiresPredicate() Assert.Equal("predicate", exception.ParamName); } + [Fact] + public void CanRegisterMultipleFilterTypesAgainstSingleService() + { + var builder = new ContainerBuilder(); + builder.RegisterInstance(new TestCombinationFilter()) + .AsWebApiActionFilterFor() + .AsWebApiAuthenticationFilterFor() + .AsWebApiAuthorizationFilterFor() + .AsWebApiExceptionFilterFor(); + + var configuration = new HttpConfiguration(); + builder.RegisterWebApiFilterProvider(configuration); + var container = builder.Build(); + + Assert.NotNull(container.Resolve()); + Assert.NotNull(container.Resolve()); + Assert.NotNull(container.Resolve()); + Assert.NotNull(container.Resolve()); + } + + [Fact] + public void AutofacActionFilterIsAdapted() + { + var builder = new ContainerBuilder(); + builder.RegisterInstance(new TestCombinationFilter()) + .AsWebApiActionFilterFor(); + + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + + var container = builder.Build(); + + Assert.NotNull(container.Resolve()); + } + [Fact] public void AsAuthorizationFilterForRequiresActionSelector() { diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs new file mode 100644 index 0000000..5b08602 --- /dev/null +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs @@ -0,0 +1,40 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System.Threading; + +namespace Autofac.Integration.WebApi.Test.TestTypes +{ + public static class TestAsyncContext + { + private static readonly AsyncLocal Data = new AsyncLocal(); + + public static string Value + { + get => Data.Value; + set => Data.Value = value; + } + } +} \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/ActionFilterOverrideWrapper.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs similarity index 55% rename from src/Autofac.Integration.WebApi/ActionFilterOverrideWrapper.cs rename to test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs index 304c25b..5391103 100644 --- a/src/Autofac.Integration.WebApi/ActionFilterOverrideWrapper.cs +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs @@ -1,5 +1,5 @@ // This software is part of the Autofac IoC container -// Copyright (c) 2013 Autofac Contributors +// Copyright (c) 2012 Autofac Contributors // https://autofac.org // // Permission is hereby granted, free of charge, to any person @@ -24,32 +24,33 @@ // OTHER DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Web.Http.Filters; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; -namespace Autofac.Integration.WebApi +namespace Autofac.Integration.WebApi.Test.TestTypes { - /// - /// Resolves a filter override for the specified metadata for each controller request. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] - internal sealed class ActionFilterOverrideWrapper : ActionFilterWrapper, IOverrideFilter + public class TestContinuationActionFilter : IAutofacContinuationActionFilter { - /// - /// Initializes a new instance of the class. - /// - /// The filter metadata. - public ActionFilterOverrideWrapper(HashSet filterMetadata) - : base(filterMetadata) + private readonly ILogger _logger; + + public TestContinuationActionFilter(ILogger logger) { + _logger = logger; } - /// - /// Gets the filters to override. - /// - public Type FiltersToOverride + public Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) { - get { return typeof(IActionFilter); } + TestAsyncContext.Value = "123"; + + _logger.Log("TestContinuationActionFilter ExecuteActionFilterAsync Before: " + TestAsyncContext.Value); + + var result = continuation(); + + _logger.Log("TestContinuationActionFilter ExecuteActionFilterAsync After: " + TestAsyncContext.Value); + + return result; } } } diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs new file mode 100644 index 0000000..f230b81 --- /dev/null +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs @@ -0,0 +1,54 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; + +namespace Autofac.Integration.WebApi.Test.TestTypes +{ + public class TestContinuationActionFilter2 : IAutofacContinuationActionFilter + { + private readonly ILogger _logger; + + public TestContinuationActionFilter2(ILogger logger) + { + _logger = logger; + } + + public Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> next) + { + _logger.Log("TestContinuationActionFilter2 ExecuteActionFilterAsync Before: " + TestAsyncContext.Value); + TestAsyncContext.Value = "456"; + var result = next(); + + _logger.Log("TestContinuationActionFilter2 ExecuteActionFilterAsync After: " + TestAsyncContext.Value); + + return result; + } + } +} \ No newline at end of file diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs new file mode 100644 index 0000000..ae62561 --- /dev/null +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs @@ -0,0 +1,60 @@ +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Autofac.Integration.WebApi.Test.TestTypes +{ + public class TestOldActionFilterAsyncContextAccess : IAutofacActionFilter + { + private readonly ILogger _logger; + + public TestOldActionFilterAsyncContextAccess(ILogger logger) + { + _logger = logger; + } + + public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) + { + _logger.Log("TestOldActionFilterAsyncAccess OnActionExecuted: " + TestAsyncContext.Value); + + return Task.CompletedTask; + } + + public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) + { + _logger.Log("TestOldActionFilterAsyncAccess OnActionExecuting: " + TestAsyncContext.Value); + + actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs new file mode 100644 index 0000000..dd9b35c --- /dev/null +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; +using System.Web.Http.Controllers; +using System.Web.Http.Filters; + +namespace Autofac.Integration.WebApi.Test.TestTypes +{ + public class TestOldActionFilterLocalAsync : IAutofacActionFilter + { + private readonly ILogger _logger; + + private static readonly AsyncLocal LocalValue = new AsyncLocal(); + + public TestOldActionFilterLocalAsync(ILogger logger) + { + _logger = logger; + } + + public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) + { + _logger.Log("TestOldActionFilterAsyncAccess2 OnActionExecuted Local Value: " + LocalValue.Value); + + return Task.CompletedTask; + } + + public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) + { + LocalValue.Value = "localAsync"; + + return Task.CompletedTask; + } + } +} \ No newline at end of file From ced5d93b185e4d11d60f0500c9fd9f7a9fe8e453 Mon Sep 17 00:00:00 2001 From: alistair Date: Sun, 28 Jul 2019 18:49:27 +0100 Subject: [PATCH 2/4] Added an explicit test for validating transaction scope is maintained, and cleaned up registrations. --- .../IAutofacContinuationActionFilter.cs | 3 +- .../RegistrationExtensions.cs | 152 +++++++----------- ...egistrationExtensionsResources.Designer.cs | 13 +- .../RegistrationExtensionsResources.resx | 3 + .../Autofac.Integration.WebApi.Test.csproj | 9 +- .../ContinuationActionFilterWrapperFixture.cs | 96 ++++++++--- .../RegistrationExtensionsFixture.cs | 2 +- .../TestTypes/TestAsyncContext.cs | 40 ----- ...tAccess.cs => TestCallbackActionFilter.cs} | 31 ++-- .../TestTypes/TestContinuationActionFilter.cs | 20 +-- ...uationActionFilterWithTransactionScope.cs} | 26 +-- .../TestOldActionFilterLocalAsync.cs | 33 ---- 12 files changed, 190 insertions(+), 238 deletions(-) delete mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs rename test/Autofac.Integration.WebApi.Test/TestTypes/{TestOldActionFilterAsyncContextAccess.cs => TestCallbackActionFilter.cs} (74%) rename test/Autofac.Integration.WebApi.Test/TestTypes/{TestContinuationActionFilter2.cs => TestContinuationActionFilterWithTransactionScope.cs} (65%) delete mode 100644 test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs diff --git a/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs b/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs index 5a35733..ab94264 100644 --- a/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs +++ b/src/Autofac.Integration.WebApi/IAutofacContinuationActionFilter.cs @@ -29,7 +29,6 @@ using System.Threading; using System.Threading.Tasks; using System.Web.Http.Controllers; -using System.Web.Http.Filters; namespace Autofac.Integration.WebApi { @@ -40,7 +39,7 @@ namespace Autofac.Integration.WebApi public interface IAutofacContinuationActionFilter { /// - /// The method called when the filter executes. The filter should call 'continuation' to + /// The method called when the filter executes. The filter should call 'next' to /// continue processing the request. /// /// The context of the current action. diff --git a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs index e26b80f..312c809 100644 --- a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs +++ b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs @@ -244,13 +244,13 @@ public static void RegisterWebApiFilterProvider(this ContainerBuilder builder, H .As() .SingleInstance(); // It would be nice to scope this per request. - // Register the adapter to turn the old IAutofacActionFilters into the new style. + // Register the adapter to turn the old IAutofacActionFilters into the new continuation style. builder.RegisterAdapter( legacy => new AutofacActionFilterAdapter(legacy)); } /// - /// Sets the provided registration to act as an for the specified controller action. + /// Sets the provided registration to act as an or for the specified controller action. /// /// The type of the controller. /// The registration. @@ -262,11 +262,11 @@ public static IRegistrationBuilder> actionSelector) where TController : IHttpController { - return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, actionSelector); + return AsActionFilterFor(registration, AutofacFilterCategory.ActionFilter, actionSelector); } /// - /// Sets the provided registration to act as an for the specified controller. + /// Sets the provided registration to act as an or for the specified controller. /// /// The type of the controller. /// The registration. @@ -279,7 +279,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an for all controllers. + /// Sets the provided registration to act as an or for all controllers. /// /// The registration. /// A registration builder allowing further configuration of the component. @@ -290,7 +290,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an , based on a predicate that filters which actions it is applied to. + /// Sets the provided registration to act as an or , based on a predicate that filters which actions it is applied to. /// /// The registration. /// A predicate that should return true if this filter should be applied to the specified action. @@ -306,7 +306,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an , based on a predicate that filters which actions it is applied to. + /// Sets the provided registration to act as an or , based on a predicate that filters which actions it is applied to. /// /// The registration. /// A predicate that should return true if this filter should be applied to the specified action. @@ -322,7 +322,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an override for the specified controller action. + /// Sets the provided registration to act as an or override for the specified controller action. /// /// The type of the controller. /// The registration. @@ -338,7 +338,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an override for the specified controller. + /// Sets the provided registration to act as an or override for the specified controller. /// /// The type of the controller. /// The registration. @@ -351,7 +351,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an override for all controllers. + /// Sets the provided registration to act as an or override for all controllers. /// /// The registration. /// A registration builder allowing further configuration of the component. @@ -362,7 +362,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an override, based on a predicate that filters which actions it is applied to. + /// Sets the provided registration to act as an or override, based on a predicate that filters which actions it is applied to. /// /// The registration. /// A predicate that should return true if this filter should be applied to the specified action. @@ -378,7 +378,7 @@ public static IRegistrationBuilder - /// Sets the provided registration to act as an override, based on a predicate that filters which actions it is applied to. + /// Sets the provided registration to act as an or override, based on a predicate that filters which actions it is applied to. /// /// The registration. /// A predicate that should return true if this filter should be applied to the specified action. @@ -936,17 +936,7 @@ private static IRegistrationBuilder()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(TFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateFilterType(); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -976,23 +966,7 @@ private static IRegistrationBuilder()) - { - isLegacyFilterType = true; - } - else if (!limitType.IsAssignableTo()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(IAutofacContinuationActionFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateActionFilterType(out var isLegacyFilterType); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -1020,17 +994,7 @@ private static IRegistrationBuilder()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(TFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateFilterType(); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -1053,23 +1017,7 @@ private static IRegistrationBuilder()) - { - isLegacyFilterType = true; - } - else if (!limitType.IsAssignableTo()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(IAutofacContinuationActionFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateActionFilterType(out var isLegacyFilterType); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -1101,17 +1049,7 @@ private static IRegistrationBuilder()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(TFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateFilterType(); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -1141,23 +1079,7 @@ private static IRegistrationBuilder()) - { - isLegacyFilterType = true; - } - else if (!limitType.IsAssignableTo()) - { - var message = string.Format( - CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, - limitType.FullName, - typeof(IAutofacContinuationActionFilter).FullName); - throw new ArgumentException(message, nameof(registration)); - } + registration.ValidateActionFilterType(out var isLegacyFilterType); // Get the filter metadata set. registration = registration.GetOrCreateMetadata(out FilterMetadata filterMeta); @@ -1182,6 +1104,44 @@ private static IRegistrationBuilder(); } + private static void ValidateFilterType(this IRegistrationBuilder registration) + { + var limitType = registration.ActivatorData.Activator.LimitType; + + if (!limitType.IsAssignableTo()) + { + var message = string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.MustBeAssignableToFilterType, + limitType.FullName, + typeof(TFilter).FullName); + throw new ArgumentException(message, nameof(registration)); + } + } + + private static void ValidateActionFilterType( + this IRegistrationBuilder registration, + out bool isLegacyFilterType) + { + var limitType = registration.ActivatorData.Activator.LimitType; + + isLegacyFilterType = false; + + if (limitType.IsAssignableTo()) + { + isLegacyFilterType = true; + } + else if (!limitType.IsAssignableTo()) + { + var message = string.Format( + CultureInfo.CurrentCulture, + RegistrationExtensionsResources.MustBeAssignableToFilterType, + limitType.FullName, + typeof(IAutofacContinuationActionFilter).FullName); + throw new ArgumentException(message, nameof(registration)); + } + } + private static void AsOverrideFor(ContainerBuilder builder, AutofacFilterCategory filterCategory) { builder.RegisterInstance(new AutofacOverrideFilter(typeof(TFilter))) diff --git a/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.Designer.cs b/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.Designer.cs index f50224f..582b63d 100644 --- a/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.Designer.cs +++ b/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18051 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace Autofac.Integration.WebApi { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class RegistrationExtensionsResources { @@ -78,6 +78,15 @@ internal static string ListMustNotBeEmptyOrContainNulls { } } + /// + /// Looks up a localized string similar to The type '{0}' must be assignable to '{1}' or '{2}'.. + /// + internal static string MustBeAssignableToActionFilterType { + get { + return ResourceManager.GetString("MustBeAssignableToActionFilterType", resourceCulture); + } + } + /// /// Looks up a localized string similar to The type '{0}' must be assignable to '{1}'.. /// diff --git a/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.resx b/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.resx index caa0153..789988d 100644 --- a/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.resx +++ b/src/Autofac.Integration.WebApi/RegistrationExtensionsResources.resx @@ -123,6 +123,9 @@ Type list may not be empty or contain all null values. + + The type '{0}' must be assignable to '{1}' or '{2}'. + The type '{0}' must be assignable to '{1}'. diff --git a/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj b/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj index 7bf19af..0bf0e08 100644 --- a/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj +++ b/test/Autofac.Integration.WebApi.Test/Autofac.Integration.WebApi.Test.csproj @@ -9,7 +9,7 @@ Properties Autofac.Integration.WebApi.Test Autofac.Integration.WebApi.Test - v4.6.1 + v4.5.2 512 $(NoWarn);CS1591;SA1602;SA1611;CA1812 ..\..\..\ @@ -55,6 +55,7 @@ + @@ -92,18 +93,18 @@ + - - + @@ -113,8 +114,6 @@ - - diff --git a/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs index a7751e4..c0d2b2a 100644 --- a/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/ContinuationActionFilterWrapperFixture.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using System.Transactions; using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; @@ -165,37 +166,87 @@ await wrapper.ExecuteActionFilterAsync( Assert.Equal(5, order.Count); } - [Fact] - public void AsyncStatePreservedBetweenFilters() + public void TransactionScopePreservedBetweenStandardFilters() { // Issue #34 - Async/await context lost between filters. var builder = new ContainerBuilder(); - var order = new List(); - builder.Register(ctx => new DelegatingLogger(s => order.Add(s))) - .As() - .SingleInstance(); + TransactionScope scope = null; + // Autofac filters will resolve in reverse order. - builder.RegisterType() + builder.Register(s => new TestCallbackActionFilter( + () => + Assert.NotNull(Transaction.Current), + () => + Assert.NotNull(Transaction.Current))) .AsWebApiActionFilterFor(c => c.Get()) .InstancePerRequest() .GetMetadata(out var testFilter1Meta); - builder.RegisterType() + builder.Register(s => new TestCallbackActionFilter( + () => + scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled), + () => + { + Assert.NotNull(Transaction.Current); + scope.Dispose(); + Assert.Null(Transaction.Current); + })) .AsWebApiActionFilterFor(c => c.Get()) .InstancePerRequest() .GetMetadata(out var testFilter2Meta); - // Old filters always run after new ones, because they're - // adapted. - builder.RegisterType() + var configuration = new HttpConfiguration(); + + builder.RegisterWebApiFilterProvider(configuration); + + var container = builder.Build(); + + var resolver = new AutofacWebApiDependencyResolver(container); + var controllerContext = CreateControllerContext(resolver); + var methodInfo = typeof(TestController).GetMethod("Get"); + var actionDescriptor = CreateActionDescriptor(methodInfo); + var actionContext = new HttpActionContext(controllerContext, actionDescriptor); + var wrapper = new ContinuationActionFilterWrapper(new HashSet + { + testFilter1Meta, + testFilter2Meta + }); + + wrapper.ExecuteActionFilterAsync( + actionContext, + CancellationToken.None, + () => + { + Assert.NotNull(Transaction.Current); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + }).Wait(); + } + + + [Fact] + public void TransactionScopePreservedBetweenContinuationFilters() + { + // Issue #34 - Async/await context lost between filters. + var builder = new ContainerBuilder(); + + // Autofac filters will resolve in reverse order. + builder.Register(s => new TestContinuationActionFilter( + () => + Assert.NotNull(Transaction.Current), + () => + Assert.NotNull(Transaction.Current))) .AsWebApiActionFilterFor(c => c.Get()) .InstancePerRequest() - .GetMetadata(out var testFilter3Meta); - builder.RegisterType() + .GetMetadata(out var testFilter1Meta); + builder.Register(s => new TestContinuationActionFilterWithTransactionScope( + () => + Assert.Null(Transaction.Current), + () => + Assert.Null(Transaction.Current))) .AsWebApiActionFilterFor(c => c.Get()) .InstancePerRequest() - .GetMetadata(out var testFilter4Meta); + .GetMetadata(out var testFilter2Meta); var configuration = new HttpConfiguration(); @@ -211,22 +262,17 @@ public void AsyncStatePreservedBetweenFilters() var wrapper = new ContinuationActionFilterWrapper(new HashSet { testFilter1Meta, - testFilter2Meta, - testFilter3Meta, - testFilter4Meta + testFilter2Meta }); wrapper.ExecuteActionFilterAsync( actionContext, CancellationToken.None, - () => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK))).Wait(); - - Assert.Equal("TestContinuationActionFilter ExecuteActionFilterAsync Before: 123", order[0]); - Assert.Equal("TestContinuationActionFilter2 ExecuteActionFilterAsync Before: 123", order[1]); - Assert.Equal("TestOldActionFilterAsyncAccess OnActionExecuting: 456", order[2]); - Assert.Equal("TestOldActionFilterAsyncAccess2 OnActionExecuted Local Value: localAsync", order[3]); - Assert.Equal("TestContinuationActionFilter2 ExecuteActionFilterAsync After: 456", order[4]); - Assert.Equal("TestContinuationActionFilter ExecuteActionFilterAsync After: 456", order[5]); + () => + { + Assert.NotNull(Transaction.Current); + return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)); + }).Wait(); } private static HttpActionDescriptor CreateActionDescriptor(MethodInfo methodInfo) diff --git a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs index 81fa2aa..fc594ff 100644 --- a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs @@ -127,7 +127,7 @@ public void AsModelBinderForTypesThrowsExceptionWhenAllTypesNullInList() [Fact] public void AsModelBinderForTypesThrowsExceptionForEmptyTypeList() { - var types = Array.Empty(); + var types = new Type[0]; var builder = new ContainerBuilder(); var registration = builder.RegisterType(); Assert.Throws(() => registration.AsModelBinderForTypes(types)); diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs deleted file mode 100644 index 5b08602..0000000 --- a/test/Autofac.Integration.WebApi.Test/TestTypes/TestAsyncContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -// This software is part of the Autofac IoC container -// Copyright (c) 2012 Autofac Contributors -// https://autofac.org -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. - -using System.Threading; - -namespace Autofac.Integration.WebApi.Test.TestTypes -{ - public static class TestAsyncContext - { - private static readonly AsyncLocal Data = new AsyncLocal(); - - public static string Value - { - get => Data.Value; - set => Data.Value = value; - } - } -} \ No newline at end of file diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestCallbackActionFilter.cs similarity index 74% rename from test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs rename to test/Autofac.Integration.WebApi.Test/TestTypes/TestCallbackActionFilter.cs index ae62561..c27e37a 100644 --- a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterAsyncContextAccess.cs +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestCallbackActionFilter.cs @@ -23,38 +23,39 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. -using System.Net; +using System; +using System.Collections.Generic; using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Web.Http; using System.Web.Http.Controllers; using System.Web.Http.Filters; +using System.Web.Http.ModelBinding; namespace Autofac.Integration.WebApi.Test.TestTypes { - public class TestOldActionFilterAsyncContextAccess : IAutofacActionFilter + public class TestCallbackActionFilter : IAutofacActionFilter { - private readonly ILogger _logger; + private readonly Action _executing; + private readonly Action _executed; - public TestOldActionFilterAsyncContextAccess(ILogger logger) + public TestCallbackActionFilter(Action executing, Action executed) { - _logger = logger; + _executing = executing; + _executed = executed; } - public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) + public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) { - _logger.Log("TestOldActionFilterAsyncAccess OnActionExecuted: " + TestAsyncContext.Value); - - return Task.CompletedTask; + _executing(); + return Task.FromResult(0); } - public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) + public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) { - _logger.Log("TestOldActionFilterAsyncAccess OnActionExecuting: " + TestAsyncContext.Value); - - actionContext.Response = new HttpResponseMessage(HttpStatusCode.OK); - - return Task.CompletedTask; + _executed(); + return Task.FromResult(0); } } } \ No newline at end of file diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs index 5391103..fcb3077 100644 --- a/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter.cs @@ -33,24 +33,24 @@ namespace Autofac.Integration.WebApi.Test.TestTypes { public class TestContinuationActionFilter : IAutofacContinuationActionFilter { - private readonly ILogger _logger; + private readonly Action _before; + private readonly Action _after; - public TestContinuationActionFilter(ILogger logger) + public TestContinuationActionFilter(Action before, Action after) { - _logger = logger; + _before = before; + _after = after; } - public Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> continuation) + public async Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> next) { - TestAsyncContext.Value = "123"; + _before(); - _logger.Log("TestContinuationActionFilter ExecuteActionFilterAsync Before: " + TestAsyncContext.Value); + var result = await next(); - var result = continuation(); - - _logger.Log("TestContinuationActionFilter ExecuteActionFilterAsync After: " + TestAsyncContext.Value); + _after(); return result; } } -} +} \ No newline at end of file diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilterWithTransactionScope.cs similarity index 65% rename from test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs rename to test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilterWithTransactionScope.cs index f230b81..933360d 100644 --- a/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilter2.cs +++ b/test/Autofac.Integration.WebApi.Test/TestTypes/TestContinuationActionFilterWithTransactionScope.cs @@ -27,26 +27,34 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using System.Transactions; using System.Web.Http.Controllers; namespace Autofac.Integration.WebApi.Test.TestTypes { - public class TestContinuationActionFilter2 : IAutofacContinuationActionFilter + public class TestContinuationActionFilterWithTransactionScope : IAutofacContinuationActionFilter { - private readonly ILogger _logger; + private readonly Action _before; + private readonly Action _after; - public TestContinuationActionFilter2(ILogger logger) + public TestContinuationActionFilterWithTransactionScope(Action before, Action after) { - _logger = logger; + _before = before; + _after = after; } - public Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> next) + public async Task ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func> next) { - _logger.Log("TestContinuationActionFilter2 ExecuteActionFilterAsync Before: " + TestAsyncContext.Value); - TestAsyncContext.Value = "456"; - var result = next(); + _before(); - _logger.Log("TestContinuationActionFilter2 ExecuteActionFilterAsync After: " + TestAsyncContext.Value); + HttpResponseMessage result; + + using (new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) + { + result = await next(); + } + + _after(); return result; } diff --git a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs b/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs deleted file mode 100644 index dd9b35c..0000000 --- a/test/Autofac.Integration.WebApi.Test/TestTypes/TestOldActionFilterLocalAsync.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Threading; -using System.Threading.Tasks; -using System.Web.Http.Controllers; -using System.Web.Http.Filters; - -namespace Autofac.Integration.WebApi.Test.TestTypes -{ - public class TestOldActionFilterLocalAsync : IAutofacActionFilter - { - private readonly ILogger _logger; - - private static readonly AsyncLocal LocalValue = new AsyncLocal(); - - public TestOldActionFilterLocalAsync(ILogger logger) - { - _logger = logger; - } - - public Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken) - { - _logger.Log("TestOldActionFilterAsyncAccess2 OnActionExecuted Local Value: " + LocalValue.Value); - - return Task.CompletedTask; - } - - public Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken) - { - LocalValue.Value = "localAsync"; - - return Task.CompletedTask; - } - } -} \ No newline at end of file From e617f52e1c5d9007b4fefa312e34f2693a0484b6 Mon Sep 17 00:00:00 2001 From: alistair Date: Sun, 28 Jul 2019 19:10:40 +0100 Subject: [PATCH 3/4] Tidying comments and some unused elements, added a test for explicit dual interface exception. --- .../AutofacFilterCategory.cs | 10 ------- ...ContinuationActionFilterOverrideWrapper.cs | 27 ++++++++++++++++++- .../ContinuationActionFilterWrapper.cs | 21 ++++++++------- .../RegistrationExtensions.cs | 3 ++- .../RegistrationExtensionsFixture.cs | 5 ++++ 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs b/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs index 50585f8..74608d4 100644 --- a/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs +++ b/src/Autofac.Integration.WebApi/AutofacFilterCategory.cs @@ -50,16 +50,6 @@ internal enum AutofacFilterCategory /// AuthenticationFilterOverride, - /// - /// Action filters using the continuation style. - /// - ContinuationActionFilter, - - /// - /// Action filter overrides using the continuation style. - /// - ContinuationActionFilterOverride, - /// /// Action filters /// diff --git a/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs b/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs index 139f3b8..99ba89d 100644 --- a/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs +++ b/src/Autofac.Integration.WebApi/ContinuationActionFilterOverrideWrapper.cs @@ -1,4 +1,29 @@ -using System; +// This software is part of the Autofac IoC container +// Copyright (c) 2012 Autofac Contributors +// https://autofac.org +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +using System; using System.Collections.Generic; using System.Web.Http.Filters; diff --git a/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs b/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs index c81817a..5fc34c9 100644 --- a/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs +++ b/src/Autofac.Integration.WebApi/ContinuationActionFilterWrapper.cs @@ -56,15 +56,6 @@ public ContinuationActionFilterWrapper(HashSet filterMetadata) _allFilters = filterMetadata; } - private bool FilterMatchesMetadata(Meta> filter) - { - var metadata = filter.Metadata.TryGetValue(AutofacWebApiFilterProvider.FilterMetadataKey, out var metadataAsObject) - ? metadataAsObject as FilterMetadata - : null; - - return _allFilters.Contains(metadata); - } - public bool AllowMultiple { get; } = true; public Task ExecuteActionFilterAsync( @@ -84,6 +75,9 @@ Func> ChainContinuation(Func return () => innerFilter.ExecuteActionFilterAsync(actionContext, cancellationToken, next); } + // We go backwards for the beginning of the set of filters, where + // the last one invokes the provided continuation, the previous one invokes the last one, and so on, + // until there's a callback that invokes the first filter. foreach (var filterStage in filters.Reverse().Where(FilterMatchesMetadata)) { result = ChainContinuation(result, filterStage.Value.Value); @@ -91,5 +85,14 @@ Func> ChainContinuation(Func return result(); } + + private bool FilterMatchesMetadata(Meta> filter) + { + var metadata = filter.Metadata.TryGetValue(AutofacWebApiFilterProvider.FilterMetadataKey, out var metadataAsObject) + ? metadataAsObject as FilterMetadata + : null; + + return _allFilters.Contains(metadata); + } } } \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs index 312c809..cff1099 100644 --- a/src/Autofac.Integration.WebApi/RegistrationExtensions.cs +++ b/src/Autofac.Integration.WebApi/RegistrationExtensions.cs @@ -1135,8 +1135,9 @@ private static void ValidateActionFilterType( { var message = string.Format( CultureInfo.CurrentCulture, - RegistrationExtensionsResources.MustBeAssignableToFilterType, + RegistrationExtensionsResources.MustBeAssignableToActionFilterType, limitType.FullName, + typeof(IAutofacActionFilter).FullName, typeof(IAutofacContinuationActionFilter).FullName); throw new ArgumentException(message, nameof(registration)); } diff --git a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs index fc594ff..e74ddb9 100644 --- a/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs +++ b/test/Autofac.Integration.WebApi.Test/RegistrationExtensionsFixture.cs @@ -283,6 +283,11 @@ public void AsActionFilterForAllControllersMustBeActionFilter() () => builder.RegisterInstance(new object()).AsWebApiActionFilterForAllControllers()); Assert.Equal("registration", exception.ParamName); + Assert.StartsWith( + "The type 'System.Object' must be assignable to 'Autofac.Integration.WebApi.IAutofacActionFilter'" + + " or 'Autofac.Integration.WebApi.IAutofacContinuationActionFilter'.", + exception.Message, + StringComparison.CurrentCulture); } [Fact] From 1389f22fed75ae7d5368892bfc175549379068a1 Mon Sep 17 00:00:00 2001 From: alistairjevans Date: Thu, 15 Aug 2019 21:29:41 +0100 Subject: [PATCH 4/4] Update license information to reference the ASP.NET stack license. --- LICENSE | 3 +++ src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/LICENSE b/LICENSE index 3e59e20..7e4a3d3 100644 --- a/LICENSE +++ b/LICENSE @@ -20,3 +20,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +This software includes code from the open source Microsoft ASP.NET web stack, +licensed under the Apache 2.0 license. You may obtain a copy of the license at +http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file diff --git a/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs b/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs index 1b8301c..ad3385e 100644 --- a/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs +++ b/src/Autofac.Integration.WebApi/AutofacActionFilterAdapter.cs @@ -22,6 +22,10 @@ // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. +// +// Portions of this file come from the Microsoft ASP.NET web stack, licensed +// under the Apache 2.0 license. You may obtain a copy of the license at +// http://www.apache.org/licenses/LICENSE-2.0 using System; using System.Diagnostics.CodeAnalysis;