Merge remote-tracking branch 'origin/release/2.2'

This commit is contained in:
Pranav K 2018-10-12 15:58:08 -07:00
commit 5f42d5063e
No known key found for this signature in database
GPG Key ID: 1963DA6D96C3057A
130 changed files with 8690 additions and 529 deletions

View File

@ -117,6 +117,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{71C626FC-6408-494B-A127-38CB64F71324}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -575,6 +581,42 @@ Global
{71C626FC-6408-494B-A127-38CB64F71324}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.ActiveCfg = Release|Any CPU
{71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -619,6 +661,9 @@ Global
{92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{1B398182-9EAE-400B-A2BD-EFFAC0168A36} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{71C626FC-6408-494B-A127-38CB64F71324} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A}

45
Mvc.sln
View File

@ -178,6 +178,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorRendering", "benchmarkapps\RazorRendering\RazorRendering.csproj", "{D7C6A696-F232-4288-BCCD-367407E4A934}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -938,6 +944,42 @@ Global
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.ActiveCfg = Release|Any CPU
{D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU
{2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU
{34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1010,6 +1052,9 @@ Global
{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E}
{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A}

View File

@ -1,7 +1,31 @@
{
"Default": {
"rules": [
"DefaultCompositeRule"
]
"Default": {
"rules": [
"DefaultCompositeRule"
],
"packages": {
"Microsoft.Extensions.ApiDescription.Design": {
"Exclusions": {
"BUILD_ITEMS_FRAMEWORK": {
"*": "Package includes tool with different target frameworks."
},
"SERVICING_ATTRIBUTE": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"WRONG_PUBLICKEYTOKEN": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_FILE_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
},
"ASSEMBLY_VERSION_MISMATCH": {
"tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process"
}
}
}
}
}
}
}

View File

@ -132,6 +132,13 @@ namespace BasicApi.Controllers
return new CreatedAtRouteResult("FindPetById", new { id = pet.Id }, pet);
}
[Authorize("pet-store-writer")]
[HttpPost("add-pet")]
public ActionResult<Pet> AddPetWithoutDb(Pet pet)
{
return pet;
}
[Authorize("pet-store-writer")]
[HttpPut]
public IActionResult EditPet(Pet pet)

View File

@ -44,5 +44,11 @@
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/postJsonWithToken.lua"
},
"Path": "/pet"
},
"BasicApi.PostWithoutDb": {
"Path": "/pet/add-pet",
"ClientProperties": {
"Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua"
}
}
}

View File

@ -0,0 +1,75 @@
// 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.Collections.Generic;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Performance
{
public abstract class ValidationVisitorBenchmarkBase
{
protected const int Iterations = 4;
protected static readonly IModelValidatorProvider[] ValidatorProviders = new IModelValidatorProvider[]
{
new DefaultModelValidatorProvider(),
new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
null),
};
protected static readonly CompositeModelValidatorProvider CompositeModelValidatorProvider = new CompositeModelValidatorProvider(ValidatorProviders);
public abstract object Model { get; }
public ModelMetadataProvider BaselineModelMetadataProvider { get; private set; }
public ModelMetadataProvider ModelMetadataProvider { get; private set; }
public ModelMetadata BaselineModelMetadata { get; private set; }
public ModelMetadata ModelMetadata { get; private set; }
public ActionContext ActionContext { get; private set; }
public ValidatorCache ValidatorCache { get; private set; }
[GlobalSetup]
public void Setup()
{
BaselineModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: false);
ModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: true);
BaselineModelMetadata = BaselineModelMetadataProvider.GetMetadataForType(Model.GetType());
ModelMetadata = ModelMetadataProvider.GetMetadataForType(Model.GetType());
ActionContext = GetActionContext();
ValidatorCache = new ValidatorCache();
}
protected static ModelMetadataProvider CreateModelMetadataProvider(bool addHasValidatorsProvider)
{
var detailsProviders = new List<IMetadataDetailsProvider>
{
new DefaultValidationMetadataProvider(),
};
if (addHasValidatorsProvider)
{
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(ValidatorProviders));
}
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
}
protected static ActionContext GetActionContext()
{
return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
}
}
}

View File

@ -0,0 +1,42 @@
// 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 BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.Performance
{
public class ValidationVisitorByteArrayBenchmark : ValidationVisitorBenchmarkBase
{
public override object Model { get; } = new byte[30];
[Benchmark(Baseline = true, Description = "validation for byte arrays baseline", OperationsPerInvoke = Iterations)]
public void Baseline()
{
// Baseline for validating a byte array of size 30, without the ModelMetadata.HasValidators optimization.
// This is the behavior as of 2.1.
var validationVisitor = new ValidationVisitor(
ActionContext,
CompositeModelValidatorProvider,
ValidatorCache,
BaselineModelMetadataProvider,
new ValidationStateDictionary());
validationVisitor.Validate(BaselineModelMetadata, "key", Model);
}
[Benchmark(Description = "validation for byte arrays", OperationsPerInvoke = Iterations)]
public void HasValidators()
{
// Validating a byte array of size 30, with the ModelMetadata.HasValidators optimization.
var validationVisitor = new ValidationVisitor(
ActionContext,
CompositeModelValidatorProvider,
ValidatorCache,
ModelMetadataProvider,
new ValidationStateDictionary());
validationVisitor.Validate(ModelMetadata, "key", Model);
}
}
}

View File

@ -0,0 +1,92 @@
// 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.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.Performance
{
public class ValidationVisitorModelWithValidatedProperties : ValidationVisitorBenchmarkBase
{
public class Person
{
[Required]
public int Id { get; set; }
[Required]
[StringLength(20)]
public string Name { get; set; }
public string Description { get; set; }
public IList<Address> Address { get; set; }
}
public class Address
{
[Required]
public string Street { get; set; }
public string Street2 { get; set; }
public string Type { get; set; }
[Required]
public string Zip { get; set; }
}
public override object Model { get; } = new Person
{
Id = 10,
Name = "Test",
Address = new List<Address>
{
new Address
{
Street = "1 Microsoft Way",
Type = "Work",
Zip = "98056",
},
new Address
{
Street = "15701 NE 39th St",
Type = "Home",
Zip = "98052",
}
},
};
[Benchmark(Baseline = true, Description = "validation for a model with some validated properties - baseline", OperationsPerInvoke = Iterations)]
public void Visit_TypeWithSomeValidatedProperties_Baseline()
{
// Baseline for validating a typical model with some properties that require validation.
// This executes without the ModelMetadata.HasValidators optimization.
var validationVisitor = new ValidationVisitor(
ActionContext,
CompositeModelValidatorProvider,
ValidatorCache,
BaselineModelMetadataProvider,
new ValidationStateDictionary());
validationVisitor.Validate(BaselineModelMetadata, "key", Model);
}
[Benchmark(Description = "validation for a model with some validated properties", OperationsPerInvoke = Iterations)]
public void Visit_TypeWithSomeValidatedProperties()
{
// Validating a typical model with some properties that require validation.
// This executes with the ModelMetadata.HasValidators optimization.
var validationVisitor = new ValidationVisitor(
ActionContext,
CompositeModelValidatorProvider,
ValidatorCache,
ModelMetadataProvider,
new ValidationStateDictionary());
validationVisitor.Validate(ModelMetadata, "key", Model);
}
}
}

View File

@ -16,92 +16,96 @@
<BenchmarksOnlyMySqlConnectorPackageVersion>0.43.0</BenchmarksOnlyMySqlConnectorPackageVersion>
<BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>2.1.1.1</BenchmarksOnlyNpgsqlEntityFrameworkCorePostgreSQLPackageVersion>
<BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>2.1.1</BenchmarksOnlyPomeloEntityFrameworkCoreMySqlPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>3.0.0-alpha1-10549</InternalAspNetCoreAnalyzersPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
<MicrosoftAspNetCoreAntiforgeryPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAntiforgeryPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthenticationPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAuthenticationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>
<MicrosoftAspNetCoreCookiePolicyPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreCookiePolicyPackageVersion>
<MicrosoftAspNetCoreCorsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreCorsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreJsonPatchPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreJsonPatchPackageVersion>
<MicrosoftAspNetCoreLocalizationPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreLocalizationPackageVersion>
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
<MicrosoftAspNetCoreRazorDesignPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreRazorDesignPackageVersion>
<MicrosoftAspNetCoreRazorLanguagePackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreRazorLanguagePackageVersion>
<MicrosoftAspNetCoreRazorRuntimePackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreRazorRuntimePackageVersion>
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-a-alpha1-address-scheme-17061</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-a-alpha1-address-scheme-17061</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreSessionPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>3.0.0-alpha1-10549</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<InternalAspNetCoreAnalyzersPackageVersion>3.0.0-alpha1-10617</InternalAspNetCoreAnalyzersPackageVersion>
<MicrosoftAspNetCoreAllPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAllPackageVersion>
<MicrosoftAspNetCoreAnalyzerTestingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAnalyzerTestingPackageVersion>
<MicrosoftAspNetCoreAntiforgeryPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAntiforgeryPackageVersion>
<MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAuthenticationCookiesPackageVersion>
<MicrosoftAspNetCoreAuthenticationCorePackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAuthenticationCorePackageVersion>
<MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAuthenticationJwtBearerPackageVersion>
<MicrosoftAspNetCoreAuthenticationPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAuthenticationPackageVersion>
<MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreAuthorizationPolicyPackageVersion>
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreChunkingCookieManagerSourcesPackageVersion>
<MicrosoftAspNetCoreCorsPackageVersion>3.0.0-a-alpha1-master-16559</MicrosoftAspNetCoreCorsPackageVersion>
<MicrosoftAspNetCoreCookiePolicyPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreCookiePolicyPackageVersion>
<MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreDiagnosticsAbstractionsPackageVersion>
<MicrosoftAspNetCoreDiagnosticsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreDiagnosticsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreHostingAbstractionsPackageVersion>
<MicrosoftAspNetCoreHostingAbstractions20PackageVersion>2.0.0</MicrosoftAspNetCoreHostingAbstractions20PackageVersion>
<MicrosoftAspNetCoreHostingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreHostingPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreHttpExtensionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreHttpExtensionsPackageVersion>
<MicrosoftAspNetCoreHttpPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreHttpPackageVersion>
<MicrosoftAspNetCoreJsonPatchPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreJsonPatchPackageVersion>
<MicrosoftAspNetCoreLocalizationPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreLocalizationPackageVersion>
<MicrosoftAspNetCoreLocalizationRoutingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreLocalizationRoutingPackageVersion>
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
<MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRangeHelperSourcesPackageVersion>
<MicrosoftAspNetCoreRazorDesignPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRazorDesignPackageVersion>
<MicrosoftAspNetCoreRazorLanguagePackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRazorLanguagePackageVersion>
<MicrosoftAspNetCoreRazorRuntimePackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRazorRuntimePackageVersion>
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreSessionPackageVersion>
<MicrosoftAspNetCoreStaticFilesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreStaticFilesPackageVersion>
<MicrosoftAspNetCoreTestHostPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreTestHostPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftAspNetCoreWebUtilitiesPackageVersion>3.0.0-alpha1-10617</MicrosoftAspNetCoreWebUtilitiesPackageVersion>
<MicrosoftAspNetWebApiClientPackageVersion>5.2.6</MicrosoftAspNetWebApiClientPackageVersion>
<MicrosoftBuildUtilitiesCorePackageVersion>15.6.82</MicrosoftBuildUtilitiesCorePackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.8.0</MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<MicrosoftCodeAnalysisRazorPackageVersion>3.0.0-alpha1-10549</MicrosoftCodeAnalysisRazorPackageVersion>
<MicrosoftCodeAnalysisRazorPackageVersion>3.0.0-alpha1-10617</MicrosoftCodeAnalysisRazorPackageVersion>
<MicrosoftDiaSymReaderNativePackageVersion>1.7.0</MicrosoftDiaSymReaderNativePackageVersion>
<MicrosoftExtensionsCachingMemoryPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsCachingMemoryPackageVersion>
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsCachingMemoryPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsCachingMemoryPackageVersion>
<MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion>
<MicrosoftExtensionsConfigurationCommandLinePackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsConfigurationCommandLinePackageVersion>
<MicrosoftExtensionsConfigurationJsonPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsConfigurationJsonPackageVersion>
<MicrosoftExtensionsConfigurationPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsConfigurationPackageVersion>
<MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
<MicrosoftExtensionsDependencyInjectionPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsDependencyInjectionPackageVersion>
<MicrosoftExtensionsDependencyModelPackageVersion>3.0.0-preview1-26907-05</MicrosoftExtensionsDependencyModelPackageVersion>
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsFileProvidersCompositePackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsFileProvidersCompositePackageVersion>
<MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsLocalizationPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLocalizationPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingDebugPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLoggingDebugPackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>
<MicrosoftExtensionsPrimitivesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsPrimitivesPackageVersion>
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>3.0.0-alpha1-10549</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftExtensionsDiagnosticAdapterPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<MicrosoftExtensionsFileProvidersCompositePackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsFileProvidersCompositePackageVersion>
<MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsFileProvidersEmbeddedPackageVersion>
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
<MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
<MicrosoftExtensionsLocalizationPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLocalizationPackageVersion>
<MicrosoftExtensionsLoggingAbstractionsPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
<MicrosoftExtensionsLoggingConsolePackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLoggingConsolePackageVersion>
<MicrosoftExtensionsLoggingDebugPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLoggingDebugPackageVersion>
<MicrosoftExtensionsLoggingPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLoggingPackageVersion>
<MicrosoftExtensionsLoggingTestingPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsLoggingTestingPackageVersion>
<MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
<MicrosoftExtensionsOptionsPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsOptionsPackageVersion>
<MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsParameterDefaultValueSourcesPackageVersion>
<MicrosoftExtensionsPrimitivesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsPrimitivesPackageVersion>
<MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsPropertyActivatorSourcesPackageVersion>
<MicrosoftExtensionsPropertyHelperSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsPropertyHelperSourcesPackageVersion>
<MicrosoftExtensionsSecurityHelperSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsSecurityHelperSourcesPackageVersion>
<MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
<MicrosoftExtensionsValueStopwatchSourcesPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
<MicrosoftExtensionsWebEncodersPackageVersion>3.0.0-alpha1-10617</MicrosoftExtensionsWebEncodersPackageVersion>
<MicrosoftNETCoreApp20PackageVersion>2.0.9</MicrosoftNETCoreApp20PackageVersion>
<MicrosoftNETCoreApp21PackageVersion>2.1.3</MicrosoftNETCoreApp21PackageVersion>
<MicrosoftNETCoreApp22PackageVersion>2.2.0-preview2-26905-02</MicrosoftNETCoreApp22PackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>3.0.0-alpha1-10549</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETSdkRazorPackageVersion>3.0.0-alpha1-10549</MicrosoftNETSdkRazorPackageVersion>
<MicrosoftNetHttpHeadersPackageVersion>3.0.0-alpha1-10617</MicrosoftNetHttpHeadersPackageVersion>
<MicrosoftNETSdkRazorPackageVersion>3.0.0-alpha1-10617</MicrosoftNETSdkRazorPackageVersion>
<MicrosoftNETTestSdkPackageVersion>15.6.1</MicrosoftNETTestSdkPackageVersion>
<MoqPackageVersion>4.9.0</MoqPackageVersion>
<MoqPackageVersion>4.10.0</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.3</NETStandardLibrary20PackageVersion>
<NewtonsoftJsonBsonPackageVersion>1.0.1</NewtonsoftJsonBsonPackageVersion>
<NewtonsoftJsonPackageVersion>11.0.2</NewtonsoftJsonPackageVersion>
<SystemComponentModelAnnotationsPackageVersion>4.6.0-preview1-26907-04</SystemComponentModelAnnotationsPackageVersion>
<SystemDiagnosticsDiagnosticSourcePackageVersion>4.6.0-preview1-26907-04</SystemDiagnosticsDiagnosticSourcePackageVersion>
<SystemNetHttpPackageVersion>4.3.2</SystemNetHttpPackageVersion>
<SystemThreadingTasksExtensionsPackageVersion>4.6.0-preview1-26907-04</SystemThreadingTasksExtensionsPackageVersion>
<XunitAnalyzersPackageVersion>0.10.0</XunitAnalyzersPackageVersion>
<XunitPackageVersion>2.3.1</XunitPackageVersion>

View File

@ -3,6 +3,6 @@
"version": "2.2.100-preview2-009404"
},
"msbuild-sdks": {
"Internal.AspNetCore.Sdk": "3.0.0-alpha1-20181004.5"
"Internal.AspNetCore.Sdk": "3.0.0-alpha1-20181011.11"
}
}

View File

@ -1,2 +1,2 @@
version:3.0.0-alpha1-20181004.5
commithash:8725f7194e9976f75f90f56afc0fba6116ac597c
version:3.0.0-alpha1-20181011.11
commithash:f57aa8ddda0abdd74ada55853587bedb4f364065

View File

@ -0,0 +1,15 @@
// 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;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class AnsiConsole
{
public static readonly AnsiTextWriter _out = new AnsiTextWriter(Console.Out);
public static void WriteLine(string text)
=> _out.WriteLine(text);
}
}

View File

@ -0,0 +1,20 @@
// 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.
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class AnsiConstants
{
public const string Reset = "\x1b[22m\x1b[39m";
public const string Bold = "\x1b[1m";
public const string Dark = "\x1b[22m";
public const string Black = "\x1b[30m";
public const string Red = "\x1b[31m";
public const string Green = "\x1b[32m";
public const string Yellow = "\x1b[33m";
public const string Blue = "\x1b[34m";
public const string Magenta = "\x1b[35m";
public const string Cyan = "\x1b[36m";
public const string Gray = "\x1b[37m";
}
}

View File

@ -0,0 +1,131 @@
// 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.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class AnsiTextWriter
{
private readonly TextWriter _writer;
public AnsiTextWriter(TextWriter writer) => _writer = writer;
public void WriteLine(string text)
{
Interpret(text);
_writer.Write(Environment.NewLine);
}
private void Interpret(string value)
{
var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m");
var start = 0;
foreach (Match match in matches)
{
var length = match.Index - start;
if (length != 0)
{
_writer.Write(value.Substring(start, length));
}
Apply(match.Groups[1].Value);
start = match.Index + match.Length;
}
if (start != value.Length)
{
_writer.Write(value.Substring(start));
}
}
private static void Apply(string parameter)
{
switch (parameter)
{
case "1":
ApplyBold();
break;
case "22":
ResetBold();
break;
case "30":
ApplyColor(ConsoleColor.Black);
break;
case "31":
ApplyColor(ConsoleColor.DarkRed);
break;
case "32":
ApplyColor(ConsoleColor.DarkGreen);
break;
case "33":
ApplyColor(ConsoleColor.DarkYellow);
break;
case "34":
ApplyColor(ConsoleColor.DarkBlue);
break;
case "35":
ApplyColor(ConsoleColor.DarkMagenta);
break;
case "36":
ApplyColor(ConsoleColor.DarkCyan);
break;
case "37":
ApplyColor(ConsoleColor.Gray);
break;
case "39":
ResetColor();
break;
default:
Debug.Fail("Unsupported parameter: " + parameter);
break;
}
}
private static void ApplyBold()
=> Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor | 8);
private static void ResetBold()
=> Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor & 7);
private static void ApplyColor(ConsoleColor color)
{
var wasBold = ((int)Console.ForegroundColor & 8) != 0;
Console.ForegroundColor = color;
if (wasBold)
{
ApplyBold();
}
}
private static void ResetColor()
{
var wasBold = ((int)Console.ForegroundColor & 8) != 0;
Console.ResetColor();
if (wasBold)
{
ApplyBold();
}
}
}
}

View File

