Rename all the service reference things (#9559)

- #7492
  - remove document generation from client project; will be included in Web API project infrastructure (coming soon)
  - adjust eng/ProjectReferences.props to new project name
  - simplify item de-duplication in `_CreateCompileItemsForServiceFileReferences` target i.e. use `Remove` attribute
    - also handle `.tsx` files in this target
- provide `%(FirstForGenerator)` metadata, #4916
- add `$(OpenApiGenerateAtDesignTime)` property (default `true` for now), #4944
- generate code in `obj` directory by default, #4945
- provide a default `%(CodeGenerator)` value, ##7491 1 of 2 (remainder will come in next milestone)

nits:
- remove a useless `StringBuilder.Append(...)` call
- remove `%(OpenApiProjectReference.SourceProject)` metadata, duplicated `%(OriginalItemSpec)`
- be more consistent about using element syntax for item metadata
This commit is contained in:
Doug Bunting 2019-04-22 11:15:54 -07:00 committed by GitHub
parent 1c19e8ee89
commit ce8f053af7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 254 additions and 623 deletions

View File

@ -8,7 +8,7 @@
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Identity.Specification.Tests" ProjectPath="$(RepositoryRoot)src\Identity\Specification.Tests\src\Microsoft.AspNetCore.Identity.Specification.Tests.csproj" />
<ProjectReferenceProvider Include="Microsoft.Web.Xdt.Extensions" ProjectPath="$(RepositoryRoot)src\SiteExtensions\Microsoft.Web.Xdt.Extensions\src\Microsoft.Web.Xdt.Extensions.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.DeveloperCertificates.XPlat" ProjectPath="$(RepositoryRoot)src\Tools\FirstRunCertGenerator\src\Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.ApiDescription.Tasks" ProjectPath="$(RepositoryRoot)src\Mvc\Extensions.ApiDescription.Design\src\Microsoft.Extensions.ApiDescription.Design.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.ApiDescription.Tasks" ProjectPath="$(RepositoryRoot)src\Mvc\Extensions.ApiDescription.Client\src\Microsoft.Extensions.ApiDescription.Client.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.SignalR.Specification.Tests" ProjectPath="$(RepositoryRoot)src\SignalR\server\Specification.Tests\src\Microsoft.AspNetCore.SignalR.Specification.Tests.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Blazor.Build" ProjectPath="$(RepositoryRoot)src\Components\Blazor\Build\src\Microsoft.AspNetCore.Blazor.Build.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore" ProjectPath="$(RepositoryRoot)src\DefaultBuilder\src\Microsoft.AspNetCore.csproj" RefProjectPath="$(RepositoryRoot)src\DefaultBuilder\ref\Microsoft.AspNetCore.csproj" />

View File

@ -10,8 +10,8 @@ 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.
/// Adds or corrects ClassName, FirstForGenerator, Namespace, and OutputPath metadata in OpenApiReference items.
/// Also stores final metadata as SerializedMetadata.
/// </summary>
public class GetFileReferenceMetadata : Task
{
@ -35,13 +35,13 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
public string OutputDirectory { get; set; }
/// <summary>
/// The ServiceFileReference items to update.
/// The OpenApiReference items to update.
/// </summary>
[Required]
public ITaskItem[] Inputs { get; set; }
/// <summary>
/// The updated ServiceFileReference items. Will include ClassName, Namespace and OutputPath metadata.
/// The updated OpenApiReference items. Will include ClassName, Namespace and OutputPath metadata.
/// </summary>
[Output]
public ITaskItem[] Outputs{ get; set; }
@ -50,36 +50,45 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
public override bool Execute()
{
var outputs = new List<ITaskItem>(Inputs.Length);
var codeGenerators = new HashSet<string>();
var destinations = new HashSet<string>();
foreach (var item in Inputs)
{
var codeGenerator = item.GetMetadata("CodeGenerator");
if (string.IsNullOrEmpty(codeGenerator))
{
// This case occurs when user overrides the required metadata with an empty string.
var type = string.IsNullOrEmpty(item.GetMetadata("SourceProject")) ?
"OpenApiReference" :
"OpenApiProjectReference";
Log.LogError(
Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", "OpenApiReference", item.ItemSpec));
continue;
}
var newItem = new TaskItem(item);
outputs.Add(newItem);
var codeGenerator = item.GetMetadata("CodeGenerator");
if (string.IsNullOrEmpty("CodeGenerator"))
if (codeGenerators.Add(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
{
type = "ServiceFileReference";
}
Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec));
newItem.SetMetadata("FirstForGenerator", "true");
}
else
{
newItem.SetMetadata("FirstForGenerator", "false");
}
var outputPath = item.GetMetadata("OutputPath");
if (string.IsNullOrEmpty(outputPath))
{
// No need to further sanitize this path.
// No need to further sanitize this path because the file must exist.
var filename = item.GetMetadata("Filename");
var isTypeScript = codeGenerator.EndsWith(TypeScriptLanguageName, StringComparison.OrdinalIgnoreCase);
var isTypeScript = codeGenerator.EndsWith(
TypeScriptLanguageName,
StringComparison.OrdinalIgnoreCase);
outputPath = $"{filename}Client{(isTypeScript ? ".ts" : Extension)}";
}
@ -94,6 +103,7 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
// 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));
continue;
}
MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath);

View File

@ -115,7 +115,6 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
{
if (string.IsNullOrEmpty(value))
{
builder.Append(value);
return;
}

View File

@ -27,15 +27,9 @@
<ItemGroup>
<Reference Include="Microsoft.Build.Utilities.Core" />
<Reference Include="System.Net.Http" Condition="'$(TargetFramework)' == 'net461'" />
</ItemGroup>
<Target Name="PopulateNuspec">
<MSBuild Projects="../../dotnet-getdocument/src/dotnet-getdocument.csproj"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="RuntimeIdentifier;TargetFrameworks;TargetFramework"
Targets="Publish" />
<PropertyGroup>
<NuspecProperties>
id=$(PackageId);

View File

@ -21,9 +21,5 @@
<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\src\bin\$configuration$\netcoreapp2.1\publish\*.*" target="tools" />
<file src="..\..\GetDocumentInsider\src\bin\$configuration$\net461\GetDocument.Insider.*" target="tools\net461" />
<file src="..\..\GetDocumentInsider\src\bin\x86\$configuration$\net461\GetDocument.Insider.*" target="tools\net461-x86" />
<file src="..\..\GetDocumentInsider\src\bin\$configuration$\netcoreapp2.0\GetDocument.Insider.*" target="tools\netcoreapp2.0" />
</files>
</package>

View File

@ -11,7 +11,7 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
= new ResourceManager("Microsoft.Extensions.ApiDescription.Tasks.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference and ServiceProjectReference items must have unique OutputPath metadata.
/// Multiple items have OutputPath='{0}'. All OpenApiReference items must have unique OutputPath metadata.
/// </summary>
internal static string DuplicateFileOutputPaths
{
@ -19,25 +19,11 @@ namespace Microsoft.Extensions.ApiDescription.Tasks
}
/// <summary>
/// Multiple items have OutputPath='{0}'. All ServiceFileReference and ServiceProjectReference items must have unique OutputPath metadata.
/// Multiple items have OutputPath='{0}'. All OpenApiReference 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>
/// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string.
/// </summary>

View File

@ -118,11 +118,7 @@
<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 and ServiceProjectReference items must have unique OutputPath metadata.</value>
<comment>ServiceProjectReference 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>
<value>Multiple items have OutputPath='{0}'. All OpenApiReference items must have unique OutputPath metadata.</value>
</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>

View File

@ -0,0 +1,67 @@
<?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)" />
<!--
Settings users may update as they see fit.
-->
<PropertyGroup>
<OpenApiDefaultGeneratorOptions Condition="'$(OpenApiDefaultGeneratorOptions)' == ''"></OpenApiDefaultGeneratorOptions>
<!--
If 'true', will generate code for OpenApiReference items during design-time builds. Otherwise, generate code only
for output files that do not yet exist during design-time builds.
-->
<OpenApiGenerateAtDesignTime Condition="'$(OpenApiGenerateAtDesignTime)' == ''">true</OpenApiGenerateAtDesignTime>
<!--
$(OpenApiDefaultOutputDirectory) value is interpreted relative to the project folder, unless already an
absolute path.
-->
<OpenApiDefaultOutputDirectory
Condition="'$(OpenApiDefaultOutputDirectory)' == ''">$(BaseIntermediateOutputPath)</OpenApiDefaultOutputDirectory>
<OpenApiDefaultOutputDirectory>$([MSBuild]::EnsureTrailingSlash('$(OpenApiDefaultOutputDirectory)'))</OpenApiDefaultOutputDirectory>
</PropertyGroup>
<!--
Well-known metadata of the code generator item groups.
-->
<ItemDefinitionGroup>
<!-- OpenApiProjectReference items may also include OpenApiReference metadata. -->
<OpenApiProjectReference />
<OpenApiReference>
<!-- Name of the class to generate. Defaults to match filename in %(OutputPath). -->
<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>NSwagCSharp</CodeGenerator>
<!-- Namespace to contain generated class. Default is $(RootNamespace). -->
<Namespace />
<!--
Options to pass to the code generator target then (likely) added to a tool's command line. Value is passed
along to the code generator but otherwise unused in this package.
-->
<Options>$(OpenApiDefaultGeneratorOptions)</Options>
<!--
Path to place generated code. Code generator may interpret path as a filename or directory. Default filename or
folder name is %(Filename)Client.[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
$(OpenApiDefaultOutputDirectory). Final value (depending on $(OpenApiDefaultOutputDirectory)) is likely to be
a path relative to the client project.
-->
<OutputPath />
</OpenApiReference>
</ItemDefinitionGroup>
</Project>

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project>
<!-- Internal settings. Not intended for customization. -->
<PropertyGroup>
<GenerateOpenApiReferenceCodeDependsOn>
_GetMetadataForOpenApiReferences;
_GenerateOpenApiReferenceCode;
_CreateCompileItemsForOpenApiReferences
</GenerateOpenApiReferenceCodeDependsOn>
<GenerateServiceFileReferenceCodeDependsOn>
_GenerateErrorsForOldItems;
_CreateOpenApiReferenceItemsForOpenApiProjectReferences;
GenerateOpenApiReferenceCode
</GenerateServiceFileReferenceCodeDependsOn>
</PropertyGroup>
<!-- OpenApiProjectReference support. -->
<ItemGroup>
<!--
Do not change %(ReferenceOutputAssembly) if project contains duplicate @(ProjectReference) and
@(OpenApiProjectReference) items.
-->
<ProjectReference Include="@(OpenApiProjectReference)" Exclude="@(ProjectReference)">
<NoWarn>NU1702</NoWarn>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
<ProjectReference Update="@(OpenApiProjectReference)">
<OpenApiReference>true</OpenApiReference>
</ProjectReference>
</ItemGroup>
<Target Name="_CreateOpenApiReferenceItemsForOpenApiProjectReferences"
AfterTargets="ResolveProjectReferences"
Condition="'$(DesignTimeBuild)' != 'true' AND '$(BuildingProject)' == 'true'">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Targets="OpenApiGetDocuments"
Projects="@(ProjectReferenceWithConfiguration)"
Condition="'%(ProjectReferenceWithConfiguration.OpenApiReference)' == 'true'"
Properties="%(ProjectReferenceWithConfiguration.SetConfiguration); %(ProjectReferenceWithConfiguration.SetPlatform); %(ProjectReferenceWithConfiguration.SetTargetFramework)"
RebaseOutputs="true"
RemoveProperties="%(ProjectReferenceWithConfiguration.GlobalPropertiesToRemove);TargetFrameworks;RuntimeIdentifier">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<ItemGroup>
<OpenApiReference Include="@(_Temporary)" Exclude="@(OpenApiReference)" RemoveMetadata="FullConfiguration" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<!-- OpenApiReference support. -->
<Target Name="_GetMetadataForOpenApiReferences" Condition="'@(OpenApiReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetFileReferenceMetadata Inputs="@(OpenApiReference)"
Extension="$(DefaultLanguageSourceExtension)"
Namespace="$(RootNamespace)"
OutputDirectory="$(OpenApiDefaultOutputDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetFileReferenceMetadata>
<ItemGroup>
<OpenApiReference Remove="@(OpenApiReference)" />
<OpenApiReference Include="@(_Temporary)" />
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_GetCurrentOpenApiReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentOpenApiReference" />
</GetCurrentItems>
</Target>
<Target Name="_InnerGenerateOpenApiReferenceCode" DependsOnTargets="_GetCurrentOpenApiReference;$(GeneratorTarget)" />
<Target Name="_GenerateOpenApiReferenceCode"
Condition="$(OpenApiGenerateAtDesignTime) OR ('$(DesignTimeBuild)' != 'true' AND '$(BuildingProject)' == 'true')"
Inputs="@(OpenApiReference)"
Outputs="%(OutputPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(OpenApiReference.OutputPath);GeneratorTarget=Generate%(CodeGenerator);GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_InnerGenerateOpenApiReferenceCode" />
</Target>
<Target Name="_CreateCompileItemsForOpenApiReferences" Condition="'@(OpenApiReference)' != ''">
<ItemGroup>
<_Files Remove="@(_Files)" />
<_Files Include="@(OpenApiReference -> '%(OutputPath)')"
Condition="$([System.IO.File]::Exists('%(OpenApiReference.OutputPath)'))">
<OutputPathExtension>$([System.IO.Path]::GetExtension('%(OpenApiReference.OutputPath)'))</OutputPathExtension>
</_Files>
<_Directories Remove="@(_Directories)" />
<_Directories Include="@(OpenApiReference -> '%(OutputPath)')"
Condition="Exists('%(OpenApiReference.OutputPath)') AND ! $([System.IO.File]::Exists('%(OpenApiReference.OutputPath)'))" />
<!-- If OutputPath is a file, add it directly to relevant items. -->
<TypeScriptCompile Include="@(_Files)"
Exclude="@(TypeScriptCompile)"
Condition="'%(_Files.OutputPathExtension)' == '.ts' OR '%(_Files.OutputPathExtension)' == '.tsx'">
<SourceDocument>%(_Files.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Include="@(_Files)"
Exclude="@(Compile)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts' AND '%(_Files.OutputPathExtension)' == '$(DefaultLanguageSourceExtension)'">
<SourceDocument>%(OpenApiReference.FullPath)</SourceDocument>
</Compile>
<!-- Otherwise, add all descendant files with the expected extension. -->
<TypeScriptCompile Include="@(_Directories -> '%(Identity)/**/*.ts;%(Identity)/**/*.tsx')"
Exclude="@(TypeScriptCompile)">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Include="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Exclude="@(Compile)"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</Compile>
<_Files Remove="@(_Files)" />
<_Directories Remove="@(_Directories)" />
</ItemGroup>
</Target>
<Target Name="GenerateOpenApiReferenceCode" DependsOnTargets="$(GenerateOpenApiReferenceCodeDependsOn)" />
<!-- Tie above code generation steps into the build. -->
<Target Name="_GenerateErrorsForOldItems">
<Error Condition="'@(ServiceProjectReference)' != ''" Text="ServiceProjectReference items are no longer supported." />
<Error Condition="'@(ServiceUriReference)' != ''" Text="ServiceUriReference items are no longer supported." />
<Error Condition="'@(ServiceFileReference)' != ''" Text="ServiceFileReference items are no longer supported." />
</Target>
<Target Name="GenerateServiceFileReferenceCode"
BeforeTargets="BeforeCompile"
DependsOnTargets="$(GenerateServiceFileReferenceCodeDependsOn)" />
</Project>

View File

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

View File

@ -1,147 +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;
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
{
private static readonly char[] InvalidFilenameCharacters = Path.GetInvalidFileNameChars();
private static readonly string[] InvalidFilenameStrings = new[] { ".." };
/// <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 documentName = item.GetMetadata("DocumentName");
if (string.IsNullOrEmpty(documentName))
{
documentName = "v1";
MetadataSerializer.SetMetadata(newItem, "DocumentName", documentName);
}
var documentPath = item.GetMetadata("DocumentPath");
if (string.IsNullOrEmpty(documentPath))
{
// No need to sanitize the filename since the project file exists.
var projectFilename = item.GetMetadata("Filename");
// Default document filename matches project filename unless given a non-default document name.
if (string.IsNullOrEmpty(documentName))
{
// This is an odd (but allowed) case that would break the sanitize one-liner below. Also,
// ensure chosen name does not match the "v1" case.
documentPath = projectFilename + "_.json";
}
else if (string.Equals("v1", documentName, StringComparison.Ordinal))
{
documentPath = projectFilename + ".json";
}
else
{
// Sanitize the document name because it may contain almost any character, including illegal
// filename characters such as '/' and '?'. (Do not treat slashes as folder separators.)
var sanitizedDocumentName = string.Join("_", documentName.Split(InvalidFilenameCharacters));
while (sanitizedDocumentName.Contains(InvalidFilenameStrings[0]))
{
sanitizedDocumentName = string.Join(
".",
sanitizedDocumentName.Split(InvalidFilenameStrings, StringSplitOptions.None));
}
documentPath = $"{projectFilename}_{sanitizedDocumentName}";
// Possible the document path already ends with .json. Don't duplicate that or a final period.
if (!documentPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
if (documentPath.EndsWith(".", StringComparison.Ordinal))
{
documentPath += "json";
}
else
{
documentPath += ".json";
}
}
}
}
documentPath = GetFullPath(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));
}
MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath);
// Add metadata which may be used as a property and passed to an inner build.
newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem));
}
Outputs = outputs.ToArray();
return !Log.HasLoggedErrors;
}
private string GetFullPath(string path)
{
if (!Path.IsPathRooted(path))
{
if (!string.IsNullOrEmpty(DocumentDirectory))
{
path = Path.Combine(DocumentDirectory, path);
}
path = Path.GetFullPath(path);
}
return path;
}
}
}

