Merge remote-tracking branch 'origin/release/2.2'
This commit is contained in:
commit
5f42d5063e
|
|
@ -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
45
Mvc.sln
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
version:3.0.0-alpha1-20181004.5
|
||||
commithash:8725f7194e9976f75f90f56afc0fba6116ac597c
|
||||
version:3.0.0-alpha1-20181011.11
|
||||
commithash:f57aa8ddda0abdd74ada55853587bedb4f364065
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
|
|||
|
||||
_serializerSettings = new DataContractSerializerSettings();
|
||||
|
||||
WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories();
|
||||
WrapperProviderFactories = new List<IWrapperProviderFactory>
|
||||
{
|
||||
new SerializableErrorWrapperProviderFactory(),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
86
src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs
generated
Normal file
86
src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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="<not-a-file !>">
|
||||
<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="<not-a-file !>">
|
||||
<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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -248,6 +248,7 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
new Type[]
|
||||
{
|
||||
typeof(MvcOptionsConfigureCompatibilityOptions),
|
||||
typeof(MvcCoreMvcOptionsSetup),
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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
Loading…
Reference in New Issue