@ -0,0 +1,20 @@
// 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;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class CommandException : Exception
{
public CommandException(string message)
: base(message)
{
}
public CommandException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,19 @@
// 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.Collections.Generic;
using System.Linq;
namespace Microsoft.DotNet.Cli.CommandLine
{
internal class CommandArgument
{
public CommandArgument() => Values = new List<string>();
public string Name { get; set; }
public string Description { get; set; }
public List<string> Values { get; private set; }
public bool MultipleValues { get; set; }
public string Value => Values.FirstOrDefault();
}
}

View File

@ -0,0 +1,604 @@
// 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;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Cli.CommandLine
{
internal class CommandLineApplication
{
private enum ParseOptionResult
{
Succeeded,
ShowHelp,
ShowVersion,
UnexpectedArgs,
}
// Indicates whether the parser should throw an exception when it runs into an unexpected argument.
// If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all
// remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property.
private readonly bool _throwOnUnexpectedArg;
public CommandLineApplication(bool throwOnUnexpectedArg = true)
{
_throwOnUnexpectedArg = throwOnUnexpectedArg;
Options = new List<CommandOption>();
Arguments = new List<CommandArgument>();
Commands = new List<CommandLineApplication>();
RemainingArguments = new List<string>();
Invoke = () => 0;
}
public CommandLineApplication Parent { get; set; }
public string Name { get; set; }
public string FullName { get; set; }
public string Syntax { get; set; }
public string Description { get; set; }
public List<CommandOption> Options { get; private set; }
public CommandOption OptionHelp { get; private set; }
public CommandOption OptionVersion { get; private set; }
public List<CommandArgument> Arguments { get; private set; }
public List<string> RemainingArguments { get; private set; }
public bool IsShowingInformation { get; protected set; } // Is showing help or version?
public Func<int> Invoke { get; set; }
public Func<string> LongVersionGetter { get; set; }
public Func<string> ShortVersionGetter { get; set; }
public List<CommandLineApplication> Commands { get; private set; }
public bool HandleResponseFiles { get; set; }
public bool AllowArgumentSeparator { get; set; }
public bool HandleRemainingArguments { get; set; }
public string ArgumentSeparatorHelpText { get; set; }
public CommandLineApplication Command(string name, bool throwOnUnexpectedArg = true)
=> Command(name, _ => { }, throwOnUnexpectedArg);
public CommandLineApplication Command(string name, Action<CommandLineApplication> configuration,
bool throwOnUnexpectedArg = true)
{
var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this };
Commands.Add(command);
configuration(command);
return command;
}
public CommandOption Option(string template, string description, CommandOptionType optionType)
=> Option(template, description, optionType, _ => { });
public CommandOption Option(string template, string description, CommandOptionType optionType, Action<CommandOption> configuration)
{
var option = new CommandOption(template, optionType) { Description = description };
Options.Add(option);
configuration(option);
return option;
}
public CommandArgument Argument(string name, string description, bool multipleValues = false)
=> Argument(name, description, _ => { }, multipleValues);
public CommandArgument Argument(string name, string description, Action<CommandArgument> configuration, bool multipleValues = false)
{
var lastArg = Arguments.LastOrDefault();
if (lastArg != null && lastArg.MultipleValues)
{
var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.",
lastArg.Name);
throw new InvalidOperationException(message);
}
var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues };
Arguments.Add(argument);
configuration(argument);
return argument;
}
public void OnExecute(Func<int> invoke) => Invoke = invoke;
public void OnExecute(Func<Task<int>> invoke) => Invoke = () => invoke().Result;
public int Execute(params string[] args)
{
var command = this;
IEnumerator<CommandArgument> arguments = null;
if (HandleResponseFiles)
{
args = ExpandResponseFiles(args).ToArray();
}
for (var index = 0; index < args.Length; index++)
{
var arg = args[index];
var isLongOption = arg.StartsWith("--");
if (isLongOption || arg.StartsWith("-"))
{
var result = ParseOption(isLongOption, command, args, ref index, out var option);
if (result == ParseOptionResult.ShowHelp)
{
command.ShowHelp();
return 0;
}
else if (result == ParseOptionResult.ShowVersion)
{
command.ShowVersion();
return 0;
}
}
else
{
var subcommand = ParseSubCommand(arg, command);
if (subcommand != null)
{
command = subcommand;
}
else
{
if (arguments == null)
{
arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator());
}
if (arguments.MoveNext())
{
arguments.Current.Values.Add(arg);
}
else
{
HandleUnexpectedArg(command, args, index, argTypeName: "command or argument");
}
}
}
}
return command.Invoke();
}
private ParseOptionResult ParseOption(
bool isLongOption,
CommandLineApplication command,
string[] args,
ref int index,
out CommandOption option)
{
option = null;
var result = ParseOptionResult.Succeeded;
var arg = args[index];
var optionPrefixLength = isLongOption ? 2 : 1;
var optionComponents = arg.Substring(optionPrefixLength).Split(new[] { ':', '=' }, 2);
var optionName = optionComponents[0];
if (isLongOption)
{
option = command.Options.SingleOrDefault(
opt => string.Equals(opt.LongName, optionName, StringComparison.Ordinal));
}
else
{
option = command.Options.SingleOrDefault(
opt => string.Equals(opt.ShortName, optionName, StringComparison.Ordinal));
if (option == null)
{
option = command.Options.SingleOrDefault(
opt => string.Equals(opt.SymbolName, optionName, StringComparison.Ordinal));
}
}
if (option == null)
{
if (isLongOption && string.IsNullOrEmpty(optionName) &&
!command._throwOnUnexpectedArg && AllowArgumentSeparator)
{
// a stand-alone "--" is the argument separator, so skip it and
// handle the rest of the args as unexpected args
index++;
}
HandleUnexpectedArg(command, args, index, argTypeName: "option");
result = ParseOptionResult.UnexpectedArgs;
}
else if (command.OptionHelp == option)
{
result = ParseOptionResult.ShowHelp;
}
else if (command.OptionVersion == option)
{
result = ParseOptionResult.ShowVersion;
}
else
{
if (optionComponents.Length == 2)
{
if (!option.TryParse(optionComponents[1]))
{
command.ShowHint();
throw new CommandParsingException(command,
$"Unexpected value '{optionComponents[1]}' for option '{optionName}'");
}
}
else
{
if (option.OptionType == CommandOptionType.NoValue ||
option.OptionType == CommandOptionType.BoolValue)
{
// No value is needed for this option
option.TryParse(null);
}
else
{
index++;
arg = args[index];
if (!option.TryParse(arg))
{
command.ShowHint();
throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{optionName}'");
}
}
}
}
return result;
}
private static CommandLineApplication ParseSubCommand(string arg, CommandLineApplication command)
{
foreach (var subcommand in command.Commands)
{
if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase))
{
return subcommand;
}
}
return null;
}
// Helper method that adds a help option
public CommandOption HelpOption(string template)
{
// Help option is special because we stop parsing once we see it
// So we store it separately for further use
OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue);
return OptionHelp;
}
public CommandOption VersionOption(string template,
string shortFormVersion,
string longFormVersion = null)
{
if (longFormVersion == null)
{
return VersionOption(template, () => shortFormVersion);
}
else
{
return VersionOption(template, () => shortFormVersion, () => longFormVersion);
}
}
// Helper method that adds a version option
public CommandOption VersionOption(string template,
Func<string> shortFormVersionGetter,
Func<string> longFormVersionGetter = null)
{
// Version option is special because we stop parsing once we see it
// So we store it separately for further use
OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue);
ShortVersionGetter = shortFormVersionGetter;
LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter;
return OptionVersion;
}
// Show short hint that reminds users to use help option
public void ShowHint()
{
if (OptionHelp != null)
{
Console.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName));
}
}
// Show full help
public void ShowHelp(string commandName = null)
{
var headerBuilder = new StringBuilder("Usage:");
var usagePrefixLength = headerBuilder.Length;
for (var cmd = this; cmd != null; cmd = cmd.Parent)
{
cmd.IsShowingInformation = true;
if (cmd != this && cmd.Arguments.Any())
{
var args = string.Join(" ", cmd.Arguments.Select(arg => arg.Name));
headerBuilder.Insert(usagePrefixLength, string.Format(" {0} {1}", cmd.Name, args));
}
else
{
headerBuilder.Insert(usagePrefixLength, string.Format(" {0}", cmd.Name));
}
}
CommandLineApplication target;
if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase))
{
target = this;
}
else
{
target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase));
if (target != null)
{
headerBuilder.AppendFormat(" {0}", commandName);
}
else
{
// The command name is invalid so don't try to show help for something that doesn't exist
target = this;
}
}
var optionsBuilder = new StringBuilder();
var commandsBuilder = new StringBuilder();
var argumentsBuilder = new StringBuilder();
var argumentSeparatorBuilder = new StringBuilder();
var maxArgLen = 0;
for (var cmd = target; cmd != null; cmd = cmd.Parent)
{
if (cmd.Arguments.Any())
{
if (cmd == target)
{
headerBuilder.Append(" [arguments]");
}
if (argumentsBuilder.Length == 0)
{
argumentsBuilder.AppendLine();
argumentsBuilder.AppendLine("Arguments:");
}
maxArgLen = Math.Max(maxArgLen, MaxArgumentLength(cmd.Arguments));
}
}
for (var cmd = target; cmd != null; cmd = cmd.Parent)
{
if (cmd.Arguments.Any())
{
var outputFormat = " {0}{1}";
foreach (var arg in cmd.Arguments)
{
argumentsBuilder.AppendFormat(
outputFormat,
arg.Name.PadRight(maxArgLen + 2),
arg.Description);
argumentsBuilder.AppendLine();
}
}
}
if (target.Options.Any())
{
headerBuilder.Append(" [options]");
optionsBuilder.AppendLine();
optionsBuilder.AppendLine("Options:");
var maxOptLen = MaxOptionTemplateLength(target.Options);
var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2);
foreach (var opt in target.Options)
{
optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description);
optionsBuilder.AppendLine();
}
}
if (target.Commands.Any())
{
headerBuilder.Append(" [command]");
commandsBuilder.AppendLine();
commandsBuilder.AppendLine("Commands:");
var maxCmdLen = MaxCommandLength(target.Commands);
var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2);
foreach (var cmd in target.Commands.OrderBy(c => c.Name))
{
commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description);
commandsBuilder.AppendLine();
}
if (OptionHelp != null)
{
commandsBuilder.AppendLine();
commandsBuilder.AppendFormat("Use \"{0} [command] --help\" for more information about a command.", Name);
commandsBuilder.AppendLine();
}
}
if (target.AllowArgumentSeparator || target.HandleRemainingArguments)
{
if (target.AllowArgumentSeparator)
{
headerBuilder.Append(" [[--] <arg>...]]");
}
else
{
headerBuilder.Append(" [args]");
}
if (!string.IsNullOrEmpty(target.ArgumentSeparatorHelpText))
{
argumentSeparatorBuilder.AppendLine();
argumentSeparatorBuilder.AppendLine("Args:");
argumentSeparatorBuilder.AppendLine($" {target.ArgumentSeparatorHelpText}");
argumentSeparatorBuilder.AppendLine();
}
}
headerBuilder.AppendLine();
var nameAndVersion = new StringBuilder();
nameAndVersion.AppendLine(GetFullNameAndVersion());
nameAndVersion.AppendLine();
Console.Write("{0}{1}{2}{3}{4}{5}", nameAndVersion, headerBuilder, argumentsBuilder, optionsBuilder, commandsBuilder, argumentSeparatorBuilder);
}
public void ShowVersion()
{
for (var cmd = this; cmd != null; cmd = cmd.Parent)
{
cmd.IsShowingInformation = true;
}
Console.WriteLine(FullName);
Console.WriteLine(LongVersionGetter());
}
public string GetFullNameAndVersion()
=> ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter());
public void ShowRootCommandFullNameAndVersion()
{
var rootCmd = this;
while (rootCmd.Parent != null)
{
rootCmd = rootCmd.Parent;
}
Console.WriteLine(rootCmd.GetFullNameAndVersion());
Console.WriteLine();
}
private static int MaxOptionTemplateLength(IEnumerable<CommandOption> options)
{
var maxLen = 0;
foreach (var opt in options)
{
maxLen = opt.Template.Length > maxLen ? opt.Template.Length : maxLen;
}
return maxLen;
}
private static int MaxCommandLength(IEnumerable<CommandLineApplication> commands)
{
var maxLen = 0;
foreach (var cmd in commands)
{
maxLen = cmd.Name.Length > maxLen ? cmd.Name.Length : maxLen;
}
return maxLen;
}
private static int MaxArgumentLength(IEnumerable<CommandArgument> arguments)
{
var maxLen = 0;
foreach (var arg in arguments)
{
maxLen = arg.Name.Length > maxLen ? arg.Name.Length : maxLen;
}
return maxLen;
}
private static void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName)
{
if (command._throwOnUnexpectedArg)
{
command.ShowHint();
throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'");
}
else
{
command.RemainingArguments.Add(args[index]);
}
}
private IEnumerable<string> ExpandResponseFiles(IEnumerable<string> args)
{
foreach (var arg in args)
{
if (!arg.StartsWith("@", StringComparison.Ordinal))
{
yield return arg;
}
else
{
var fileName = arg.Substring(1);
var responseFileArguments = ParseResponseFile(fileName);
// ParseResponseFile can suppress expanding this response file by
// returning null. In that case, we'll treat the response
// file token as a regular argument.
if (responseFileArguments == null)
{
yield return arg;
}
else
{
foreach (var responseFileArgument in responseFileArguments)
{
yield return responseFileArgument.Trim();
}
}
}
}
}
private IEnumerable<string> ParseResponseFile(string fileName)
{
if (!HandleResponseFiles)
{
return null;
}
if (!File.Exists(fileName))
{
throw new InvalidOperationException($"Response file '{fileName}' doesn't exist.");
}
return File.ReadLines(fileName);
}
private class CommandArgumentEnumerator : IEnumerator<CommandArgument>
{
private readonly IEnumerator<CommandArgument> _enumerator;
public CommandArgumentEnumerator(IEnumerator<CommandArgument> enumerator) => _enumerator = enumerator;
public CommandArgument Current => _enumerator.Current;
object IEnumerator.Current => Current;
public void Dispose() => _enumerator.Dispose();
public bool MoveNext()
{
if (Current == null || !Current.MultipleValues)
{
return _enumerator.MoveNext();
}
// If current argument allows multiple values, we don't move forward and
// all later values will be added to current CommandArgument.Values
return true;
}
public void Reset() => _enumerator.Reset();
}
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace Microsoft.DotNet.Cli.CommandLine
{
internal static class CommandLineApplicationExtensions
{
public static CommandOption Option(this CommandLineApplication command, string template, string description)
=> command.Option(
template,
description,
template.IndexOf('<') != -1
? template.EndsWith(">...")
? CommandOptionType.MultipleValue
: CommandOptionType.SingleValue
: CommandOptionType.NoValue);
}
}

View File

@ -0,0 +1,125 @@
// 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.Linq;
namespace Microsoft.DotNet.Cli.CommandLine
{
internal class CommandOption
{
public CommandOption(string template, CommandOptionType optionType)
{
Template = template;
OptionType = optionType;
Values = new List<string>();
foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries))
{
if (part.StartsWith("--"))
{
LongName = part.Substring(2);
}
else if (part.StartsWith("-"))
{
var optName = part.Substring(1);
// If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?")
if (optName.Length == 1 && !IsEnglishLetter(optName[0]))
{
SymbolName = optName;
}
else
{
ShortName = optName;
}
}
else if (part.StartsWith("<") && part.EndsWith(">"))
{
ValueName = part.Substring(1, part.Length - 2);
}
else if (optionType == CommandOptionType.MultipleValue && part.StartsWith("<") && part.EndsWith(">..."))
{
ValueName = part.Substring(1, part.Length - 5);
}
else
{
throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
}
}
if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName))
{
throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
}
}
public string Template { get; set; }
public string ShortName { get; set; }
public string LongName { get; set; }
public string SymbolName { get; set; }
public string ValueName { get; set; }
public string Description { get; set; }
public List<string> Values { get; private set; }
public bool? BoolValue { get; private set; }
public CommandOptionType OptionType { get; private set; }
public bool TryParse(string value)
{
switch (OptionType)
{
case CommandOptionType.MultipleValue:
Values.Add(value);
break;
case CommandOptionType.SingleValue:
if (Values.Any())
{
return false;
}
Values.Add(value);
break;
case CommandOptionType.BoolValue:
if (Values.Any())
{
return false;
}
if (value == null)
{
// add null to indicate that the option was present, but had no value
Values.Add(null);
BoolValue = true;
}
else
{
if (!bool.TryParse(value, out var boolValue))
{
return false;
}
Values.Add(value);
BoolValue = boolValue;
}
break;
case CommandOptionType.NoValue:
if (value != null)
{
return false;
}
// Add a value to indicate that this option was specified
Values.Add("on");
break;
default:
break;
}
return true;
}
public bool HasValue() => Values.Any();
public string Value() => HasValue() ? Values[0] : null;
private static bool IsEnglishLetter(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace Microsoft.DotNet.Cli.CommandLine
{
internal enum CommandOptionType
{
MultipleValue,
SingleValue,
BoolValue,
NoValue
}
}

View File

@ -0,0 +1,15 @@
// 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;
namespace Microsoft.DotNet.Cli.CommandLine
{
internal class CommandParsingException : Exception
{
public CommandParsingException(CommandLineApplication command, string message)
: base(message) => Command = command;
public CommandLineApplication Command { get; }
}
}

View File

@ -0,0 +1,38 @@
// 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.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal abstract class CommandBase
{
public virtual void Configure(CommandLineApplication command)
{
var verbose = command.Option("-v|--verbose", Resources.VerboseDescription);
var noColor = command.Option("--no-color", Resources.NoColorDescription);
var prefixOutput = command.Option("--prefix-output", Resources.PrefixDescription);
command.HandleResponseFiles = true;
command.OnExecute(
() =>
{
Reporter.IsVerbose = verbose.HasValue();
Reporter.NoColor = noColor.HasValue();
Reporter.PrefixOutput = prefixOutput.HasValue();
Validate();
return Execute();
});
}
protected virtual void Validate()
{
}
protected virtual int Execute()
=> 0;
}
}

View File

@ -0,0 +1,167 @@
// 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.IO;
using System.Linq;
using System.Reflection;
#if NETCOREAPP2_0
using System.Runtime.Loader;
#endif
using Microsoft.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class GetDocumentCommand : ProjectCommandBase
{
internal const string FallbackDocumentName = "v1";
internal const string FallbackMethod = "Generate";
internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider";
private CommandOption _documentName;
private CommandOption _method;
private CommandOption _output;
private CommandOption _service;
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
_documentName = command.Option(
"--documentName <Name>",
Resources.FormatDocumentDescription(FallbackDocumentName));
_method = command.Option("--method <Name>", Resources.FormatMethodDescription(FallbackMethod));
_output = command.Option("--output <Path>", Resources.OutputDescription);
_service = command.Option("--service <QualifiedName>", Resources.FormatServiceDescription(FallbackService));
}
protected override void Validate()
{
base.Validate();
if (!_output.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_output.LongName));
}
if (_method.HasValue() && !_service.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_service.LongName));
}
if (_service.HasValue() && !_method.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(_method.LongName));
}
}
protected override int Execute()
{
var thisAssembly = typeof(GetDocumentCommand).Assembly;
var toolsDirectory = ToolsDirectory.Value();
var packagedAssemblies = Directory
.EnumerateFiles(toolsDirectory, "*.dll")
.Except(new[] { Path.GetFullPath(thisAssembly.Location) })
.ToDictionary(path => Path.GetFileNameWithoutExtension(path), path => new AssemblyInfo(path));
// Explicitly load all assemblies we need first to preserve target project as much as possible. This
// executable is always run in the target project's context (either through location or .deps.json file).
foreach (var keyValuePair in packagedAssemblies)
{
try
{
keyValuePair.Value.Assembly = Assembly.Load(new AssemblyName(keyValuePair.Key));
}
catch
{
// Ignore all failures because missing assemblies should be loadable from tools directory.
}
}
#if NETCOREAPP2_0
AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) =>
{
var name = assemblyName.Name;
if (!packagedAssemblies.TryGetValue(name, out var info))
{
return null;
}
var assemblyPath = info.Path;
if (!File.Exists(assemblyPath))
{
throw new InvalidOperationException(
$"Referenced assembly '{name}' was not found in '{toolsDirectory}'.");
}
return loadContext.LoadFromAssemblyPath(assemblyPath);
};
#elif NET461
AppDomain.CurrentDomain.AssemblyResolve += (source, eventArgs) =>
{
var assemblyName = new AssemblyName(eventArgs.Name);
var name = assemblyName.Name;
if (!packagedAssemblies.TryGetValue(name, out var info))
{
return null;
}
var assembly = info.Assembly;
if (assembly != null)
{
// Loaded already
return assembly;
}
var assemblyPath = info.Path;
if (!File.Exists(assemblyPath))
{
throw new InvalidOperationException(
$"Referenced assembly '{name}' was not found in '{toolsDirectory}'.");
}
return Assembly.LoadFile(assemblyPath);
};
#else
#error target frameworks need to be updated.
#endif
// Now safe to reference the application's code.
try
{
var assemblyPath = AssemblyPath.Value();
var context = new GetDocumentCommandContext
{
AssemblyPath = assemblyPath,
AssemblyDirectory = Path.GetDirectoryName(assemblyPath),
AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath),
DocumentName = _documentName.Value(),
Method = _method.Value(),
Output = _output.Value(),
Service = _service.Value(),
};
return GetDocumentCommandWorker.Process(context);
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.ToString());
return 1;
}
}
private class AssemblyInfo
{
public AssemblyInfo(string path)
{
Path = path;
}
public string Path { get; }
public Assembly Assembly { get; set; }
}
}
}

View File

@ -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 System;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
[Serializable]
public class GetDocumentCommandContext
{
public string AssemblyDirectory { get; set; }
public string AssemblyName { get; set; }
public string AssemblyPath { get; set; }
public string DocumentName { get; set; }
public string Method { get; set; }
public string Output { get; set; }
public string Service { get; set; }
}
}

View File

