Skip to content
This repository was archived by the owner on Nov 7, 2018. It is now read-only.

Add DataAnnotations based validation #272

Merged
merged 8 commits into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES! Another project finally has full docs!

<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