View File

@ -1,124 +0,0 @@
<?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)" />
<!--
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>
<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 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 "GenerateAsync".
-->
<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>
<ServiceFileReference>
<!-- Name of the class to generate. Defaults to match final segment of %(OutputPath). -->
<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 %(Filename)Client.[cs|ts]. Filenames and relative paths (if explicitly set) are combined with
$(ServiceFileReferenceDirectory). Final value (depending on $(ServiceFileReferenceDirectory)) is likely to be
a path relative to the client project.
-->
<OutputPath />
</ServiceFileReference>
</ItemDefinitionGroup>
</Project>

View File

@ -1,295 +0,0 @@
<?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>
<GenerateServiceFileReferenceCodesDependsOn>
GenerateServiceProjectReferenceDocuments;
_GetMetadataForServiceFileReferences;
_GenerateServiceFileReferenceCodes;
_CreateCompileItemsForServiceFileReferences
</GenerateServiceFileReferenceCodesDependsOn>
</PropertyGroup>
<!-- ServiceProjectReference support -->
<!--
Metadata setup phase 1: Ensure items have TargetFramework metadata. Calls GetTargetFrameworks in the target
project. Inputs and outputs cause MSBuild to run target unconditionally and to batch it (run once per project).
-->
<Target Name="_GetTargetFrameworkForServiceProjectReferences"
Inputs="%(ServiceProjectReference.FullPath)"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
RebaseOutputs="true"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetFrameworks"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<!--
Please excuse the mess necessary to extract information from _Temporary and use it in ServiceProjectReference.
-->
<PropertyGroup>
<_TargetFrameworks>%(_Temporary.TargetFrameworks)</_TargetFrameworks>
<_TargetFramework>$(_TargetFrameworks.Split(';')[0])</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)" Condition="'%(FullPath)' == '$(_FullPath)'">
<TargetFramework Condition="'%(TargetFramework)' == ''">$(_TargetFramework)</TargetFramework>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetFramework />
<_TargetFrameworks />
</PropertyGroup>
</Target>
<!--
Metadata setup phase 2: Ensure items have TargetPath metadata. Calls GetTargetPath in the target project.
Inputs and outputs cause MSBuild to run target unconditionally and batch it (run once per TargetFramework x
project combination).
-->
<Target Name="_GetTargetPathForServiceProjectReferences"
Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')"
Outputs="&lt;not-a-file !&gt;">
<PropertyGroup>
<_FullPath>%(ServiceProjectReference.FullPath)</_FullPath>
<_TargetFramework>%(ServiceProjectReference.TargetFramework)</_TargetFramework>
</PropertyGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<MSBuild Projects="$(_FullPath)"
Properties="TargetFramework=$(_TargetFramework)"
RebaseOutputs="true"
RemoveProperties="TargetFrameworks;RuntimeIdentifier"
Targets="GetTargetPath"
UseResultsCache="true">
<Output TaskParameter="TargetOutputs" ItemName="_Temporary" />
</MSBuild>
<PropertyGroup>
<_TargetPath>%(_Temporary.FullPath)</_TargetPath>
</PropertyGroup>
<ItemGroup>
<ServiceProjectReference Update="@(ServiceProjectReference)"
Condition="'%(FullPath)' == '$(_FullPath)' AND '%(TargetFramework)' == '$(_TargetFramework)'">
<TargetPath>$(_TargetPath)</TargetPath>
</ServiceProjectReference>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<PropertyGroup>
<_FullPath />
<_TargetPath />
<_TargetFramework />
</PropertyGroup>
</Target>
<!-- Metadata setup phase 3: Ensure items have DocumentPath metadata. -->
<Target Name="_GetMetadataForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
<GetProjectReferenceMetadata Inputs="@(ServiceProjectReference)"
DocumentDirectory="$(ServiceProjectReferenceDirectory)">
<Output TaskParameter="Outputs" ItemName="_Temporary" />
</GetProjectReferenceMetadata>
<ItemGroup>
<ServiceProjectReference Remove="@(ServiceProjectReference)" />
<ServiceProjectReference Include="@(_Temporary)" />
</ItemGroup>
<ItemGroup>
<_Temporary Remove="@(_Temporary)" />
</ItemGroup>
</Target>
<Target Name="_BuildServiceProjectReferences"
Condition="'$(BuildProjectReferences)' == 'true'"
Inputs="@(ServiceProjectReference)"
Outputs="%(TargetPath)">
<MSBuild Projects="@(ServiceProjectReference -> Distinct())"
BuildInParallel="$(BuildInParallel)"
RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier"
Targets="%(Targets)" />
</Target>
<Target Name="_GetCurrentServiceProjectReference">
<GetCurrentItems Input="$(GeneratorMetadata)">
<Output TaskParameter="Outputs" ItemName="CurrentServiceProjectReference" />
</GetCurrentItems>
</Target>
<Target Name="_GenerateServiceProjectReferenceDocument"
DependsOnTargets="_GetCurrentServiceProjectReference;$(GeneratorTarget)" />
<Target Name="_GenerateServiceProjectReferenceDocuments"
Inputs="@(ServiceProjectReference)"
Outputs="%(DocumentPath)">
<MSBuild BuildInParallel="$(BuildInParallel)"
Projects="$(MSBuildProjectFullPath)"
Properties="GeneratorTargetPath=%(ServiceProjectReference.DocumentPath);GeneratorTarget=Generate%(DocumentGenerator)Document;GeneratorMetadata=%(SerializedMetadata)"
RemoveProperties="TargetFrameworks"
Targets="_GenerateServiceProjectReferenceDocument" />
</Target>
<Target Name="_CreateFileItemsForServiceProjectReferences" Condition="'@(ServiceProjectReference)' != ''">
<!-- GetProjectReferenceMetadata task guarantees %(DocumentPath) values are unique. -->
<ItemGroup>
<ServiceFileReference Remove="@(ServiceProjectReference -> '%(DocumentPath)')" />
<!-- Condition here is temporary. Useful while GenerateDefaultDocument fails. -->
<ServiceFileReference Include="@(ServiceProjectReference -> '%(DocumentPath)')"
Condition="Exists('%(ServiceProjectReference.DocumentPath)')">
<SourceProject>%(ServiceProjectReference.FullPath)</SourceProject>
</ServiceFileReference>
</ItemGroup>
</Target>
<Target Name="GenerateServiceProjectReferenceDocuments"
DependsOnTargets="$(GenerateServiceProjectReferenceDocumentsDependsOn)" />
<!-- GenerateDefaultDocument -->
<Target Name="GenerateDefaultDocument">
<ItemGroup>
<!-- @(CurrentServiceProjectReference) item group will never contain more than one item. -->
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project "%(FullPath)"</Command>
<Configuration Condition="'%(Configuration)' == ''">$(Configuration)</Configuration>
<GenerateDefaultDocumentOptions
Condition="'%(GenerateDefaultDocumentOptions)' == ''">$(GenerateDefaultDocumentDefaultOptions)</GenerateDefaultDocumentOptions>
</CurrentServiceProjectReference>
<CurrentServiceProjectReference Update="@(CurrentServiceProjectReference)">
<Command>%(Command) --documentName "%(DocumentName)" --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>
<!-- 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, 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 descendant files with the expected extension. -->
<TypeScriptCompile Remove="@(_Directories -> '%(Identity)/**/*.ts')" />
<TypeScriptCompile Include="@(_Directories -> '%(Identity)/**/*.ts')">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</TypeScriptCompile>
<Compile Remove="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'" />
<Compile Include="@(_Directories -> '%(Identity)/**/*.$(DefaultLanguageSourceExtension)')"
Condition="'$(DefaultLanguageSourceExtension)' != '.ts'">
<SourceDocument>%(_Directories.FullPath)</SourceDocument>
</Compile>
<_Files Remove="@(_Files)" />
<_Directories Remove="@(_Directories)" />
</ItemGroup>
</Target>
<Target Name="GenerateServiceFileReferenceCodes"
BeforeTargets="BeforeCompile"
DependsOnTargets="$(GenerateServiceFileReferenceCodesDependsOn)" />
</Project>

View File

@ -85,7 +85,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "dotne
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "GetDocumentInsider\src\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Design", "Extensions.ApiDescription.Design\src\Microsoft.Extensions.ApiDescription.Design.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client\src\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_dependencies", "_dependencies", "{5FE3048A-E96B-44F8-A7C4-FC590D7E04B4}"
EndProject
@ -277,7 +277,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mvc.Analyzers", "Mvc.Analyz
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mvc.Api.Analyzers", "Mvc.Api.Analyzers", "{49887FD5-2E52-4567-929E-9151DC88E4D4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Design", "Extensions.ApiDescription.Design", "{62CF82C1-B75D-4041-A7E9-EF39FF7B885F}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions.ApiDescription.Client", "Extensions.ApiDescription.Client", "{62CF82C1-B75D-4041-A7E9-EF39FF7B885F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core.TestCommon", "shared\Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", "{2906BF70-82BE-4427-870A-E87281D01008}"
EndProject