@ -0,0 +1,167 @@
// 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.IO;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class GetDocumentCommandWorker
{
public static int Process(GetDocumentCommandContext context)
{
var assemblyName = new AssemblyName(context.AssemblyName);
var assembly = Assembly.Load(assemblyName);
var entryPointType = assembly.EntryPoint?.DeclaringType;
if (entryPointType == null)
{
Reporter.WriteError(Resources.FormatMissingEntryPoint(context.AssemblyPath));
return 2;
}
var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName);
if (services == null)
{
return 3;
}
var success = TryProcess(context, services);
if (!success)
{
// As part of the aspnet/Mvc#8425 fix, return 4 here.
return 0;
}
return 0;
}
public static bool TryProcess(GetDocumentCommandContext context, IServiceProvider services)
{
var documentName = string.IsNullOrEmpty(context.DocumentName) ?
GetDocumentCommand.FallbackDocumentName :
context.DocumentName;
var methodName = string.IsNullOrEmpty(context.Method) ?
GetDocumentCommand.FallbackMethod :
context.Method;
var serviceName = string.IsNullOrEmpty(context.Service) ?
GetDocumentCommand.FallbackService :
context.Service;
Reporter.WriteInformation(Resources.FormatUsingDocument(documentName));
Reporter.WriteInformation(Resources.FormatUsingMethod(methodName));
Reporter.WriteInformation(Resources.FormatUsingService(serviceName));
try
{
var serviceType = Type.GetType(serviceName, throwOnError: true);
var method = serviceType.GetMethod(methodName, new[] { typeof(TextWriter), typeof(string) });
var service = services.GetRequiredService(serviceType);
var success = true;
using (var writer = File.CreateText(context.Output))
{
if (method.ReturnType == typeof(bool))
{
success = (bool)method.Invoke(service, new object[] { writer, documentName });
}
else
{
method.Invoke(service, new object[] { writer, documentName });
}
}
if (!success)
{
// As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName);
Reporter.WriteWarning(message);
}
return success;
}
catch (Exception ex)
{
var message = FormatException(ex);
// As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists.
Reporter.WriteWarning(message);
return false;
}
}
// TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available.
private static IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName)
{
var args = new[] { Array.Empty<string>() };
var methodInfo = entryPointType.GetMethod("BuildWebHost");
if (methodInfo != null)
{
// BuildWebHost (old style has highest priority)
var parameters = methodInfo.GetParameters();
if (!methodInfo.IsStatic ||
parameters.Length != 1 ||
typeof(string[]) != parameters[0].ParameterType ||
typeof(IWebHost) != methodInfo.ReturnType)
{
Reporter.WriteError(
"BuildWebHost method found in {assemblyPath} does not have expected signature.");
return null;
}
try
{
var webHost = (IWebHost)methodInfo.Invoke(obj: null, parameters: args);
return webHost.Services;
}
catch (Exception ex)
{
Reporter.WriteError($"BuildWebHost method threw: {FormatException(ex)}");
return null;
}
}
if ((methodInfo = entryPointType.GetMethod("CreateWebHostBuilder")) != null)
{
// CreateWebHostBuilder
var parameters = methodInfo.GetParameters();
if (!methodInfo.IsStatic ||
parameters.Length != 1 ||
typeof(string[]) != parameters[0].ParameterType ||
typeof(IWebHostBuilder) != methodInfo.ReturnType)
{
Reporter.WriteError(
"CreateWebHostBuilder method found in {assemblyPath} does not have expected signature.");
return null;
}
try
{
var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args);
return builder.Build().Services;
}
catch (Exception ex)
{
Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}");
return null;
}
}
return null;
}
private static string FormatException(Exception exception)
{
return $"{exception.GetType().FullName}: {exception.Message}";
}
}
}

View File

@ -0,0 +1,17 @@
// 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.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class HelpCommandBase : CommandBase
{
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
command.HelpOption("-h|--help");
}
}
}

View File

@ -0,0 +1,37 @@
// 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.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal abstract class ProjectCommandBase : HelpCommandBase
{
public CommandOption AssemblyPath { get; private set; }
public CommandOption ToolsDirectory { get; private set; }
public override void Configure(CommandLineApplication command)
{
base.Configure(command);
AssemblyPath = command.Option("-a|--assembly <PATH>", Resources.AssemblyDescription);
ToolsDirectory = command.Option("--tools-directory <PATH>", Resources.ToolsDirectoryDescription);
}
protected override void Validate()
{
base.Validate();
if (!AssemblyPath.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(AssemblyPath.LongName));
}
if (!ToolsDirectory.HasValue())
{
throw new CommandException(Resources.FormatMissingOption(ToolsDirectory.LongName));
}
}
}
}

View File

@ -0,0 +1,22 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<AssemblyName>GetDocument.Insider</AssemblyName>
<Description>GetDocument Command-line Tool inside man</Description>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="$(MicrosoftAspNetCoreHostingAbstractions20PackageVersion)" />
</ItemGroup>
<Target Name="BuildX86" AfterTargets="Build" Condition=" '$(TargetFramework)' == 'net461' And '$(Platform)' != 'x86' ">
<MSBuild Projects="$(MSBuildProjectFullPath)" Properties="TargetFramework=$(TargetFramework);Platform=x86" Targets="Build" />
</Target>
</Project>

View File

@ -0,0 +1,16 @@
// 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.Reflection;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class ProductInfo
{
public static string GetVersion()
=> typeof(ProductInfo)
.Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
.InformationalVersion;
}
}

View File

@ -0,0 +1,48 @@
// 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.Text;
using Microsoft.DotNet.Cli.CommandLine;
using Microsoft.Extensions.ApiDescription.Tool.Commands;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class Program
{
private static int Main(string[] args)
{
if (Console.IsOutputRedirected)
{
Console.OutputEncoding = Encoding.UTF8;
}
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
Name = "GetDocument.Insider"
};
new GetDocumentCommand().Configure(app);
try
{
return app.Execute(args);
}
catch (Exception ex)
{
if (ex is CommandException || ex is CommandParsingException)
{
Reporter.WriteVerbose(ex.ToString());
}
else
{
Reporter.WriteInformation(ex.ToString());
}
Reporter.WriteError(ex.Message);
return 1;
}
}
}
}

View File

@ -0,0 +1,240 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tool
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The assembly to use.
/// </summary>
internal static string AssemblyDescription
{
get => GetString("AssemblyDescription");
}
/// <summary>
/// The assembly to use.
/// </summary>
internal static string FormatAssemblyDescription()
=> GetString("AssemblyDescription");
/// <summary>
/// Missing required option '--{0}'.
/// </summary>
internal static string MissingOption
{
get => GetString("MissingOption");
}
/// <summary>
/// Missing required option '--{0}'.
/// </summary>
internal static string FormatMissingOption(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0);
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string NoColorDescription
{
get => GetString("NoColorDescription");
}
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string OutputDescription
{
get => GetString("OutputDescription");
}
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
/// <summary>
/// Prefix console output with logging level.
/// </summary>
internal static string PrefixDescription
{
get => GetString("PrefixDescription");
}
/// <summary>
/// Prefix console output with logging level.
/// </summary>
internal static string FormatPrefixDescription()
=> GetString("PrefixDescription");
/// <summary>
/// Show verbose output.
/// </summary>
internal static string VerboseDescription
{
get => GetString("VerboseDescription");
}
/// <summary>
/// Show verbose output.
/// </summary>
internal static string FormatVerboseDescription()
=> GetString("VerboseDescription");
/// <summary>
/// Location from which inside man was copied (in the .NET Framework case) or loaded.
/// </summary>
internal static string ToolsDirectoryDescription
{
get => GetString("ToolsDirectoryDescription");
}
/// <summary>
/// Location from which inside man was copied (in the .NET Framework case) or loaded.
/// </summary>
internal static string FormatToolsDirectoryDescription()
=> GetString("ToolsDirectoryDescription");
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// </summary>
internal static string MethodDescription
{
get => GetString("MethodDescription");
}
/// <summary>
/// The name of the method to invoke on the '--service' instance. Default value '{0}'.
/// </summary>
internal static string FormatMethodDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodDescription"), p0);
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string ServiceDescription
{
get => GetString("ServiceDescription");
}
/// <summary>
/// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.
/// </summary>
internal static string FormatServiceDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0);
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string DocumentDescription
{
get => GetString("DocumentDescription");
}
/// <summary>
/// The name of the document to pass to the '--method' method. Default value '{0}'.
/// </summary>
internal static string FormatDocumentDescription(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DocumentDescription"), p0);
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string UsingDocument
{
get => GetString("UsingDocument");
}
/// <summary>
/// Using document name '{0}'.
/// </summary>
internal static string FormatUsingDocument(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingDocument"), p0);
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string UsingMethod
{
get => GetString("UsingMethod");
}
/// <summary>
/// Using method '{0}'.
/// </summary>
internal static string FormatUsingMethod(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingMethod"), p0);
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string UsingService
{
get => GetString("UsingService");
}
/// <summary>
/// Using service '{0}'.
/// </summary>
internal static string FormatUsingService(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0);
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string MethodInvocationFailed
{
get => GetString("MethodInvocationFailed");
}
/// <summary>
/// Method '{0}' of service '{1}' failed to generate document '{2}'.
/// </summary>
internal static string FormatMethodInvocationFailed(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("MethodInvocationFailed"), p0, p1, p2);
/// <summary>
/// Assembly '{0}' does not contain an entry point.
/// </summary>
internal static string MissingEntryPoint
{
get => GetString("MissingEntryPoint");
}
/// <summary>
/// Assembly '{0}' does not contain an entry point.
/// </summary>
internal static string FormatMissingEntryPoint(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,58 @@
// 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.Linq;
using static Microsoft.Extensions.ApiDescription.Tool.AnsiConstants;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class Reporter
{
public static bool IsVerbose { get; set; }
public static bool NoColor { get; set; }
public static bool PrefixOutput { get; set; }
public static string Colorize(string value, Func<string, string> colorizeFunc)
=> NoColor ? value : colorizeFunc(value);
public static void WriteError(string message)
=> WriteLine(Prefix("error: ", Colorize(message, x => Bold + Red + x + Reset)));
public static void WriteWarning(string message)
=> WriteLine(Prefix("warn: ", Colorize(message, x => Bold + Yellow + x + Reset)));
public static void WriteInformation(string message)
=> WriteLine(Prefix("info: ", message));
public static void WriteData(string message)
=> WriteLine(Prefix("data: ", Colorize(message, x => Bold + Gray + x + Reset)));
public static void WriteVerbose(string message)
{
if (IsVerbose)
{
WriteLine(Prefix("verbose: ", Colorize(message, x => Bold + Black + x + Reset)));
}
}
private static string Prefix(string prefix, string value)
=> PrefixOutput
? string.Join(
Environment.NewLine,
value.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Select(l => prefix + l))
: value;
private static void WriteLine(string value)
{
if (NoColor)
{
Console.WriteLine(value);
}
else
{
AnsiConsole.WriteLine(value);
}
}
}
}

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="AssemblyDescription" xml:space="preserve">
<value>The assembly to use.</value>
</data>
<data name="MissingOption" xml:space="preserve">
<value>Missing required option '--{0}'.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix console output with logging level.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
</data>
<data name="ToolsDirectoryDescription" xml:space="preserve">
<value>Location from which inside man was copied (in the .NET Framework case) or loaded.</value>
</data>
<data name="MethodDescription" xml:space="preserve">
<value>The name of the method to invoke on the '--service' instance. Default value '{0}'.</value>
</data>
<data name="ServiceDescription" xml:space="preserve">
<value>The qualified name of the service type to retrieve from dependency injection. Default value '{0}'.</value>
</data>
<data name="DocumentDescription" xml:space="preserve">
<value>The name of the document to pass to the '--method' method. Default value '{0}'.</value>
</data>
<data name="UsingDocument" xml:space="preserve">
<value>Using document name '{0}'.</value>
</data>
<data name="UsingMethod" xml:space="preserve">
<value>Using method '{0}'.</value>
</data>
<data name="UsingService" xml:space="preserve">
<value>Using service '{0}'.</value>
</data>
<data name="MethodInvocationFailed" xml:space="preserve">
<value>Method '{0}' of service '{1}' failed to generate document '{2}'.</value>
</data>
<data name="MissingEntryPoint" xml:space="preserve">
<value>Assembly '{0}' does not contain an entry point.</value>
</data>
</root>

View File

@ -325,6 +325,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
/// </summary>
public abstract bool ValidateChildren { get; }
/// <summary>
/// Gets a value that indicates if the model, or one of it's properties, or elements has associatated validators.
/// </summary>
/// <remarks>
/// When <see langword="false"/>, validation can be assume that the model is valid (<see cref="ModelValidationState.Valid"/>) without
/// inspecting the object graph.
/// </remarks>
public virtual bool? HasValidators { get; }
/// <summary>
/// Gets a collection of metadata items for validators.
/// </summary>

View File

@ -146,6 +146,8 @@ namespace Microsoft.Extensions.DependencyInjection
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcOptionsConfigureCompatibilityOptions>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcOptions>, MvcCoreMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
services.TryAddEnumerable(

View File

@ -8,24 +8,25 @@ using System.Threading;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.Internal
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Sets up default options for <see cref="MvcOptions"/>.
/// </summary>
public class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>
internal class MvcCoreMvcOptionsSetup : IConfigureOptions<MvcOptions>, IPostConfigureOptions<MvcOptions>
{
private readonly IHttpRequestStreamReaderFactory _readerFactory;
private readonly ILoggerFactory _loggerFactory;
// Used in tests
public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory)
: this(readerFactory, NullLoggerFactory.Instance)
{
@ -83,6 +84,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider());
}
public void PostConfigure(string name, MvcOptions options)
{
// HasValidatorsValidationMetadataProvider uses the results of other ValidationMetadataProvider to determine if a model requires
// validation. It is imperative that this executes later than all other metadata provider. We'll register it as part of PostConfigure.
// This should ensure it appears later than all of the details provider registered by MVC and user configured details provider registered
// as part of ConfigureOptions.
options.ModelMetadataDetailsProviders.Add(new HasValidatorsValidationMetadataProvider(options.ModelValidatorProviders));
}
internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList<IMetadataDetailsProvider> modelMetadataDetailsProviders)
{
// Don't bind the Type class by default as it's expensive. A user can override this behavior

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
private bool? _isRequired;
private ModelPropertyCollection _properties;
private bool? _validateChildren;
private bool? _hasValidators;
private ReadOnlyCollection<object> _validatorMetadata;
/// <summary>
@ -427,6 +429,84 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
}
}
/// <inheritdoc />
public override bool? HasValidators
{
get
{
if (!_hasValidators.HasValue)
{
var visited = new HashSet<DefaultModelMetadata>();
_hasValidators = CalculateHasValidators(visited, this);
}
return _hasValidators.Value;
}
}
internal static bool CalculateHasValidators(HashSet<DefaultModelMetadata> visited, ModelMetadata metadata)
{
RuntimeHelpers.EnsureSufficientExecutionStack();
if (metadata?.GetType() != typeof(DefaultModelMetadata))
{
// The calculation is valid only for DefaultModelMetadata instances. Null, other ModelMetadata instances
// or subtypes of DefaultModelMetadata will be treated as always requiring validation.
return true;
}
var defaultModelMetadata = (DefaultModelMetadata)metadata;
if (defaultModelMetadata._hasValidators.HasValue)
{
// Return a previously calculated value if available.
return defaultModelMetadata._hasValidators.Value;
}
if (defaultModelMetadata.ValidationMetadata.HasValidators != false)
{
// Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null).
// In either case, assume it has validators.
return true;
}
// Before inspecting properties or elements of a collection, ensure we do not have a cycle.
// Consider a model like so
//
// Employee { BusinessDivision Division; int Id; string Name; }
// BusinessDivision { int Id; List<Employee> Employees }
//
// If we get to the Employee element from Employee.Division.Employees, we can return false for that instance
// and allow other properties of BusinessDivision and Employee to determine if it has validators.
if (!visited.Add(defaultModelMetadata))
{
return false;
}
// We have inspected the current element. Inspect properties or elements that may contribute to this value.
if (defaultModelMetadata.IsEnumerableType)
{
if (CalculateHasValidators(visited, defaultModelMetadata.ElementMetadata))
{
return true;
}
}
else if (defaultModelMetadata.IsComplexType)
{
foreach (var property in defaultModelMetadata.Properties)
{
if (CalculateHasValidators(visited, property))
{
return true;
}
}
}
// We've come this far. The ModelMetadata does not have any validation
return false;
}
/// <inheritdoc />
public override IReadOnlyList<object> ValidatorMetadata
{

View File

@ -0,0 +1,53 @@
// 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.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
internal class HasValidatorsValidationMetadataProvider : IValidationMetadataProvider
{
private readonly bool _hasOnlyMetadataBasedValidators;
private readonly IMetadataBasedModelValidatorProvider[] _validatorProviders;
public HasValidatorsValidationMetadataProvider(IList<IModelValidatorProvider> modelValidatorProviders)
{
if (modelValidatorProviders.Count > 0 && modelValidatorProviders.All(p => p is IMetadataBasedModelValidatorProvider))
{
_hasOnlyMetadataBasedValidators = true;
_validatorProviders = modelValidatorProviders.Cast<IMetadataBasedModelValidatorProvider>().ToArray();
}
}
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!_hasOnlyMetadataBasedValidators)
{
return;
}
for (var i = 0; i < _validatorProviders.Length; i++)
{
var provider = _validatorProviders[i];
if (provider.HasValidators(context.Key.ModelType, context.ValidationMetadata.ValidatorMetadata))
{
context.ValidationMetadata.HasValidators = true;
return;
}
}
if (context.ValidationMetadata.HasValidators == null)
{
context.ValidationMetadata.HasValidators = false;
}
}
}
}

View File

@ -41,5 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
/// in this list, to be consumed later by an <see cref="Validation.IModelValidatorProvider"/>.
/// </remarks>
public IList<object> ValidatorMetadata { get; } = new List<object>();
/// <summary>
/// Gets a value that indicates if the model has validators .
/// </summary>
public bool? HasValidators { get; set; }
}
}

View File

@ -1,9 +1,10 @@
// 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.AspNetCore.Mvc.ModelBinding.Validation;
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Mvc.Internal
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
/// <summary>
/// A default <see cref="IModelValidatorProvider"/>.
@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
/// The <see cref="DefaultModelValidatorProvider"/> provides validators from <see cref="IModelValidator"/>
/// instances in <see cref="ModelBinding.ModelMetadata.ValidatorMetadata"/>.
/// </remarks>
public class DefaultModelValidatorProvider : IModelValidatorProvider
internal sealed class DefaultModelValidatorProvider : IMetadataBasedModelValidatorProvider
{
/// <inheritdoc />
public void CreateValidators(ModelValidatorProviderContext context)
@ -28,13 +29,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
continue;
}
var validator = validatorItem.ValidatorMetadata as IModelValidator;
if (validator != null)
if (validatorItem.ValidatorMetadata is IModelValidator validator)
{
validatorItem.Validator = validator;
validatorItem.IsReusable = true;
}
}
}
public bool HasValidators(Type modelType, IList<object> validatorMetadata)
{
for (var i = 0; i < validatorMetadata.Count; i++)
{
if (validatorMetadata[i] is IModelValidator)
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,30 @@
// 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 Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
/// <summary>
/// An <see cref="IModelValidatorProvider" /> that provides <see cref="IModelValidator"/> instances
/// exclusively using values in <see cref="ModelMetadata.ValidatorMetadata"/> or the model type.
/// <para>
/// <see cref="IMetadataBasedModelValidatorProvider" /> can be used to statically determine if a given
/// <see cref="ModelMetadata"/> instance can incur any validation. The value for <see cref="ModelMetadata.HasValidators"/>
/// can be calculated if all instances in <see cref="MvcOptions.ModelValidatorProviders"/> are <see cref="IMetadataBasedModelValidatorProvider" />.
/// </para>
/// </summary>
public interface IMetadataBasedModelValidatorProvider : IModelValidatorProvider
{
/// <summary>
/// Gets a value that determines if the <see cref="IModelValidatorProvider"/> can
/// produce any validators given the <paramref name="modelType"/> and <paramref name="modelType"/>.
/// </summary>
/// <param name="modelType">The <see cref="Type"/> of the model.</param>
/// <param name="validatorMetadata">The list of metadata items for validators. <seealso cref="ValidationMetadata.ValidatorMetadata"/>.</param>
/// <returns></returns>
bool HasValidators(Type modelType, IList<object> validatorMetadata);
}
}

View File

@ -265,6 +265,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
CurrentPath.Pop(model);
return true;
}
// If the metadata indicates that no validators exist AND the aggregate state for the key says that the model graph
// is not invalid (i.e. is one of Unvalidated, Valid, or Skipped) we can safely mark the graph as valid.
else if (metadata.HasValidators == false &&
ModelState.GetFieldValidationState(key) != ModelValidationState.Invalid)
{
// No validators will be created for this graph of objects. Mark it as valid if it wasn't previously validated.
var entries = ModelState.FindKeysWithPrefix(key);
foreach (var item in entries)
{
if (item.Value.ValidationState == ModelValidationState.Unvalidated)
{
item.Value.ValidationState = ModelValidationState.Valid;
}
}
CurrentPath.Pop(model);
return true;
}
using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy))
{

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc
/// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be
/// "about:blank".
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "type")]
public string Type { get; set; }
/// <summary>
@ -26,25 +26,25 @@ namespace Microsoft.AspNetCore.Mvc
/// of the problem, except for purposes of localization(e.g., using proactive content negotiation;
/// see[RFC7231], Section 3.4).
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "title")]
public string Title { get; set; }
/// <summary>
/// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "status")]
public int? Status { get; set; }
/// <summary>
/// A human-readable explanation specific to this occurrence of the problem.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "detail")]
public string Detail { get; set; }
/// <summary>
/// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced.
/// </summary>
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "instance")]
public string Instance { get; set; }
/// <summary>

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc
{
@ -64,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc
/// <summary>
/// Gets or sets the validation errors associated with this instance of <see cref="ValidationProblemDetails"/>.
/// </summary>
[JsonProperty(PropertyName = "errors")]
public IDictionary<string, string[]> Errors { get; } = new Dictionary<string, string[]>(StringComparer.Ordinal);
}
}

