Skip to content

Commit

Permalink
Add DataAnnotations based validation (#272)
Browse files Browse the repository at this point in the history
  • Loading branch information
HaoK committed Aug 28, 2018
1 parent e93181d commit dfcde1b
Show file tree
Hide file tree
Showing 21 changed files with 551 additions and 18 deletions.
7 changes: 7 additions & 0 deletions Options.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
version.xml = version.xml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Options.DataAnnotations", "src\Microsoft.Extensions.Options.DataAnnotations\Microsoft.Extensions.Options.DataAnnotations.csproj", "{D0EB1487-D9E9-4C58-A907-BCD595993251}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -55,6 +57,10 @@ Global
{BA4EF3CE-1829-4E0E-8281-BD503FF8A682}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BA4EF3CE-1829-4E0E-8281-BD503FF8A682}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BA4EF3CE-1829-4E0E-8281-BD503FF8A682}.Release|Any CPU.Build.0 = Release|Any CPU
{D0EB1487-D9E9-4C58-A907-BCD595993251}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0EB1487-D9E9-4C58-A907-BCD595993251}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0EB1487-D9E9-4C58-A907-BCD595993251}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0EB1487-D9E9-4C58-A907-BCD595993251}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -63,6 +69,7 @@ Global
{16BADE2F-1184-4518-8A70-B68A19D0805B} = {0A4664A0-CB48-4338-A6B7-02E28DF62CBA}
{6ACF4BAB-2F09-4DA6-B273-27E4282865EB} = {10221BD9-FD19-4809-B680-7628CB87926B}
{BA4EF3CE-1829-4E0E-8281-BD503FF8A682} = {10221BD9-FD19-4809-B680-7628CB87926B}
{D0EB1487-D9E9-4C58-A907-BCD595993251} = {10221BD9-FD19-4809-B680-7628CB87926B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A4D0BBDB-82DD-4B7E-981F-E6B90F3850B6}
Expand Down
1 change: 1 addition & 0 deletions build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<MicrosoftNETCoreApp21PackageVersion>2.1.2</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview1-26618-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<SystemComponentModelAnnotationsPackageVersion>4.6.0-preview1-26617-01</SystemComponentModelAnnotationsPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>
<XunitRunnerVisualStudioPackageVersion>2.4.0</XunitRunnerVisualStudioPackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ public ConfigurationChangeTokenSource(string name, IConfiguration config)
Name = name ?? Options.DefaultName;
}

/// <summary>
/// The name of the option instance being changed.
/// </summary>
public string Name { get; }

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Description>Provides additional configuration specific functionality related to Options.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn);CS1591</NoWarn>
<NoWarn>$(NoWarn)</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;configuration;options</PackageTags>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace Microsoft.Extensions.Options
{
/// <summary>
/// Implementation of <see cref="IValidateOptions{TOptions}"/> that uses DataAnnotation's <see cref="Validator"/> for validation.
/// </summary>
/// <typeparam name="TOptions">The instance being validated.</typeparam>
public class DataAnnotationValidateOptions<TOptions> : IValidateOptions<TOptions> where TOptions : class
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="name"></param>
public DataAnnotationValidateOptions(string name)
{
Name = name;
}

/// <summary>
/// The options name.
/// </summary>
public string Name { get; }

/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
public ValidateOptionsResult Validate(string name, TOptions options)
{
// Null name is used to configure all named options.
if (Name == null || name == Name)
{
var validationResults = new List<ValidationResult>();
if (Validator.TryValidateObject(options,
new ValidationContext(options, serviceProvider: null, items: null),
validationResults,
validateAllProperties: true))
{
return ValidateOptionsResult.Success;
}

return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
validationResults.Select(r => "DataAnnotation validation failed for members " +
String.Join(", ", r.MemberNames) +
" with the error '" + r.ErrorMessage + "'.")));
}

// Ignored if not validating this instance.
return ValidateOptionsResult.Skip;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Description>Provides additional DataAnnotations specific functionality related to Options.</Description>
<TargetFramework>netstandard2.0</TargetFramework>
<NoWarn>$(NoWarn)</NoWarn>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;validation;options</PackageTags>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Extensions.Options\Microsoft.Extensions.Options.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="$(MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion)" />
<PackageReference Include="System.ComponentModel.Annotations" Version="$(SystemComponentModelAnnotationsPackageVersion)" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.Extensions.Options;

