diff --git a/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs b/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs new file mode 100644 index 00000000..23b1ba74 --- /dev/null +++ b/src/OpenIddict.Mvc/Internal/OpenIddictMvcConfiguration.cs @@ -0,0 +1,39 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using AspNet.Security.OpenIdConnect.Primitives; +using JetBrains.Annotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; + +namespace OpenIddict.Mvc.Internal +{ + /// + /// Contains the methods required to ensure that the OpenIddict MVC configuration is valid. + /// Note: this API supports the OpenIddict infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future minor releases. + /// + public class OpenIddictMvcConfiguration : IConfigureOptions + { + /// + /// Registers the OpenIddict MVC components in the MVC options. + /// + /// The options instance to initialize. + public void Configure([NotNull] MvcOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ModelBinderProviders.Insert(0, new OpenIddictMvcBinderProvider()); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(OpenIdConnectRequest))); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(OpenIdConnectResponse))); + } + } +} diff --git a/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs index 19320f1b..ff22394a 100644 --- a/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs +++ b/src/OpenIddict.Mvc/OpenIddictMvcExtensions.cs @@ -7,6 +7,8 @@ using System; using JetBrains.Annotations; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using OpenIddict.Mvc.Internal; namespace Microsoft.Extensions.DependencyInjection @@ -29,20 +31,9 @@ namespace Microsoft.Extensions.DependencyInjection throw new ArgumentNullException(nameof(builder)); } - builder.Services.Configure(options => - { - // Skip the binder registration if it was already added to the providers collection. - for (var index = 0; index < options.ModelBinderProviders.Count; index++) - { - var provider = options.ModelBinderProviders[index]; - if (provider is OpenIddictMvcBinderProvider) - { - return; - } - } - - options.ModelBinderProviders.Insert(0, new OpenIddictMvcBinderProvider()); - }); + // Register the options initializer used by the OpenIddict MVC binding/validation components. + // Note: TryAddEnumerable() is used here to ensure the initializer is only registered once. + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, OpenIddictMvcConfiguration>()); return new OpenIddictMvcBuilder(builder.Services); } diff --git a/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs b/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs new file mode 100644 index 00000000..8b76a50b --- /dev/null +++ b/test/OpenIddict.Mvc.Tests/Internal/OpenIddictMvcConfigurationTests.cs @@ -0,0 +1,71 @@ +/* + * Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) + * See https://github.com/openiddict/openiddict-core for more information concerning + * the license and the contributors participating to this project. + */ + +using System; +using System.Linq; +using AspNet.Security.OpenIdConnect.Primitives; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using OpenIddict.Mvc.Internal; +using Xunit; + +namespace OpenIddict.Mvc.Tests +{ + public class OpenIddictConfigurationExtensionsTests + { + [Fact] + public void Configure_ThrowsAnExceptionForNullOptions() + { + // Arrange + var configuration = new OpenIddictMvcConfiguration(); + + // Act and assert + var exception = Assert.Throws(() => configuration.Configure(null)); + + Assert.Equal("options", exception.ParamName); + } + + [Fact] + public void Configure_RegistersModelBinderProvider() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictServerBuilder(services); + + // Act + builder.UseMvc(); + + var options = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + } + + [Fact] + public void Configure_RegistersModelMetadataDetailsProviders() + { + // Arrange + var services = new ServiceCollection(); + services.AddOptions(); + + var builder = new OpenIddictServerBuilder(services); + + // Act + builder.UseMvc(); + + var options = services.BuildServiceProvider().GetRequiredService>(); + + // Assert + var providers = options.Value.ModelMetadataDetailsProviders.OfType(); + Assert.Contains(providers, provider => provider.Type == typeof(OpenIdConnectRequest)); + Assert.Contains(providers, provider => provider.Type == typeof(OpenIdConnectResponse)); + } + } +} diff --git a/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs index 027eeec6..6421d311 100644 --- a/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs +++ b/test/OpenIddict.Mvc.Tests/OpenIddictMvcExtensionsTests.cs @@ -41,7 +41,7 @@ namespace OpenIddict.Mvc.Tests } [Fact] - public void UseMvc_RegistersModelBinderProvider() + public void UseMvc_RegistersConfiguration() { // Arrange var services = new ServiceCollection(); @@ -52,11 +52,9 @@ namespace OpenIddict.Mvc.Tests // Act builder.UseMvc(); - var provider = services.BuildServiceProvider(); - var options = provider.GetRequiredService>(); - // Assert - Assert.Contains(options.Value.ModelBinderProviders, binder => binder is OpenIddictMvcBinderProvider); + Assert.Contains(services, service => service.ServiceType == typeof(IConfigureOptions) && + service.ImplementationType == typeof(OpenIddictMvcConfiguration)); } } }