View File

@ -2,19 +2,21 @@
// 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 Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
namespace Microsoft.AspNetCore.Mvc.DataAnnotations
{
/// <summary>
/// An implementation of <see cref="IModelValidatorProvider"/> which provides validators
/// for attributes which derive from <see cref="ValidationAttribute"/>. It also provides
/// a validator for types which implement <see cref="IValidatableObject"/>.
/// </summary>
public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider
internal sealed class DataAnnotationsModelValidatorProvider : IMetadataBasedModelValidatorProvider
{
private readonly IOptions<MvcDataAnnotationsLocalizationOptions> _options;
private readonly IStringLocalizerFactory _stringLocalizerFactory;
@ -66,8 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
continue;
}
var attribute = validatorItem.ValidatorMetadata as ValidationAttribute;
if (attribute == null)
if (!(validatorItem.ValidatorMetadata is ValidationAttribute attribute))
{
continue;
}
@ -98,5 +99,23 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
});
}
}
public bool HasValidators(Type modelType, IList<object> validatorMetadata)
{
if (typeof(IValidatableObject).IsAssignableFrom(modelType))
{
return true;
}
for (var i = 0; i < validatorMetadata.Count; i++)
{
if (validatorMetadata[i] is ValidationAttribute)
{
return true;
}
}
return false;
}
}
}

View File

@ -4,3 +4,9 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -3,7 +3,7 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -14,6 +14,29 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public static class MvcXmlMvcBuilderExtensions
{
/// <summary>
/// Adds configuration of <see cref="MvcXmlOptions"/> for the application.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
public static IMvcBuilder AddXmlOptions(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
@ -30,6 +53,31 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddXmlDataContractSerializerFormatters(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
AddXmlDataContractSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
@ -46,18 +94,44 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcBuilder"/>.</returns>
public static IMvcBuilder AddXmlSerializerFormatters(
this IMvcBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
AddXmlSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
// Internal for testing.
internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlDataContractSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlDataContractSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
// Internal for testing.
internal static void AddXmlSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
}
}

View File

@ -3,7 +3,7 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
@ -14,6 +14,30 @@ namespace Microsoft.Extensions.DependencyInjection
/// </summary>
public static class MvcXmlMvcCoreBuilderExtensions
{
/// <summary>
/// Adds configuration of <see cref="MvcXmlOptions"/> for the application.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlOptions(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
@ -30,6 +54,31 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML DataContractSerializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlDataContractSerializerFormatters(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
if (setupAction == null)
{
throw new ArgumentNullException(nameof(setupAction));
}
AddXmlDataContractSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
@ -46,18 +95,44 @@ namespace Microsoft.Extensions.DependencyInjection
return builder;
}
/// <summary>
/// Adds the XML Serializer formatters to MVC.
/// </summary>
/// <param name="builder">The <see cref="IMvcCoreBuilder"/>.</param>
/// <param name="setupAction">The <see cref="MvcXmlOptions"/> which need to be configured.</param>
/// /// <returns>The <see cref="IMvcCoreBuilder"/>.</returns>
public static IMvcCoreBuilder AddXmlSerializerFormatters(
this IMvcCoreBuilder builder,
Action<MvcXmlOptions> setupAction)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
AddXmlSerializerFormatterServices(builder.Services);
builder.Services.Configure(setupAction);
return builder;
}
// Internal for testing.
internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlDataContractSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlDataContractSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
// Internal for testing.
internal static void AddXmlSerializerFormatterServices(IServiceCollection services)
{
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, MvcXmlSerializerMvcOptionsSetup>());
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, XmlSerializerMvcOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IPostConfigureOptions<MvcXmlOptions>, MvcXmlOptionsConfigureCompatibilityOptions>());
}
}
}

View File

@ -0,0 +1,68 @@
// 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.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Provides configuration for XML formatters.
/// </summary>
public class MvcXmlOptions : IEnumerable<ICompatibilitySwitch>
{
private readonly CompatibilitySwitch<bool> _allowRfc7807CompliantProblemDetailsFormat;
private readonly IReadOnlyList<ICompatibilitySwitch> _switches;
/// <summary>
/// Creates a new instance of <see cref="MvcXmlOptions"/>.
/// </summary>
public MvcXmlOptions()
{
_allowRfc7807CompliantProblemDetailsFormat = new CompatibilitySwitch<bool>(nameof(AllowRfc7807CompliantProblemDetailsFormat));
_switches = new ICompatibilitySwitch[]
{
_allowRfc7807CompliantProblemDetailsFormat,
};
}
/// <summary>
/// Gets or sets a value inidicating whether <see cref="ProblemDetails"/> and <see cref="ValidationProblemDetails"/>
/// are serialized in a format compliant with the RFC 7807 specification (https://tools.ietf.org/html/rfc7807).
/// </summary>
/// <value>
/// The default value is <see langword="true"/> if the version is
/// <see cref="CompatibilityVersion.Version_2_2"/> or later; <see langword="false"/> otherwise.
/// </value>
/// <remarks>
/// <para>
/// This property is associated with a compatibility switch and can provide a different behavior depending on
/// the configured compatibility version for the application. See <see cref="CompatibilityVersion"/> for
/// guidance and examples of setting the application's compatibility version.
/// </para>
/// <para>
/// Configuring the desired value of the compatibility switch by calling this property's setter will take
/// precedence over the value implied by the application's <see cref="CompatibilityVersion"/>.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_1"/> or
/// lower then this setting will have the value <see langword="false"/> unless explicitly configured.
/// </para>
/// <para>
/// If the application's compatibility version is set to <see cref="CompatibilityVersion.Version_2_2"/> or
/// higher then this setting will have the value <see langword="true"/> unless explicitly configured.
/// </para>
/// </remarks>
public bool AllowRfc7807CompliantProblemDetailsFormat
{
get => _allowRfc7807CompliantProblemDetailsFormat.Value;
set => _allowRfc7807CompliantProblemDetailsFormat.Value = value;
}
public IEnumerator<ICompatibilitySwitch> GetEnumerator() => _switches.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}

View File

@ -0,0 +1,36 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.Formatters.Xml;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc
{
internal sealed class MvcXmlOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions<MvcXmlOptions>
{
public MvcXmlOptionsConfigureCompatibilityOptions(
ILoggerFactory loggerFactory,
IOptions<MvcCompatibilityOptions> compatibilityOptions)
: base(loggerFactory, compatibilityOptions)
{
}
protected override IReadOnlyDictionary<string, object> DefaultValues
{
get
{
var values = new Dictionary<string, object>();
if (Version >= CompatibilityVersion.Version_2_2)
{
values[nameof(MvcXmlOptions.AllowRfc7807CompliantProblemDetailsFormat)] = true;
}
return values;
}
}
}
}

View File

@ -0,0 +1,179 @@
// 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.Globalization;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Wrapper class for <see cref="Mvc.ProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ProblemDetails))]
[Obsolete("This type is deprecated and will be removed in a future version")]
public class ProblemDetails21Wrapper : IXmlSerializable, IUnwrappable
{
protected static readonly string EmptyKey = SerializableErrorWrapper.EmptyKey;
public ProblemDetails21Wrapper()
: this(new ProblemDetails())
{
}
public ProblemDetails21Wrapper(ProblemDetails problemDetails)
{
ProblemDetails = problemDetails;
}
internal ProblemDetails ProblemDetails { get; }
/// <inheritdoc />
public XmlSchema GetSchema() => null;
/// <inheritdoc />
public virtual void ReadXml(XmlReader reader)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (reader.IsEmptyElement)
{
reader.Read();
return;
}
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
var key = XmlConvert.DecodeName(reader.LocalName);
ReadValue(reader, key);
reader.MoveToContent();
}
reader.ReadEndElement();
}
/// <summary>
/// Reads the value for the specified <paramref name="name"/> from the <paramref name="reader"/>.
/// </summary>
/// <param name="reader">The <see cref="XmlReader"/>.</param>
/// <param name="name">The name of the node.</param>
protected virtual void ReadValue(XmlReader reader, string name)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
var value = reader.ReadInnerXml();
switch (name)
{
case "Detail":
ProblemDetails.Detail = value;
break;
case "Instance":
ProblemDetails.Instance = value;
break;
case "Status":
ProblemDetails.Status = string.IsNullOrEmpty(value) ?
(int?)null :
int.Parse(value, CultureInfo.InvariantCulture);
break;
case "Title":
ProblemDetails.Title = value;
break;
case "Type":
ProblemDetails.Type = value;
break;
default:
if (string.Equals(name, EmptyKey, StringComparison.Ordinal))
{
name = string.Empty;
}
ProblemDetails.Extensions.Add(name, value);
break;
}
}
/// <inheritdoc />
public virtual void WriteXml(XmlWriter writer)
{
if (!string.IsNullOrEmpty(ProblemDetails.Detail))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Detail"),
ProblemDetails.Detail);
}
if (!string.IsNullOrEmpty(ProblemDetails.Instance))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Instance"),
ProblemDetails.Instance);
}
if (ProblemDetails.Status.HasValue)
{
writer.WriteStartElement(XmlConvert.EncodeLocalName("Status"));
writer.WriteValue(ProblemDetails.Status.Value);
writer.WriteEndElement();
}
if (!string.IsNullOrEmpty(ProblemDetails.Title))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Title"),
ProblemDetails.Title);
}
if (!string.IsNullOrEmpty(ProblemDetails.Type))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName("Type"),
ProblemDetails.Type);
}
foreach (var keyValuePair in ProblemDetails.Extensions)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (string.IsNullOrEmpty(key))
{
key = EmptyKey;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
writer.WriteValue(value);
}
writer.WriteEndElement();
}
}
object IUnwrappable.Unwrap(Type declaredType)
{
if (declaredType == null)
{
throw new ArgumentNullException(nameof(declaredType));
}
return ProblemDetails;
}
}
}

View File

@ -12,9 +12,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
/// <summary>
/// Wrapper class for <see cref="Mvc.ProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ProblemDetails))]
[XmlRoot("problem", Namespace = Namespace)]
public class ProblemDetailsWrapper : IXmlSerializable, IUnwrappable
{
internal const string Namespace = "urn:ietf:rfc:7807";
/// <summary>
/// Key used to represent dictionary elements with empty keys
/// </summary>
@ -83,25 +85,25 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
switch (name)
{
case nameof(ProblemDetails.Detail):
case "detail":
ProblemDetails.Detail = value;
break;
case nameof(ProblemDetails.Instance):
case "instance":
ProblemDetails.Instance = value;
break;
case nameof(ProblemDetails.Status):
case "status":
ProblemDetails.Status = string.IsNullOrEmpty(value) ?
(int?)null :
int.Parse(value, CultureInfo.InvariantCulture);
break;
case nameof(ProblemDetails.Title):
case "title":
ProblemDetails.Title = value;
break;
case nameof(ProblemDetails.Type):
case "type":
ProblemDetails.Type = value;
break;
@ -122,20 +124,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
if (!string.IsNullOrEmpty(ProblemDetails.Detail))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName(nameof(ProblemDetails.Detail)),
XmlConvert.EncodeLocalName("detail"),
ProblemDetails.Detail);
}
if (!string.IsNullOrEmpty(ProblemDetails.Instance))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName(nameof(ProblemDetails.Instance)),
XmlConvert.EncodeLocalName("instance"),
ProblemDetails.Instance);
}
if (ProblemDetails.Status.HasValue)
{
writer.WriteStartElement(XmlConvert.EncodeLocalName(nameof(ProblemDetails.Status)));
writer.WriteStartElement(XmlConvert.EncodeLocalName("status"));
writer.WriteValue(ProblemDetails.Status.Value);
writer.WriteEndElement();
}
@ -143,14 +145,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
if (!string.IsNullOrEmpty(ProblemDetails.Title))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName(nameof(ProblemDetails.Title)),
XmlConvert.EncodeLocalName("title"),
ProblemDetails.Title);
}
if (!string.IsNullOrEmpty(ProblemDetails.Type))
{
writer.WriteElementString(
XmlConvert.EncodeLocalName(nameof(ProblemDetails.Type)),
XmlConvert.EncodeLocalName("type"),
ProblemDetails.Type);
}

View File

@ -0,0 +1,65 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
internal class ProblemDetailsWrapperProviderFactory : IWrapperProviderFactory
{
private readonly MvcXmlOptions _options;
public ProblemDetailsWrapperProviderFactory(MvcXmlOptions options)
{
_options = options;
}
public IWrapperProvider GetProvider(WrapperProviderContext context)
{
if (context.DeclaredType == typeof(ProblemDetails))
{
if (_options.AllowRfc7807CompliantProblemDetailsFormat)
{
return new WrapperProvider(typeof(ProblemDetailsWrapper), p => new ProblemDetailsWrapper((ProblemDetails)p));
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
return new WrapperProvider(typeof(ProblemDetails21Wrapper), p => new ProblemDetails21Wrapper((ProblemDetails)p));
#pragma warning restore CS0618 // Type or member is obsolete
}
}
if (context.DeclaredType == typeof(ValidationProblemDetails))
{
if (_options.AllowRfc7807CompliantProblemDetailsFormat)
{
return new WrapperProvider(typeof(ValidationProblemDetailsWrapper), p => new ValidationProblemDetailsWrapper((ValidationProblemDetails)p));
}
else
{
#pragma warning disable CS0618 // Type or member is obsolete
return new WrapperProvider(typeof(ValidationProblemDetails21Wrapper), p => new ValidationProblemDetails21Wrapper((ValidationProblemDetails)p));
#pragma warning restore CS0618 // Type or member is obsolete
}
}
return null;
}
private class WrapperProvider : IWrapperProvider
{
public WrapperProvider(Type wrappingType, Func<object, object> wrapDelegate)
{
WrappingType = wrappingType;
WrapDelegate = wrapDelegate;
}
public Type WrappingType { get; }
public Func<object, object> WrapDelegate { get; }
public object Wrap(object original) => WrapDelegate(original);
}
}
}

View File

@ -0,0 +1,127 @@
// 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.Xml;
using System.Xml.Serialization;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// Wrapper class for <see cref="ValidationProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ValidationProblemDetails))]
[Obsolete("This type is deprecated and will be removed in a future version")]
public class ValidationProblemDetails21Wrapper : ProblemDetails21Wrapper, IUnwrappable
{
private static readonly string ErrorKey = "MVC-Errors";
/// <summary>
/// Initializes a new instance of <see cref="ValidationProblemDetailsWrapper"/>.
/// </summary>
public ValidationProblemDetails21Wrapper()
: this(new ValidationProblemDetails())
{
}
/// <summary>
/// Initializes a new instance of <see cref="ValidationProblemDetailsWrapper"/> for the specified
/// <paramref name="problemDetails"/>.
/// </summary>
/// <param name="problemDetails">The <see cref="ProblemDetails"/>.</param>
public ValidationProblemDetails21Wrapper(ValidationProblemDetails problemDetails)
: base(problemDetails)
{
ProblemDetails = problemDetails;
}
internal new ValidationProblemDetails ProblemDetails { get; }
/// <inheritdoc />
protected override void ReadValue(XmlReader reader, string name)
{
if (reader == null)
{
throw new ArgumentNullException(nameof(reader));
}
if (string.Equals(name, ErrorKey, StringComparison.Ordinal))
{
reader.Read();
ReadErrorProperty(reader);
}
else
{
base.ReadValue(reader, name);
}
}
private void ReadErrorProperty(XmlReader reader)
{
if (reader.IsEmptyElement)
{
return;
}
while (reader.NodeType != XmlNodeType.EndElement)
{
var key = XmlConvert.DecodeName(reader.LocalName);
var value = reader.ReadInnerXml();
if (string.Equals(EmptyKey, key, StringComparison.Ordinal))
{
key = string.Empty;
}
ProblemDetails.Errors.Add(key, new[] { value });
reader.MoveToContent();
}
}
/// <inheritdoc />
public override void WriteXml(XmlWriter writer)
{
if (writer == null)
{
throw new ArgumentNullException(nameof(writer));
}
base.WriteXml(writer);
if (ProblemDetails.Errors.Count == 0)
{
return;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(ErrorKey));
foreach (var keyValuePair in ProblemDetails.Errors)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
if (string.IsNullOrEmpty(key))
{
key = EmptyKey;
}
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
writer.WriteValue(value);
}
writer.WriteEndElement();
}
writer.WriteEndElement();
}
object IUnwrappable.Unwrap(Type declaredType)
{
if (declaredType == null)
{
throw new ArgumentNullException(nameof(declaredType));
}
return ProblemDetails;
}
}
}

View File

@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
/// <summary>
/// Wrapper class for <see cref="ValidationProblemDetails"/> to enable it to be serialized by the xml formatters.
/// </summary>
[XmlRoot(nameof(ValidationProblemDetails))]
[XmlRoot("problem", Namespace = "urn:ietf:rfc:7807")]
public class ValidationProblemDetailsWrapper : ProblemDetailsWrapper, IUnwrappable
{
private static readonly string ErrorKey = "MVC-Errors";

View File

@ -44,24 +44,5 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
return null;
}
internal static IList<IWrapperProviderFactory> GetDefaultProviderFactories()
{
var wrapperProviderFactories = new List<IWrapperProviderFactory>();
wrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory());
wrapperProviderFactories.Add(new WrapperProviderFactory(
typeof(ProblemDetails),
typeof(ProblemDetailsWrapper),
value => new ProblemDetailsWrapper((ProblemDetails)value)));
wrapperProviderFactories.Add(new WrapperProviderFactory(
typeof(ValidationProblemDetails),
typeof(ValidationProblemDetailsWrapper),
value => new ValidationProblemDetailsWrapper((ValidationProblemDetails)value)));
return wrapperProviderFactories;
}
}
}

View File

@ -1,50 +0,0 @@
// 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;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
internal class WrapperProviderFactory : IWrapperProviderFactory
{
public WrapperProviderFactory(Type declaredType, Type wrappingType, Func<object, object> wrapper)
{
DeclaredType = declaredType;
WrappingType = wrappingType;
Wrapper = wrapper;
}
public Type DeclaredType { get; }
public Type WrappingType { get; }
public Func<object, object> Wrapper { get; }
public IWrapperProvider GetProvider(WrapperProviderContext context)
{
if (context.DeclaredType == DeclaredType)
{
return new WrapperProvider(this);
}
return null;
}
private class WrapperProvider : IWrapperProvider
{
private readonly WrapperProviderFactory _wrapperFactory;
public WrapperProvider(WrapperProviderFactory wrapperFactory)
{
_wrapperFactory = wrapperFactory;
}
public Type WrappingType => _wrapperFactory.WrappingType;
public object Wrap(object original)
{
return _wrapperFactory.Wrapper(original);
}
}
}
}

View File

@ -46,7 +46,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_serializerSettings = new DataContractSerializerSettings();
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
}
/// <summary>

View File

@ -2,34 +2,36 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Xml;
using System.Xml.Linq;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// A <see cref="IConfigureOptions{TOptions}"/> implementation which will add the
/// data contract serializer formatters to <see cref="MvcOptions"/>.
/// </summary>
public class MvcXmlDataContractSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
internal sealed class XmlDataContractSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly MvcXmlOptions _xmlOptions;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Initializes a new instance of <see cref="MvcXmlDataContractSerializerMvcOptionsSetup"/>.
/// Initializes a new instance of <see cref="XmlDataContractSerializerMvcOptionsSetup"/>.
/// </summary>
/// <param name="xmlOptions"><see cref="MvcXmlOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public MvcXmlDataContractSerializerMvcOptionsSetup(ILoggerFactory loggerFactory)
public XmlDataContractSerializerMvcOptionsSetup(
IOptions<MvcXmlOptions> xmlOptions,
ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_loggerFactory = loggerFactory;
_xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <summary>
@ -40,8 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider());
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options));
var inputFormatter = new XmlDataContractSerializerInputFormatter(options);
inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.InputFormatters.Add(inputFormatter);
var outputFormatter = new XmlDataContractSerializerOutputFormatter(_loggerFactory);
outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.OutputFormatters.Add(outputFormatter);
// Do not override any user mapping
var key = "xml";

View File

@ -76,7 +76,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
_serializerSettings = new DataContractSerializerSettings();
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>()
{
new SerializableErrorWrapperProviderFactory(),
};
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
_logger = loggerFactory?.CreateLogger(GetType());

View File

@ -43,7 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml);
SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax);
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
}
/// <summary>

View File