namespace Microsoft.Extensions.DependencyInjection
{
/// <summary>
/// Extension methods for adding configuration related options services to the DI container via <see cref="OptionsBuilder{TOptions}"/>.
/// </summary>
public static class OptionsBuilderDataAnnotationsExtensions
{
/// <summary>
/// Register this options instance for validation of its DataAnnotations.
/// </summary>
/// <typeparam name="TOptions">The options type to be configured.</typeparam>
/// <param name="optionsBuilder">The options builder to add the services to.</param>
/// <returns>The <see cref="OptionsBuilder{TOptions}"/> so that additional calls can be chained.</returns>
public static OptionsBuilder<TOptions> ValidateDataAnnotations<TOptions>(this OptionsBuilder<TOptions> optionsBuilder) where TOptions : class
{
optionsBuilder.Services.AddSingleton<IValidateOptions<TOptions>>(new DataAnnotationValidateOptions<TOptions>(optionsBuilder.Name));
return optionsBuilder;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{
}
102 changes: 96 additions & 6 deletions src/Microsoft.Extensions.Options/ConfigureNamedOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public ConfigureNamedOptions(string name, Action<TOptions> action)
/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name"></param>
/// <param name="options"></param>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -51,6 +51,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

Expand Down Expand Up @@ -86,8 +90,16 @@ public ConfigureNamedOptions(string name, TDep dependency, Action<TOptions, TDep
/// </summary>
public Action<TOptions, TDep> Action { get; }

/// <summary>
/// The dependency.
/// </summary>
public TDep Dependency { get; }

/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -102,6 +114,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

Expand Down Expand Up @@ -141,10 +157,21 @@ public ConfigureNamedOptions(string name, TDep1 dependency, TDep2 dependency2, A
/// </summary>
public Action<TOptions, TDep1, TDep2> Action { get; }

/// <summary>
/// The first dependency.
/// </summary>
public TDep1 Dependency1 { get; }

/// <summary>
/// The second dependency.
/// </summary>
public TDep2 Dependency2 { get; }

/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -159,6 +186,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

Expand Down Expand Up @@ -202,13 +233,26 @@ public ConfigureNamedOptions(string name, TDep1 dependency, TDep2 dependency2, T
/// </summary>
public Action<TOptions, TDep1, TDep2, TDep3> Action { get; }

/// <summary>
/// The first dependency.
/// </summary>
public TDep1 Dependency1 { get; }

/// <summary>
/// The second dependency.
/// </summary>
public TDep2 Dependency2 { get; }

/// <summary>
/// The third dependency.
/// </summary>
public TDep3 Dependency3 { get; }


/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -223,6 +267,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

Expand Down Expand Up @@ -270,15 +318,31 @@ public ConfigureNamedOptions(string name, TDep1 dependency1, TDep2 dependency2,
/// </summary>
public Action<TOptions, TDep1, TDep2, TDep3, TDep4> Action { get; }

/// <summary>
/// The first dependency.
/// </summary>
public TDep1 Dependency1 { get; }

/// <summary>
/// The second dependency.
/// </summary>
public TDep2 Dependency2 { get; }

/// <summary>
/// The third dependency.
/// </summary>
public TDep3 Dependency3 { get; }

/// <summary>
/// The fourth dependency.
/// </summary>
public TDep4 Dependency4 { get; }


/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -293,6 +357,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

Expand Down Expand Up @@ -344,17 +412,36 @@ public ConfigureNamedOptions(string name, TDep1 dependency1, TDep2 dependency2,
/// </summary>
public Action<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5> Action { get; }

/// <summary>
/// The first dependency.
/// </summary>
public TDep1 Dependency1 { get; }

/// <summary>
/// The second dependency.
/// </summary>
public TDep2 Dependency2 { get; }

/// <summary>
/// The third dependency.
/// </summary>
public TDep3 Dependency3 { get; }

/// <summary>
/// The fourth dependency.
/// </summary>
public TDep4 Dependency4 { get; }

/// <summary>
/// The fifth dependency.
/// </summary>
public TDep5 Dependency5 { get; }


/// <summary>
/// Invokes the registered configure Action if the name matches.
/// </summary>
/// <param name="name">The name of the options instance being configured.</param>
/// <param name="options">The options instance to configure.</param>
public virtual void Configure(string name, TOptions options)
{
if (options == null)
Expand All @@ -369,7 +456,10 @@ public virtual void Configure(string name, TOptions options)
}
}

/// <summary>
/// Invoked to configure a TOptions instance with the <see cref="Options.DefaultName"/>.
/// </summary>
/// <param name="options">The options instance to configure.</param>
public void Configure(TOptions options) => Configure(Options.DefaultName, options);
}

}
2 changes: 1 addition & 1 deletion src/Microsoft.Extensions.Options/IOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Microsoft.Extensions.Options
public interface IOptions<out TOptions> where TOptions : class, new()
{
/// <summary>
/// The default configured TOptions instance, equivalent to Get(string.Empty).
/// The default configured TOptions instance
/// </summary>
TOptions Value { get; }
}
Expand Down
Loading

0 comments on commit dfcde1b

Please sign in to comment.