@ -2,32 +2,32 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml
{
/// <summary>
/// A <see cref="IConfigureOptions{TOptions}"/> implementation which will add the
/// XML serializer formatters to <see cref="MvcOptions"/>.
/// </summary>
public class MvcXmlSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
internal sealed class XmlSerializerMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly MvcXmlOptions _xmlOptions;
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// Initializes a new instance of <see cref="MvcXmlSerializerMvcOptionsSetup"/>.
/// Initializes a new instance of <see cref="XmlSerializerMvcOptionsSetup"/>.
/// </summary>
/// <param name="xmlOptions"><see cref="MvcXmlOptions"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public MvcXmlSerializerMvcOptionsSetup(ILoggerFactory loggerFactory)
public XmlSerializerMvcOptionsSetup(
IOptions<MvcXmlOptions> xmlOptions,
ILoggerFactory loggerFactory)
{
if (loggerFactory == null)
{
throw new ArgumentNullException(nameof(loggerFactory));
}
_loggerFactory = loggerFactory;
_xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions));
_loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory));
}
/// <summary>
@ -46,8 +46,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
MediaTypeHeaderValues.ApplicationXml);
}
options.OutputFormatters.Add(new XmlSerializerOutputFormatter(_loggerFactory));
options.InputFormatters.Add(new XmlSerializerInputFormatter(options));
var inputFormatter = new XmlSerializerInputFormatter(options);
inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.InputFormatters.Add(inputFormatter);
var outputFormatter = new XmlSerializerOutputFormatter(_loggerFactory);
outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions));
options.OutputFormatters.Add(outputFormatter);
}
}
}

View File

@ -73,7 +73,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
WriterSettings = writerSettings;
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
WrapperProviderFactories = new List<IWrapperProviderFactory>
{
new SerializableErrorWrapperProviderFactory(),
};
WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories));
_logger = loggerFactory?.CreateLogger(GetType());

View File

@ -1,4 +1,7 @@
using System;
// 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;
namespace Microsoft.AspNetCore.Mvc.Razor
{

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Net;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;

View File

@ -0,0 +1,227 @@
// 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.IO;
using System.Net.Http;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Task = System.Threading.Tasks.Task;
using Utilities = Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Downloads a file.
/// </summary>
public class DownloadFile : Utilities.Task, ICancelableTask
{
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
/// <summary>
/// The URI to download.
/// </summary>
[Required]
public string Uri { get; set; }
/// <summary>
/// Destination for the downloaded file. If the file already exists, it is not re-downloaded unless
/// <see cref="Overwrite"/> is true.
/// </summary>
[Required]
public string DestinationPath { get; set; }
/// <summary>
/// Should <see cref="DestinationPath"/> be overwritten. When <c>true</c>, the file is downloaded and its hash
/// compared to the existing file. If those hashes do not match (or <see cref="DestinationPath"/> does not
/// exist), <see cref="DestinationPath"/> is overwritten.
/// </summary>
public bool Overwrite { get; set; }
/// <summary>
/// The maximum amount of time in seconds to allow for downloading the file. Defaults to 2 minutes.
/// </summary>
public int TimeoutSeconds { get; set; } = 60 * 2;
/// <inheritdoc/>
public void Cancel() => _cts.Cancel();
/// <inheritdoc/>
public override bool Execute() => ExecuteAsync().Result;
public async Task<bool> ExecuteAsync()
{
if (string.IsNullOrEmpty(Uri))
{
Log.LogError("Uri parameter must not be null or empty.");
return false;
}
if (string.IsNullOrEmpty(Uri))
{
Log.LogError("DestinationPath parameter must not be null or empty.");
return false;
}
var builder = new UriBuilder(Uri);
if (!string.Equals(System.Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(System.Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase))
{
Log.LogError($"{nameof(Uri)} parameter does not have scheme {System.Uri.UriSchemeHttp} or " +
$"{System.Uri.UriSchemeHttps}.");
return false;
}
await DownloadFileAsync(Uri, DestinationPath, Overwrite, _cts.Token, TimeoutSeconds, Log);
return !Log.HasLoggedErrors;
}
private static async Task DownloadFileAsync(
string uri,
string destinationPath,
bool overwrite,
CancellationToken cancellationToken,
int timeoutSeconds,
TaskLoggingHelper log)
{
var destinationExists = File.Exists(destinationPath);
if (destinationExists && !overwrite)
{
log.LogMessage($"Not downloading '{uri}' to overwrite existing file '{destinationPath}'.");
return;
}
log.LogMessage(MessageImportance.High, $"Downloading '{uri}' to '{destinationPath}'.");
using (var httpClient = new HttpClient())
{
await DownloadAsync(uri, destinationPath, httpClient, cancellationToken, log, timeoutSeconds);
}
}
public static async Task DownloadAsync(
string uri,
string destinationPath,
HttpClient httpClient,
CancellationToken cancellationToken,
TaskLoggingHelper log,
int timeoutSeconds)
{
// Timeout if the response has not begun within 1 minute
httpClient.Timeout = TimeSpan.FromMinutes(1);
var destinationExists = File.Exists(destinationPath);
var reachedCopy = false;
try
{
using (var response = await httpClient.GetAsync(uri, cancellationToken))
{
response.EnsureSuccessStatusCode();
cancellationToken.ThrowIfCancellationRequested();
using (var responseStreamTask = response.Content.ReadAsStreamAsync())
{
var finished = await Task.WhenAny(
responseStreamTask,
Task.Delay(TimeSpan.FromSeconds(timeoutSeconds)));
if (!ReferenceEquals(responseStreamTask, finished))
{
throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds.");
}
using (var responseStream = await responseStreamTask)
{
if (destinationExists)
{
// Check hashes before using the downloaded information.
var downloadHash = GetHash(responseStream);
responseStream.Position = 0L;
byte[] destinationHash;
using (var destinationStream = File.OpenRead(destinationPath))
{
destinationHash = GetHash(destinationStream);
}
var sameHashes = downloadHash.Length == destinationHash.Length;
for (var i = 0; sameHashes && i < downloadHash.Length; i++)
{
sameHashes = downloadHash[i] == destinationHash[i];
}
if (sameHashes)
{
log.LogMessage($"Not overwriting existing and matching file '{destinationPath}'.");
return;
}
}
else
{
// May need to create directory to hold the file.
var destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!string.IsNullOrEmpty(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
}
// Create or overwrite the destination file.
reachedCopy = true;
using (var outStream = File.Create(destinationPath))
{
await responseStream.CopyToAsync(outStream);
}
}
}
}
}
catch (HttpRequestException ex) when (destinationExists)
{
if (ex.InnerException is SocketException socketException)
{
log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'.");
}
else
{
log.LogWarning($"Unable to download {uri}: {ex.Message}");
}
}
catch (Exception ex)
{
log.LogError($"Downloading '{uri}' failed.");
log.LogErrorFromException(ex, showStackTrace: true);
if (reachedCopy)
{
File.Delete(destinationPath);
}
}
}
private static byte[] GetHash(Stream stream)
{
SHA256 algorithm;
try
{
algorithm = SHA256.Create();
}
catch (TargetInvocationException)
{
// SHA256.Create is documented to throw this exception on FIPS-compliant machines. See
// https://msdn.microsoft.com/en-us/library/z08hz7ad Fall back to a FIPS-compliant SHA256 algorithm.
algorithm = new SHA256CryptoServiceProvider();
}
using (algorithm)
{
return algorithm.ComputeHash(stream);
}
}
}
}

View File

@ -0,0 +1,34 @@
// 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.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Restore <see cref="ITaskItem"/>s from given property value.
/// </summary>
public class GetCurrentItems : Task
{
/// <summary>
/// The property value to deserialize.
/// </summary>
[Required]
public string Input { get; set; }
/// <summary>
/// The restored <see cref="ITaskItem"/>s. Will never contain more than one item.
/// </summary>
[Output]
public ITaskItem[] Outputs { get; set; }
/// <inheritdoc />
public override bool Execute()
{
Outputs = new[] { MetadataSerializer.DeserializeMetadata(Input) };
return true;
}
}
}

View File

@ -0,0 +1,132 @@
// 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.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects ClassName, Namespace and OutputPath metadata in ServiceFileReference items. Also stores final
/// metadata as SerializedMetadata.
/// </summary>
public class GetFileReferenceMetadata : Task
{
private const string TypeScriptLanguageName = "TypeScript";
/// <summary>
/// Extension to use in default OutputPath metadata value. Ignored when generating TypeScript.
/// </summary>
[Required]
public string Extension { get; set; }
/// <summary>
/// Default Namespace metadata value.
/// </summary>
[Required]
public string Namespace { get; set; }
/// <summary>
/// Default directory for OutputPath values.
/// </summary>
public string OutputDirectory { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include ClassName, Namespace and OutputPath metadata.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
outputs.Add(newItem);
var codeGenerator = item.GetMetadata("CodeGenerator");
if (string.IsNullOrEmpty("CodeGenerator"))
{
// This case occurs when user forgets to specify the required metadata. We have no default here.
string type;
if (!string.IsNullOrEmpty(item.GetMetadata("SourceProject")))
{
type = "ServiceProjectReference";
}
else if (!string.IsNullOrEmpty(item.GetMetadata("SourceUri")))
{
type = "ServiceUriReference";
}
else
{
type = "ServiceFileReference";
}
Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec));
}
var className = item.GetMetadata("ClassName");
if (string.IsNullOrEmpty(className))
{
var filename = item.GetMetadata("Filename");
className = $"{filename}Client";
if (char.IsLower(className[0]))
{
className = char.ToUpper(className[0]) + className.Substring(startIndex: 1);
}
MetadataSerializer.SetMetadata(newItem, "ClassName", className);
}
var @namespace = item.GetMetadata("Namespace");
if (string.IsNullOrEmpty(@namespace))
{
MetadataSerializer.SetMetadata(newItem, "Namespace", Namespace);
}
var outputPath = item.GetMetadata("OutputPath");
if (string.IsNullOrEmpty(outputPath))
{
var isTypeScript = codeGenerator.EndsWith(TypeScriptLanguageName, StringComparison.OrdinalIgnoreCase);
outputPath = $"{className}{(isTypeScript ? ".ts" : Extension)}";
}
// Place output file in correct directory (relative to project directory).
if (!Path.IsPathRooted(outputPath) && !string.IsNullOrEmpty(OutputDirectory))
{
outputPath = Path.Combine(OutputDirectory, outputPath);
}
if (!destinations.Add(outputPath))
{
// This case may occur when user is experimenting e.g. with multiple code generators or options.
// May also occur when user accidentally duplicates OutputPath metadata.
Log.LogError(Resources.FormatDuplicateFileOutputPaths(outputPath));
}
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);
// Add metadata which may be used as a property and passed to an inner build.
newItem.RemoveMetadata("SerializedMetadata");
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
}
}

View File

@ -0,0 +1,103 @@
// 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.Collections.Generic;
using System.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. Also stores final
/// metadata as SerializedMetadata.
/// </summary>
public class GetProjectReferenceMetadata : Task
{
/// <summary>
/// Default directory for DocumentPath values.
/// </summary>
public string DocumentDirectory { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata
/// will contain full paths.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
outputs.Add(newItem);
var documentGenerator = item.GetMetadata("DocumentGenerator");
if (string.IsNullOrEmpty(documentGenerator))
{
// This case occurs when user overrides the default metadata.
Log.LogError(Resources.FormatInvalidEmptyMetadataValue(
"DocumentGenerator",
"ServiceProjectReference",
item.ItemSpec));
}
var documentPath = item.GetMetadata("DocumentPath");
if (string.IsNullOrEmpty(documentPath))
{
var filename = item.GetMetadata("Filename");
var documentName = item.GetMetadata("DocumentName");
if (string.IsNullOrEmpty(documentName))
{
documentName = "v1";
}
documentPath = $"{filename}.{documentName}.json";
}
documentPath = GetFullPath(documentPath);
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
if (!destinations.Add(documentPath))
{
// This case may occur when user is experimenting e.g. with multiple generators or options.
// May also occur when user accidentally duplicates DocumentPath metadata.
Log.LogError(Resources.FormatDuplicateProjectDocumentPaths(documentPath));
}
// Add metadata which may be used as a property and passed to an inner build.
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(DocumentDirectory))
{
path = Path.Combine(DocumentDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -0,0 +1,128 @@
// 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.IO;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Adds or corrects DocumentPath metadata in ServiceUriReference items.
/// </summary>
public class GetUriReferenceMetadata : Task
{
/// <summary>
/// Default directory for DocumentPath metadata values.
/// </summary>
public string DocumentDirectory { get; set; }
/// <summary>
/// The ServiceUriReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceUriReference items. Will include DocumentPath metadata with full paths.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
/// <inheritdoc />
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var newItem = new TaskItem(item);
outputs.Add(newItem);
var documentPath = item.GetMetadata("DocumentPath");
if (string.IsNullOrEmpty(documentPath))
{
var uri = item.ItemSpec;
var builder = new UriBuilder(uri);
if (!builder.Uri.IsAbsoluteUri)
{
Log.LogError($"{nameof(Inputs)} item '{uri}' is not an absolute URI.");
return false;
}
if (!string.Equals(Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) &&
!string.Equals(Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase))
{
Log.LogError($"{nameof(Inputs)} item '{uri}' does not have scheme {Uri.UriSchemeHttp} or " +
$"{Uri.UriSchemeHttps}.");
return false;
}
var host = builder.Host
.Replace("/", string.Empty)
.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Replace(':', '_');
var path = builder.Path
.Replace("!", string.Empty)
.Replace("'", string.Empty)
.Replace("$", string.Empty)
.Replace("%", string.Empty)
.Replace("&", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty)
.Replace("*", string.Empty)
.Replace("@", string.Empty)
.Replace("~", string.Empty)
.Replace('/', '_')
.Replace(':', '_')
.Replace(';', '_')
.Replace('+', '_')
.Replace('=', '_');
documentPath = host + path;
if (char.IsLower(documentPath[0]))
{
documentPath = char.ToUpper(documentPath[0]) + documentPath.Substring(startIndex: 1);
}
if (!documentPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
documentPath = $"{documentPath}.json";
}
}
documentPath = GetFullPath(documentPath);
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
if (!destinations.Add(documentPath))
{
// This case may occur when user is experimenting e.g. with multiple code generators or options.
// May also occur when user accidentally duplicates DocumentPath metadata.
Log.LogError(Resources.FormatDuplicateUriDocumentPaths(documentPath));
}
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(DocumentDirectory))
{
path = Path.Combine(DocumentDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -0,0 +1,147 @@
// 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.Collections.Generic;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.Extensions.ApiDescription.Tasks
{
/// <summary>
/// Utility methods to serialize and deserialize <see cref="ITaskItem"/> metadata.
/// </summary>
/// <remarks>
/// Based on and uses the same escaping as
/// https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Shared/EscapingUtilities.cs
/// </remarks>
public static class MetadataSerializer
{
private static readonly char[] CharsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' };
private static readonly HashSet<char> CharsToEscapeHash = new HashSet<char>(CharsToEscape);
/// <summary>
/// Add the given <paramref name="key"/> and <paramref name="value"/> to the <paramref name="item"/>. Or,
/// modify existing value to be <paramref name="value"/>.
/// </summary>
/// <param name="item">The <see cref="ITaskItem"/> to update.</param>
/// <param name="key">The name of the new metadata.</param>
/// <param name="value">The value of the new metadata. Assumed to be unescaped.</param>
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
public static void SetMetadata(ITaskItem item, string key, string value)
{
if (item is ITaskItem2 item2)
{
item2.SetMetadataValueLiteral(key, value);
return;
}
if (value.IndexOfAny(CharsToEscape) == -1)
{
item.SetMetadata(key, value);
return;
}
var builder = new StringBuilder();
EscapeValue(value, builder);
item.SetMetadata(key, builder.ToString());
}
/// <summary>
/// Serialize metadata for use as a property value passed into an inner build.
/// </summary>
/// <param name="item">The item to serialize.</param>
/// <returns>A <see cref="string"/> containing the serialized metadata.</returns>
/// <remarks>Uses same hex-encoded format as MSBuild's EscapeUtilities.</remarks>
public static string SerializeMetadata(ITaskItem item)
{
var builder = new StringBuilder();
if (item is ITaskItem2 item2)
{
builder.Append($"Identity={item2.EvaluatedIncludeEscaped}");
var metadata = item2.CloneCustomMetadataEscaped();
foreach (var key in metadata.Keys)
{
var value = metadata[key];
builder.Append($"|{key.ToString()}={value.ToString()}");
}
}
else
{
builder.Append($"Identity=");
EscapeValue(item.ItemSpec, builder);
var metadata = item.CloneCustomMetadata();
foreach (var key in metadata.Keys)
{
builder.Append($"|{key.ToString()}=");
var value = metadata[key];
EscapeValue(value.ToString(), builder);
}
}
return builder.ToString();
}
/// <summary>
/// Recreate an <see cref="ITaskItem"/> with metadata encoded in given <paramref name="value"/>.
/// </summary>
/// <param name="value">The serialized metadata.</param>
/// <returns>The deserialized <see cref="ITaskItem"/>.</returns>
public static ITaskItem DeserializeMetadata(string value)
{
var metadata = value.Split('|');
var item = new TaskItem();
// TaskItem implements ITaskITem2 explicitly and ITaskItem implicitly.
var item2 = (ITaskItem2)item;
foreach (var segment in metadata)
{
var keyAndValue = segment.Split(new[] { '=' }, count: 2);
if (string.Equals("Identity", keyAndValue[0]))
{
item2.EvaluatedIncludeEscaped = keyAndValue[1];
continue;
}
item2.SetMetadata(keyAndValue[0], keyAndValue[1]);
}
return item;
}
private static void EscapeValue(string value, StringBuilder builder)
{
if (string.IsNullOrEmpty(value))
{
builder.Append(value);
return;
}
if (value.IndexOfAny(CharsToEscape) == -1)
{
builder.Append(value);
return;
}
foreach (var @char in value)
{
if (CharsToEscapeHash.Contains(@char))
{
builder.Append('%');
builder.Append(HexDigitChar(@char / 0x10));
builder.Append(HexDigitChar(@char & 0x0F));
continue;
}
builder.Append(@char);
}
}
private static char HexDigitChar(int x)
{
return (char)(x + (x < 10 ? '0' : ('a' - 10)));
}
}
}

View File

@ -0,0 +1,91 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<!-- Execute PopulateNuspec fairly late. -->
<GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);PopulateNuspec</GenerateNuspecDependsOn>
<!-- Do not complain about lack of lib folder. -->
<NoPackageAnalysis>true</NoPackageAnalysis>
<AssemblyName>Microsoft.Extensions.ApiDescription.Tasks</AssemblyName>
<Description>MSBuild tasks and targets for code generation</Description>
<EnableApiCheck>false</EnableApiCheck>
<IncludeBuildOutput>false</IncludeBuildOutput>
<IncludeSource>false</IncludeSource>
<IncludeSymbols>false</IncludeSymbols>
<NuspecFile>$(MSBuildProjectName).nuspec</NuspecFile>
<PackageId>$(MSBuildProjectName)</PackageId>
<PackageTags>Build Tasks;MSBuild;Swagger;Open API;code generation; Web API client</PackageTags>
<RootNamespace>$(AssemblyName)</RootNamespace>
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core"
Version="$(MicrosoftBuildUtilitiesCorePackageVersion)" />
<PackageReference Include="System.Net.Http"
Condition="'$(TargetFramework)' == 'net461'"
Version="$(SystemNetHttpPackageVersion)" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != ''">
<SignedPackageFile Include="$(TargetPath)">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tasks/$(TargetFramework)/$(TargetFileName)</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
</ItemGroup>
<!-- Add other signed files in a single inner build, avoiding duplications in this multi-TFM project. -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<SignedPackageFile Include="../dotnet-getdocument/bin/$(Configuration)/netcoreapp2.1/publish/dotnet-getdocument.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/dotnet-getdocument.dll</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/x86/$(Configuration)/net461/GetDocument.Insider.exe">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/net461-x86/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../GetDocumentInsider/bin/$(Configuration)/netcoreapp2.0/GetDocument.Insider.dll">
<Certificate>$(AssemblySigningCertName)</Certificate>
<PackagePath>tools/netcoreapp2.0/GetDocument.Insider.exe</PackagePath>
<StrongName>$(AssemblySigningStrongName)</StrongName>
</SignedPackageFile>
<SignedPackageFile Include="../dotnet-getdocument/bin/$(Configuration)/netcoreapp2.1/publish/Newtonsoft.Json.dll">
<PackagePath>tools/Newtonsoft.Json.dll"</PackagePath>
<Certificate>$(AssemblySigning3rdPartyCertName)</Certificate>
</SignedPackageFile>
</ItemGroup>
<Target Name="PopulateNuspec">
<MSBuild Projects="../dotnet-getdocument/dotnet-getdocument.csproj"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="RuntimeIdentifier;TargetFrameworks;TargetFramework"
Targets="Publish" />
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);
authors=$(Authors);
configuration=$(Configuration);
copyright=$(Copyright);
description=$(PackageDescription);
iconUrl=$(PackageIconUrl);
licenseUrl=$(PackageLicenseUrl);
owners=$(Company);
projectUrl=$(PackageProjectUrl);
repositoryCommit=$(RepositoryCommit);
repositoryUrl=$(RepositoryUrl);
tags=$(PackageTags.Replace(';', ' '));
version=$(PackageVersion);
</NuspecProperties>
</PropertyGroup>
</Target>
</Project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>$id$</id>
<authors>$authors$</authors>
<copyright>$copyright$</copyright>
<description>$description$</description>
<developmentDependency>true</developmentDependency>
<iconUrl>$iconUrl$</iconUrl>
<licenseUrl>$licenseUrl$</licenseUrl>
<minClientVersion>2.8</minClientVersion>
<owners>$owners$</owners>
<projectUrl>$projectUrl$</projectUrl>
<repository type="git" url="$repositoryUrl$" commit="$repositoryCommit$" />
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<tags>$tags$</tags>
<version>$version$</version>
</metadata>
<files>
<file src="build\*" target="build" />
<file src="buildMultiTargeting\*" target="buildMultiTargeting" />
<file src="bin\$configuration$\net461\Microsoft.Extensions.ApiDescription.Tasks.*" target="tasks\net461" />
<file src="bin\$configuration$\netstandard2.0\Microsoft.Extensions.ApiDescription.Tasks.*" target="tasks\netstandard2.0" />
<file src="..\dotnet-getdocument\bin\$configuration$\netcoreapp2.1\publish\*.*" target="tools" />
<file src="..\GetDocumentInsider\bin\$configuration$\net461\GetDocument.Insider.*" target="tools\net461" />
<file src="..\GetDocumentInsider\bin\x86\$configuration$\net461\GetDocument.Insider.*" target="tools\net461-x86" />
<file src="..\GetDocumentInsider\bin\$configuration$\netcoreapp2.0\GetDocument.Insider.*" target="tools\netcoreapp2.0" />
</files>
</package>

View File

@ -0,0 +1,86 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tasks
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tasks.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.
/// </summary>
internal static string DuplicateFileOutputPaths
{
get => GetString("DuplicateFileOutputPaths");
}
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.
/// </summary>
internal static string FormatDuplicateFileOutputPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateFileOutputPaths"), p0);
/// <summary>
/// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.
/// </summary>
internal static string DuplicateProjectDocumentPaths
{
get => GetString("DuplicateProjectDocumentPaths");
}
/// <summary>
/// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.
/// </summary>
internal static string FormatDuplicateProjectDocumentPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateProjectDocumentPaths"), p0);
/// <summary>
/// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.
/// </summary>
internal static string DuplicateUriDocumentPaths
{
get => GetString("DuplicateUriDocumentPaths");
}
/// <summary>
/// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.
/// </summary>
internal static string FormatDuplicateUriDocumentPaths(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUriDocumentPaths"), p0);
/// <summary>
/// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.
/// </summary>
internal static string InvalidEmptyMetadataValue
{
get => GetString("InvalidEmptyMetadataValue");
}
/// <summary>
/// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.
/// </summary>
internal static string FormatInvalidEmptyMetadataValue(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidEmptyMetadataValue"), p0, p1, p2);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="DuplicateFileOutputPaths" xml:space="preserve">
<value>Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata.</value>
<comment>ServiceProjectReference and ServiceUriReference items become ServiceFileReference items and all ServiceFileReference items must have unique OutputPath metadata.</comment>
</data>
<data name="DuplicateProjectDocumentPaths" xml:space="preserve">
<value>Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata.</value>
</data>
<data name="DuplicateUriDocumentPaths" xml:space="preserve">
<value>Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata.</value>
<comment>Ignore corner case of ServiceProjectReference and ServiceUriReference items having the same DocumentPath.</comment>
</data>
<data name="InvalidEmptyMetadataValue" xml:space="preserve">
<value>Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.</value>
</data>
</root>

View File

@ -0,0 +1,140 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<PropertyGroup>
<_ApiDescriptionTasksAssemblyTarget
Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0</_ApiDescriptionTasksAssemblyTarget>
<_ApiDescriptionTasksAssemblyTarget
Condition="'$(MSBuildRuntimeType)' != 'Core'">net461</_ApiDescriptionTasksAssemblyTarget>
<_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Tasks.dll</_ApiDescriptionTasksAssemblyPath>
<_ApiDescriptionTasksAssemblyTarget />
</PropertyGroup>
<UsingTask TaskName="GetCurrentItems" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetFileReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetProjectReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="GetUriReferenceMetadata" AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<UsingTask TaskName="Microsoft.Extensions.ApiDescription.Tasks.DownloadFile"
AssemblyFile="$(_ApiDescriptionTasksAssemblyPath)" />
<!--
Settings users may update as they see fit. All $(...Directory) values are interpreted relative to the client
project folder, unless already an absolute path.
-->
<PropertyGroup>
<ServiceProjectReferenceCheckIfNewer
Condition="'$(ServiceProjectReferenceCheckIfNewer)' == ''">true</ServiceProjectReferenceCheckIfNewer>
<ServiceProjectReferenceDirectory
Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)'))</ServiceProjectReferenceDirectory>
<ServiceUriReferenceCheckIfNewer
Condition="'$(ServiceUriReferenceCheckIfNewer)' == ''">true</ServiceUriReferenceCheckIfNewer>
<ServiceUriReferenceDirectory
Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)'))</ServiceUriReferenceDirectory>
<ServiceFileReferenceCheckIfNewer
Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true</ServiceFileReferenceCheckIfNewer>
<ServiceFileReferenceDirectory
Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)'))</ServiceFileReferenceDirectory>
<GenerateDefaultDocumentDefaultOptions Condition="'$(GenerateDefaultDocumentDefaultOptions)' == ''" />
</PropertyGroup>
<!--
Well-known metadata of the code and document generator item groups. ServiceProjectReference and ServiceUriReference
items may also include ServiceFileReference metadata.
-->
<ItemDefinitionGroup>
<ServiceProjectReference>
<!--
Name of the API description document generator. Builds will invoke a target named
"Generate%(DocumentGenerator)Document" to do actual document retrieval / generation.
-->
<DocumentGenerator>Default</DocumentGenerator>
<!-- Server project metadata which is likely applicable to all document generators. -->
<!--
Server project's chosen configuration. Corresponds to $(Configuration) which likely matches client project's
$(Configuration).
-->
<Configuration />
<!--
Server project's extensions path. Corresponds to $(MSBuildProjectExtensionsPath). User must set this if
server project's value is not 'obj/'.
-->
<ProjectExtensionsPath />
<!-- Server project's target framework. Defaults to $(TargetFramework) or first of $(TargetFrameworks). -->
<TargetFramework />
<!--
Full path of the server project's generated assembly. Corresponds to $(TargetPath). Because common code builds
server projects, file exists prior to document generator invocation.
-->
<TargetPath />
<!--
Semicolon-separated list of targets in the server project that should be built. Default is empty, indicating
the default targets of the server project. Does not honor $(ProjectReferenceBuildTargets) because that property
is too general for these references and it's normally empty too.
-->
<Targets />
<!--
Metadata specific to the Default document generator (though other document generators are free to use it).
-->
<!--
Options added to Default document generator tool's command line. Defaults to
$(GenerateDefaultDocumentDefaultOptions) if that is set in the client project.
-->
<GenerateDefaultDocumentOptions />
<!--
Name of the document to generate. Passed to the %(Method) when using Default document generator. Default is set
in server project, falling back to "v1".
-->
<DocumentName />
<!--
Full path where the API description document is placed. Default filename is %(Filename).%(DocumentName).json.
Filenames and relative paths (if explicitly set) are combined with $(ServiceProjectReferenceDirectory).
-->
<DocumentPath />
<!--
Method Default document generator should invoke on the %(Service) to generate document.
Default is set in server project, falling back to "Generate".
-->
<Method />
<!--
Service Default document generator should retrieve from DI to generate document.
Default is set in server project, falling back to "Microsoft.Extensions.ApiDescription.IDocumentProvider".
-->
<Service />
</ServiceProjectReference>
<ServiceUriReference>
<!--
Full path where the API description document is placed. Default filename is based on %(Identity).
Filenames and relative paths (if explicitly set) are combined with $(ServiceUriReferenceDirectory).
-->
<DocumentPath />
</ServiceUriReference>
<ServiceFileReference>
<!-- Name of the class to generate. Defaults to %(Filename)Client but with an uppercase first letter. -->
<ClassName />
<!--
Code generator to use. Required and must end with "CSharp" or "TypeScript" (the currently-supported target
languages) unless %(OutputPath) is set. Builds will invoke a target named "Generate%(CodeGenerator)" to do
actual code generation.
-->
<CodeGenerator />
<!--
Namespace to contain generated class. Default is $(RootNamespace).
-->
<Namespace />
<!--
Path to place generated code. Code generator may interpret path as a filename or directory. Default filename or
folder name is %(ClassName).[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
$(ServiceFileReferenceDirectory). Final value (depending on $(ServiceFileReferenceDirectory)) is likely to be
a path relative to the client project.
-->
<OutputPath />
</ServiceFileReference>
</ItemDefinitionGroup>
</Project>

View File

@ -0,0 +1,335 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<!-- Internal settings. Not intended for customization. -->
<PropertyGroup>
<GenerateServiceProjectReferenceDocumentsDependsOn>
_GetTargetFrameworkForServiceProjectReferences;
_GetTargetPathForServiceProjectReferences;
_GetMetadataForServiceProjectReferences;
_BuildServiceProjectReferences;
_GenerateServiceProjectReferenceDocuments;
_CreateFileItemsForServiceProjectReferences
</GenerateServiceProjectReferenceDocumentsDependsOn>
<GenerateServiceUriReferenceDocumentsDependsOn>
_GetMetadataForServiceUriReferences;
_GenerateServiceUriReferenceDocuments
</GenerateServiceUriReferenceDocumentsDependsOn>
<GenerateServiceFileReferenceCodesDependsOn>
GenerateServiceProjectReferenceDocuments;
GenerateServiceUriReferenceDocuments;
_GetMetadataForServiceFileReferences;
_GenerateServiceFileReferenceCodes;
_CreateCompileItemsForServiceFileReferences
</GenerateServiceFileReferenceCodesDependsOn>
</PropertyGroup>
<!-- ServiceProjectReference support -->
<!--
Metadata setup phase 1: Ensure items have TargetFramework metadata. Calls GetTargetFrameworks in the target
project. Inputs and outputs cause MSBuild to run target unconditionally and to batch it (run once per project).
-->
<Target Name="_GetTargetFrameworkForServiceProjectReferences"
Inputs="%(ServiceProjectReference.FullPath)"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
RebaseOutputs="true"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetFrameworks"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<!--
Please excuse the mess necessary to extract information from _Temporary and use it in ServiceProjectReference.
-->
<PropertyGroup>
<_TargetFrameworks>%(_Temporary.TargetFrameworks)</_TargetFrameworks>
<_TargetFramework>$(_TargetFrameworks.Split(';')[0])</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)" Condition="'%(FullPath)' == '$(_FullPath)'">
<TargetFramework Condition="'%(TargetFramework)' == ''">$(_TargetFramework)</TargetFramework>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetFramework />
<_TargetFrameworks />
</PropertyGroup>
</Target>
<!--
Metadata setup phase 2: Ensure items have TargetPath metadata. Calls GetTargetPath in the target project.
Inputs and outputs cause MSBuild to run target unconditionally and batch it (run once per TargetFramework x
project combination).
-->
<Target Name="_GetTargetPathForServiceProjectReferences"
Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
<_TargetFramework>%(ServiceProjectReference.TargetFramework)</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
Properties="TargetFramework=$(_TargetFramework)"
RebaseOutputs="true"
RemoveProperties="TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetPath"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<PropertyGroup>
<_TargetPath>%(_Temporary.FullPath)</_TargetPath>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)"
Condition="'%(FullPath)' == '$(_FullPath)' AND '%(TargetFramework)' == '$(_TargetFramework)'">
<TargetPath>$(_TargetPath)</TargetPath>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetPath />
<_TargetFramework />
</PropertyGroup>
</Target>
<!-- Metadata setup phase 3: Ensure items have DocumentPath metadata. -->
<Target Name="_GetMetadataForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetProjectReferenceMetadata Inputs="@(ServiceProjectReference)"
DocumentDirectory="$(ServiceProjectReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetProjectReferenceMetadata>
<ItemGroup>
<ServiceProjectReference Remove="@(ServiceProjectReference)" />
<ServiceProjectReference Include="@(_Temporary)" />
</ItemGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_BuildServiceProjectReferences"
Condition="'$(BuildProjectReferences)' == 'true'"
Inputs="@(ServiceProjectReference)"
Outputs="%(TargetPath)">
<MSBuild Projects="@(ServiceProjectReference -> Distinct())"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="%(Targets)" />
</Target>
<Target Name="_GetCurrentServiceProjectReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentServiceProjectReference" />
</GetCurrentItems>
</Target>
<Target Name="_GenerateServiceProjectReferenceDocument"
DependsOnTargets="_GetCurrentServiceProjectReference;$(GeneratorTarget)" />
<Target Name="_GenerateServiceProjectReferenceDocuments"
Inputs="@(ServiceProjectReference)"
Outputs="%(DocumentPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceProjectReference.DocumentPath);GeneratorTarget=Generate%(DocumentGenerator)Document;GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_GenerateServiceProjectReferenceDocument" />
</Target>
<Target Name="_CreateFileItemsForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<!-- GetProjectReferenceMetadata task guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceProjectReference -> '%(DocumentPath)')" />
<!-- Condition here is temporary. Useful while GenerateDefaultDocument fails. -->
<ServiceFileReference Include="@(ServiceProjectReference -> '%(DocumentPath)')"
Condition="Exists('%(ServiceProjectReference.DocumentPath)')">
<SourceProject>%(ServiceProjectReference.FullPath)</SourceProject>
</ServiceFileReference>
</ItemGroup>
</Target>
<Target Name="GenerateServiceProjectReferenceDocuments"
DependsOnTargets="$(GenerateServiceProjectReferenceDocumentsDependsOn)" />
<!-- GenerateDefaultDocument -->
<Target Name="GenerateDefaultDocument">
<ItemGroup>
<!-- @(CurrentServiceProjectReference) item group will never contain more than one item. -->
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath)</Command>
<Configuration Condition="'%(Configuration)' == ''">$(Configuration)</Configuration>
<GenerateDefaultDocumentOptions
Condition="'%(GenerateDefaultDocumentOptions)' == ''">$(GenerateDefaultDocumentDefaultOptions)</GenerateDefaultDocumentOptions>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --framework %(TargetFramework) --output %(DocumentPath)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Method)' != ''">%(Command) --method %(Method)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command Condition="'%(Service)' != ''">%(Command) --service %(Service)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command
Condition="'%(ProjectExtensionsPath)' != ''">%(Command) --projectExtensionsPath %(ProjectExtensionsPath)</Command>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --configuration %(Configuration) %(GenerateDefaultDocumentOptions)</Command>
</CurrentServiceProjectReference>
</ItemGroup>
<Message Importance="high" Text="%0AGenerateDefaultDocument:" />
<Message Importance="high" Text=" %(CurrentServiceProjectReference.Command)" />
<Exec Command="%(CurrentServiceProjectReference.Command)"
IgnoreExitCode="$([System.IO.File]::Exists('%(DocumentPath)'))" />
</Target>
<!-- ServiceUriReference support -->
<Target Name="_GetMetadataForServiceUriReferences" Condition="'@(ServiceUriReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetUriReferenceMetadata Inputs="@(ServiceUriReference)" DocumentDirectory="$(ServiceUriReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetUriReferenceMetadata>
<ItemGroup>
<ServiceUriReference Remove="@(ServiceUriReference)" />
<ServiceUriReference Include="@(_Temporary)" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_GenerateServiceUriReferenceDocuments" Condition="'@(ServiceUriReference)' != ''">
<Microsoft.Extensions.ApiDescription.Tasks.DownloadFile Uri="%(ServiceUriReference.Identity)"
DestinationPath="%(DocumentPath)"
Overwrite="$(ServiceUriReferenceCheckIfNewer)" />
<!-- GetUriReferenceMetadata task guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceUriReference -> '%(DocumentPath)')" />
<ServiceFileReference Include="@(ServiceUriReference -> '%(DocumentPath)')">
<SourceUri>%(ServiceUriReference.Identity)</SourceUri>
</ServiceFileReference>
</ItemGroup>
</Target>
<Target Name="GenerateServiceUriReferenceDocuments"
DependsOnTargets="$(GenerateServiceUriReferenceDocumentsDependsOn)" />
<!-- ServiceFileReference support -->
<Target Name="_GetMetadataForServiceFileReferences" Condition="'@(ServiceFileReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetFileReferenceMetadata Inputs="@(ServiceFileReference)"
Extension="$(DefaultLanguageSourceExtension)"
Namespace="$(RootNamespace)"
OutputDirectory="$(ServiceFileReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetFileReferenceMetadata>
<ItemGroup>
<ServiceFileReference Remove="@(ServiceFileReference)" />
<ServiceFileReference Include="@(_Temporary)" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_GetCurrentServiceFileReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentServiceFileReference" />
</GetCurrentItems>
</Target>
<Target Name="_GenerateServiceFileReferenceCode"
DependsOnTargets="_GetCurrentServiceFileReference;$(GeneratorTarget)" />
<Target Name="_GenerateServiceFileReferenceCodes" Inputs="@(ServiceFileReference)" Outputs="%(OutputPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceFileReference.OutputPath);GeneratorTarget=Generate%(CodeGenerator);GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_GenerateServiceFileReferenceCode" />
</Target>
<Target Name="_CreateCompileItemsForServiceFileReferences" Condition="'@(ServiceFileReference)' != ''">
<!--
While %(DocumentPath) metadata may include duplicates (due to overlaps between ServiceUriReference and
ServiceProjectReference items), GetFileReferenceMetadata task guarantees %(OutputPath) values are unique.
-->
<ItemGroup>
<_Files Remove="@(_Files)" />
<_Files Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="$([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))">
<OutputPathExtension>$([System.IO.Path]::GetExtension('%(ServiceFileReference.OutputPath)'))</OutputPathExtension>
</_Files>
<_Directories Remove="@(_Directories)" />
<_Directories Include="@(ServiceFileReference -> '%(OutputPath)')"
Condition="Exists('%(ServiceFileReference.OutputPath)') AND ! $([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))" />
<!-- If OutputPath is a file, add it directly to relevant items. -->
<TypeScriptCompile Remove="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'" />
<TypeScriptCompile Include="@(_Files)" Condition="'%(_Files.OutputPathExtension)' == '.ts'">
<SourceDocument>%(_Files.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'" />
<Compile Include="@(_Files)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'">
<SourceDocument>%(ServiceFileReference.FullPath)</SourceDocument>
</Compile>
<!-- Otherwise, add all descendent files with the expected extension. -->
<TypeScriptCompile Remove="@(_Directories -> '%(Identity)/**/*.ts')" />
<TypeScriptCompile Include="@(_Directories -> '%(Identity)/**/*.ts')">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'" />
<Compile Include="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</Compile>
<_Files Remove="@(_Files)" />
<_Directories Remove="@(_Directories)" />
</ItemGroup>
</Target>
<Target Name="GenerateServiceFileReferenceCodes"
BeforeTargets="BeforeCompile"
DependsOnTargets="$(GenerateServiceFileReferenceCodesDependsOn)" />
</Project>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<Target Name="GenerateServiceFileReferenceCodes" BeforeTargets="BeforeCompile">
<MsBuild Projects="$(MSBuildProjectFile)"
Targets="GenerateServiceFileReferenceCodes"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0])"
RemoveProperties="TargetFrameworks;RuntimeIdentifier" />
</Target>
</Project>

View File

@ -0,0 +1,234 @@
// 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.IO;
using System.Linq;
using System.Runtime.Versioning;
using Microsoft.DotNet.Cli.CommandLine;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Microsoft.Extensions.ApiDescription.Tool.Commands
{
internal class InvokeCommand : HelpCommandBase
{
private const string InsideManName = "GetDocument.Insider";
private IList<string> _args;
private CommandOption _configuration;
private CommandOption _output;
private CommandOption _project;
private CommandOption _projectExtensionsPath;
private CommandOption _runtime;
private CommandOption _targetFramework;
public override void Configure(CommandLineApplication command)
{
var options = new ProjectOptions();
options.Configure(command);
_configuration = options.Configuration;
_project = options.Project;
_projectExtensionsPath = options.ProjectExtensionsPath;
_runtime = options.Runtime;
_targetFramework = options.TargetFramework;
_output = command.Option("--output <Path>", Resources.OutputDescription);
command.VersionOption("--version", ProductInfo.GetVersion);
_args = command.RemainingArguments;
base.Configure(command);
}
protected override int Execute()
{
var projectFile = FindProjects(
_project.Value(),
Resources.NoProject,
Resources.MultipleProjects);
Reporter.WriteVerbose(Resources.FormatUsingProject(projectFile));
var project = Project.FromFile(
projectFile,
_projectExtensionsPath.Value(),
_targetFramework.Value(),
_configuration.Value(),
_runtime.Value());
if (!File.Exists(project.TargetPath))
{
throw new CommandException(Resources.MustBuild);
}
var thisPath = Path.GetFullPath(Path.GetDirectoryName(typeof(InvokeCommand).Assembly.Location));
string executable = null;
var cleanupExecutable = false;
try
{
string toolsDirectory;
var args = new List<string>();
var targetFramework = new FrameworkName(project.TargetFrameworkMoniker);
switch (targetFramework.Identifier)
{
case ".NETFramework":
cleanupExecutable = true;
executable = Path.Combine(project.OutputPath, InsideManName + ".exe");
toolsDirectory = Path.Combine(
thisPath,
project.PlatformTarget == "x86" ? "net461-x86" : "net461");
var executableSource = Path.Combine(toolsDirectory, InsideManName + ".exe");
File.Copy(executableSource, executable, overwrite: true);
if (!string.IsNullOrEmpty(project.ConfigPath))
{
File.Copy(project.ConfigPath, executable + ".config", overwrite: true);
}
break;
case ".NETCoreApp":
executable = "dotnet";
toolsDirectory = Path.Combine(thisPath, "netcoreapp2.0");
if (targetFramework.Version < new Version(2, 0))
{
throw new CommandException(
Resources.FormatNETCoreApp1Project(project.ProjectName, targetFramework.Version));
}
args.Add("exec");
args.Add("--depsFile");
args.Add(project.ProjectDepsFilePath);
if (!string.IsNullOrEmpty(project.ProjectAssetsFile))
{
using (var reader = new JsonTextReader(File.OpenText(project.ProjectAssetsFile)))
{
var projectAssets = JToken.ReadFrom(reader);
var packageFolders = projectAssets["packageFolders"]
.Children<JProperty>()
.Select(p => p.Name);
foreach (var packageFolder in packageFolders)
{
args.Add("--additionalProbingPath");
args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
}
}
}
if (File.Exists(project.ProjectRuntimeConfigFilePath))
{
args.Add("--runtimeConfig");
args.Add(project.ProjectRuntimeConfigFilePath);
}
else if (!string.IsNullOrEmpty(project.RuntimeFrameworkVersion))
{
args.Add("--fx-version");
args.Add(project.RuntimeFrameworkVersion);
}
args.Add(Path.Combine(toolsDirectory, InsideManName + ".dll"));
break;
case ".NETStandard":
throw new CommandException(Resources.FormatNETStandardProject(project.ProjectName));
default:
throw new CommandException(
Resources.FormatUnsupportedFramework(project.ProjectName, targetFramework.Identifier));
}
args.AddRange(_args);
args.Add("--assembly");
args.Add(project.TargetPath);
args.Add("--tools-directory");
args.Add(toolsDirectory);
if (!(args.Contains("--method") || string.IsNullOrEmpty(project.DefaultMethod)))
{
args.Add("--method");
args.Add(project.DefaultMethod);
}
if (!(args.Contains("--service") || string.IsNullOrEmpty(project.DefaultService)))
{
args.Add("--service");
args.Add(project.DefaultService);
}
if (_output.HasValue())
{
args.Add("--output");
args.Add(Path.GetFullPath(_output.Value()));
}
if (Reporter.IsVerbose)
{
args.Add("--verbose");
}
if (Reporter.NoColor)
{
args.Add("--no-color");
}
if (Reporter.PrefixOutput)
{
args.Add("--prefix-output");
}
return Exe.Run(executable, args, project.ProjectDirectory);
}
finally
{
if (cleanupExecutable && !string.IsNullOrEmpty(executable))
{
File.Delete(executable);
File.Delete(executable + ".config");
}
}
}
private static string FindProjects(
string path,
string errorWhenNoProject,
string errorWhenMultipleProjects)
{
var specified = true;
if (path == null)
{
specified = false;
path = Directory.GetCurrentDirectory();
}
else if (!Directory.Exists(path)) // It's not a directory
{
return path;
}
var projectFiles = Directory
.EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly)
.Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase))
.Take(2)
.ToList();
if (projectFiles.Count == 0)
{
throw new CommandException(
specified
? Resources.FormatNoProjectInDirectory(path)
: errorWhenNoProject);
}
if (projectFiles.Count != 1)
{
throw new CommandException(
specified
? Resources.FormatMultipleProjectsInDirectory(path)
: errorWhenMultipleProjects);
}
return projectFiles[0];
}
}
}

View File

@ -0,0 +1,118 @@
// 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.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class Exe
{
public static int Run(
string executable,
IReadOnlyList<string> args,
string workingDirectory = null,
bool interceptOutput = false)
{
var arguments = ToArguments(args);
Reporter.WriteVerbose(executable + " " + arguments);
var startInfo = new ProcessStartInfo
{
FileName = executable,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = interceptOutput
};
if (workingDirectory != null)
{
startInfo.WorkingDirectory = workingDirectory;
}
var process = Process.Start(startInfo);
if (interceptOutput)
{
string line;
while ((line = process.StandardOutput.ReadLine()) != null)
{
Reporter.WriteVerbose(line);
}
}
process.WaitForExit();
return process.ExitCode;
}
private static string ToArguments(IReadOnlyList<string> args)
{
var builder = new StringBuilder();
for (var i = 0; i < args.Count; i++)
{
if (i != 0)
{
builder.Append(" ");
}
if (args[i].IndexOf(' ') == -1)
{
builder.Append(args[i]);
continue;
}
builder.Append("\"");
var pendingBackslashs = 0;
for (var j = 0; j < args[i].Length; j++)
{
switch (args[i][j])
{
case '\"':
if (pendingBackslashs != 0)
{
builder.Append('\\', pendingBackslashs * 2);
pendingBackslashs = 0;
}
builder.Append("\\\"");
break;
case '\\':
pendingBackslashs++;
break;
default:
if (pendingBackslashs != 0)
{
if (pendingBackslashs == 1)
{
builder.Append("\\");
}
else
{
builder.Append('\\', pendingBackslashs * 2);
}
pendingBackslashs = 0;
}
builder.Append(args[i][j]);
break;
}
}
if (pendingBackslashs != 0)
{
builder.Append('\\', pendingBackslashs * 2);
}
builder.Append("\"");
}
return builder.ToString();
}
}
}

View File

@ -0,0 +1,42 @@
// 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 Microsoft.DotNet.Cli.CommandLine;
using Microsoft.Extensions.ApiDescription.Tool.Commands;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal static class Program
{
private static int Main(string[] args)
{
var app = new CommandLineApplication(throwOnUnexpectedArg: false)
{
FullName = Resources.CommandFullName,
};
new InvokeCommand().Configure(app);
try
{
return app.Execute(args);
}
catch (Exception ex)
{
if (ex is CommandException || ex is CommandParsingException)
{
Reporter.WriteVerbose(ex.ToString());
}
else
{
Reporter.WriteInformation(ex.ToString());
}
Reporter.WriteError(ex.Message);
return 1;
}
}
}
}

View File

@ -0,0 +1,235 @@
// 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.Diagnostics;
using System.IO;
using System.Linq;
using IODirectory = System.IO.Directory;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class Project
{
private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets";
private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Tool." + ResourceFilename;
private Project()
{
}
public string AssemblyName { get; private set; }
public string ConfigPath { get; private set; }
public string Configuration { get; private set; }
public string DefaultDocumentName { get; private set; }
public string DefaultMethod { get; private set; }
public string DefaultService { get; private set; }
public string OutputPath { get; private set; }
public string Platform { get; private set; }
public string PlatformTarget { get; private set; }
public string ProjectAssetsFile { get; private set; }
public string ProjectDepsFilePath { get; private set; }
public string ProjectDirectory { get; private set; }
public string ProjectExtensionsPath { get; private set; }
public string ProjectName { get; private set; }
public string ProjectRuntimeConfigFilePath { get; private set; }
public string RuntimeFrameworkVersion { get; private set; }
public string RuntimeIdentifier { get; private set; }
public string TargetFramework { get; private set; }
public string TargetFrameworkMoniker { get; private set; }
public string TargetPath { get; private set; }
public static Project FromFile(
string projectFile,
string buildExtensionsDirectory,
string framework = null,
string configuration = null,
string runtime = null)
{
if (string.IsNullOrEmpty(projectFile))
{
throw new ArgumentNullException(nameof(projectFile));
}
if (string.IsNullOrEmpty(buildExtensionsDirectory))
{
buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(projectFile), "obj");
}
IODirectory.CreateDirectory(buildExtensionsDirectory);
var assembly = typeof(Project).Assembly;
var targetsPath = Path.Combine(
buildExtensionsDirectory,
$"{Path.GetFileName(projectFile)}.{ResourceFilename}");
using (var input = assembly.GetManifestResourceStream(MSBuildResourceName))
{
using (var output = File.OpenWrite(targetsPath))
{
// NB: Copy always in case it changes
Reporter.WriteVerbose(Resources.FormatWritingFile(targetsPath));
input.CopyTo(output);
}
}
IDictionary<string, string> metadata;
var metadataPath = Path.GetTempFileName();
try
{
var args = new List<string>
{
"msbuild",
"/target:WriteServiceProjectReferenceMetadata",
"/verbosity:quiet",
"/nologo",
$"/property:ServiceProjectReferenceMetadataPath={metadataPath}",
projectFile,
};
if (!string.IsNullOrEmpty(framework))
{
args.Add($"/property:TargetFramework={framework}");
}
if (!string.IsNullOrEmpty(configuration))
{
args.Add($"/property:Configuration={configuration}");
}
if (!string.IsNullOrEmpty(runtime))
{
args.Add($"/property:RuntimeIdentifier={runtime}");
}
var exitCode = Exe.Run("dotnet", args);
if (exitCode != 0)
{
throw new CommandException(Resources.GetMetadataFailed);
}
metadata = File
.ReadLines(metadataPath)
.Select(l => l.Split(new[] { ':' }, 2))
.ToDictionary(s => s[0], s => s[1].TrimStart());
}
finally
{
File.Delete(metadataPath);
File.Delete(targetsPath);
}
var project = new Project
{
DefaultDocumentName = metadata[nameof(DefaultDocumentName)],
DefaultMethod = metadata[nameof(DefaultMethod)],
DefaultService = metadata[nameof(DefaultService)],
AssemblyName = metadata[nameof(AssemblyName)],
Configuration = metadata[nameof(Configuration)],
OutputPath = metadata[nameof(OutputPath)],
Platform = metadata[nameof(Platform)],
PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)],
ProjectAssetsFile = metadata[nameof(ProjectAssetsFile)],
ProjectDepsFilePath = metadata[nameof(ProjectDepsFilePath)],
ProjectDirectory = metadata[nameof(ProjectDirectory)],
ProjectExtensionsPath = metadata[nameof(ProjectExtensionsPath)],
ProjectName = metadata[nameof(ProjectName)],
ProjectRuntimeConfigFilePath = metadata[nameof(ProjectRuntimeConfigFilePath)],
RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)],
RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)],
TargetFramework = metadata[nameof(TargetFramework)],
TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)],
TargetPath = metadata[nameof(TargetPath)],
};
if (string.IsNullOrEmpty(project.OutputPath))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(OutputPath), nameof(OutputPath)));
}
if (string.IsNullOrEmpty(project.ProjectDirectory))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(ProjectDirectory), "MSBuildProjectDirectory"));
}
if (string.IsNullOrEmpty(project.TargetPath))
{
throw new CommandException(
Resources.FormatGetMetadataValueFailed(nameof(TargetPath), nameof(TargetPath)));
}
if (!Path.IsPathRooted(project.ProjectDirectory))
{
project.OutputPath = Path.GetFullPath(
Path.Combine(IODirectory.GetCurrentDirectory(), project.ProjectDirectory));
}
if (!Path.IsPathRooted(project.OutputPath))
{
project.OutputPath = Path.GetFullPath(Path.Combine(project.ProjectDirectory, project.OutputPath));
}
if (!Path.IsPathRooted(project.ProjectExtensionsPath))
{
project.ProjectExtensionsPath = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectExtensionsPath));
}
if (!Path.IsPathRooted(project.TargetPath))
{
project.TargetPath = Path.GetFullPath(Path.Combine(project.OutputPath, project.TargetPath));
}
// Some document generation tools support non-ASP.NET Core projects. Any of the remaining properties may
// thus be null empty.
var configPath = $"{project.TargetPath}.config";
if (File.Exists(configPath))
{
project.ConfigPath = configPath;
}
if (!(string.IsNullOrEmpty(project.ProjectAssetsFile) || Path.IsPathRooted(project.ProjectAssetsFile)))
{
project.ProjectAssetsFile = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectAssetsFile));
}
if (!(string.IsNullOrEmpty(project.ProjectDepsFilePath) || Path.IsPathRooted(project.ProjectDepsFilePath)))
{
project.ProjectDepsFilePath = Path.GetFullPath(
Path.Combine(project.ProjectDirectory, project.ProjectDepsFilePath));
}
if (!(string.IsNullOrEmpty(project.ProjectRuntimeConfigFilePath) ||
Path.IsPathRooted(project.ProjectRuntimeConfigFilePath)))
{
project.ProjectRuntimeConfigFilePath = Path.GetFullPath(
Path.Combine(project.OutputPath, project.ProjectRuntimeConfigFilePath));
}
return project;
}
}
}

View File

@ -0,0 +1,31 @@
// 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.DotNet.Cli.CommandLine;
namespace Microsoft.Extensions.ApiDescription.Tool
{
internal class ProjectOptions
{
public CommandOption Configuration { get; private set; }
public CommandOption Project { get; private set; }
public CommandOption ProjectExtensionsPath { get; private set; }
public CommandOption Runtime { get; private set; }
public CommandOption TargetFramework { get; private set; }
public void Configure(CommandLineApplication command)
{
Configuration = command.Option("--configuration <CONFIGURATION>", Resources.ConfigurationDescription);
Project = command.Option("-p|--project <PROJECT>", Resources.ProjectDescription);
ProjectExtensionsPath = command.Option(
"--projectExtensionsPath <PATH>",
Resources.ProjectExtensionsPathDescription);
Runtime = command.Option("--runtime <RUNTIME_IDENTIFIER>", Resources.RuntimeDescription);
TargetFramework = command.Option("--framework <FRAMEWORK>", Resources.TargetFrameworkDescription);
}
}
}

View File

@ -0,0 +1,338 @@
// <auto-generated />
namespace Microsoft.Extensions.ApiDescription.Tool
{
using System.Globalization;
using System.Reflection;
using System.Resources;
internal static class Resources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// The configuration to use.
/// </summary>
internal static string ConfigurationDescription
{
get => GetString("ConfigurationDescription");
}
/// <summary>
/// The configuration to use.
/// </summary>
internal static string FormatConfigurationDescription()
=> GetString("ConfigurationDescription");
/// <summary>
/// dotnet-getdocument
/// </summary>
internal static string CommandFullName
{
get => GetString("CommandFullName");
}
/// <summary>
/// dotnet-getdocument
/// </summary>
internal static string FormatCommandFullName()
=> GetString("CommandFullName");
/// <summary>
/// The target framework.
/// </summary>
internal static string TargetFrameworkDescription
{
get => GetString("TargetFrameworkDescription");
}
/// <summary>
/// The target framework.
/// </summary>
internal static string FormatTargetFrameworkDescription()
=> GetString("TargetFrameworkDescription");
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// </summary>
internal static string GetMetadataFailed
{
get => GetString("GetMetadataFailed");
}
/// <summary>
/// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.
/// </summary>
internal static string FormatGetMetadataFailed()
=> GetString("GetMetadataFailed");
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string MultipleProjects
{
get => GetString("MultipleProjects");
}
/// <summary>
/// More than one project was found in the current working directory. Use the --project option.
/// </summary>
internal static string FormatMultipleProjects()
=> GetString("MultipleProjects");
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string MultipleProjectsInDirectory
{
get => GetString("MultipleProjectsInDirectory");
}
/// <summary>
/// More than one project was found in directory '{0}'. Specify one using its file name.
/// </summary>
internal static string FormatMultipleProjectsInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MultipleProjectsInDirectory"), p0);
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string NETCoreApp1Project
{
get => GetString("NETCoreApp1Project");
}
/// <summary>
/// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.
/// </summary>
internal static string FormatNETCoreApp1Project(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETCoreApp1Project"), p0, p1);
/// <summary>
/// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework.
/// </summary>
internal static string NETStandardProject
{
get => GetString("NETStandardProject");
}
/// <summary>
/// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework.
/// </summary>
internal static string FormatNETStandardProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NETStandardProject"), p0);
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string NoColorDescription
{
get => GetString("NoColorDescription");
}
/// <summary>
/// Do not colorize output.
/// </summary>
internal static string FormatNoColorDescription()
=> GetString("NoColorDescription");
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string NoProject
{
get => GetString("NoProject");
}
/// <summary>
/// No project was found. Change the current working directory or use the --project option.
/// </summary>
internal static string FormatNoProject()
=> GetString("NoProject");
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string NoProjectInDirectory
{
get => GetString("NoProjectInDirectory");
}
/// <summary>
/// No project was found in directory '{0}'.
/// </summary>
internal static string FormatNoProjectInDirectory(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("NoProjectInDirectory"), p0);
/// <summary>
/// Prefix output with level.
/// </summary>
internal static string PrefixDescription
{
get => GetString("PrefixDescription");
}
/// <summary>
/// Prefix output with level.
/// </summary>
internal static string FormatPrefixDescription()
=> GetString("PrefixDescription");
/// <summary>
/// The project to use.
/// </summary>
internal static string ProjectDescription
{
get => GetString("ProjectDescription");
}
/// <summary>
/// The project to use.
/// </summary>
internal static string FormatProjectDescription()
=> GetString("ProjectDescription");
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string ProjectExtensionsPathDescription
{
get => GetString("ProjectExtensionsPathDescription");
}
/// <summary>
/// The MSBuild project extensions path. Defaults to "obj".
/// </summary>
internal static string FormatProjectExtensionsPathDescription()
=> GetString("ProjectExtensionsPathDescription");
/// <summary>
/// The runtime identifier to use.
/// </summary>
internal static string RuntimeDescription
{
get => GetString("RuntimeDescription");
}
/// <summary>
/// The runtime identifier to use.
/// </summary>
internal static string FormatRuntimeDescription()
=> GetString("RuntimeDescription");
/// <summary>
/// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.
/// </summary>
internal static string UnsupportedFramework
{
get => GetString("UnsupportedFramework");
}
/// <summary>
/// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.
/// </summary>
internal static string FormatUnsupportedFramework(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedFramework"), p0, p1);
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string UsingProject
{
get => GetString("UsingProject");
}
/// <summary>
/// Using project '{0}'.
/// </summary>
internal static string FormatUsingProject(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UsingProject"), p0);
/// <summary>
/// Show verbose output.
/// </summary>
internal static string VerboseDescription
{
get => GetString("VerboseDescription");
}
/// <summary>
/// Show verbose output.
/// </summary>
internal static string FormatVerboseDescription()
=> GetString("VerboseDescription");
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string WritingFile
{
get => GetString("WritingFile");
}
/// <summary>
/// Writing '{0}'...
/// </summary>
internal static string FormatWritingFile(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0);
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// </summary>
internal static string MustBuild
{
get => GetString("MustBuild");
}
/// <summary>
/// Project output not found. Project must be up-to-date when using this tool.
/// </summary>
internal static string FormatMustBuild()
=> GetString("MustBuild");
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string OutputDescription
{
get => GetString("OutputDescription");
}
/// <summary>
/// The file to write the result to.
/// </summary>
internal static string FormatOutputDescription()
=> GetString("OutputDescription");
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set.
/// </summary>
internal static string GetMetadataValueFailed
{
get => GetString("GetMetadataValueFailed");
}
/// <summary>
/// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set.
/// </summary>
internal static string FormatGetMetadataValueFailed(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("GetMetadataValueFailed"), p0, p1);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
System.Diagnostics.Debug.Assert(value != null);
if (formatterNames != null)
{
for (var i = 0; i < formatterNames.Length; i++)
{
value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
}
}
return value;
}
}
}

View File

@ -0,0 +1,186 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ConfigurationDescription" xml:space="preserve">
<value>The configuration to use.</value>
</data>
<data name="CommandFullName" xml:space="preserve">
<value>dotnet-getdocument</value>
</data>
<data name="TargetFrameworkDescription" xml:space="preserve">
<value>The target framework.</value>
</data>
<data name="GetMetadataFailed" xml:space="preserve">
<value>Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option.</value>
</data>
<data name="MultipleProjects" xml:space="preserve">
<value>More than one project was found in the current working directory. Use the --project option.</value>
</data>
<data name="MultipleProjectsInDirectory" xml:space="preserve">
<value>More than one project was found in directory '{0}'. Specify one using its file name.</value>
</data>
<data name="NETCoreApp1Project" xml:space="preserve">
<value>Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher.</value>
</data>
<data name="NETStandardProject" xml:space="preserve">
<value>Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework.</value>
</data>
<data name="NoColorDescription" xml:space="preserve">
<value>Do not colorize output.</value>
</data>
<data name="NoProject" xml:space="preserve">
<value>No project was found. Change the current working directory or use the --project option.</value>
</data>
<data name="NoProjectInDirectory" xml:space="preserve">
<value>No project was found in directory '{0}'.</value>
</data>
<data name="PrefixDescription" xml:space="preserve">
<value>Prefix output with level.</value>
</data>
<data name="ProjectDescription" xml:space="preserve">
<value>The project to use.</value>
</data>
<data name="ProjectExtensionsPathDescription" xml:space="preserve">
<value>The MSBuild project extensions path. Defaults to "obj".</value>
</data>
<data name="RuntimeDescription" xml:space="preserve">
<value>The runtime identifier to use.</value>
</data>
<data name="UnsupportedFramework" xml:space="preserve">
<value>Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework.</value>
</data>
<data name="UsingProject" xml:space="preserve">
<value>Using project '{0}'.</value>
</data>
<data name="VerboseDescription" xml:space="preserve">
<value>Show verbose output.</value>
</data>
<data name="WritingFile" xml:space="preserve">
<value>Writing '{0}'...</value>
</data>
<data name="MustBuild" xml:space="preserve">
<value>Project output not found. Project must be up-to-date when using this tool.</value>
</data>
<data name="OutputDescription" xml:space="preserve">
<value>The file to write the result to.</value>
</data>
<data name="GetMetadataValueFailed" xml:space="preserve">
<value>Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set.</value>
</data>
</root>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Collect properties only in inner build. Execute unconditionally before WriteServiceProjectReferenceMetadata. -->
<Target Name="GetServiceProjectReferenceMetadata"
BeforeTargets="WriteServiceProjectReferenceMetadata"
Condition="'$(TargetFramework)' != ''"
Returns="@(ServiceProjectReferenceMetadata)">
<ItemGroup Condition="'$(TargetFramework)' != ''">
<ServiceProjectReferenceMetadata Include="DefaultDocumentName: $(DefaultServiceProjectDocumentName)" />
<ServiceProjectReferenceMetadata Include="DefaultMethod: $(DefaultServiceProjectMethod)" />
<ServiceProjectReferenceMetadata Include="DefaultService: $(DefaultServiceProjectService)" />
<ServiceProjectReferenceMetadata Include="AssemblyName: $(AssemblyName)" />
<ServiceProjectReferenceMetadata Include="Configuration: $(Configuration)" />
<ServiceProjectReferenceMetadata Include="OutputPath: $(OutputPath)" />
<ServiceProjectReferenceMetadata Include="Platform: $(Platform)" />
<ServiceProjectReferenceMetadata Include="PlatformTarget: $(PlatformTarget)" />
<ServiceProjectReferenceMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" />
<ServiceProjectReferenceMetadata Include="ProjectDepsFilePath: $(ProjectDepsFilePath)" />
<ServiceProjectReferenceMetadata Include="ProjectDirectory: $(MSBuildProjectDirectory)" />
<ServiceProjectReferenceMetadata Include="ProjectExtensionsPath: $(MSBuildProjectExtensionsPath)" />
<ServiceProjectReferenceMetadata Include="ProjectName: $(MSBuildProjectName)" />
<ServiceProjectReferenceMetadata Include="ProjectRuntimeConfigFilePath: $(ProjectRuntimeConfigFilePath)" />
<ServiceProjectReferenceMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
<ServiceProjectReferenceMetadata Include="RuntimeIdentifier: $(RuntimeIdentifier)" />
<ServiceProjectReferenceMetadata Include="TargetFramework: $(TargetFramework)" />
<ServiceProjectReferenceMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
<ServiceProjectReferenceMetadata Include="TargetPath: $(TargetPath)" />
</ItemGroup>
</Target>
<!-- Write information only in inner build. -->
<Target Name="WriteServiceProjectReferenceMetadata" Returns="@(ServiceProjectReferenceMetadata)">
<MSBuild Condition="'$(TargetFramework)' == ''"
Projects="$(MSBuildProjectFile)"
Targets="WriteServiceProjectReferenceMetadata"
Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);ServiceProjectReferenceMetadataFile=$(ServiceProjectReferenceMetadataFile)" />
<WriteLinesToFile Condition="'$(TargetFramework)' != ''"
File="$(ServiceProjectReferenceMetadataPath)"
Lines="@(ServiceProjectReferenceMetadata)" />
</Target>
</Project>

View File

@ -0,0 +1,25 @@
<Project Sdk="Internal.AspNetCore.Sdk">
<PropertyGroup>
<AssemblyName>dotnet-getdocument</AssemblyName>
<Description>GetDocument Command-line Tool outside man</Description>
<EnableApiCheck>false</EnableApiCheck>
<IsPackable>false</IsPackable>
<OutputType>Exe</OutputType>
<RootNamespace>Microsoft.Extensions.ApiDescription.Tool</RootNamespace>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="../GetDocumentInsider/Ansi*.cs" />
<Compile Include="../GetDocumentInsider/CommandException.cs" />
<Compile Include="../GetDocumentInsider/CommandLineUtils/*.cs" LinkBase="CommandLineUtils" />
<Compile Include="../GetDocumentInsider/Commands/CommandBase.cs" Link="Commands/CommandBase.cs" />
<Compile Include="../GetDocumentInsider/Commands/HelpCommandBase.cs" Link="Commands/HelpCommandBase.cs" />
<Compile Include="../GetDocumentInsider/ProductInfo.cs" />
<Compile Include="../GetDocumentInsider/Reporter.cs" />
<EmbeddedResource Include="ServiceProjectReferenceMetadata.targets" />
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -248,6 +248,7 @@ namespace Microsoft.AspNetCore.Mvc
new Type[]
{
typeof(MvcOptionsConfigureCompatibilityOptions),
typeof(MvcCoreMvcOptionsSetup),
}
},
{

View File

@ -1170,11 +1170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
var modelState = actionContext.ModelState;
var validationState = new ValidationStateDictionary();
var validator = CreateValidator(typeof(List<string>));
var validator = CreateValidator(typeof(List<ValidatedModel>));
var model = new List<string>()
var model = new List<ValidatedModel>()
{
"15",
new ValidatedModel { Value = "15" },
};
modelState.SetModelValue("userIds[0]", "15", "15");
@ -1192,6 +1192,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Empty(entry.Errors);
}
private class ValidatedModel
{
[Required]
public string Value { get; set; }
}
[Fact]
public void Validate_SuppressesValidation_ForExcludedType_Stream()
{
@ -1317,7 +1323,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ model, new ValidationStateEntry() }
};
var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance);
var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]);
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model));
@ -1325,6 +1330,102 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.NotNull(ex.HelpLink);
}
[Fact]
public void Validate_TypeWithoutValidators()
{
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new ModelWithoutValidation();
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
actionContext.ModelState.SetModelValue("Property1", new ValueProviderResult("value1"));
actionContext.ModelState.SetModelValue("Property2", new ValueProviderResult("value2"));
// Act
validator.Validate(actionContext, validationState, string.Empty, model);
// Assert
var modelState = actionContext.ModelState;
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
Assert.True(modelState.IsValid);
var entry = modelState["Property1"];
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
entry = modelState["Property2"];
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
}
[Fact]
public void Validate_TypeWithoutValidators_DoesNotUpdateValidationState()
{
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new ModelWithoutValidation();
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
var modelState = actionContext.ModelState;
modelState.SetModelValue("Property1", new ValueProviderResult("value1"));
modelState.SetModelValue("Property2", new ValueProviderResult("value2"));
modelState["Property2"].ValidationState = ModelValidationState.Skipped;
// Act
validator.Validate(actionContext, validationState, string.Empty, model);
// Assert
Assert.Equal(ModelValidationState.Valid, modelState.ValidationState);
Assert.True(modelState.IsValid);
var entry = modelState["Property1"];
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
entry = modelState["Property2"];
Assert.Equal(ModelValidationState.Skipped, entry.ValidationState);
}
[Fact]
public void Validate_TypeWithoutValidators_DoesNotResetInvalidState()
{
var actionContext = new ActionContext();
var validator = CreateValidator();
var model = new ModelWithoutValidation();
var validationState = new ValidationStateDictionary
{
{ model, new ValidationStateEntry() }
};
var modelState = actionContext.ModelState;
modelState.SetModelValue("Property1", new ValueProviderResult("value1"));
modelState.SetModelValue("Property2", new ValueProviderResult("value2"));
modelState["Property2"].ValidationState = ModelValidationState.Invalid;
// Act
validator.Validate(actionContext, validationState, string.Empty, model);
// Assert
Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState);
Assert.False(modelState.IsValid);
var entry = modelState["Property1"];
Assert.Equal(ModelValidationState.Valid, entry.ValidationState);
entry = modelState["Property2"];
Assert.Equal(ModelValidationState.Invalid, entry.ValidationState);
}
private class ModelWithoutValidation
{
public string Property1 { get; set; }
public string Property2 { get; set; }
}
private static DefaultObjectValidator CreateValidator(Type excludedType)
{
var excludeFilters = new List<SuppressChildValidationMetadataProvider>();
@ -1352,6 +1453,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private class ThrowingProperty
{
[Required]
public string WatchOut
{
get
@ -1520,6 +1622,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Depth = depth;
}
[Range(-10, 400)]
public int Depth { get; }
public int MaxAllowedDepth { get; }

View File

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -909,6 +910,351 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
metadataProvider.VerifyAll();
}
[Fact]
public void CalculateHasValidators_ParameterMetadata_TypeHasNoValidators()
{
// Arrange
var parameter = GetType()
.GetMethod(nameof(CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod), BindingFlags.Static | BindingFlags.NonPublic)
.GetParameters()[0];
var modelIdentity = ModelMetadataIdentity.ForParameter(parameter);
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.False(result);
}
private static void CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod(string model) { }
[Fact]
public void CalculateHasValidators_PropertyMetadata_TypeHasNoValidators()
{
// Arrange
var property = GetType()
.GetProperty(nameof(CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty), BindingFlags.Static | BindingFlags.NonPublic);
var modelIdentity = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, GetType());
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.False(result);
}
private static int CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty { get; set; }
[Fact]
public void CalculateHasValidators_TypeWithoutProperties_TypeHasNoValidators()
{
// Arrange
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), false);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.False(result);
}
[Fact]
public void CalculateHasValidators_SimpleType_TypeHasValidators()
{
// Arrange
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), true);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_ReturnsTrue_SimpleType_TypeHasNonDeterministicValidators()
{
// Arrange
var modelIdentity = ModelMetadataIdentity.ForType(typeof(string));
var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of<IModelMetadataProvider>(), null);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_TypeWithProperties_PropertyIsNotDefaultModelMetadata()
{
// Arrange
var modelType = typeof(TypeWithProperties);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
var propertyMetadata = new Mock<ModelMetadata>(propertyIdentity);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { propertyMetadata.Object, })
.Verifiable();
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_TypeWithProperties_HasValidatorForAnyPropertyIsTrue()
{
// Arrange
var modelType = typeof(TypeWithProperties);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), typeof(string));
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, true);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { property1Metadata, property2Metadata })
.Verifiable();
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_TypeWithProperties_HasValidatorsForPropertyIsNotDeterminstic()
{
// Arrange
var modelType = typeof(TypeWithProperties);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string));
var propertyMetadata = CreateModelMetadata(propertyIdentity, metadataProvider.Object, null);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { propertyMetadata, })
.Verifiable();
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_TypeWithProperties_HasValidatorForAllPropertiesIsFalse()
{
// Arrange
var modelType = typeof(TypeWithProperties);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), modelType);
var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false);
var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), modelType);
var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, false);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { property1Metadata, property2Metadata })
.Verifiable();
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.False(result);
}
[Fact]
public void CalculateHasValidators_SelfReferencingType_HasValidatorOnNestedProperty()
{
// Arrange
var modelType = typeof(Employee);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, true); // BusinessUnit.Id has validators.
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, })
.Verifiable();
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
.Returns(new[] { unitHeadMetadata, unitIdMetadata, })
.Verifiable();
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_SelfReferencingType_HasValidatorOnSelfReferencedProperty()
{
// Arrange
var modelType = typeof(Employee);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, true); // BusinessUnit.Head has validators
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, });
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
.Returns(new[] { unitHeadMetadata, unitIdMetadata, });
metadataProvider
.Setup(mp => mp.GetMetadataForType(modelType))
.Returns(modelMetadata);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_CollectionElementHasValidators()
{
// Arrange
var modelType = typeof(Employee);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { employeeIdMetadata, employeeEmployeesMetadata, });
metadataProvider
.Setup(mp => mp.GetMetadataForType(modelType))
.Returns(CreateModelMetadata(modelIdentity, metadataProvider.Object, true)); // Employees.Employee has validators
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.True(result);
}
[Fact]
public void CalculateHasValidators_SelfReferencingType_NoValidatorsInGraph()
{
// Arrange
var modelType = typeof(Employee);
var modelIdentity = ModelMetadataIdentity.ForType(modelType);
var metadataProvider = new Mock<IModelMetadataProvider>();
var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType);
var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false);
var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType);
var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false);
var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType);
var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false);
var employeeEmployeesId = ModelMetadataIdentity.ForProperty(typeof(List<Employee>), nameof(Employee.Employees), modelType);
var employeeEmployeesIdMetadata = CreateModelMetadata(employeeEmployeesId, metadataProvider.Object, false);
var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType);
var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false);
var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType);
var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false);
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(modelType))
.Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesIdMetadata, });
metadataProvider
.Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit)))
.Returns(new[] { unitHeadMetadata, unitIdMetadata, });
metadataProvider
.Setup(mp => mp.GetMetadataForType(modelType))
.Returns(modelMetadata);
// Act
var result = DefaultModelMetadata.CalculateHasValidators(new HashSet<DefaultModelMetadata>(), modelMetadata);
// Assert
Assert.False(result);
}
private static DefaultModelMetadata CreateModelMetadata(
ModelMetadataIdentity modelIdentity,
IModelMetadataProvider metadataProvider,
bool? hasValidators)
{
return new DefaultModelMetadata(
metadataProvider,
new SetHasValidatorsCompositeMetadataDetailsProvider { HasValidators = hasValidators },
new DefaultMetadataDetails(modelIdentity, new ModelAttributes(new object[0], new object[0], new object[0])));
}
private void ActionMethod(string input)
{
}
@ -921,5 +1267,41 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
public int PublicGetPublicSetProperty { get; set; }
}
public class Employee
{
public int Id { get; set; }
public BusinessUnit Unit { get; set; }
public Employee Manager { get; set; }
public List<Employee> Employees { get; set; }
}
public class BusinessUnit
{
public Employee Head { get; set; }
public int Id { get; set; }
}
private class SetHasValidatorsCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider
{
public bool? HasValidators { get; set; }
public void CreateBindingMetadata(BindingMetadataProviderContext context)
{
}
public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
{
}
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
context.ValidationMetadata.HasValidators = HasValidators;
}
}
}
}

View File

@ -0,0 +1,131 @@
// 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.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata
{
public class HasValidatorsValidationMetadataProviderTest
{
[Fact]
public void CreateValidationMetadata_DoesNotSetHasValidators_IfNonMetadataBasedProviderExists()
{
// Arrange
var validationProviders = new IModelValidatorProvider[]
{
new DefaultModelValidatorProvider(),
Mock.Of<IModelValidatorProvider>(),
};
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
var key = ModelMetadataIdentity.ForType(typeof(object));
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
var context = new ValidationMetadataProviderContext(key, modelAttributes);
// Act
metadataProvider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.HasValidators);
}
[Fact]
public void CreateValidationMetadata_DoesNotSetHasValidators_IfProviderIsConfigured()
{
// Arrange
var validationProviders = new IModelValidatorProvider[0];
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
var key = ModelMetadataIdentity.ForType(typeof(object));
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
var context = new ValidationMetadataProviderContext(key, modelAttributes);
// Act
metadataProvider.CreateValidationMetadata(context);
// Assert
Assert.Null(context.ValidationMetadata.HasValidators);
}
[Fact]
public void CreateValidationMetadata_SetsHasValidatorsToTrue_IfProviderReturnsTrue()
{
// Arrange
var metadataBasedModelValidatorProvider = new Mock<IMetadataBasedModelValidatorProvider>();
metadataBasedModelValidatorProvider.Setup(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()))
.Returns(true)
.Verifiable();
var validationProviders = new IModelValidatorProvider[]
{
new DefaultModelValidatorProvider(),
metadataBasedModelValidatorProvider.Object,
};
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
var key = ModelMetadataIdentity.ForType(typeof(object));
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
var context = new ValidationMetadataProviderContext(key, modelAttributes);
// Act
metadataProvider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.HasValidators);
metadataBasedModelValidatorProvider.Verify();
}
[Fact]
public void CreateValidationMetadata_SetsHasValidatorsToFalse_IfNoProviderReturnsTrue()
{
// Arrange
var provider = Mock.Of<IMetadataBasedModelValidatorProvider>(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()) == false);
var validationProviders = new IModelValidatorProvider[]
{
new DefaultModelValidatorProvider(),
provider,
};
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
var key = ModelMetadataIdentity.ForType(typeof(object));
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
var context = new ValidationMetadataProviderContext(key, modelAttributes);
// Act
metadataProvider.CreateValidationMetadata(context);
// Assert
Assert.False(context.ValidationMetadata.HasValidators);
}
[Fact]
public void CreateValidationMetadata_DoesNotOverrideExistingHasValidatorsValue()
{
// Arrange
var provider = Mock.Of<IMetadataBasedModelValidatorProvider>(p => p.HasValidators(typeof(object), It.IsAny<IList<object>>()) == false);
var validationProviders = new IModelValidatorProvider[]
{
new DefaultModelValidatorProvider(),
provider,
};
var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders);
var key = ModelMetadataIdentity.ForType(typeof(object));
var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]);
var context = new ValidationMetadataProviderContext(key, modelAttributes);
// Initialize this value.
context.ValidationMetadata.HasValidators = true;
// Act
metadataProvider.CreateValidationMetadata(context);
// Assert
Assert.True(context.ValidationMetadata.HasValidators);
}
}
}

View File

@ -6,11 +6,9 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Internal
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation
{
// Integration tests for the default configuration of ModelMetadata and Validation providers
public class DefaultModelValidatorProviderTest
@ -145,6 +143,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is StringLengthAttribute);
}
[Fact]
public void HasValidators_ReturnsTrue_IfMetadataIsIModelValidator()
{
// Arrange
var validatorProvider = new DefaultModelValidatorProvider();
var attributes = new object[] { new RequiredAttribute(), new CustomModelValidatorAttribute(), new BindRequiredAttribute(), };
// Act
var result = validatorProvider.HasValidators(typeof(object), attributes);
// Assert
Assert.True(result);
}
[Fact]
public void HasValidators_ReturnsFalse_IfNoMetadataIsIModelValidator()
{
// Arrange
var validatorProvider = new DefaultModelValidatorProvider();
var attributes = new object[] { new RequiredAttribute(), new BindRequiredAttribute(), };
// Act
var result = validatorProvider.HasValidators(typeof(object), attributes);
// Assert
Assert.False(result);
}
private static IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
{
return metadata.ValidatorMetadata.Select(v => new ValidatorItem(v)).ToList();

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Xunit;
@ -37,6 +38,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders);
var validationProviders = TestModelValidatorProvider.CreateDefaultProvider();
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders));
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
}
@ -57,6 +61,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding
detailsProviders.AddRange(providers);
var validationProviders = TestModelValidatorProvider.CreateDefaultProvider();
detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders));
var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders);
return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions()));
}

View File

@ -3,8 +3,6 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;

View File

@ -1,16 +1,18 @@
// 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;
using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
namespace Microsoft.AspNetCore.Mvc.DataAnnotations
{
public class DataAnnotationsModelValidatorProviderTest
{
@ -110,6 +112,56 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal
Assert.Single(providerContext.Results);
}
[Fact]
public void HasValidators_ReturnsTrue_IfModelIsIValidatableObject()
{
// Arrange
var provider = GetProvider();
var mockValidatable = Mock.Of<IValidatableObject>();
// Act
var result = provider.HasValidators(mockValidatable.GetType(), Array.Empty<object>());
// Assert
Assert.True(result);
}
[Fact]
public void HasValidators_ReturnsTrue_IfMetadataContainsValidationAttribute()
{
// Arrange
var provider = GetProvider();
var attributes = new object[] { new BindNeverAttribute(), new DummyValidationAttribute() };
// Act
var result = provider.HasValidators(typeof(object), attributes);
// Assert
Assert.True(result);
}
[Fact]
public void HasValidators_ReturnsFalse_IfNoDataAnnotationsValidationIsAvailable()
{
// Arrange
var provider = GetProvider();
var attributes = new object[] { new BindNeverAttribute(), };
// Act
var result = provider.HasValidators(typeof(object), attributes);
// Assert
Assert.False(result);
}
private static DataAnnotationsModelValidatorProvider GetProvider()
{
return new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
}
private IList<ValidatorItem> GetValidatorItems(ModelMetadata metadata)
{
var items = new List<ValidatorItem>(metadata.ValidatorMetadata.Count);

View File

@ -1,42 +0,0 @@
// 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.Logging.Abstractions;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal
{
public class MvcXmlDataContractSerializerMvcOptionsSetupTest
{
[Fact]
public void AddsFormatterMapping()
{
// Arrange
var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("application/xml", mappedContentType);
}
[Fact]
public void DoesNotOverrideExistingMapping()
{
// Arrange
var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance);
var options = new MvcOptions();
options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml");
// Act
optionsSetup.Configure(options);
// Assert
var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml");
Assert.Equal("text/xml", mappedContentType);
}
}
}

Some files were not shown because too many files have changed in this diff Show More