This commit is contained in:
James Newton-King 2022-03-27 20:29:23 +08:00 коммит произвёл GitHub
Родитель 8b742dc672
Коммит b4b43e39bc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
184 изменённых файлов: 13831 добавлений и 78 удалений

Просмотреть файл

@ -559,7 +559,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Grpc", "Grpc", "{8DAC59BE-C
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{E763DA15-8F4E-446C-99B8-309053C75598}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTests", "src\Grpc\test\InteropTests\InteropTests.csproj", "{3ADC50B9-2EBB-422A-8424-F9FC67841CA1}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTests", "src\Grpc\Interop\test\InteropTests\InteropTests.csproj", "{3ADC50B9-2EBB-422A-8424-F9FC67841CA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Testing", "Testing", "{05A169C7-4F20-4516-B10A-B13C5649D346}"
EndProject
@ -1318,9 +1318,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.Object
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{00B2DD87-7E2A-4460-BE1B-5E18B1062B7F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "src\Grpc\test\testassets\InteropClient\InteropClient.csproj", "{C3A0F425-669F-46A8-893F-CF449A6DAE56}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "src\Grpc\Interop\test\testassets\InteropClient\InteropClient.csproj", "{C3A0F425-669F-46A8-893F-CF449A6DAE56}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "src\Grpc\test\testassets\InteropWebsite\InteropWebsite.csproj", "{19189670-E206-471D-94F8-7D3D545E5020}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "src\Grpc\Interop\test\testassets\InteropWebsite\InteropWebsite.csproj", "{19189670-E206-471D-94F8-7D3D545E5020}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm.Performance.ConsoleHost", "src\Components\benchmarkapps\Wasm.Performance\ConsoleHost\Wasm.Performance.ConsoleHost.csproj", "{E9408723-E6A9-4715-B906-3B25B0238ABA}"
EndProject
@ -1654,16 +1654,48 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SDK-Analyzers", "SDK-Analyz
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Components", "Components", "{CC45FA2D-128B-485D-BA6D-DFD9735CB3C3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.SdkAnalyzers", "src\Tools\SDK-Analyzers\Components\src\Microsoft.AspNetCore.Components.SdkAnalyzers.csproj", "{825BCF97-67A9-4834-B3A8-C3DC97A90E41}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.SdkAnalyzers", "src\Tools\SDK-Analyzers\Components\src\Microsoft.AspNetCore.Components.SdkAnalyzers.csproj", "{825BCF97-67A9-4834-B3A8-C3DC97A90E41}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Components.SdkAnalyzers.Tests", "src\Tools\SDK-Analyzers\Components\test\Microsoft.AspNetCore.Components.SdkAnalyzers.Tests.csproj", "{DC349A25-0DBF-4468-99E1-B95C22D3A7EF}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Components.SdkAnalyzers.Tests", "src\Tools\SDK-Analyzers\Components\test\Microsoft.AspNetCore.Components.SdkAnalyzers.Tests.csproj", "{DC349A25-0DBF-4468-99E1-B95C22D3A7EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "LinkabilityChecker", "LinkabilityChecker", "{94F95276-7CDF-44A8-B159-D09702EF6794}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinkabilityChecker", "src\Tools\LinkabilityChecker\LinkabilityChecker.csproj", "{EA7D844B-C180-41C7-9D55-273AD88BF71F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{7A331A1C-E2C4-4E37-B0A0-B5AA10661229}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "JsonTranscoding", "JsonTranscoding", "{DD076DDA-7956-4361-A7D4-2B8025AB3DFD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "perf", "perf", "{5CDB8ABC-9DD0-4A9F-8948-EED5FFE89F67}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{151E6F9E-107B-4DDC-A2B1-95115801FD14}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B43BE3EB-9846-4484-88D8-05165202A0FC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{9A8AE587-A3DB-4211-8354-430C4CCBEB9B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestsWebsite", "src\Grpc\JsonTranscoding\test\testassets\IntegrationTestsWebsite\IntegrationTestsWebsite.csproj", "{2E28881D-A188-47AF-800A-B5877AD8C288}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "src\Grpc\JsonTranscoding\test\testassets\Sandbox\Sandbox.csproj", "{A53696E8-6065-41BA-84FB-E89E0DACFF6C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding", "src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj", "{109C702D-DACE-4F82-A490-15E5AFA94005}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.Swagger", "src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.Swagger\Microsoft.AspNetCore.Grpc.Swagger.csproj", "{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.Swagger.Tests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.Swagger.Tests\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj", "{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj", "{F18E97AE-3A3F-424D-8DC2-4D001A167F98}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests", "src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj", "{8C3E422A-F281-4B93-A567-88C7A1ED0412}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Grpc.Microbenchmarks", "src\Grpc\JsonTranscoding\perf\Microsoft.AspNetCore.Grpc.Microbenchmarks\Microsoft.AspNetCore.Grpc.Microbenchmarks.csproj", "{EB14F068-AD55-4970-B9B4-1FBE33704243}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IIS.LongTests", "src\Servers\IIS\IIS\test\IIS.LongTests\IIS.LongTests.csproj", "{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildAfterTargetingPack", "BuildAfterTargetingPack", "{489020F2-80D9-4468-A5D3-07E785837A5D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildAfterTargetingPack", "src\BuildAfterTargetingPack\BuildAfterTargetingPack.csproj", "{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -9959,6 +9991,134 @@ Global
{EA7D844B-C180-41C7-9D55-273AD88BF71F}.Release|x64.Build.0 = Release|Any CPU
{EA7D844B-C180-41C7-9D55-273AD88BF71F}.Release|x86.ActiveCfg = Release|Any CPU
{EA7D844B-C180-41C7-9D55-273AD88BF71F}.Release|x86.Build.0 = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|arm64.ActiveCfg = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|arm64.Build.0 = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|x64.ActiveCfg = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|x64.Build.0 = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|x86.ActiveCfg = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Debug|x86.Build.0 = Debug|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|Any CPU.Build.0 = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|arm64.ActiveCfg = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|arm64.Build.0 = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|x64.ActiveCfg = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|x64.Build.0 = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|x86.ActiveCfg = Release|Any CPU
{2E28881D-A188-47AF-800A-B5877AD8C288}.Release|x86.Build.0 = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|arm64.ActiveCfg = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|arm64.Build.0 = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|x64.ActiveCfg = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|x64.Build.0 = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|x86.ActiveCfg = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Debug|x86.Build.0 = Debug|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|Any CPU.Build.0 = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|arm64.ActiveCfg = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|arm64.Build.0 = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|x64.ActiveCfg = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|x64.Build.0 = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|x86.ActiveCfg = Release|Any CPU
{A53696E8-6065-41BA-84FB-E89E0DACFF6C}.Release|x86.Build.0 = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|Any CPU.Build.0 = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|arm64.ActiveCfg = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|arm64.Build.0 = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|x64.ActiveCfg = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|x64.Build.0 = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|x86.ActiveCfg = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Debug|x86.Build.0 = Debug|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|Any CPU.ActiveCfg = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|Any CPU.Build.0 = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|arm64.ActiveCfg = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|arm64.Build.0 = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x64.ActiveCfg = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x64.Build.0 = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x86.ActiveCfg = Release|Any CPU
{109C702D-DACE-4F82-A490-15E5AFA94005}.Release|x86.Build.0 = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|arm64.ActiveCfg = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|arm64.Build.0 = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x64.ActiveCfg = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x64.Build.0 = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x86.ActiveCfg = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Debug|x86.Build.0 = Debug|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|Any CPU.Build.0 = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|arm64.ActiveCfg = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|arm64.Build.0 = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x64.ActiveCfg = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x64.Build.0 = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x86.ActiveCfg = Release|Any CPU
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11}.Release|x86.Build.0 = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|arm64.ActiveCfg = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|arm64.Build.0 = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x64.ActiveCfg = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x64.Build.0 = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x86.ActiveCfg = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Debug|x86.Build.0 = Debug|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|Any CPU.Build.0 = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|arm64.ActiveCfg = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|arm64.Build.0 = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x64.ActiveCfg = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x64.Build.0 = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x86.ActiveCfg = Release|Any CPU
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3}.Release|x86.Build.0 = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|arm64.ActiveCfg = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|arm64.Build.0 = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|x64.ActiveCfg = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|x64.Build.0 = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|x86.ActiveCfg = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Debug|x86.Build.0 = Debug|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|Any CPU.Build.0 = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|arm64.ActiveCfg = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|arm64.Build.0 = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|x64.ActiveCfg = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|x64.Build.0 = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|x86.ActiveCfg = Release|Any CPU
{F18E97AE-3A3F-424D-8DC2-4D001A167F98}.Release|x86.Build.0 = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|arm64.ActiveCfg = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|arm64.Build.0 = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|x64.ActiveCfg = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|x64.Build.0 = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|x86.ActiveCfg = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Debug|x86.Build.0 = Debug|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|Any CPU.Build.0 = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|arm64.ActiveCfg = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|arm64.Build.0 = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|x64.ActiveCfg = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|x64.Build.0 = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|x86.ActiveCfg = Release|Any CPU
{8C3E422A-F281-4B93-A567-88C7A1ED0412}.Release|x86.Build.0 = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|arm64.ActiveCfg = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|arm64.Build.0 = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|x64.ActiveCfg = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|x64.Build.0 = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|x86.ActiveCfg = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Debug|x86.Build.0 = Debug|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|Any CPU.Build.0 = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|arm64.ActiveCfg = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|arm64.Build.0 = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|x64.ActiveCfg = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|x64.Build.0 = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|x86.ActiveCfg = Release|Any CPU
{EB14F068-AD55-4970-B9B4-1FBE33704243}.Release|x86.Build.0 = Release|Any CPU
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Debug|arm64.ActiveCfg = Debug|Any CPU
@ -9975,6 +10135,22 @@ Global
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Release|x64.Build.0 = Release|Any CPU
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Release|x86.ActiveCfg = Release|Any CPU
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04}.Release|x86.Build.0 = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|arm64.ActiveCfg = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|arm64.Build.0 = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|x64.ActiveCfg = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|x64.Build.0 = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|x86.ActiveCfg = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Debug|x86.Build.0 = Debug|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|Any CPU.Build.0 = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|arm64.ActiveCfg = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|arm64.Build.0 = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|x64.ActiveCfg = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|x64.Build.0 = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|x86.ActiveCfg = Release|Any CPU
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -10247,7 +10423,7 @@ Global
{58955E85-0D55-45FF-97EE-BDD096522954} = {BCD032DD-D088-4F72-B80F-48D0EA845F87}
{D708256C-4A68-4B15-AAE5-6EFA41223A70} = {BCD032DD-D088-4F72-B80F-48D0EA845F87}
{8DAC59BE-CB96-4F04-909C-56C22E7665EB} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
{E763DA15-8F4E-446C-99B8-309053C75598} = {8DAC59BE-CB96-4F04-909C-56C22E7665EB}
{E763DA15-8F4E-446C-99B8-309053C75598} = {7A331A1C-E2C4-4E37-B0A0-B5AA10661229}
{3ADC50B9-2EBB-422A-8424-F9FC67841CA1} = {E763DA15-8F4E-446C-99B8-309053C75598}
{05A169C7-4F20-4516-B10A-B13C5649D346} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
{3D06E2C9-44F7-408D-802C-42D7E55F08E7} = {05A169C7-4F20-4516-B10A-B13C5649D346}
@ -10725,6 +10901,7 @@ Global
{DF4637DA-5F07-4903-8461-4E2DAB235F3C} = {7F99E967-3DC1-4198-9D55-47CD9471D0B6}
{AAB50C64-39AA-4AED-8E9C-50D68E7751AD} = {7F99E967-3DC1-4198-9D55-47CD9471D0B6}
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38} = {EB5E294B-9ED5-43BF-AFA9-1CD2327F3DC1}
{11BE4471-6C3D-4758-881A-97B6A16F21F6} = {EB5E294B-9ED5-43BF-AFA9-1CD2327F3DC1}
{022B4B80-E813-4256-8034-11A68146F4EF} = {E5963C9F-20A6-4385-B364-814D2581FADF}
{FF413F1C-A998-4FA2-823F-52AC0916B35C} = {022B4B80-E813-4256-8034-11A68146F4EF}
{3A1EC883-EF9C-43E8-95E5-6B527428867B} = {022B4B80-E813-4256-8034-11A68146F4EF}
@ -10797,7 +10974,23 @@ Global
{DC349A25-0DBF-4468-99E1-B95C22D3A7EF} = {CC45FA2D-128B-485D-BA6D-DFD9735CB3C3}
{94F95276-7CDF-44A8-B159-D09702EF6794} = {0B200A66-B809-4ED3-A790-CB1C2E80975E}
{EA7D844B-C180-41C7-9D55-273AD88BF71F} = {94F95276-7CDF-44A8-B159-D09702EF6794}
{7A331A1C-E2C4-4E37-B0A0-B5AA10661229} = {8DAC59BE-CB96-4F04-909C-56C22E7665EB}
{DD076DDA-7956-4361-A7D4-2B8025AB3DFD} = {8DAC59BE-CB96-4F04-909C-56C22E7665EB}
{5CDB8ABC-9DD0-4A9F-8948-EED5FFE89F67} = {DD076DDA-7956-4361-A7D4-2B8025AB3DFD}
{151E6F9E-107B-4DDC-A2B1-95115801FD14} = {DD076DDA-7956-4361-A7D4-2B8025AB3DFD}
{B43BE3EB-9846-4484-88D8-05165202A0FC} = {DD076DDA-7956-4361-A7D4-2B8025AB3DFD}
{9A8AE587-A3DB-4211-8354-430C4CCBEB9B} = {B43BE3EB-9846-4484-88D8-05165202A0FC}
{2E28881D-A188-47AF-800A-B5877AD8C288} = {9A8AE587-A3DB-4211-8354-430C4CCBEB9B}
{A53696E8-6065-41BA-84FB-E89E0DACFF6C} = {9A8AE587-A3DB-4211-8354-430C4CCBEB9B}
{109C702D-DACE-4F82-A490-15E5AFA94005} = {151E6F9E-107B-4DDC-A2B1-95115801FD14}
{E3C5FAD2-8AB7-47C8-AAFD-8262551A5D11} = {151E6F9E-107B-4DDC-A2B1-95115801FD14}
{90CF4DC6-AC53-459F-9EAB-623A11EADAA3} = {B43BE3EB-9846-4484-88D8-05165202A0FC}
{F18E97AE-3A3F-424D-8DC2-4D001A167F98} = {B43BE3EB-9846-4484-88D8-05165202A0FC}
{8C3E422A-F281-4B93-A567-88C7A1ED0412} = {B43BE3EB-9846-4484-88D8-05165202A0FC}
{EB14F068-AD55-4970-B9B4-1FBE33704243} = {5CDB8ABC-9DD0-4A9F-8948-EED5FFE89F67}
{B7DAA48B-8E5E-4A5D-9FEB-E6D49AE76A04} = {41BB7BA4-AC08-4E9A-83EA-6D587A5B951C}
{489020F2-80D9-4468-A5D3-07E785837A5D} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
{8FED7E65-A7DD-4F13-8980-BF03E77B6C85} = {489020F2-80D9-4468-A5D3-07E785837A5D}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}

Просмотреть файл

@ -239,6 +239,7 @@
<Import Project="artifacts\bin\GenerateFiles\Directory.Build.props" Condition=" '$(MSBuildProjectName)' != 'GenerateFiles' " />
<Import Project="eng\Dependencies.props" />
<Import Project="eng\ProjectReferences.props" />
<Import Project="eng\RequiresDelayedBuildProjects.props" />
<Import Project="eng\SharedFramework.Local.props" />
<Import Project="eng\SharedFramework.External.props" />
<Import Project="eng\targets\Cpp.Common.props" Condition="'$(MSBuildProjectExtension)' == '.vcxproj'" />

Просмотреть файл

@ -1,6 +1,16 @@
<Project>
<Import Project="Common.props" />
<!--
@(RequiresDelayedBuild) projects can be extended many ways but this isn't fully automated because the new
projects likely aren't referenced initially. To add new projects, edit RequiresDelayedBuildProjects.props
manually, update the $(BuildMainlyReferenceProviders)' == 'true' item group near the bottom of this file,
or edit BuildAfterTargetingPack.csproj. Then run GenerateProjectList.ps1 (even for the first option to ensure
the format is correct) and undo any temporary changes. When complete, only BuildAfterTargetingPack.csproj and
other @(RequiresDelayedBuild) projects should mention projects listed in RequiresDelayedBuildProjects.props.
-->
<Import Project="RequiresDelayedBuildProjects.props" />
<!-- These projects are always excluded, even when -projects is specified on command line. -->
<ItemGroup>
<!-- Explicitly excluded projects -->
@ -16,8 +26,11 @@
<!-- Exclude the websockets samples for now because they use classic .csproj, which is not yet supported in our build. -->
<ProjectToExclude Include="$(RepoRoot)src\Middleware\WebSockets\samples\**\*.csproj" />
<!-- These projects are meant to be referenced only by tests. -->
<ProjectToExclude Include="$(RepoRoot)src\**\testassets\**\*.*proj"
<!-- These projects are meant to be referenced only by tests or via BuildAfterTargetingPack.csproj. -->
<ProjectToExclude
Include="$(RepoRoot)src\**\testassets\**\*.*proj;
@(RequiresDelayedBuild);
"
Exclude="$(RepoRoot)src\Components\WebAssembly\testassets\WasmLinkerTest\*.*proj;
$(RepoRoot)src\Components\WebView\Samples\PhotinoPlatform\testassets\PhotinoTestApp\*.*proj;
$(RepoRoot)src\Http\Routing\test\testassets\RoutingSandbox\*.*proj;
@ -190,6 +203,7 @@
$(RepoRoot)src\ProjectTemplates\*\*.csproj;
$(RepoRoot)src\submodules\spa-templates\src\*.csproj;
$(RepoRoot)src\Extensions\**\*.csproj;
$(RepoRoot)src\BuildAfterTargetingPack\*.csproj;
"
Exclude="
@(ProjectToBuild);
@ -229,6 +243,7 @@
$(RepoRoot)src\HealthChecks\**\src\*.csproj;
$(RepoRoot)src\Testing\**\src\*.csproj;
$(RepoRoot)src\Extensions\**\src\*.csproj;
$(RepoRoot)src\BuildAfterTargetingPack\*.csproj;
"
Exclude="
@(ProjectToBuild);

Просмотреть файл

@ -1,5 +1,4 @@
<Project>
<PropertyGroup>
<BuildManaged>true</BuildManaged>
<RepoRoot
@ -17,13 +16,15 @@
BuildInParallel="true"
SkipNonexistentTargets="true"
SkipNonexistentProjects="true">
<Output TaskParameter="TargetOutputs" ItemName="_ProjectReferenceProvider" />
<Output TaskParameter="TargetOutputs" ItemName="_ProvidesReferenceOrRequiresDelay" />
</MSBuild>
<ItemGroup>
<_SharedFrameworkAndPackageRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'true')->Distinct())" />
<_SharedFrameworkRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'false')->Distinct())" />
<_TrimmableProject Include="@(_ProjectReferenceProvider->WithMetadataValue('Trimmable', 'true')->Distinct())" />
<_ProjectReferenceProvider Include="@(_ProvidesReferenceOrRequiresDelay->WithMetadataValue('IsProjectReferenceProvider','true')->Distinct())" />
<_RequiresDelayedBuild Include="@(_ProvidesReferenceOrRequiresDelay->WithMetadataValue('RequiresDelayedBuild','true')->Distinct())" />
<_SharedFrameworkAndPackageRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'true'))" />
<_SharedFrameworkRef Include="@(_ProjectReferenceProvider->WithMetadataValue('IsAspNetCoreApp','true')->WithMetadataValue('IsPackable', 'false'))" />
<_TrimmableProject Include="@(_ProjectReferenceProvider->WithMetadataValue('Trimmable', 'true'))" />
</ItemGroup>
<PropertyGroup>
@ -57,7 +58,7 @@
This file contains a complete list of the assemblies which are part of the shared framework.
This project is generated using the <IsAspNetCoreApp> and <IsPackable> properties from each .csproj in this repository.
This file is generated using the <IsAspNetCoreApp/> and <IsPackable/> properties from each .csproj in this repository.
-->
<Project>
<ItemGroup>
@ -97,6 +98,30 @@
<WriteLinesToFile File="$(TrimmableProjectsList)" Lines="$(TrimmableProjectsListContent)" Overwrite="true" />
<Message Importance="High" Text="Generated $(TrimmableProjectsList)" />
</Target>
<PropertyGroup>
<DelayedBuildFile>$(MSBuildThisFileDirectory)RequiresDelayedBuildProjects.props</DelayedBuildFile>
<DelayedBuildContent><![CDATA[<!--
This file is automatically generated. Run `./eng/scripts/GenerateProjectList.ps1` to update.
This file contains a list of projects that must be restored etc. after App.Ref and App.Runtime are fully built.
This file is generated using <RequiresDelayedBuild/> properties. Content may overlap ProjectReferences.csproj
but that is not required (projects that are not project reference providers are also supported).
-->
<Project>
<ItemGroup>
@(_RequiresDelayedBuild->'<RequiresDelayedBuild Include="%24(RepoRoot)%(ProjectFileRelativePath)" />', '%0A ')
</ItemGroup>
</Project>
]]></DelayedBuildContent>
</PropertyGroup>
<!-- Workaround https://github.com/Microsoft/msbuild/issues/1024 -->
<WriteLinesToFile Condition="'$(OS)' == 'Windows_NT'"
File="$(DelayedBuildFile)" Lines="$([MSBuild]::Escape($(DelayedBuildContent)))" Overwrite="true" />
<Exec Condition="'$(OS)' != 'Windows_NT'"
Command="echo '$(DelayedBuildContent.Replace('\t','\\t'))' > $(DelayedBuildFile)" />
<Message Importance="High" Text="Generated $(DelayedBuildFile)" />
</Target>
</Project>

Просмотреть файл

@ -178,8 +178,10 @@ and are generated based on the last package release.
<LatestPackageReference Include="Duende.IdentityServer.EntityFramework.Storage" />
<LatestPackageReference Include="Duende.IdentityServer.Storage" />
<LatestPackageReference Include="FSharp.Core" />
<LatestPackageReference Include="Google.Api.CommonProtos" />
<LatestPackageReference Include="Google.Protobuf" />
<LatestPackageReference Include="Grpc.AspNetCore" />
<LatestPackageReference Include="Grpc.AspNetCore.Server" />
<LatestPackageReference Include="Grpc.Auth" />
<LatestPackageReference Include="Grpc.Net.Client" />
<LatestPackageReference Include="Grpc.Tools" />
@ -202,6 +204,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="Serilog.Extensions.Logging" />
<LatestPackageReference Include="Serilog.Sinks.File" />
<LatestPackageReference Include="StackExchange.Redis" />
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
<LatestPackageReference Include="System.Reactive.Linq" />
<LatestPackageReference Include="xunit.abstractions" />
<LatestPackageReference Include="xunit.analyzers" />

Просмотреть файл

@ -157,5 +157,7 @@
<ProjectReferenceProvider Include="Microsoft.Extensions.Diagnostics.HealthChecks" ProjectPath="$(RepoRoot)src\HealthChecks\HealthChecks\src\Microsoft.Extensions.Diagnostics.HealthChecks.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Testing" ProjectPath="$(RepoRoot)src\Testing\src\Microsoft.AspNetCore.Testing.csproj" />
<ProjectReferenceProvider Include="Microsoft.Extensions.Features" ProjectPath="$(RepoRoot)src\Extensions\Features\src\Microsoft.Extensions.Features.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Grpc.JsonTranscoding" ProjectPath="$(RepoRoot)src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj" />
<ProjectReferenceProvider Include="Microsoft.AspNetCore.Grpc.Swagger" ProjectPath="$(RepoRoot)src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.Swagger\Microsoft.AspNetCore.Grpc.Swagger.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,20 @@
<!--
This file is automatically generated. Run `./eng/scripts/GenerateProjectList.ps1` to update.
This file contains a list of projects that must be restored etc. after App.Ref and App.Runtime are fully built.
This file is generated using <RequiresDelayedBuild/> properties. Content may overlap ProjectReferences.csproj
but that is not required (projects that are not project reference providers are also supported).
-->
<Project>
<ItemGroup>
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\perf\Microsoft.AspNetCore.Grpc.Microbenchmarks\Microsoft.AspNetCore.Grpc.Microbenchmarks.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.Swagger\Microsoft.AspNetCore.Grpc.Swagger.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\test\Microsoft.AspNetCore.Grpc.Swagger.Tests\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\test\testassets\IntegrationTestsWebsite\IntegrationTestsWebsite.csproj" />
<RequiresDelayedBuild Include="$(RepoRoot)src\Grpc\JsonTranscoding\test\testassets\Sandbox\Sandbox.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -3,7 +3,7 @@
This file contains a complete list of the assemblies which are part of the shared framework.
This project is generated using the <IsAspNetCoreApp> and <IsPackable> properties from each .csproj in this repository.
This file is generated using the <IsAspNetCoreApp/> and <IsPackable/> properties from each .csproj in this repository.
-->
<Project>
<ItemGroup>

Просмотреть файл

@ -238,11 +238,13 @@
<CastleCoreVersion>4.2.1</CastleCoreVersion>
<CommandLineParserVersion>2.3.0</CommandLineParserVersion>
<FSharpCoreVersion>6.0.0</FSharpCoreVersion>
<GoogleProtobufVersion>3.18.0</GoogleProtobufVersion>
<GrpcAspNetCoreVersion>2.40.0</GrpcAspNetCoreVersion>
<GrpcAuthVersion>2.40.0</GrpcAuthVersion>
<GrpcNetClientVersion>2.40.0</GrpcNetClientVersion>
<GrpcToolsVersion>2.40.0</GrpcToolsVersion>
<GoogleApiCommonProtosVersion>2.5.0</GoogleApiCommonProtosVersion>
<GoogleProtobufVersion>3.18.1</GoogleProtobufVersion>
<GrpcAspNetCoreVersion>2.43.0</GrpcAspNetCoreVersion>
<GrpcAspNetCoreServerVersion>2.43.0</GrpcAspNetCoreServerVersion>
<GrpcAuthVersion>2.43.0</GrpcAuthVersion>
<GrpcNetClientVersion>2.43.0</GrpcNetClientVersion>
<GrpcToolsVersion>2.43.0</GrpcToolsVersion>
<DuendeIdentityServerAspNetIdentityVersion>5.2.0</DuendeIdentityServerAspNetIdentityVersion>
<DuendeIdentityServerEntityFrameworkVersion>5.2.0</DuendeIdentityServerEntityFrameworkVersion>
<DuendeIdentityServerVersion>5.2.0</DuendeIdentityServerVersion>

Просмотреть файл

@ -311,12 +311,18 @@
<Warning Condition=" '$(IsProjectReferenceProvider)' == 'true' AND '$(AssemblyName)' != '$(MSBuildProjectName)' "
Text="Project name &quot;$(MSBuildProjectName)&quot; is confusing; assembly is named &quot;$(AssemblyName)&quot;." />
<ItemGroup Condition=" '$(IsProjectReferenceProvider)' == 'true' ">
<ItemGroup Condition=" '$(IsProjectReferenceProvider)' == 'true' OR '$(RequiresDelayedBuild)' == 'true' ">
<ProvidesReference Include="$(AssemblyName)">
<IsAspNetCoreApp>$([MSBuild]::ValueOrDefault($(IsAspNetCoreApp),'false'))</IsAspNetCoreApp>
<IsPackable>$([MSBuild]::ValueOrDefault($(IsPackable),'false'))</IsPackable>
<ProjectFileRelativePath>$([MSBuild]::MakeRelative($(RepoRoot), $(MSBuildProjectFullPath)))</ProjectFileRelativePath>
<Trimmable>$([MSBuild]::ValueOrDefault($(Trimmable),'false'))</Trimmable>
<!-- True if the project may be referenced using a @(Reference) item. -->
<IsProjectReferenceProvider>$([MSBuild]::ValueOrDefault($(IsProjectReferenceProvider),'false'))</IsProjectReferenceProvider>
<!-- True if project must be restored etc. after App.Ref and App.Runtime are fully built. -->
<RequiresDelayedBuild>$([MSBuild]::ValueOrDefault($(RequiresDelayedBuild),'false'))</RequiresDelayedBuild>
</ProvidesReference>
</ItemGroup>
</Target>

Просмотреть файл

@ -0,0 +1,94 @@
<Project>
<!-- Fake a unit test project unless that would prevent building this project. -->
<PropertyGroup>
<IsUnitTestProject Condition=" '$(SkipTestBuild)' != 'true' ">true</IsUnitTestProject>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<!--
Forward targets to @(RequiresDelayedBuild) projects after App.Ref has built. Intended for projects that
cannot restore without our App.Ref layout and aren't referenced elsewhere in the repo. For projects
referenced elsewhere, use something else e.g. @(TestAssetProjectReference) items and
FunctionalTestWithAssets.targets for test asset projects that need special handling.
-->
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<!-- Nothing from this project ships whether we're faking a unit test project or not. -->
<IsShipping>false</IsShipping>
<!-- Build.props is main thing that references this project. -->
<IsProjectReferenceProvider>false</IsProjectReferenceProvider>
</PropertyGroup>
<ItemGroup>
<!--
When adding new projects to @(RequiresDelayedBuild), temporarily reference the projects here, run
GenerateProjectList.ps1, then undo changes in this file. See comments in Build.props for other options.
-->
<!-- RequiresDelayedBuild Include="..." / -->
<!-- Enforce build order. Need shared Fx before building the important projects. -->
<ProjectReference Include="$(RepoRoot)\src\Framework\App.Ref\src\Microsoft.AspNetCore.App.Ref.csproj"
Private="false"
ReferenceOutputAssembly="false"
SkipGetTargetFrameworkProperties="true" />
</ItemGroup>
<!-- Cannot build in source-build because that does not create an App.Ref layout. -->
<Target Name="BuildDelayedProjects"
BeforeTargets="Build"
Condition=" '$(DotNetBuildFromSource)' != 'true' "
Returns="@(TargetPathWithTargetPlatformMoniker)">
<MSBuild Projects="@(RequiresDelayedBuild)"
BuildInParallel="$(BuildInParallel)"
Properties="MSBuildRestoreSessionId=$([System.Guid]::NewGuid())"
Targets="Restore" />
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Build">
<Output TaskParameter="TargetOutputs" ItemName="TargetPathWithTargetPlatformMoniker" />
</MSBuild>
</Target>
<Target Name="CleanDelayedProjects" BeforeTargets="Clean" Condition=" '$(DotNetBuildFromSource)' != 'true' ">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Clean" />
</Target>
<Target Name="CreateHelixPayloadDelayedProjects"
BeforeTargets="CreateHelixPayload"
Condition=" '$(DotNetBuildFromSource)' != 'true' "
Returns="@(HelixWorkItem)">
<MSBuild Projects="@(RequiresDelayedBuild)"
BuildInParallel="$(BuildInParallel)"
SkipNonexistentTargets="true"
Targets="CreateHelixPayload">
<Output TaskParameter="TargetOutputs" ItemName="HelixWorkItem" />
</MSBuild>
</Target>
<Target Name="GetReferencesProvidedDelayedProjects"
BeforeTargets="GetReferencesProvided"
Returns="@(ProvidesReference)">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="GetReferencesProvided">
<Output TaskParameter="TargetOutputs" ItemName="ProvidesReference" />
</MSBuild>
</Target>
<Target Name="PackDelayedProjects" BeforeTargets="Pack" Condition=" '$(DotNetBuildFromSource)' != 'true' ">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Pack" />
</Target>
<Target Name="PublishDelayedProjects" BeforeTargets="Publish" Condition=" '$(DotNetBuildFromSource)' != 'true' ">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Publish" />
</Target>
<Target Name="TestDelayedProjects" BeforeTargets="Test" Condition=" '$(DotNetBuildFromSource)' != 'true' ">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="Test" />
</Target>
<Target Name="VSTestDelayedProjects" BeforeTargets="VSTest" Condition=" '$(DotNetBuildFromSource)' != 'true' ">
<MSBuild Projects="@(RequiresDelayedBuild)" BuildInParallel="$(BuildInParallel)" Targets="VSTest" />
</Target>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

Просмотреть файл

@ -1,11 +1,32 @@
{
{
"solution": {
"path": "..\\..\\AspNetCore.sln",
"projects" : [
"src\\Grpc\\test\\InteropTests\\InteropTests.csproj",
"src\\Grpc\\test\\testassets\\InteropWebsite\\InteropWebsite.csproj",
"src\\Grpc\\test\\testassets\\InteropClient\\InteropClient.csproj",
"projects": [
"src\\Extensions\\Features\\src\\Microsoft.Extensions.Features.csproj",
"src\\Grpc\\JsonTranscoding\\perf\\Microsoft.AspNetCore.Grpc.Microbenchmarks\\Microsoft.AspNetCore.Grpc.Microbenchmarks.csproj",
"src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.JsonTranscoding\\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj",
"src\\Grpc\\JsonTranscoding\\src\\Microsoft.AspNetCore.Grpc.Swagger\\Microsoft.AspNetCore.Grpc.Swagger.csproj",
"src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests.csproj",
"src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests\\Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests.csproj",
"src\\Grpc\\JsonTranscoding\\test\\Microsoft.AspNetCore.Grpc.Swagger.Tests\\Microsoft.AspNetCore.Grpc.Swagger.Tests.csproj",
"src\\Grpc\\JsonTranscoding\\test\\testassets\\IntegrationTestsWebsite\\IntegrationTestsWebsite.csproj",
"src\\Grpc\\JsonTranscoding\\test\\testassets\\Sandbox\\Sandbox.csproj",
"src\\Grpc\\Interop\\test\\InteropTests\\InteropTests.csproj",
"src\\Grpc\\Interop\\test\\testassets\\InteropClient\\InteropClient.csproj",
"src\\Grpc\\Interop\\test\\testassets\\InteropWebsite\\InteropWebsite.csproj",
"src\\Hosting\\Abstractions\\src\\Microsoft.AspNetCore.Hosting.Abstractions.csproj",
"src\\Hosting\\Hosting\\src\\Microsoft.AspNetCore.Hosting.csproj",
"src\\Hosting\\Server.Abstractions\\src\\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj",
"src\\Hosting\\TestHost\\src\\Microsoft.AspNetCore.TestHost.csproj",
"src\\Http\\Headers\\src\\Microsoft.Net.Http.Headers.csproj",
"src\\Http\\Http.Abstractions\\src\\Microsoft.AspNetCore.Http.Abstractions.csproj",
"src\\Http\\Http.Extensions\\src\\Microsoft.AspNetCore.Http.Extensions.csproj",
"src\\Http\\Http.Features\\src\\Microsoft.AspNetCore.Http.Features.csproj",
"src\\Http\\Http\\src\\Microsoft.AspNetCore.Http.csproj",
"src\\Http\\WebUtilities\\src\\Microsoft.AspNetCore.WebUtilities.csproj",
"src\\ObjectPool\\src\\Microsoft.Extensions.ObjectPool.csproj",
"src\\Servers\\Connections.Abstractions\\src\\Microsoft.AspNetCore.Connections.Abstractions.csproj",
"src\\Testing\\src\\Microsoft.AspNetCore.Testing.csproj"
]
}
}
}

Просмотреть файл

@ -1,11 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Xunit.Abstractions;

Просмотреть файл

@ -1,12 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Xunit.Abstractions;

Просмотреть файл

@ -1,12 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.IO;
using System.Threading.Tasks;
using InteropTests.Helpers;
using Microsoft.AspNetCore.Testing;
using Xunit;
using Xunit.Abstractions;
namespace InteropTests;

Просмотреть файл

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ContainsFunctionalTestAssets>true</ContainsFunctionalTestAssets>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Shared\Process\ProcessEx.cs" Link="Helpers\ProcessEx.cs" />
<Compile Include="..\..\..\Shared\Process\ProcessExtensions.cs" Link="Helpers\ProcessExtensions.cs" />
<Compile Include="..\..\..\..\Shared\Process\ProcessEx.cs" Link="Helpers\ProcessEx.cs" />
<Compile Include="..\..\..\..\Shared\Process\ProcessExtensions.cs" Link="Helpers\ProcessExtensions.cs" />
<!-- Enforce build order. Need shared Fx before PublishTestAssets runs. -->
<ProjectReference Include="$(RepoRoot)\src\Framework\App.Ref\src\Microsoft.AspNetCore.App.Ref.csproj"

Просмотреть файл

@ -16,9 +16,7 @@
#endregion
using System;
using System.Collections;
using System.Threading.Tasks;
namespace InteropTestsClient;

Просмотреть файл

@ -16,9 +16,6 @@
#endregion
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Grpc.Core;
namespace InteropTestsClient;

Просмотреть файл

@ -16,8 +16,6 @@
#endregion
using System.Net.Http;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;

Просмотреть файл

@ -16,15 +16,9 @@
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using CommandLine;
using Google.Apis.Auth.OAuth2;
using Google.Protobuf;

Просмотреть файл

@ -16,7 +16,6 @@
#endregion
using System;
using System.Reflection;
namespace InteropTestsClient;

Просмотреть файл

@ -16,9 +16,6 @@
#endregion
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Grpc.Core;
namespace InteropTestsWebsite;

Просмотреть файл

@ -16,13 +16,7 @@
#endregion
using System;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace InteropTestsWebsite;

Просмотреть файл

@ -3,8 +3,8 @@
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:54288/",
"sslPort": 44374
"applicationUrl": "http://localhost:54462/",
"sslPort": 44363
}
},
"profiles": {
@ -24,4 +24,4 @@
}
}
}
}
}

Просмотреть файл

@ -16,12 +16,8 @@
#endregion
using System;
using System.Reflection;
using Grpc.Testing;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Net.Http.Headers;
namespace InteropTestsWebsite;

Просмотреть файл

@ -16,9 +16,6 @@
#endregion
using System;
using System.Linq;
using System.Threading.Tasks;
using Google.Protobuf;
using Grpc.Core;
using InteropTestsWebsite;
@ -50,9 +47,10 @@ public class TestServiceImpl : TestService.TestServiceBase
foreach (var responseParam in request.ResponseParameters)
{
// TODO(JamesNK): Remove nullable override after Grpc.Core.Api update
responseStream.WriteOptions = !(responseParam.Compressed?.Value ?? false)
? new WriteOptions(WriteFlags.NoCompress)
: null;
: null!;
var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) };
await responseStream.WriteAsync(response);

Просмотреть файл

@ -0,0 +1,15 @@
<Project>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
<PropertyGroup>
<!-- Projects must be restored after App.Ref and App.Runtime layouts are complete. -->
<RequiresDelayedBuild>true</RequiresDelayedBuild>
<!-- Required because Swashbuckle.AspNetCore uses shared runtime -->
<UseAspNetCoreSharedRuntime>true</UseAspNetCoreSharedRuntime>
<!-- Workaround lack of Linux MUSL x64 support. -->
<ExcludeFromBuild
Condition=" '$(TargetOsName)' == 'linux-musl' and '$(TargetArchitecture)' == 'x64' ">true</ExcludeFromBuild>
</PropertyGroup>
</Project>

Просмотреть файл

@ -0,0 +1,39 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Greet;
using Microsoft.AspNetCore.Grpc.JsonTranscoding;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
namespace Microsoft.AspNetCore.Grpc.Microbenchmarks.Json;
public class JsonReading
{
private string _requestJson = default!;
private JsonSerializerOptions _serializerOptions = default!;
private JsonParser _jsonFormatter = default!;
[GlobalSetup]
public void GlobalSetup()
{
_requestJson = (new HelloRequest() { Name = "Hello world" }).ToString();
_serializerOptions = JsonConverterHelper.CreateSerializerOptions(new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty));
_jsonFormatter = new JsonParser(new JsonParser.Settings(recursionLimit: 100));
}
[Benchmark]
public void ReadMessage_JsonSerializer()
{
JsonSerializer.Deserialize(_requestJson, typeof(HelloRequest), _serializerOptions);
}
[Benchmark]
public void ReadMessage_JsonFormatter()
{
_jsonFormatter.Parse(_requestJson, HelloRequest.Descriptor);
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using BenchmarkDotNet.Attributes;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Greet;
using Microsoft.AspNetCore.Grpc.JsonTranscoding;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
namespace Microsoft.AspNetCore.Grpc.Microbenchmarks.Json;
public class JsonWriting
{
private HelloRequest _request = default!;
private JsonSerializerOptions _serializerOptions = default!;
private JsonFormatter _jsonFormatter = default!;
[GlobalSetup]
public void GlobalSetup()
{
_request = new HelloRequest() { Name = "Hello world" };
_serializerOptions = JsonConverterHelper.CreateSerializerOptions(
new JsonContext(new GrpcJsonSettings { WriteIndented = false }, TypeRegistry.Empty));
_jsonFormatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: false));
}
[Benchmark]
public void WriteMessage_JsonSerializer()
{
JsonSerializer.Serialize(_request, _serializerOptions);
}
[Benchmark]
public void WriteMessage_JsonFormatter()
{
_jsonFormatter.Format(_request);
}
}

Просмотреть файл

@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<ServerGarbageCollection>true</ServerGarbageCollection>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TieredCompilation>false</TieredCompilation>
<DefineConstants>$(DefineConstants);IS_BENCHMARKS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Protobuf Include=".\Proto\chat.proto" GrpcServices="Server" />
<Protobuf Include=".\Proto\greet.proto" GrpcServices="Client" />
<Reference Include="BenchmarkDotNet" />
<Reference Include="Google.Protobuf" />
<Reference Include="Grpc.Tools" PrivateAssets="All" />
<Reference Include="Microsoft.AspNetCore.Grpc.JsonTranscoding" />
<Compile Include="$(SharedSourceRoot)BenchmarkRunner\*.cs" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark]

Просмотреть файл

@ -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.
syntax = "proto3";
package chat;
// The greeting service definition.
service Chatter {
// Sends a greeting
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}
// The chat message.
message ChatMessage {
string name = 1;
string message = 2;
}

Просмотреть файл

@ -0,0 +1,26 @@
// 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.
syntax = "proto3";
package greet;
import "google/protobuf/timestamp.proto";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply);
rpc SayHellos (HelloRequest) returns (stream HelloReply);
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
google.protobuf.Timestamp timestamp = 2;
}

Просмотреть файл

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding;
/// <summary>
/// Provides settings for serializing JSON.
/// </summary>
public sealed class GrpcJsonSettings
{
/// <summary>
/// Whether fields which would otherwise not be included in the formatted data
/// should be formatted even when the value is not present, or has the default value.
/// This option only affects fields which don't support "presence" (e.g.
/// singular non-optional proto3 primitive fields).
/// </summary>
public bool IgnoreDefaultValues { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether <see cref="Enum"/> values are written as integers instead of strings.
/// Default value is false.
/// </summary>
public bool WriteEnumsAsIntegers { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether <see cref="Int64"/> and <see cref="UInt64"/> values are written as strings instead of numbers.
/// Default value is false.
/// </summary>
public bool WriteInt64sAsStrings { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether JSON should use pretty printing.
/// Default value is false.
/// </summary>
public bool WriteIndented { get; set; }
}

Просмотреть файл

@ -0,0 +1,35 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Google.Api;
using Google.Protobuf.Reflection;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding;
/// <summary>
/// Metadata for a gRPC JSON transcoding endpoint.
/// </summary>
public sealed class GrpcJsonTranscodingMetadata
{
/// <summary>
/// Creates a new instance of <see cref="GrpcJsonTranscodingMetadata"/> with the provided Protobuf
/// <see cref="Google.Protobuf.Reflection.MethodDescriptor"/> and <see cref="Google.Api.HttpRule"/>.
/// </summary>
/// <param name="methodDescriptor">The Protobuf <see cref="Google.Protobuf.Reflection.MethodDescriptor"/>.</param>
/// <param name="httpRule">The <see cref="Google.Api.HttpRule"/>.</param>
public GrpcJsonTranscodingMetadata(MethodDescriptor methodDescriptor, HttpRule httpRule)
{
MethodDescriptor = methodDescriptor;
HttpRule = httpRule;
}
/// <summary>
/// Gets the Protobuf <see cref="Google.Protobuf.Reflection.MethodDescriptor"/>.
/// </summary>
public MethodDescriptor MethodDescriptor { get; }
/// <summary>
/// Gets the <see cref="Google.Api.HttpRule"/>.
/// </summary>
public HttpRule HttpRule { get; }
}

Просмотреть файл

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf.Reflection;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding;
/// <summary>
/// Options used to configure gRPC JSON transcoding service instances.
/// </summary>
public sealed class GrpcJsonTranscodingOptions
{
private readonly Lazy<JsonSerializerOptions> _unaryOptions;
private readonly Lazy<JsonSerializerOptions> _serverStreamingOptions;
public GrpcJsonTranscodingOptions()
{
_unaryOptions = new Lazy<JsonSerializerOptions>(
() => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry)),
LazyThreadSafetyMode.ExecutionAndPublication);
_serverStreamingOptions = new Lazy<JsonSerializerOptions>(
() => JsonConverterHelper.CreateSerializerOptions(new JsonContext(JsonSettings, TypeRegistry), isStreamingOptions: true),
LazyThreadSafetyMode.ExecutionAndPublication);
}
internal JsonSerializerOptions UnarySerializerOptions => _unaryOptions.Value;
internal JsonSerializerOptions ServerStreamingSerializerOptions => _serverStreamingOptions.Value;
/// <summary>
/// Gets or sets the <see cref="Google.Protobuf.Reflection.TypeRegistry"/> used to lookup types from type names.
/// </summary>
public TypeRegistry TypeRegistry { get; set; } = TypeRegistry.Empty;
/// <summary>
/// Gets or sets the <see cref="GrpcJsonSettings"/> used to serialize messages.
/// </summary>
public GrpcJsonSettings JsonSettings { get; set; } = new GrpcJsonSettings();
}

Просмотреть файл

@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Grpc.AspNetCore.Server;
using Grpc.AspNetCore.Server.Model;
using Microsoft.AspNetCore.Grpc.JsonTranscoding;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for the gRPC JSON transcoding services.
/// </summary>
public static class GrpcJsonTranscodingServiceExtensions
{
/// <summary>
/// Adds gRPC JSON transcoding services to the specified <see cref="IGrpcServerBuilder" />.
/// </summary>
/// <param name="grpcBuilder">The <see cref="IGrpcServerBuilder"/>.</param>
/// <returns>The same instance of the <see cref="IGrpcServerBuilder"/> for chaining.</returns>
public static IGrpcServerBuilder AddJsonTranscoding(this IGrpcServerBuilder grpcBuilder)
{
if (grpcBuilder == null)
{
throw new ArgumentNullException(nameof(grpcBuilder));
}
grpcBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IServiceMethodProvider<>), typeof(JsonTranscodingServiceMethodProvider<>)));
return grpcBuilder;
}
/// <summary>
/// Adds gRPC JSON transcoding services to the specified <see cref="IGrpcServerBuilder" />.
/// </summary>
/// <param name="grpcBuilder">The <see cref="IGrpcServerBuilder"/>.</param>
/// <param name="configureOptions">An <see cref="Action{GrpcJsonTranscodingOptions}"/> to configure the provided <see cref="GrpcJsonTranscodingOptions"/>.</param>
/// <returns>The same instance of the <see cref="IGrpcServerBuilder"/> for chaining.</returns>
public static IGrpcServerBuilder AddJsonTranscoding(this IGrpcServerBuilder grpcBuilder, Action<GrpcJsonTranscodingOptions> configureOptions)
{
if (grpcBuilder == null)
{
throw new ArgumentNullException(nameof(grpcBuilder));
}
grpcBuilder.Services.Configure(configureOptions);
return grpcBuilder.AddJsonTranscoding();
}
}

Просмотреть файл

@ -0,0 +1,294 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using Google.Api;
using Google.Protobuf.Reflection;
using Grpc.AspNetCore.Server;
using Grpc.AspNetCore.Server.Model;
using Grpc.Core;
using Grpc.Shared;
using Grpc.Shared.Server;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.Logging;
using MethodOptions = global::Grpc.Shared.Server.MethodOptions;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
internal sealed partial class JsonTranscodingProviderServiceBinder<TService> : ServiceBinderBase where TService : class
{
private delegate (RequestDelegate RequestDelegate, List<object> Metadata) CreateRequestDelegate<TRequest, TResponse>(
Method<TRequest, TResponse> method,
string httpVerb,
HttpRule httpRule,
MethodDescriptor methodDescriptor,
CallHandlerDescriptorInfo descriptorInfo,
MethodOptions methodOptions);
private readonly ServiceMethodProviderContext<TService> _context;
private readonly IServiceInvokerResolver<TService> _invokerResolver;
private readonly ServiceDescriptor _serviceDescriptor;
private readonly GrpcServiceOptions _globalOptions;
private readonly GrpcServiceOptions<TService> _serviceOptions;
private readonly IGrpcServiceActivator<TService> _serviceActivator;
private readonly GrpcJsonTranscodingOptions _JsonTranscodingOptions;
private readonly ILoggerFactory _loggerFactory;
private readonly ILogger _logger;
internal JsonTranscodingProviderServiceBinder(
ServiceMethodProviderContext<TService> context,
IServiceInvokerResolver<TService> invokerResolver,
ServiceDescriptor serviceDescriptor,
GrpcServiceOptions globalOptions,
GrpcServiceOptions<TService> serviceOptions,
ILoggerFactory loggerFactory,
IGrpcServiceActivator<TService> serviceActivator,
GrpcJsonTranscodingOptions JsonTranscodingOptions)
{
_context = context;
_invokerResolver = invokerResolver;
_serviceDescriptor = serviceDescriptor;
_globalOptions = globalOptions;
_serviceOptions = serviceOptions;
_serviceActivator = serviceActivator;
_JsonTranscodingOptions = JsonTranscodingOptions;
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger<JsonTranscodingProviderServiceBinder<TService>>();
}
public override void AddMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, ClientStreamingServerMethod<TRequest, TResponse> handler)
{
if (TryGetMethodDescriptor(method.Name, out var methodDescriptor) &&
ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out _))
{
Log.StreamingMethodNotSupported(_logger, method.Name, method.ServiceName);
}
}
public override void AddMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, DuplexStreamingServerMethod<TRequest, TResponse> handler)
{
if (TryGetMethodDescriptor(method.Name, out var methodDescriptor) &&
ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out _))
{
Log.StreamingMethodNotSupported(_logger, method.Name, method.ServiceName);
}
}
public override void AddMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, ServerStreamingServerMethod<TRequest, TResponse> handler)
{
if (TryGetMethodDescriptor(method.Name, out var methodDescriptor))
{
if (ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out var httpRule))
{
LogMethodHttpRule(method, httpRule);
ProcessHttpRule(method, methodDescriptor, httpRule, CreateServerStreamingRequestDelegate);
}
else
{
// Consider setting to enable mapping to methods without HttpRule
// AddMethodCore(method, method.FullName, "GET", string.Empty, string.Empty, methodDescriptor);
}
}
else
{
Log.MethodDescriptorNotFound(_logger, method.Name, typeof(TService));
}
}
public override void AddMethod<TRequest, TResponse>(Method<TRequest, TResponse> method, UnaryServerMethod<TRequest, TResponse> handler)
{
if (TryGetMethodDescriptor(method.Name, out var methodDescriptor))
{
if (ServiceDescriptorHelpers.TryGetHttpRule(methodDescriptor, out var httpRule))
{
LogMethodHttpRule(method, httpRule);
ProcessHttpRule(method, methodDescriptor, httpRule, CreateUnaryRequestDelegate);
}
else
{
// Consider setting to enable mapping to methods without HttpRule
// AddMethodCore(method, method.FullName, "GET", string.Empty, string.Empty, methodDescriptor);
}
}
else
{
Log.MethodDescriptorNotFound(_logger, method.Name, typeof(TService));
}
}
private void LogMethodHttpRule(IMethod method, HttpRule httpRule)
{
if (_logger.IsEnabled(LogLevel.Trace))
{
Log.HttpRuleFound(_logger, method.Name, method.ServiceName, httpRule.ToString());
}
}
private void ProcessHttpRule<TRequest, TResponse>(
Method<TRequest, TResponse> method,
MethodDescriptor methodDescriptor,
HttpRule httpRule,
CreateRequestDelegate<TRequest, TResponse> createRequestDelegate)
where TRequest : class
where TResponse : class
{
if (ServiceDescriptorHelpers.TryResolvePattern(httpRule, out var pattern, out var httpVerb))
{
AddMethodCore(method, httpRule, pattern, httpVerb, httpRule.Body, httpRule.ResponseBody, methodDescriptor, createRequestDelegate);
}
foreach (var additionalRule in httpRule.AdditionalBindings)
{
ProcessHttpRule(method, methodDescriptor, additionalRule, createRequestDelegate);
}
}
private (RequestDelegate RequestDelegate, List<object> Metadata) CreateUnaryRequestDelegate<TRequest, TResponse>(
Method<TRequest, TResponse> method,
string httpVerb,
HttpRule httpRule,
MethodDescriptor methodDescriptor,
CallHandlerDescriptorInfo descriptorInfo,
MethodOptions methodOptions)
where TRequest : class
where TResponse : class
{
var (invoker, metadata) = _invokerResolver.CreateModelCore<UnaryServerMethod<TService, TRequest, TResponse>>(
method.Name,
new[] { typeof(TRequest), typeof(ServerCallContext) },
httpVerb,
httpRule,
methodDescriptor);
var methodInvoker = new UnaryServerMethodInvoker<TService, TRequest, TResponse>(invoker, method, methodOptions, _serviceActivator);
var callHandler = new UnaryServerCallHandler<TService, TRequest, TResponse>(
methodInvoker,
_loggerFactory,
descriptorInfo,
_JsonTranscodingOptions.UnarySerializerOptions);
return (callHandler.HandleCallAsync, metadata);
}
private (RequestDelegate RequestDelegate, List<object> Metadata) CreateServerStreamingRequestDelegate<TRequest, TResponse>(
Method<TRequest, TResponse> method,
string httpVerb,
HttpRule httpRule,
MethodDescriptor methodDescriptor,
CallHandlerDescriptorInfo descriptorInfo,
MethodOptions methodOptions)
where TRequest : class
where TResponse : class
{
var (invoker, metadata) = _invokerResolver.CreateModelCore<ServerStreamingServerMethod<TService, TRequest, TResponse>>(
method.Name,
new[] { typeof(TRequest), typeof(IServerStreamWriter<TResponse>), typeof(ServerCallContext) },
httpVerb,
httpRule,
methodDescriptor);
var methodInvoker = new ServerStreamingServerMethodInvoker<TService, TRequest, TResponse>(invoker, method, methodOptions, _serviceActivator);
var callHandler = new ServerStreamingServerCallHandler<TService, TRequest, TResponse>(
methodInvoker,
_loggerFactory,
descriptorInfo,
_JsonTranscodingOptions.ServerStreamingSerializerOptions);
return (callHandler.HandleCallAsync, metadata);
}
private void AddMethodCore<TRequest, TResponse>(
Method<TRequest, TResponse> method,
HttpRule httpRule,
string pattern,
string httpVerb,
string body,
string responseBody,
MethodDescriptor methodDescriptor,
CreateRequestDelegate<TRequest, TResponse> createRequestDelegate)
where TRequest : class
where TResponse : class
{
try
{
var (routePattern, descriptorInfo) = ParseRoute(pattern, body, responseBody, methodDescriptor);
var methodOptions = MethodOptions.Create(new[] { _globalOptions, _serviceOptions });
var (requestDelegate, metadata) = createRequestDelegate(method, httpVerb, httpRule, methodDescriptor, descriptorInfo, methodOptions);
_context.AddMethod<TRequest, TResponse>(method, routePattern, metadata, requestDelegate);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error binding {method.Name} on {typeof(TService).Name} to HTTP API.", ex);
}
}
private static (RoutePattern routePattern, CallHandlerDescriptorInfo descriptorInfo) ParseRoute(string pattern, string body, string responseBody, MethodDescriptor methodDescriptor)
{
if (!pattern.StartsWith('/'))
{
// This validation is consistent with grpc-gateway code generation.
// We should match their validation to be a good member of the eco-system.
throw new InvalidOperationException($"Path template '{pattern}' must start with a '/'.");
}
var routePattern = RoutePatternFactory.Parse(pattern);
return (RoutePatternFactory.Parse(pattern), CreateDescriptorInfo(body, responseBody, methodDescriptor, routePattern));
}
private static CallHandlerDescriptorInfo CreateDescriptorInfo(string body, string responseBody, MethodDescriptor methodDescriptor, RoutePattern routePattern)
{
var routeParameterDescriptors = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routePattern, methodDescriptor.InputType);
var bodyDescriptor = ServiceDescriptorHelpers.ResolveBodyDescriptor(body, typeof(TService), methodDescriptor);
FieldDescriptor? responseBodyDescriptor = null;
if (!string.IsNullOrEmpty(responseBody))
{
responseBodyDescriptor = methodDescriptor.OutputType.FindFieldByName(responseBody);
if (responseBodyDescriptor == null)
{
throw new InvalidOperationException($"Couldn't find matching field for response body '{responseBody}' on {methodDescriptor.OutputType.Name}.");
}
}
var descriptorInfo = new CallHandlerDescriptorInfo(
responseBodyDescriptor,
bodyDescriptor?.Descriptor,
bodyDescriptor?.IsDescriptorRepeated ?? false,
bodyDescriptor?.FieldDescriptors,
routeParameterDescriptors);
return descriptorInfo;
}
private bool TryGetMethodDescriptor(string methodName, [NotNullWhen(true)]out MethodDescriptor? methodDescriptor)
{
for (var i = 0; i < _serviceDescriptor.Methods.Count; i++)
{
var method = _serviceDescriptor.Methods[i];
if (method.Name == methodName)
{
methodDescriptor = method;
return true;
}
}
methodDescriptor = null;
return false;
}
private static partial class Log
{
[LoggerMessage(1, LogLevel.Warning, "Unable to find method descriptor for {MethodName} on {ServiceType}.", EventName = "MethodDescriptorNotFound")]
public static partial void MethodDescriptorNotFound(ILogger logger, string methodName, Type serviceType);
[LoggerMessage(2, LogLevel.Warning, "Unable to bind {MethodName} on {ServiceName} to gRPC JSON transcoding. Client and bidirectional streaming methods are not supported.", EventName = "StreamingMethodNotSupported")]
public static partial void StreamingMethodNotSupported(ILogger logger, string methodName, string serviceName);
[LoggerMessage(3, LogLevel.Trace, "Found HttpRule mapping. Method {MethodName} on {ServiceName}. HttpRule payload: {HttpRulePayload}", EventName = "HttpRuleFound", SkipEnabledCheck = true)]
public static partial void HttpRuleFound(ILogger logger, string methodName, string serviceName, string httpRulePayload);
}
}

Просмотреть файл

@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Google.Protobuf.Reflection;
using Grpc.AspNetCore.Server;
using Grpc.AspNetCore.Server.Model;
using Grpc.Shared;
using Grpc.Shared.Server;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
internal sealed partial class JsonTranscodingServiceMethodProvider<TService> : IServiceMethodProvider<TService> where TService : class
{
private readonly ILogger<JsonTranscodingServiceMethodProvider<TService>> _logger;
private readonly GrpcServiceOptions _globalOptions;
private readonly GrpcServiceOptions<TService> _serviceOptions;
private readonly GrpcJsonTranscodingOptions _JsonTranscodingOptions;
private readonly ILoggerFactory _loggerFactory;
private readonly IGrpcServiceActivator<TService> _serviceActivator;
public JsonTranscodingServiceMethodProvider(
ILoggerFactory loggerFactory,
IOptions<GrpcServiceOptions> globalOptions,
IOptions<GrpcServiceOptions<TService>> serviceOptions,
IGrpcServiceActivator<TService> serviceActivator,
IOptions<GrpcJsonTranscodingOptions> JsonTranscodingOptions)
{
_logger = loggerFactory.CreateLogger<JsonTranscodingServiceMethodProvider<TService>>();
_globalOptions = globalOptions.Value;
_serviceOptions = serviceOptions.Value;
_JsonTranscodingOptions = JsonTranscodingOptions.Value;
_loggerFactory = loggerFactory;
_serviceActivator = serviceActivator;
}
public void OnServiceMethodDiscovery(ServiceMethodProviderContext<TService> context)
{
var bindMethodInfo = BindMethodFinder.GetBindMethod(typeof(TService));
// Invoke BindService(ServiceBinderBase, BaseType)
if (bindMethodInfo != null)
{
// The second parameter is always the service base type
var serviceParameter = bindMethodInfo.GetParameters()[1];
ServiceDescriptor? serviceDescriptor = null;
try
{
serviceDescriptor = ServiceDescriptorHelpers.GetServiceDescriptor(bindMethodInfo.DeclaringType!);
}
catch (Exception ex)
{
Log.ServiceDescriptorError(_logger, typeof(TService), ex);
}
if (serviceDescriptor != null)
{
var binder = new JsonTranscodingProviderServiceBinder<TService>(
context,
new ReflectionServiceInvokerResolver<TService>(serviceParameter.ParameterType),
serviceDescriptor,
_globalOptions,
_serviceOptions,
_loggerFactory,
_serviceActivator,
_JsonTranscodingOptions);
try
{
bindMethodInfo.Invoke(null, new object?[] { binder, null });
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error binding gRPC service '{typeof(TService).Name}'.", ex);
}
}
}
else
{
Log.BindMethodNotFound(_logger, typeof(TService));
}
}
private static partial class Log
{
[LoggerMessage(1, LogLevel.Warning, "Could not find bind method for {ServiceType}.", EventName = "BindMethodNotFound")]
public static partial void BindMethodNotFound(ILogger logger, Type serviceType);
[LoggerMessage(2, LogLevel.Warning, "Error getting service descriptor for {ServiceType}.", EventName = "ServiceDescriptorError")]
public static partial void ServiceDescriptorError(ILogger logger, Type serviceType, Exception ex);
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Google.Api;
using Google.Protobuf.Reflection;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
internal interface IServiceInvokerResolver<TService> where TService : class
{
/// <summary>
/// Creates a service invoker delegate and associated metadata using the service type, method name, and HTTP binding.
/// </summary>
(TDelegate invoker, List<object> metadata) CreateModelCore<TDelegate>(
string methodName,
Type[] methodParameters,
string verb,
HttpRule httpRule,
MethodDescriptor methodDescriptor) where TDelegate : Delegate;
}

Просмотреть файл

@ -0,0 +1,85 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using Google.Api;
using Google.Protobuf.Reflection;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Binding;
internal sealed class ReflectionServiceInvokerResolver<TService>
: IServiceInvokerResolver<TService> where TService : class
{
private readonly Type _declaringType;
public ReflectionServiceInvokerResolver(Type declaringType)
{
_declaringType = declaringType;
}
public (TDelegate invoker, List<object> metadata) CreateModelCore<TDelegate>(
string methodName,
Type[] methodParameters,
string verb,
HttpRule httpRule,
MethodDescriptor methodDescriptor) where TDelegate : Delegate
{
var handlerMethod = GetMethod(methodName, methodParameters);
if (handlerMethod == null)
{
throw new InvalidOperationException($"Could not find '{methodName}' on {typeof(TService)}.");
}
var invoker = (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), handlerMethod);
var metadata = new List<object>();
// Add type metadata first so it has a lower priority
metadata.AddRange(typeof(TService).GetCustomAttributes(inherit: true));
// Add method metadata last so it has a higher priority
metadata.AddRange(handlerMethod.GetCustomAttributes(inherit: true));
metadata.Add(new HttpMethodMetadata(new[] { verb }));
// Add protobuf service method descriptor.
// Is used by swagger generation to identify gRPC JSON transcoding APIs.
metadata.Add(new GrpcJsonTranscodingMetadata(methodDescriptor, httpRule));
return (invoker, metadata);
}
private MethodInfo? GetMethod(string methodName, Type[] methodParameters)
{
Type? currentType = typeof(TService);
while (currentType != null)
{
var matchingMethod = currentType.GetMethod(
methodName,
BindingFlags.Public | BindingFlags.Instance,
binder: null,
types: methodParameters,
modifiers: null);
if (matchingMethod == null)
{
return null;
}
// Validate that the method overrides the virtual method on the base service type.
// If there is a method with the same name it will hide the base method. Ignore it,
// and continue searching on the base type.
if (matchingMethod.IsVirtual)
{
var baseDefinitionMethod = matchingMethod.GetBaseDefinition();
if (baseDefinitionMethod != null && baseDefinitionMethod.DeclaringType == _declaringType)
{
return matchingMethod;
}
}
currentType = currentType.BaseType;
}
return null;
}
}

Просмотреть файл

@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Google.Protobuf.Reflection;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
internal sealed class CallHandlerDescriptorInfo
{
public CallHandlerDescriptorInfo(
FieldDescriptor? responseBodyDescriptor,
MessageDescriptor? bodyDescriptor,
bool bodyDescriptorRepeated,
List<FieldDescriptor>? bodyFieldDescriptors,
Dictionary<string, List<FieldDescriptor>> routeParameterDescriptors)
{
ResponseBodyDescriptor = responseBodyDescriptor;
BodyDescriptor = bodyDescriptor;
BodyDescriptorRepeated = bodyDescriptorRepeated;
BodyFieldDescriptors = bodyFieldDescriptors;
RouteParameterDescriptors = routeParameterDescriptors;
if (BodyFieldDescriptors != null)
{
BodyFieldDescriptorsPath = string.Join('.', BodyFieldDescriptors.Select(d => d.Name));
}
PathDescriptorsCache = new ConcurrentDictionary<string, List<FieldDescriptor>?>();
}
public FieldDescriptor? ResponseBodyDescriptor { get; }
public MessageDescriptor? BodyDescriptor { get; }
[MemberNotNullWhen(true, nameof(BodyFieldDescriptors), nameof(BodyFieldDescriptorsPath))]
public bool BodyDescriptorRepeated { get; }
public List<FieldDescriptor>? BodyFieldDescriptors { get; }
public Dictionary<string, List<FieldDescriptor>> RouteParameterDescriptors { get; }
public ConcurrentDictionary<string, List<FieldDescriptor>?> PathDescriptorsCache { get; }
public string? BodyFieldDescriptorsPath { get; }
}

Просмотреть файл

@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Grpc.AspNetCore.Server;
using Grpc.Core;
using Grpc.Shared.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
internal abstract class ServerCallHandlerBase<TService, TRequest, TResponse>
where TService : class
where TRequest : class
where TResponse : class
{
private const string LoggerName = "Grpc.AspNetCore.Grpc.JsonTranscoding.ServerCallHandler";
protected ServerMethodInvokerBase<TService, TRequest, TResponse> MethodInvoker { get; }
public CallHandlerDescriptorInfo DescriptorInfo { get; }
public JsonSerializerOptions SerializerOptions { get; }
protected ILogger Logger { get; }
protected ServerCallHandlerBase(
ServerMethodInvokerBase<TService, TRequest, TResponse> methodInvoker,
ILoggerFactory loggerFactory,
CallHandlerDescriptorInfo descriptorInfo,
JsonSerializerOptions serializerOptions)
{
MethodInvoker = methodInvoker;
DescriptorInfo = descriptorInfo;
SerializerOptions = serializerOptions;
Logger = loggerFactory.CreateLogger(LoggerName);
}
public Task HandleCallAsync(HttpContext httpContext)
{
var serverCallContext = new JsonTranscodingServerCallContext(httpContext, MethodInvoker.Options, MethodInvoker.Method, DescriptorInfo, Logger);
httpContext.Features.Set<IServerCallContextFeature>(serverCallContext);
try
{
serverCallContext.Initialize();
var handleCallTask = HandleCallAsyncCore(httpContext, serverCallContext);
if (handleCallTask.IsCompletedSuccessfully)
{
return Task.CompletedTask;
}
else
{
return AwaitHandleCall(serverCallContext, MethodInvoker.Method, IsStreaming, SerializerOptions, handleCallTask);
}
}
catch (Exception ex)
{
return serverCallContext.ProcessHandlerErrorAsync(ex, MethodInvoker.Method.Name, IsStreaming, SerializerOptions);
}
static async Task AwaitHandleCall(JsonTranscodingServerCallContext serverCallContext, Method<TRequest, TResponse> method, bool isStreaming, JsonSerializerOptions serializerOptions, Task handleCall)
{
try
{
await handleCall;
}
catch (Exception ex)
{
await serverCallContext.ProcessHandlerErrorAsync(ex, method.Name, isStreaming, serializerOptions);
}
}
}
protected abstract Task HandleCallAsyncCore(HttpContext httpContext, JsonTranscodingServerCallContext serverCallContext);
protected virtual bool IsStreaming => false;
}

Просмотреть файл

@ -0,0 +1,44 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Grpc.Shared.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
internal sealed class ServerStreamingServerCallHandler<TService, TRequest, TResponse> : ServerCallHandlerBase<TService, TRequest, TResponse>
where TService : class
where TRequest : class
where TResponse : class
{
private readonly ServerStreamingServerMethodInvoker<TService, TRequest, TResponse> _invoker;
public ServerStreamingServerCallHandler(
ServerStreamingServerMethodInvoker<TService, TRequest, TResponse> unaryMethodInvoker,
ILoggerFactory loggerFactory,
CallHandlerDescriptorInfo descriptorInfo,
JsonSerializerOptions options) : base(unaryMethodInvoker, loggerFactory, descriptorInfo, options)
{
_invoker = unaryMethodInvoker;
}
protected override async Task HandleCallAsyncCore(HttpContext httpContext, JsonTranscodingServerCallContext serverCallContext)
{
// Decode request
var request = await JsonRequestHelpers.ReadMessage<TRequest>(serverCallContext, SerializerOptions);
var streamWriter = new HttpContextStreamWriter<TResponse>(serverCallContext, SerializerOptions);
try
{
await _invoker.Invoke(httpContext, serverCallContext, request, streamWriter);
}
finally
{
streamWriter.Complete();
}
}
protected override bool IsStreaming => true;
}

Просмотреть файл

@ -0,0 +1,49 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Grpc.Core;
using Grpc.Shared.Server;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
internal sealed class UnaryServerCallHandler<TService, TRequest, TResponse> : ServerCallHandlerBase<TService, TRequest, TResponse>
where TService : class
where TRequest : class
where TResponse : class
{
private readonly UnaryServerMethodInvoker<TService, TRequest, TResponse> _invoker;
public UnaryServerCallHandler(
UnaryServerMethodInvoker<TService, TRequest, TResponse> unaryMethodInvoker,
ILoggerFactory loggerFactory,
CallHandlerDescriptorInfo descriptorInfo,
JsonSerializerOptions options) : base(unaryMethodInvoker, loggerFactory, descriptorInfo, options)
{
_invoker = unaryMethodInvoker;
}
protected override async Task HandleCallAsyncCore(HttpContext httpContext, JsonTranscodingServerCallContext serverCallContext)
{
var request = await JsonRequestHelpers.ReadMessage<TRequest>(serverCallContext, SerializerOptions);
var response = await _invoker.Invoke(httpContext, serverCallContext, request);
if (serverCallContext.Status.StatusCode != StatusCode.OK)
{
throw new RpcException(serverCallContext.Status);
}
if (response == null)
{
// This is consistent with Grpc.Core when a null value is returned
throw new RpcException(new Status(StatusCode.Cancelled, "No message returned from method."));
}
serverCallContext.EnsureResponseHeaders();
await JsonRequestHelpers.SendMessage(serverCallContext, SerializerOptions, response);
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static class CommonGrpcProtocolHelpers
{
public static string ConvertToRpcExceptionMessage(Exception ex)
{
// RpcException doesn't allow for an inner exception. To ensure the user is getting enough information about the
// error we will concatenate any inner exception messages together.
return ex.InnerException == null ? $"{ex.GetType().Name}: {ex.Message}" : BuildErrorMessage(ex);
}
private static string BuildErrorMessage(Exception ex)
{
// Concatenate inner exceptions messages together.
var sb = new StringBuilder();
var first = true;
Exception? current = ex;
do
{
if (!first)
{
sb.Append(' ');
}
else
{
first = false;
}
sb.Append(current.GetType().Name);
sb.Append(": ");
sb.Append(current.Message);
}
while ((current = current.InnerException) != null);
return sb.ToString();
}
}

Просмотреть файл

@ -0,0 +1,17 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static class ErrorMessageHelper
{
internal static string BuildErrorMessage(string message, Exception exception, bool? includeExceptionDetails)
{
if (includeExceptionDetails ?? false)
{
return message + " " + CommonGrpcProtocolHelpers.ConvertToRpcExceptionMessage(exception);
}
return message;
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static class GrpcProtocolConstants
{
internal const string TimeoutHeader = "grpc-timeout";
internal const string MessageEncodingHeader = "grpc-encoding";
internal const string MessageAcceptEncodingHeader = "grpc-accept-encoding";
internal static readonly ReadOnlyMemory<byte> StreamingDelimiter = new byte[] { (byte)'\n' };
internal static readonly HashSet<string> FilteredHeaders = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
MessageEncodingHeader,
MessageAcceptEncodingHeader,
TimeoutHeader,
HeaderNames.ContentType,
HeaderNames.TE,
HeaderNames.Host,
HeaderNames.AcceptEncoding
};
}

Просмотреть файл

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static class GrpcProtocolHelpers
{
public static byte[] ParseBinaryHeader(string base64)
{
string decodable;
switch (base64.Length % 4)
{
case 0:
// base64 has the required padding
decodable = base64;
break;
case 2:
// 2 chars padding
decodable = base64 + "==";
break;
case 3:
// 3 chars padding
decodable = base64 + "=";
break;
default:
// length%4 == 1 should be illegal
throw new FormatException("Invalid base64 header value");
}
return Convert.FromBase64String(decodable);
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Grpc.Core;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static partial class GrpcServerLog
{
[LoggerMessage(1, LogLevel.Information, "Request content-type of '{ContentType}' is not supported.", EventName = "UnsupportedRequestContentType")]
public static partial void UnsupportedRequestContentType(ILogger logger, string? contentType);
[LoggerMessage(2, LogLevel.Error, "Error when executing service method '{ServiceMethod}'.", EventName = "ErrorExecutingServiceMethod")]
public static partial void ErrorExecutingServiceMethod(ILogger logger, string serviceMethod, Exception ex);
[LoggerMessage(3, LogLevel.Information, "Error status code '{StatusCode}' with detail '{Detail}' raised.", EventName = "RpcConnectionError")]
public static partial void RpcConnectionError(ILogger logger, StatusCode statusCode, string detail);
[LoggerMessage(4, LogLevel.Debug, "Reading message.", EventName = "ReadingMessage")]
public static partial void ReadingMessage(ILogger logger);
[LoggerMessage(5, LogLevel.Trace, "Deserializing to '{MessageType}'.", EventName = "DeserializingMessage")]
public static partial void DeserializingMessage(ILogger logger, Type messageType);
[LoggerMessage(6, LogLevel.Trace, "Received message.", EventName = "ReceivedMessage")]
public static partial void ReceivedMessage(ILogger logger);
[LoggerMessage(7, LogLevel.Information, "Error reading message.", EventName = "ErrorReadingMessage")]
public static partial void ErrorReadingMessage(ILogger logger, Exception ex);
[LoggerMessage(8, LogLevel.Debug, "Sending message.", EventName = "SendingMessage")]
public static partial void SendingMessage(ILogger logger);
[LoggerMessage(9, LogLevel.Debug, "Message sent.", EventName = "MessageSent")]
public static partial void MessageSent(ILogger logger);
[LoggerMessage(10, LogLevel.Information, "Error sending message.", EventName = "ErrorSendingMessage")]
public static partial void ErrorSendingMessage(ILogger logger, Exception ex);
[LoggerMessage(11, LogLevel.Trace, "Serialized '{MessageType}'.", EventName = "SerializedMessage")]
public static partial void SerializedMessage(ILogger logger, Type messageType);
}

Просмотреть файл

@ -0,0 +1,238 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using Grpc.AspNetCore.Server;
using Grpc.Core;
using Grpc.Shared;
using Grpc.Shared.Server;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.CallHandlers;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal sealed class JsonTranscodingServerCallContext : ServerCallContext, IServerCallContextFeature
{
// TODO(JamesNK): Remove nullable override after Grpc.Core.Api update
private static readonly AuthContext UnauthenticatedContext = new AuthContext(null!, new Dictionary<string, List<AuthProperty>>());
private readonly IMethod _method;
public HttpContext HttpContext { get; }
public MethodOptions Options { get; }
public CallHandlerDescriptorInfo DescriptorInfo { get; }
public bool IsJsonRequestContent { get; private set; }
// Default request encoding to UTF8 so an encoding is available
// if the request sends an invalid/unsupported encoding.
public Encoding RequestEncoding { get; private set; } = Encoding.UTF8;
internal ILogger Logger { get; }
private string? _peer;
private Metadata? _requestHeaders;
private AuthContext? _authContext;
public JsonTranscodingServerCallContext(HttpContext httpContext, MethodOptions options, IMethod method, CallHandlerDescriptorInfo descriptorInfo, ILogger logger)
{
HttpContext = httpContext;
Options = options;
_method = method;
DescriptorInfo = descriptorInfo;
Logger = logger;
}
public void Initialize()
{
IsJsonRequestContent = JsonRequestHelpers.HasJsonContentType(HttpContext.Request, out var charset);
RequestEncoding = JsonRequestHelpers.GetEncodingFromCharset(charset) ?? Encoding.UTF8;
// HttpContext.Items is publically exposed as ServerCallContext.UserState.
// Because this is a custom ServerCallContext, HttpContext must be added to UserState so GetHttpContext() continues to work.
// https://github.com/grpc/grpc-dotnet/blob/7ef184f3c4cd62fbc3cde55e4bb3e16b58258ca1/src/Grpc.AspNetCore.Server/ServerCallContextExtensions.cs#L53-L61
HttpContext.Items["__HttpContext"] = HttpContext;
}
public ServerCallContext ServerCallContext => this;
protected override string MethodCore => _method.FullName;
protected override string HostCore => HttpContext.Request.Host.Value;
protected override string PeerCore
{
get
{
// Follows the standard at https://github.com/grpc/grpc/blob/master/doc/naming.md
if (_peer == null)
{
_peer = BuildPeer();
}
return _peer;
}
}
private string BuildPeer()
{
var connection = HttpContext.Connection;
if (connection.RemoteIpAddress != null)
{
switch (connection.RemoteIpAddress.AddressFamily)
{
case AddressFamily.InterNetwork:
return $"ipv4:{connection.RemoteIpAddress}:{connection.RemotePort}";
case AddressFamily.InterNetworkV6:
return $"ipv6:[{connection.RemoteIpAddress}]:{connection.RemotePort}";
default:
// TODO(JamesNK) - Test what should be output when used with UDS and named pipes
return $"unknown:{connection.RemoteIpAddress}:{connection.RemotePort}";
}
}
else
{
return "unknown"; // Match Grpc.Core
}
}
internal async Task ProcessHandlerErrorAsync(Exception ex, string method, bool isStreaming, JsonSerializerOptions options)
{
Status status;
if (ex is RpcException rpcException)
{
// RpcException is thrown by client code to modify the status returned from the server.
// Log the status and detail. Don't log the exception to reduce log verbosity.
GrpcServerLog.RpcConnectionError(Logger, rpcException.StatusCode, rpcException.Status.Detail);
status = rpcException.Status;
}
else
{
GrpcServerLog.ErrorExecutingServiceMethod(Logger, method, ex);
var message = ErrorMessageHelper.BuildErrorMessage("Exception was thrown by handler.", ex, Options.EnableDetailedErrors);
// Note that the exception given to status won't be returned to the client.
// It is still useful to set in case an interceptor accesses the status on the server.
status = new Status(StatusCode.Unknown, message, ex);
}
await JsonRequestHelpers.SendErrorResponse(HttpContext.Response, RequestEncoding, status, options);
if (isStreaming)
{
await HttpContext.Response.Body.WriteAsync(GrpcProtocolConstants.StreamingDelimiter);
}
}
// Deadline returns max value when there isn't a deadline.
protected override DateTime DeadlineCore => DateTime.MaxValue;
protected override Metadata RequestHeadersCore
{
get
{
if (_requestHeaders == null)
{
_requestHeaders = new Metadata();
foreach (var header in HttpContext.Request.Headers)
{
// gRPC metadata contains a subset of the request headers
// Filter out pseudo headers (start with :) and other known headers
if (header.Key.StartsWith(':') || GrpcProtocolConstants.FilteredHeaders.Contains(header.Key))
{
continue;
}
else if (header.Key.EndsWith(Metadata.BinaryHeaderSuffix, StringComparison.OrdinalIgnoreCase))
{
_requestHeaders.Add(header.Key, GrpcProtocolHelpers.ParseBinaryHeader(header.Value!));
}
else
{
_requestHeaders.Add(header.Key, header.Value!);
}
}
}
return _requestHeaders;
}
}
protected override CancellationToken CancellationTokenCore => HttpContext.RequestAborted;
protected override Metadata ResponseTrailersCore => throw new NotImplementedException();
protected override Status StatusCore { get; set; }
protected override WriteOptions WriteOptionsCore
{
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
protected override AuthContext AuthContextCore
{
get
{
if (_authContext == null)
{
var clientCertificate = HttpContext.Connection.ClientCertificate;
_authContext = clientCertificate == null
? UnauthenticatedContext
: AuthContextHelpers.CreateAuthContext(clientCertificate);
}
return _authContext;
}
}
// TODO(JamesNK): Remove nullable override after Grpc.Core.Api update
protected override IDictionary<object, object> UserStateCore => HttpContext.Items!;
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions? options)
{
throw new NotImplementedException();
}
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
{
// Headers can only be written once. Throw on subsequent call to write response header instead of silent no-op.
if (HttpContext.Response.HasStarted)
{
throw new InvalidOperationException("Response headers can only be sent once per call.");
}
if (responseHeaders != null)
{
foreach (var entry in responseHeaders)
{
if (entry.IsBinary)
{
HttpContext.Response.Headers[entry.Key] = Convert.ToBase64String(entry.ValueBytes);
}
else
{
HttpContext.Response.Headers[entry.Key] = entry.Value;
}
}
}
EnsureResponseHeaders();
return HttpContext.Response.BodyWriter.FlushAsync().GetAsTask();
}
internal void EnsureResponseHeaders()
{
if (!HttpContext.Response.HasStarted)
{
HttpContext.Response.StatusCode = StatusCodes.Status200OK;
HttpContext.Response.ContentType = MediaType.ReplaceEncoding("application/json", RequestEncoding);
}
}
}

Просмотреть файл

@ -0,0 +1,81 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Grpc.Core;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal sealed class HttpContextStreamWriter<TResponse> : IServerStreamWriter<TResponse>
where TResponse : class
{
private readonly JsonTranscodingServerCallContext _context;
private readonly JsonSerializerOptions _serializerOptions;
private readonly object _writeLock;
private Task? _writeTask;
private bool _completed;
public HttpContextStreamWriter(JsonTranscodingServerCallContext context, JsonSerializerOptions serializerOptions)
{
_context = context;
_serializerOptions = serializerOptions;
_writeLock = new object();
}
public WriteOptions WriteOptions
{
get => _context.WriteOptions;
set => _context.WriteOptions = value;
}
public Task WriteAsync(TResponse message)
{
if (message == null)
{
return Task.FromException(new ArgumentNullException(nameof(message)));
}
if (_completed || _context.CancellationToken.IsCancellationRequested)
{
return Task.FromException(new InvalidOperationException("Can't write the message because the request is complete."));
}
lock (_writeLock)
{
// Pending writes need to be awaited first
if (IsWriteInProgressUnsynchronized)
{
return Task.FromException(new InvalidOperationException("Can't write the message because the previous write is in progress."));
}
// Save write task to track whether it is complete. Must be set inside lock.
_writeTask = WriteMessageAndDelimiter(message);
}
return _writeTask;
}
private async Task WriteMessageAndDelimiter(TResponse message)
{
await JsonRequestHelpers.SendMessage(_context, _serializerOptions, message);
await _context.HttpContext.Response.Body.WriteAsync(GrpcProtocolConstants.StreamingDelimiter);
}
public void Complete()
{
_completed = true;
}
/// <summary>
/// A value indicating whether there is an async write already in progress.
/// Should only check this property when holding the write lock.
/// </summary>
private bool IsWriteInProgressUnsynchronized
{
get
{
var writeTask = _writeTask;
return writeTask != null && !writeTask.IsCompleted;
}
}
}

Просмотреть файл

@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Grpc.Shared;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class AnyConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
internal const string AnyTypeUrlField = "@type";
internal const string AnyWellKnownTypeValueField = "value";
public AnyConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using var d = JsonDocument.ParseValue(ref reader);
if (!d.RootElement.TryGetProperty(AnyTypeUrlField, out var urlField))
{
throw new InvalidOperationException("Any message with no @type.");
}
var typeUrl = urlField.GetString();
var typeName = Any.GetTypeName(typeUrl);
var descriptor = Context.TypeRegistry.Find(typeName);
if (descriptor == null)
{
throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
}
IMessage data;
if (ServiceDescriptorHelpers.IsWellKnownType(descriptor))
{
if (!d.RootElement.TryGetProperty(AnyWellKnownTypeValueField, out var valueField))
{
throw new InvalidOperationException($"Expected '{AnyWellKnownTypeValueField}' property for well-known type Any body.");
}
data = (IMessage)JsonSerializer.Deserialize(valueField, descriptor.ClrType, options)!;
}
else
{
data = (IMessage)JsonSerializer.Deserialize(d.RootElement, descriptor.ClrType, options)!;
}
var message = new TMessage();
message.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.SetValue(message, typeUrl);
message.Descriptor.Fields[Any.ValueFieldNumber].Accessor.SetValue(message, data.ToByteString());
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
var typeUrl = (string)value.Descriptor.Fields[Any.TypeUrlFieldNumber].Accessor.GetValue(value);
var data = (ByteString)value.Descriptor.Fields[Any.ValueFieldNumber].Accessor.GetValue(value);
var typeName = Any.GetTypeName(typeUrl);
var descriptor = Context.TypeRegistry.Find(typeName);
if (descriptor == null)
{
throw new InvalidOperationException($"Type registry has no descriptor for type name '{typeName}'.");
}
var valueMessage = descriptor.Parser.ParseFrom(data);
writer.WriteStartObject();
writer.WriteString(AnyTypeUrlField, typeUrl);
if (ServiceDescriptorHelpers.IsWellKnownType(descriptor))
{
writer.WritePropertyName(AnyWellKnownTypeValueField);
if (ServiceDescriptorHelpers.IsWrapperType(descriptor))
{
var wrappedValue = valueMessage.Descriptor.Fields[JsonConverterHelper.WrapperValueFieldNumber].Accessor.GetValue(valueMessage);
JsonSerializer.Serialize(writer, wrappedValue, wrappedValue.GetType(), options);
}
else
{
JsonSerializer.Serialize(writer, valueMessage, valueMessage.GetType(), options);
}
}
else
{
MessageConverter<Any>.WriteMessageFields(writer, valueMessage, Context.Settings, options);
}
writer.WriteEndObject();
}
}

Просмотреть файл

@ -0,0 +1,26 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using System.Text.Json.Serialization;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class BoolConverter : JsonConverter<bool>
{
public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return reader.GetBoolean();
}
public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WriteBooleanValue(value);
}
public override void WriteAsPropertyName(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
{
writer.WritePropertyName(value ? "true" : "false");
}
}

Просмотреть файл

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class ByteStringConverter : JsonConverter<ByteString>
{
public override ByteString? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// TODO - handle base64 strings without padding
return UnsafeByteOperations.UnsafeWrap(reader.GetBytesFromBase64());
}
public override void Write(Utf8JsonWriter writer, ByteString value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToBase64());
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class DurationConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public DurationConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new InvalidOperationException("Expected string value for Duration.");
}
var (seconds, nanos) = Legacy.ParseDuration(reader.GetString()!);
var message = new TMessage();
if (message is Duration duration)
{
duration.Seconds = seconds;
duration.Nanos = nanos;
}
else
{
message.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.SetValue(message, seconds);
message.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.SetValue(message, nanos);
}
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
int nanos;
long seconds;
if (value is Duration duration)
{
nanos = duration.Nanos;
seconds = duration.Seconds;
}
else
{
nanos = (int)value.Descriptor.Fields[Duration.NanosFieldNumber].Accessor.GetValue(value);
seconds = (long)value.Descriptor.Fields[Duration.SecondsFieldNumber].Accessor.GetValue(value);
}
var text = Legacy.GetDurationText(nanos, seconds);
writer.WriteStringValue(text);
}
}

Просмотреть файл

@ -0,0 +1,125 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Google.Protobuf.Reflection;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class EnumConverter<TEnum> : SettingsConverterBase<TEnum> where TEnum : Enum
{
public EnumConverter(JsonContext context) : base(context)
{
}
public override TEnum? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.String:
var enumDescriptor = ResolveEnumDescriptor(typeToConvert);
if (enumDescriptor == null)
{
throw new InvalidOperationException($"Unable to resolve descriptor for {typeToConvert}.");
}
var valueDescriptor = enumDescriptor.FindValueByName(reader.GetString()!);
return ConvertFromInteger(valueDescriptor.Number);
case JsonTokenType.Number:
return ConvertFromInteger(reader.GetInt32());
case JsonTokenType.Null:
return default;
default:
throw new InvalidOperationException($"Unexpected JSON token: {reader.TokenType}.");
}
}
private static EnumDescriptor? ResolveEnumDescriptor(Type typeToConvert)
{
var containingType = typeToConvert?.DeclaringType?.DeclaringType;
if (containingType != null)
{
var messageDescriptor = JsonConverterHelper.GetMessageDescriptor(containingType);
if (messageDescriptor != null)
{
for (var i = 0; i < messageDescriptor.EnumTypes.Count; i++)
{
if (messageDescriptor.EnumTypes[i].ClrType == typeToConvert)
{
return messageDescriptor.EnumTypes[i];
}
}
}
}
return null;
}
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
{
if (Context.Settings.WriteEnumsAsIntegers)
{
writer.WriteNumberValue(ConvertToInteger(value));
}
else
{
var name = Legacy.OriginalEnumValueHelper.GetOriginalName(value);
if (name != null)
{
writer.WriteStringValue(name);
}
else
{
writer.WriteNumberValue(ConvertToInteger(value));
}
}
}
private static TEnum ConvertFromInteger(int integer)
{
if (!TryConvertToEnum(integer, out var value))
{
throw new InvalidOperationException($"Integer can't be converted to enum {typeof(TEnum).FullName}.");
}
return value;
}
private static int ConvertToInteger(TEnum value)
{
if (!TryConvertToInteger(value, out var integer))
{
throw new InvalidOperationException($"Enum {typeof(TEnum).FullName} can't be converted to integer.");
}
return integer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryConvertToInteger(TEnum value, out int integer)
{
if (Unsafe.SizeOf<int>() == Unsafe.SizeOf<TEnum>())
{
integer = Unsafe.As<TEnum, int>(ref value);
return true;
}
integer = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryConvertToEnum(int integer, [NotNullWhen(true)] out TEnum? value)
{
if (Unsafe.SizeOf<int>() == Unsafe.SizeOf<TEnum>())
{
value = Unsafe.As<int, TEnum>(ref integer);
return true;
}
value = default;
return false;
}
}

Просмотреть файл

@ -0,0 +1,53 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Linq;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class FieldMaskConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public FieldMaskConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new TMessage();
if (reader.TokenType != JsonTokenType.String)
{
throw new InvalidOperationException("Expected string value for FieldMask.");
}
// TODO: Do we *want* to remove empty entries? Probably okay to treat "" as "no paths", but "foo,,bar"?
// Note: This logic replicates Google.Protobuf. Should follow their lead.
var jsonPaths = reader.GetString()!.Split(',', StringSplitOptions.RemoveEmptyEntries);
var messagePaths = (IList)message.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(message);
foreach (var path in jsonPaths)
{
messagePaths.Add(Legacy.ToSnakeCase(path));
}
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
// Note: This logic replicates Google.Protobuf. Should follow their lead.
var paths = (IList<string>)value.Descriptor.Fields[FieldMask.PathsFieldNumber].Accessor.GetValue(value);
var firstInvalid = paths.FirstOrDefault(p => !Legacy.IsPathValid(p));
if (firstInvalid == null)
{
writer.WriteStringValue(string.Join(",", paths.Select(Legacy.ToJsonName)));
}
else
{
throw new InvalidOperationException($"Invalid field mask to be converted to JSON: {firstInvalid}.");
}
}
}

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Text.Json;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class Int64Converter : SettingsConverterBase<long>
{
public Int64Converter(JsonContext context) : base(context)
{
}
public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
return long.Parse(reader.GetString()!, CultureInfo.InvariantCulture);
}
return reader.GetInt64();
}
public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
{
if (!Context.Settings.WriteInt64sAsStrings)
{
writer.WriteNumberValue(value);
}
else
{
writer.WriteStringValue(value.ToString("d", CultureInfo.InvariantCulture));
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Google.Protobuf.Reflection;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class JsonContext
{
public JsonContext(GrpcJsonSettings settings, TypeRegistry typeRegistry)
{
Settings = settings;
TypeRegistry = typeRegistry;
}
public GrpcJsonSettings Settings { get; }
public TypeRegistry TypeRegistry { get; }
}

Просмотреть файл

@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal class JsonConverterFactoryForEnum : JsonConverterFactory
{
private readonly JsonContext _context;
public JsonConverterFactoryForEnum(JsonContext context)
{
_context = context;
}
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert.IsEnum;
}
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
var converter = (JsonConverter)Activator.CreateInstance(
typeof(EnumConverter<>).MakeGenericType(new Type[] { typeToConvert }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { _context },
culture: null)!;
return converter;
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal class JsonConverterFactoryForMessage : JsonConverterFactory
{
private readonly JsonContext _context;
public JsonConverterFactoryForMessage(JsonContext context)
{
_context = context;
}
public override bool CanConvert(Type typeToConvert)
{
return typeof(IMessage).IsAssignableFrom(typeToConvert);
}
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
JsonConverter converter = (JsonConverter)Activator.CreateInstance(
typeof(MessageConverter<>).MakeGenericType(new Type[] { typeToConvert }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { _context },
culture: null)!;
return converter;
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal class JsonConverterFactoryForWellKnownTypes : JsonConverterFactory
{
private readonly JsonContext _context;
public JsonConverterFactoryForWellKnownTypes(JsonContext context)
{
_context = context;
}
public override bool CanConvert(Type typeToConvert)
{
if (!typeof(IMessage).IsAssignableFrom(typeToConvert))
{
return false;
}
var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert);
if (descriptor == null)
{
return false;
}
return WellKnownTypeNames.ContainsKey(descriptor.FullName);
}
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert)!;
var converterType = WellKnownTypeNames[descriptor.FullName];
var converter = (JsonConverter)Activator.CreateInstance(
converterType.MakeGenericType(new Type[] { typeToConvert }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { _context },
culture: null)!;
return converter;
}
private static readonly Dictionary<string, Type> WellKnownTypeNames = new Dictionary<string, Type>
{
[Any.Descriptor.FullName] = typeof(AnyConverter<>),
[Duration.Descriptor.FullName] = typeof(DurationConverter<>),
[Timestamp.Descriptor.FullName] = typeof(TimestampConverter<>),
[FieldMask.Descriptor.FullName] = typeof(FieldMaskConverter<>),
[Struct.Descriptor.FullName] = typeof(StructConverter<>),
[ListValue.Descriptor.FullName] = typeof(ListValueConverter<>),
[Value.Descriptor.FullName] = typeof(ValueConverter<>),
};
}

Просмотреть файл

@ -0,0 +1,50 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Grpc.Shared;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal class JsonConverterFactoryForWrappers : JsonConverterFactory
{
private readonly JsonContext _context;
public JsonConverterFactoryForWrappers(JsonContext context)
{
_context = context;
}
public override bool CanConvert(Type typeToConvert)
{
if (!typeof(IMessage).IsAssignableFrom(typeToConvert))
{
return false;
}
var descriptor = JsonConverterHelper.GetMessageDescriptor(typeToConvert);
if (descriptor == null)
{
return false;
}
return ServiceDescriptorHelpers.IsWrapperType(descriptor);
}
public override JsonConverter CreateConverter(
Type typeToConvert, JsonSerializerOptions options)
{
var converter = (JsonConverter)Activator.CreateInstance(
typeof(WrapperConverter<>).MakeGenericType(new Type[] { typeToConvert }),
BindingFlags.Instance | BindingFlags.Public,
binder: null,
args: new object[] { _context },
culture: null)!;
return converter;
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Reflection;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes;
using Grpc.Shared;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal static class JsonConverterHelper
{
internal const int WrapperValueFieldNumber = Int32Value.ValueFieldNumber;
internal static JsonSerializerOptions CreateSerializerOptions(JsonContext context, bool isStreamingOptions = false)
{
// Streaming is line delimited between messages. That means JSON can't be indented as it adds new lines.
// For streaming to work, indenting must be disabled when streaming.
var writeIndented = !isStreamingOptions ? context.Settings.WriteIndented : false;
var options = new JsonSerializerOptions
{
WriteIndented = writeIndented,
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};
options.Converters.Add(new NullValueConverter());
options.Converters.Add(new ByteStringConverter());
options.Converters.Add(new Int64Converter(context));
options.Converters.Add(new UInt64Converter(context));
options.Converters.Add(new BoolConverter());
options.Converters.Add(new JsonConverterFactoryForEnum(context));
options.Converters.Add(new JsonConverterFactoryForWrappers(context));
options.Converters.Add(new JsonConverterFactoryForWellKnownTypes(context));
options.Converters.Add(new JsonConverterFactoryForMessage(context));
return options;
}
internal static Type GetFieldType(FieldDescriptor descriptor)
{
switch (descriptor.FieldType)
{
case FieldType.Bool:
return typeof(bool);
case FieldType.Bytes:
return typeof(ByteString);
case FieldType.String:
return typeof(string);
case FieldType.Double:
return typeof(double);
case FieldType.SInt32:
case FieldType.Int32:
case FieldType.SFixed32:
return typeof(int);
case FieldType.Enum:
return descriptor.EnumType.ClrType;
case FieldType.Fixed32:
case FieldType.UInt32:
return typeof(uint);
case FieldType.Fixed64:
case FieldType.UInt64:
return typeof(ulong);
case FieldType.SFixed64:
case FieldType.Int64:
case FieldType.SInt64:
return typeof(long);
case FieldType.Float:
return typeof(float);
case FieldType.Message:
case FieldType.Group: // Never expect to get this, but...
if (ServiceDescriptorHelpers.IsWrapperType(descriptor.MessageType))
{
var t = GetFieldType(descriptor.MessageType.Fields[WrapperValueFieldNumber]);
if (t.IsValueType)
{
return typeof(Nullable<>).MakeGenericType(t);
}
return t;
}
return descriptor.MessageType.ClrType;
default:
throw new ArgumentException("Invalid field type");
}
}
internal static MessageDescriptor? GetMessageDescriptor(Type typeToConvert)
{
var property = typeToConvert.GetProperty("Descriptor", BindingFlags.Static | BindingFlags.Public, binder: null, typeof(MessageDescriptor), Type.EmptyTypes, modifiers: null);
if (property == null)
{
return null;
}
return property.GetValue(null, null) as MessageDescriptor;
}
public static void PopulateMap(ref Utf8JsonReader reader, JsonSerializerOptions options, IMessage message, FieldDescriptor fieldDescriptor)
{
var mapFields = fieldDescriptor.MessageType.Fields.InFieldNumberOrder();
var mapKey = mapFields[0];
var mapValue = mapFields[1];
var keyType = GetFieldType(mapKey);
var valueType = GetFieldType(mapValue);
var repeatedFieldType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType);
var newValues = (IDictionary)JsonSerializer.Deserialize(ref reader, repeatedFieldType, options)!;
var existingValue = (IDictionary)fieldDescriptor.Accessor.GetValue(message);
foreach (DictionaryEntry item in newValues)
{
existingValue[item.Key] = item.Value;
}
}
public static void PopulateList(ref Utf8JsonReader reader, JsonSerializerOptions options, IMessage message, FieldDescriptor fieldDescriptor)
{
var fieldType = GetFieldType(fieldDescriptor);
var repeatedFieldType = typeof(List<>).MakeGenericType(fieldType);
var newValues = (IList)JsonSerializer.Deserialize(ref reader, repeatedFieldType, options)!;
var existingValue = (IList)fieldDescriptor.Accessor.GetValue(message);
foreach (var item in newValues)
{
existingValue.Add(item);
}
}
}

Просмотреть файл

@ -0,0 +1,403 @@
#pragma warning disable IDE0073 // The file header does not match the required text
#region Copyright notice and license
// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc. All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
#pragma warning restore IDE0073 // The file header does not match the required text
using System.Collections.Concurrent;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
// Source here is from https://github.com/protocolbuffers/protobuf
// Most of this code will be replaced over time with optimized implementations.
internal static class Legacy
{
private static readonly Regex TimestampRegex = new Regex(@"^(?<datetime>[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-5][0-9])(?<subseconds>\.[0-9]{1,9})?(?<offset>(Z|[+-][0-1][0-9]:[0-5][0-9]))$", RegexOptions.Compiled);
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
// Constants determined programmatically, but then hard-coded so they can be constant expressions.
private const long BclSecondsAtUnixEpoch = 62135596800;
internal const long UnixSecondsAtBclMaxValue = 253402300799;
internal const long UnixSecondsAtBclMinValue = -BclSecondsAtUnixEpoch;
internal const int MaxNanos = Duration.NanosecondsPerSecond - 1;
private static readonly int[] SubsecondScalingFactors = { 0, 100000000, 100000000, 10000000, 1000000, 100000, 10000, 1000, 100, 10, 1 };
private static readonly Regex DurationRegex = new Regex(@"^(?<sign>-)?(?<int>[0-9]{1,12})(?<subseconds>\.[0-9]{1,9})?s$", RegexOptions.Compiled);
public static (long seconds, int nanos) ParseTimestamp(string value)
{
var match = TimestampRegex.Match(value);
if (!match.Success)
{
throw new InvalidOperationException($"Invalid Timestamp value: {value}");
}
var dateTime = match.Groups["datetime"].Value;
var subseconds = match.Groups["subseconds"].Value;
var offset = match.Groups["offset"].Value;
try
{
DateTime parsed = DateTime.ParseExact(
dateTime,
"yyyy-MM-dd'T'HH:mm:ss",
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
// TODO: It would be nice not to have to create all these objects... easy to optimize later though.
Timestamp timestamp = Timestamp.FromDateTime(parsed);
int nanosToAdd = 0;
if (subseconds != "")
{
// This should always work, as we've got 1-9 digits.
int parsedFraction = int.Parse(subseconds.AsSpan(1), CultureInfo.InvariantCulture);
nanosToAdd = parsedFraction * SubsecondScalingFactors[subseconds.Length];
}
int secondsToAdd = 0;
if (offset != "Z")
{
// This is the amount we need to *subtract* from the local time to get to UTC - hence - => +1 and vice versa.
int sign = offset[0] == '-' ? 1 : -1;
int hours = int.Parse(offset.AsSpan(1, 2), CultureInfo.InvariantCulture);
int minutes = int.Parse(offset.AsSpan(4, 2), CultureInfo.InvariantCulture);
int totalMinutes = hours * 60 + minutes;
if (totalMinutes > 18 * 60)
{
throw new InvalidOperationException($"Invalid Timestamp value: {value}");
}
if (totalMinutes == 0 && sign == 1)
{
// This is an offset of -00:00, which means "unknown local offset". It makes no sense for a timestamp.
throw new InvalidOperationException($"Invalid Timestamp value: {value}");
}
// We need to *subtract* the offset from local time to get UTC.
secondsToAdd = sign * totalMinutes * 60;
}
// Ensure we've got the right signs. Currently unnecessary, but easy to do.
if (secondsToAdd < 0 && nanosToAdd > 0)
{
secondsToAdd++;
nanosToAdd = nanosToAdd - Duration.NanosecondsPerSecond;
}
if (secondsToAdd != 0 || nanosToAdd != 0)
{
timestamp += new Duration { Nanos = nanosToAdd, Seconds = secondsToAdd };
// The resulting timestamp after offset change would be out of our expected range. Currently the Timestamp message doesn't validate this
// anywhere, but we shouldn't parse it.
if (timestamp.Seconds < UnixSecondsAtBclMinValue || timestamp.Seconds > UnixSecondsAtBclMaxValue)
{
throw new InvalidOperationException($"Invalid Timestamp value: {value}");
}
}
return (timestamp.Seconds, timestamp.Nanos);
}
catch (FormatException)
{
throw new InvalidOperationException($"Invalid Timestamp value: {value}");
}
}
private static bool IsNormalized(long seconds, int nanoseconds) =>
nanoseconds >= 0 &&
nanoseconds <= MaxNanos &&
seconds >= UnixSecondsAtBclMinValue &&
seconds <= UnixSecondsAtBclMaxValue;
public static string GetTimestampText(int nanos, long seconds)
{
if (IsNormalized(seconds, nanos))
{
// Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
DateTime dateTime = UnixEpoch.AddSeconds(seconds);
var builder = new StringBuilder();
builder.Append(dateTime.ToString("yyyy'-'MM'-'dd'T'HH:mm:ss", CultureInfo.InvariantCulture));
if (nanos != 0)
{
builder.Append('.');
// Output to 3, 6 or 9 digits.
if (nanos % 1000000 == 0)
{
builder.Append((nanos / 1000000).ToString("d3", CultureInfo.InvariantCulture));
}
else if (nanos % 1000 == 0)
{
builder.Append((nanos / 1000).ToString("d6", CultureInfo.InvariantCulture));
}
else
{
builder.Append(nanos.ToString("d9", CultureInfo.InvariantCulture));
}
}
builder.Append('Z');
return builder.ToString();
}
else
{
throw new InvalidOperationException("Non-normalized timestamp value.");
}
}
public static (long seconds, int nanos) ParseDuration(string value)
{
var match = DurationRegex.Match(value);
if (!match.Success)
{
throw new InvalidOperationException("Invalid Duration value: " + value);
}
var sign = match.Groups["sign"].Value;
var secondsText = match.Groups["int"].Value;
// Prohibit leading insignificant zeroes
if (secondsText[0] == '0' && secondsText.Length > 1)
{
throw new InvalidOperationException("Invalid Duration value: " + value);
}
var subseconds = match.Groups["subseconds"].Value;
var multiplier = sign == "-" ? -1 : 1;
try
{
long seconds = long.Parse(secondsText, CultureInfo.InvariantCulture) * multiplier;
int nanos = 0;
if (subseconds != "")
{
// This should always work, as we've got 1-9 digits.
int parsedFraction = int.Parse(subseconds.AsSpan(1), CultureInfo.InvariantCulture);
nanos = parsedFraction * SubsecondScalingFactors[subseconds.Length] * multiplier;
}
if (!IsNormalized(seconds, nanos))
{
throw new InvalidOperationException("Invalid Duration value: " + value);
}
return (seconds, nanos);
}
catch (FormatException)
{
throw new InvalidOperationException("Invalid Duration value: " + value);
}
}
public static string GetDurationText(int nanos, long seconds)
{
if (IsNormalized(seconds, nanos))
{
var builder = new StringBuilder();
// The seconds part will normally provide the minus sign if we need it, but not if it's 0...
if (seconds == 0 && nanos < 0)
{
builder.Append('-');
}
builder.Append(seconds.ToString("d", CultureInfo.InvariantCulture));
AppendNanoseconds(builder, Math.Abs(nanos));
builder.Append('s');
return builder.ToString();
}
else
{
throw new InvalidOperationException("Non-normalized duration value.");
}
}
/// <summary>
/// Appends a number of nanoseconds to a StringBuilder. Either 0 digits are added (in which
/// case no "." is appended), or 3 6 or 9 digits. This is internal for use in Timestamp as well
/// as Duration.
/// </summary>
internal static void AppendNanoseconds(StringBuilder builder, int nanos)
{
if (nanos != 0)
{
builder.Append('.');
// Output to 3, 6 or 9 digits.
if (nanos % 1000000 == 0)
{
builder.Append((nanos / 1000000).ToString("d3", CultureInfo.InvariantCulture));
}
else if (nanos % 1000 == 0)
{
builder.Append((nanos / 1000).ToString("d6", CultureInfo.InvariantCulture));
}
else
{
builder.Append(nanos.ToString("d9", CultureInfo.InvariantCulture));
}
}
}
// Ported from src/google/protobuf/util/internal/utility.cc
internal static string ToSnakeCase(string text)
{
var builder = new StringBuilder(text.Length * 2);
// Note: this is probably unnecessary now, but currently retained to be as close as possible to the
// C++, whilst still throwing an exception on underscores.
bool wasNotUnderscore = false; // Initialize to false for case 1 (below)
bool wasNotCap = false;
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (c >= 'A' && c <= 'Z') // ascii_isupper
{
// Consider when the current character B is capitalized:
// 1) At beginning of input: "B..." => "b..."
// (e.g. "Biscuit" => "biscuit")
// 2) Following a lowercase: "...aB..." => "...a_b..."
// (e.g. "gBike" => "g_bike")
// 3) At the end of input: "...AB" => "...ab"
// (e.g. "GoogleLAB" => "google_lab")
// 4) Followed by a lowercase: "...ABc..." => "...a_bc..."
// (e.g. "GBike" => "g_bike")
if (wasNotUnderscore && // case 1 out
(wasNotCap || // case 2 in, case 3 out
(i + 1 < text.Length && // case 3 out
(text[i + 1] >= 'a' && text[i + 1] <= 'z')))) // ascii_islower(text[i + 1])
{ // case 4 in
// We add an underscore for case 2 and case 4.
builder.Append('_');
}
// ascii_tolower, but we already know that c *is* an upper case ASCII character...
builder.Append((char)(c + 'a' - 'A'));
wasNotUnderscore = true;
wasNotCap = false;
}
else
{
builder.Append(c);
if (c == '_')
{
throw new InvalidOperationException($"Invalid field mask: {text}");
}
wasNotUnderscore = true;
wasNotCap = true;
}
}
return builder.ToString();
}
internal static string ToJsonName(string name)
{
var result = new StringBuilder(name.Length);
var isNextUpperCase = false;
foreach (var ch in name)
{
if (ch == '_')
{
isNextUpperCase = true;
}
else if (isNextUpperCase)
{
result.Append(char.ToUpperInvariant(ch));
isNextUpperCase = false;
}
else
{
result.Append(ch);
}
}
return result.ToString();
}
/// <summary>
/// Checks whether the given path is valid for a field mask.
/// </summary>
/// <returns>true if the path is valid; false otherwise</returns>
internal static bool IsPathValid(string input)
{
for (var i = 0; i < input.Length; i++)
{
var c = input[i];
if (c >= 'A' && c <= 'Z')
{
return false;
}
if (c == '_' && i < input.Length - 1)
{
var next = input[i + 1];
if (next < 'a' || next > 'z')
{
return false;
}
}
}
return true;
}
// Effectively a cache of mapping from enum values to the original name as specified in the proto file,
// fetched by reflection.
// The need for this is unfortunate, as is its unbounded size, but realistically it shouldn't cause issues.
internal static class OriginalEnumValueHelper
{
private static readonly ConcurrentDictionary<Type, Dictionary<object, string>> _dictionaries
= new ConcurrentDictionary<Type, Dictionary<object, string>>();
internal static string? GetOriginalName(object value)
{
var enumType = value.GetType();
Dictionary<object, string>? nameMapping;
lock (_dictionaries)
{
if (!_dictionaries.TryGetValue(enumType, out nameMapping))
{
nameMapping = GetNameMapping(enumType);
_dictionaries[enumType] = nameMapping;
}
}
// If this returns false, originalName will be null, which is what we want.
nameMapping.TryGetValue(value, out var originalName);
return originalName;
}
private static Dictionary<object, string> GetNameMapping(Type enumType)
{
return enumType.GetTypeInfo().DeclaredFields
.Where(f => f.IsStatic)
.Where(f => f.GetCustomAttributes<OriginalNameAttribute>()
.FirstOrDefault()?.PreferredAlias ?? true)
.ToDictionary(f => f.GetValue(null)!,
f => f.GetCustomAttributes<OriginalNameAttribute>()
.FirstOrDefault()
// If the attribute hasn't been applied, fall back to the name of the field.
?.Name ?? f.Name);
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class ListValueConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public ListValueConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new TMessage();
JsonConverterHelper.PopulateList(ref reader, options, message, message.Descriptor.Fields[ListValue.ValuesFieldNumber]);
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
var list = (IList)value.Descriptor.Fields[ListValue.ValuesFieldNumber].Accessor.GetValue(value);
JsonSerializer.Serialize(writer, list, list.GetType(), options);
}
}

Просмотреть файл

@ -0,0 +1,187 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
// This converter should be temporary until System.Text.Json supports overriding contacts.
// We want to eliminate this converter because System.Text.Json has to buffer content in converters.
internal sealed class MessageConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
private readonly Dictionary<string, FieldDescriptor> _jsonFieldMap;
public MessageConverter(JsonContext context) : base(context)
{
_jsonFieldMap = CreateJsonFieldMap((new TMessage()).Descriptor.Fields.InFieldNumberOrder());
}
public override TMessage Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
var message = new TMessage();
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new InvalidOperationException($"Unexpected JSON token: {reader.TokenType}");
}
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonTokenType.EndObject:
return message;
case JsonTokenType.PropertyName:
if (_jsonFieldMap.TryGetValue(reader.GetString()!, out var fieldDescriptor))
{
if (fieldDescriptor.ContainingOneof != null)
{
if (fieldDescriptor.ContainingOneof.Accessor.GetCaseFieldDescriptor(message) != null)
{
throw new InvalidOperationException($"Multiple values specified for oneof {fieldDescriptor.ContainingOneof.Name}.");
}
}
if (fieldDescriptor.IsMap)
{
JsonConverterHelper.PopulateMap(ref reader, options, message, fieldDescriptor);
}
else if (fieldDescriptor.IsRepeated)
{
JsonConverterHelper.PopulateList(ref reader, options, message, fieldDescriptor);
}
else
{
var fieldType = JsonConverterHelper.GetFieldType(fieldDescriptor);
var propertyValue = JsonSerializer.Deserialize(ref reader, fieldType, options);
fieldDescriptor.Accessor.SetValue(message, propertyValue);
}
}
else
{
reader.Skip();
}
break;
case JsonTokenType.Comment:
// Ignore
break;
default:
throw new InvalidOperationException($"Unexpected JSON token: {reader.TokenType}");
}
}
throw new Exception();
}
public override void Write(
Utf8JsonWriter writer,
TMessage value,
JsonSerializerOptions options)
{
WriteMessage(writer, value, options);
}
private void WriteMessage(Utf8JsonWriter writer, IMessage message, JsonSerializerOptions options)
{
writer.WriteStartObject();
WriteMessageFields(writer, message, Context.Settings, options);
writer.WriteEndObject();
}
internal static void WriteMessageFields(Utf8JsonWriter writer, IMessage message, GrpcJsonSettings settings, JsonSerializerOptions options)
{
var fields = message.Descriptor.Fields;
foreach (var field in fields.InFieldNumberOrder())
{
var accessor = field.Accessor;
var value = accessor.GetValue(message);
if (!ShouldFormatFieldValue(message, field, value, !settings.IgnoreDefaultValues))
{
continue;
}
writer.WritePropertyName(accessor.Descriptor.JsonName);
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
private static Dictionary<string, FieldDescriptor> CreateJsonFieldMap(IList<FieldDescriptor> fields)
{
var map = new Dictionary<string, FieldDescriptor>();
foreach (var field in fields)
{
map[field.Name] = field;
map[field.JsonName] = field;
}
return new Dictionary<string, FieldDescriptor>(map);
}
/// <summary>
/// Determines whether or not a field value should be serialized according to the field,
/// its value in the message, and the settings of this formatter.
/// </summary>
private static bool ShouldFormatFieldValue(IMessage message, FieldDescriptor field, object value, bool formatDefaultValues) =>
field.HasPresence
// Fields that support presence *just* use that
? field.Accessor.HasValue(message)
// Otherwise, format if either we've been asked to format default values, or if it's
// not a default value anyway.
: formatDefaultValues || !IsDefaultValue(field, value);
private static bool IsDefaultValue(FieldDescriptor descriptor, object value)
{
if (descriptor.IsMap)
{
IDictionary dictionary = (IDictionary)value;
return dictionary.Count == 0;
}
if (descriptor.IsRepeated)
{
IList list = (IList)value;
return list.Count == 0;
}
switch (descriptor.FieldType)
{
case FieldType.Bool:
return (bool)value == false;
case FieldType.Bytes:
return (ByteString)value == ByteString.Empty;
case FieldType.String:
return (string)value == "";
case FieldType.Double:
return (double)value == 0.0;
case FieldType.SInt32:
case FieldType.Int32:
case FieldType.SFixed32:
case FieldType.Enum:
return (int)value == 0;
case FieldType.Fixed32:
case FieldType.UInt32:
return (uint)value == 0;
case FieldType.Fixed64:
case FieldType.UInt64:
return (ulong)value == 0;
case FieldType.SFixed64:
case FieldType.Int64:
case FieldType.SInt64:
return (long)value == 0;
case FieldType.Float:
return (float)value == 0f;
case FieldType.Message:
case FieldType.Group: // Never expect to get this, but...
return value == null;
default:
throw new ArgumentException("Invalid field type");
}
}
}

Просмотреть файл

@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using System.Text.Json.Serialization;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class NullValueConverter : JsonConverter<NullValue>
{
public override bool HandleNull => true;
public override NullValue Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.String:
if (reader.GetString() == "NULL_VALUE")
{
return NullValue.NullValue;
}
else
{
throw new InvalidOperationException($"Invalid enum value: {reader.GetString()} for enum type: google.protobuf.NullValue");
}
case JsonTokenType.Number:
return (NullValue)reader.GetInt32();
case JsonTokenType.Null:
return NullValue.NullValue;
default:
throw new InvalidOperationException($"Unexpected JSON token: {reader.TokenType}");
}
}
public override void Write(Utf8JsonWriter writer, NullValue value, JsonSerializerOptions options)
{
writer.WriteNullValue();
}
}

Просмотреть файл

@ -0,0 +1,16 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json.Serialization;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal abstract class SettingsConverterBase<T> : JsonConverter<T>
{
public SettingsConverterBase(JsonContext context)
{
Context = context;
}
public JsonContext Context { get; }
}

Просмотреть файл

@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class StructConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public StructConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new TMessage();
JsonConverterHelper.PopulateMap(ref reader, options, message, message.Descriptor.Fields[Struct.FieldsFieldNumber]);
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
writer.WriteStartObject();
var fields = (IDictionary)value.Descriptor.Fields[Struct.FieldsFieldNumber].Accessor.GetValue(value);
foreach (DictionaryEntry entry in fields)
{
var k = (string)entry.Key;
var v = (IMessage?)entry.Value;
if (string.IsNullOrEmpty(k) || v == null)
{
throw new InvalidOperationException("Struct fields cannot have an empty key or a null value.");
}
writer.WritePropertyName(k);
JsonSerializer.Serialize(writer, v, v.GetType(), options);
}
writer.WriteEndObject();
}
}

Просмотреть файл

@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class TimestampConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public TimestampConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.String)
{
throw new InvalidOperationException("Expected string value for Timestamp.");
}
var (seconds, nanos) = Legacy.ParseTimestamp(reader.GetString()!);
var message = new TMessage();
if (message is Timestamp timestamp)
{
timestamp.Seconds = seconds;
timestamp.Nanos = nanos;
}
else
{
message.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.SetValue(message, seconds);
message.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.SetValue(message, nanos);
}
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
int nanos;
long seconds;
if (value is Timestamp timestamp)
{
nanos = timestamp.Nanos;
seconds = timestamp.Seconds;
}
else
{
nanos = (int)value.Descriptor.Fields[Timestamp.NanosFieldNumber].Accessor.GetValue(value);
seconds = (long)value.Descriptor.Fields[Timestamp.SecondsFieldNumber].Accessor.GetValue(value);
}
var text = Legacy.GetTimestampText(nanos, seconds);
writer.WriteStringValue(text);
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class UInt64Converter : SettingsConverterBase<ulong>
{
public UInt64Converter(JsonContext context) : base(context)
{
}
public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType == JsonTokenType.String)
{
return ulong.Parse(reader.GetString()!, CultureInfo.InvariantCulture);
}
return reader.GetUInt64();
}
public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options)
{
if (!Context.Settings.WriteInt64sAsStrings)
{
writer.WriteNumberValue(value);
}
else
{
writer.WriteStringValue(value.ToString("d", CultureInfo.InvariantCulture));
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class ValueConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public ValueConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new TMessage();
var fields = message.Descriptor.Fields;
switch (reader.TokenType)
{
case JsonTokenType.StartObject:
{
var field = fields[Value.StructValueFieldNumber];
var structMessage = JsonSerializer.Deserialize(ref reader, field.MessageType.ClrType, options);
field.Accessor.SetValue(message, structMessage);
break;
}
case JsonTokenType.StartArray:
{
var field = fields[Value.ListValueFieldNumber];
var list = JsonSerializer.Deserialize(ref reader, field.MessageType.ClrType, options);
field.Accessor.SetValue(message, list);
break;
}
case JsonTokenType.Comment:
break;
case JsonTokenType.String:
fields[Value.StringValueFieldNumber].Accessor.SetValue(message, reader.GetString()!);
break;
case JsonTokenType.Number:
fields[Value.NumberValueFieldNumber].Accessor.SetValue(message, reader.GetDouble());
break;
case JsonTokenType.True:
fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, true);
break;
case JsonTokenType.False:
fields[Value.BoolValueFieldNumber].Accessor.SetValue(message, false);
break;
case JsonTokenType.Null:
fields[Value.NullValueFieldNumber].Accessor.SetValue(message, 0);
break;
default:
throw new InvalidOperationException("Unexpected token type: " + reader.TokenType);
}
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
var specifiedField = value.Descriptor.Oneofs[0].Accessor.GetCaseFieldDescriptor(value);
if (specifiedField == null)
{
throw new InvalidOperationException("Value message must contain a value for the oneof.");
}
object v = specifiedField.Accessor.GetValue(value);
switch (specifiedField.FieldNumber)
{
case Value.BoolValueFieldNumber:
case Value.StringValueFieldNumber:
case Value.NumberValueFieldNumber:
case Value.StructValueFieldNumber:
case Value.ListValueFieldNumber:
JsonSerializer.Serialize(writer, v, v.GetType(), options);
break;
case Value.NullValueFieldNumber:
writer.WriteNullValue();
break;
default:
throw new InvalidOperationException("Unexpected case in struct field: " + specifiedField.FieldNumber);
}
}
}

Просмотреть файл

@ -0,0 +1,33 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Google.Protobuf;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
internal sealed class WrapperConverter<TMessage> : SettingsConverterBase<TMessage> where TMessage : IMessage, new()
{
public WrapperConverter(JsonContext context) : base(context)
{
}
public override TMessage? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new TMessage();
var valueDescriptor = message.Descriptor.Fields[JsonConverterHelper.WrapperValueFieldNumber];
var t = JsonConverterHelper.GetFieldType(valueDescriptor);
var value = JsonSerializer.Deserialize(ref reader, t, options);
valueDescriptor.Accessor.SetValue(message, value);
return message;
}
public override void Write(Utf8JsonWriter writer, TMessage value, JsonSerializerOptions options)
{
var valueDescriptor = value.Descriptor.Fields[JsonConverterHelper.WrapperValueFieldNumber];
var innerValue = valueDescriptor.Accessor.GetValue(value);
JsonSerializer.Serialize(writer, innerValue, innerValue.GetType(), options);
}
}

Просмотреть файл

@ -0,0 +1,346 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Linq;
using System.Text;
using System.Text.Json;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Grpc.Core;
using Grpc.Gateway.Runtime;
using Grpc.Shared;
using Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal.Json;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Grpc.JsonTranscoding.Internal;
internal static class JsonRequestHelpers
{
public const string JsonContentType = "application/json";
public const string JsonContentTypeWithCharset = "application/json; charset=utf-8";
public static bool HasJsonContentType(HttpRequest request, out StringSegment charset)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (!MediaTypeHeaderValue.TryParse(request.ContentType, out var mt))
{
charset = default;
return false;
}
// Matches application/json
if (mt.MediaType.Equals(JsonContentType, StringComparison.OrdinalIgnoreCase))
{
charset = mt.Charset;
return true;
}
// Matches +json, e.g. application/ld+json
if (mt.Suffix.Equals("json", StringComparison.OrdinalIgnoreCase))
{
charset = mt.Charset;
return true;
}
charset = default;
return false;
}
public static (Stream stream, bool usesTranscodingStream) GetStream(Stream innerStream, Encoding? encoding)
{
if (encoding == null || encoding.CodePage == Encoding.UTF8.CodePage)
{
return (innerStream, false);
}
var stream = Encoding.CreateTranscodingStream(innerStream, encoding, Encoding.UTF8, leaveOpen: true);
return (stream, true);
}
public static Encoding? GetEncodingFromCharset(StringSegment charset)
{
if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
{
// This is an optimization for utf-8 that prevents the Substring caused by
// charset.Value
return Encoding.UTF8;
}
try
{
// charset.Value might be an invalid encoding name as in charset=invalid.
return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
}
catch (Exception ex)
{
throw new InvalidOperationException($"Unable to read the request as JSON because the request content type charset '{charset}' is not a known encoding.", ex);
}
}
public static async Task SendErrorResponse(HttpResponse response, Encoding encoding, Status status, JsonSerializerOptions options)
{
if (!response.HasStarted)
{
response.StatusCode = MapStatusCodeToHttpStatus(status.StatusCode);
response.ContentType = MediaType.ReplaceEncoding("application/json", encoding);
}
var e = new Error
{
Error_ = status.Detail,
Message = status.Detail,
Code = (int)status.StatusCode
};
await WriteResponseMessage(response, encoding, e, options);
}
public static int MapStatusCodeToHttpStatus(StatusCode statusCode)
{
switch (statusCode)
{
case StatusCode.OK:
return StatusCodes.Status200OK;
case StatusCode.Cancelled:
return StatusCodes.Status408RequestTimeout;
case StatusCode.Unknown:
return StatusCodes.Status500InternalServerError;
case StatusCode.InvalidArgument:
return StatusCodes.Status400BadRequest;
case StatusCode.DeadlineExceeded:
return StatusCodes.Status504GatewayTimeout;
case StatusCode.NotFound:
return StatusCodes.Status404NotFound;
case StatusCode.AlreadyExists:
return StatusCodes.Status409Conflict;
case StatusCode.PermissionDenied:
return StatusCodes.Status403Forbidden;
case StatusCode.Unauthenticated:
return StatusCodes.Status401Unauthorized;
case StatusCode.ResourceExhausted:
return StatusCodes.Status429TooManyRequests;
case StatusCode.FailedPrecondition:
// Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
return StatusCodes.Status400BadRequest;
case StatusCode.Aborted:
return StatusCodes.Status409Conflict;
case StatusCode.OutOfRange:
return StatusCodes.Status400BadRequest;
case StatusCode.Unimplemented:
return StatusCodes.Status501NotImplemented;
case StatusCode.Internal:
return StatusCodes.Status500InternalServerError;
case StatusCode.Unavailable:
return StatusCodes.Status503ServiceUnavailable;
case StatusCode.DataLoss:
return StatusCodes.Status500InternalServerError;
}
return StatusCodes.Status500InternalServerError;
}
public static async Task WriteResponseMessage(HttpResponse response, Encoding encoding, object responseBody, JsonSerializerOptions options)
{
var (stream, usesTranscodingStream) = GetStream(response.Body, encoding);
try
{
await JsonSerializer.SerializeAsync(stream, responseBody, options);
}
finally
{
if (usesTranscodingStream)
{
await stream.DisposeAsync();
}
}
}
public static async Task<TRequest> ReadMessage<TRequest>(JsonTranscodingServerCallContext serverCallContext, JsonSerializerOptions serializerOptions) where TRequest : class
{
try
{
GrpcServerLog.ReadingMessage(serverCallContext.Logger);
IMessage requestMessage;
if (serverCallContext.DescriptorInfo.BodyDescriptor != null)
{
if (!serverCallContext.IsJsonRequestContent)
{
GrpcServerLog.UnsupportedRequestContentType(serverCallContext.Logger, serverCallContext.HttpContext.Request.ContentType);
throw new RpcException(new Status(StatusCode.InvalidArgument, "Request content-type of application/json is required."));
}
var (stream, usesTranscodingStream) = GetStream(serverCallContext.HttpContext.Request.Body, serverCallContext.RequestEncoding);
try
{
if (serverCallContext.DescriptorInfo.BodyDescriptorRepeated)
{
requestMessage = (IMessage)Activator.CreateInstance<TRequest>();
// TODO: JsonSerializer currently doesn't support deserializing values onto an existing object or collection.
// Either update this to use new functionality in JsonSerializer or improve work-around perf.
var type = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.BodyFieldDescriptors.Last());
var listType = typeof(List<>).MakeGenericType(type);
GrpcServerLog.DeserializingMessage(serverCallContext.Logger, listType);
var repeatedContent = (IList)(await JsonSerializer.DeserializeAsync(stream, listType, serializerOptions))!;
ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, serverCallContext.DescriptorInfo.BodyFieldDescriptors, repeatedContent);
}
else
{
IMessage bodyContent;
try
{
GrpcServerLog.DeserializingMessage(serverCallContext.Logger, serverCallContext.DescriptorInfo.BodyDescriptor.ClrType);
bodyContent = (IMessage)(await JsonSerializer.DeserializeAsync(stream, serverCallContext.DescriptorInfo.BodyDescriptor.ClrType, serializerOptions))!;
}
catch (JsonException)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, "Request JSON payload is not correctly formatted."));
}
catch (Exception exception)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, exception.Message));
}
if (serverCallContext.DescriptorInfo.BodyFieldDescriptors != null)
{
requestMessage = (IMessage)Activator.CreateInstance<TRequest>();
ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, serverCallContext.DescriptorInfo.BodyFieldDescriptors, bodyContent!); // TODO - check nullability
}
else
{
requestMessage = bodyContent;
}
}
}
finally
{
if (usesTranscodingStream)
{
await stream.DisposeAsync();
}
}
}
else
{
requestMessage = (IMessage)Activator.CreateInstance<TRequest>();
}
foreach (var parameterDescriptor in serverCallContext.DescriptorInfo.RouteParameterDescriptors)
{
var routeValue = serverCallContext.HttpContext.Request.RouteValues[parameterDescriptor.Key];
if (routeValue != null)
{
ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, parameterDescriptor.Value, routeValue);
}
}
foreach (var item in serverCallContext.HttpContext.Request.Query)
{
if (CanBindQueryStringVariable(serverCallContext, item.Key))
{
var pathDescriptors = GetPathDescriptors(serverCallContext, requestMessage, item.Key);
if (pathDescriptors != null)
{
var value = item.Value.Count == 1 ? (object?)item.Value[0] : item.Value;
ServiceDescriptorHelpers.RecursiveSetValue(requestMessage, pathDescriptors, value);
}
}
}
GrpcServerLog.ReceivedMessage(serverCallContext.Logger);
return (TRequest)requestMessage;
}
catch (Exception ex)
{
GrpcServerLog.ErrorReadingMessage(serverCallContext.Logger, ex);
throw;
}
}
private static List<FieldDescriptor>? GetPathDescriptors(JsonTranscodingServerCallContext serverCallContext, IMessage requestMessage, string path)
{
return serverCallContext.DescriptorInfo.PathDescriptorsCache.GetOrAdd(path, p =>
{
ServiceDescriptorHelpers.TryResolveDescriptors(requestMessage.Descriptor, p, out var pathDescriptors);
return pathDescriptors;
});
}
public static async Task SendMessage<TResponse>(JsonTranscodingServerCallContext serverCallContext, JsonSerializerOptions serializerOptions, TResponse message) where TResponse : class
{
var response = serverCallContext.HttpContext.Response;
try
{
GrpcServerLog.SendingMessage(serverCallContext.Logger);
object responseBody;
Type responseType;
if (serverCallContext.DescriptorInfo.ResponseBodyDescriptor != null)
{
// TODO: Support recursive response body?
responseBody = serverCallContext.DescriptorInfo.ResponseBodyDescriptor.Accessor.GetValue((IMessage)message);
responseType = JsonConverterHelper.GetFieldType(serverCallContext.DescriptorInfo.ResponseBodyDescriptor);
}
else
{
responseBody = message;
responseType = message.GetType();
}
await JsonRequestHelpers.WriteResponseMessage(response, serverCallContext.RequestEncoding, responseBody, serializerOptions);
GrpcServerLog.SerializedMessage(serverCallContext.Logger, responseType);
GrpcServerLog.MessageSent(serverCallContext.Logger);
}
catch (Exception ex)
{
GrpcServerLog.ErrorSendingMessage(serverCallContext.Logger, ex);
throw;
}
}
private static bool CanBindQueryStringVariable(JsonTranscodingServerCallContext serverCallContext, string variable)
{
if (serverCallContext.DescriptorInfo.BodyDescriptor != null)
{
if (serverCallContext.DescriptorInfo.BodyFieldDescriptors == null || serverCallContext.DescriptorInfo.BodyFieldDescriptors.Count == 0)
{
return false;
}
if (variable == serverCallContext.DescriptorInfo.BodyFieldDescriptorsPath)
{
return false;
}
if (variable.StartsWith(serverCallContext.DescriptorInfo.BodyFieldDescriptorsPath!, StringComparison.Ordinal))
{
return false;
}
}
if (serverCallContext.DescriptorInfo.RouteParameterDescriptors.ContainsKey(variable))
{
return false;
}
return true;
}
}

Просмотреть файл

@ -0,0 +1,26 @@
syntax = "proto3";
package grpc.gateway.runtime;
option go_package = "internal";
import "google/protobuf/any.proto";
// Error is the generic error returned from unary RPCs.
message Error {
string error = 1;
// This is to make the error more compatible with users that expect errors to be Status objects:
// https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto
// It should be the exact same message as the Error field.
int32 code = 2;
string message = 3;
repeated google.protobuf.Any details = 4;
}
// StreamError is a response type which is returned when
// streaming rpc returns an error.
message StreamError {
int32 grpc_code = 1;
int32 http_code = 2;
string message = 3;
string http_status = 4;
repeated google.protobuf.Any details = 5;
}

Просмотреть файл

@ -0,0 +1,33 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>HTTP API for gRPC ASP.NET Core</Description>
<PackageTags>gRPC RPC HTTP/2 REST</PackageTags>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Grpc.JsonTranscoding.IntegrationTests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.Grpc.JsonTranscoding.Tests" />
<InternalsVisibleTo Include="Microsoft.AspNetCore.Grpc.Microbenchmarks" />
<Compile Include="..\Shared\Server\BindMethodFinder.cs" Link="Internal\Shared\Server\BindMethodFinder.cs" />
<Compile Include="..\Shared\Server\ClientStreamingServerMethodInvoker.cs" Link="Internal\Shared\Server\ClientStreamingServerMethodInvoker.cs" />
<Compile Include="..\Shared\Server\DuplexStreamingServerMethodInvoker.cs" Link="Internal\Shared\Server\DuplexStreamingServerMethodInvoker.cs" />
<Compile Include="..\Shared\Server\InterceptorPipelineBuilder.cs" Link="Internal\Shared\Server\InterceptorPipelineBuilder.cs" />
<Compile Include="..\Shared\Server\MethodOptions.cs" Link="Internal\Shared\Server\MethodOptions.cs" />
<Compile Include="..\Shared\Server\ServerMethodInvokerBase.cs" Link="Internal\Shared\Server\ServerMethodInvokerBase.cs" />
<Compile Include="..\Shared\Server\ServerStreamingServerMethodInvoker.cs" Link="Internal\Shared\Server\ServerStreamingServerMethodInvoker.cs" />
<Compile Include="..\Shared\Server\UnaryServerMethodInvoker.cs" Link="Internal\Shared\Server\UnaryServerMethodInvoker.cs" />
<Compile Include="..\Shared\AuthContextHelpers.cs" Link="Internal\Shared\AuthContextHelpers.cs" />
<Compile Include="..\Shared\ServiceDescriptorHelpers.cs" Link="Internal\Shared\ServiceDescriptorHelpers.cs" />
<Compile Include="..\Shared\X509CertificateHelpers.cs" Link="Internal\Shared\X509CertificateHelpers.cs" />
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" LinkBase="Internal\Shared" />
<Protobuf Include="Internal\Protos\errors.proto" Access="Internal" />
<Reference Include="Google.Api.CommonProtos" />
<Reference Include="Google.Protobuf" />
<Reference Include="Grpc.AspNetCore.Server" />
<Reference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1 @@
#nullable enable

Просмотреть файл

@ -0,0 +1,24 @@
#nullable enable
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.GrpcJsonSettings() -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.IgnoreDefaultValues.get -> bool
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.IgnoreDefaultValues.set -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteEnumsAsIntegers.get -> bool
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteEnumsAsIntegers.set -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteIndented.get -> bool
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteIndented.set -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteInt64sAsStrings.get -> bool
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings.WriteInt64sAsStrings.set -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingMetadata
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingMetadata.GrpcJsonTranscodingMetadata(Google.Protobuf.Reflection.MethodDescriptor! methodDescriptor, Google.Api.HttpRule! httpRule) -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingMetadata.HttpRule.get -> Google.Api.HttpRule!
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingMetadata.MethodDescriptor.get -> Google.Protobuf.Reflection.MethodDescriptor!
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions.GrpcJsonTranscodingOptions() -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions.JsonSettings.get -> Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonSettings!
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions.JsonSettings.set -> void
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions.TypeRegistry.get -> Google.Protobuf.Reflection.TypeRegistry!
Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions.TypeRegistry.set -> void
Microsoft.Extensions.DependencyInjection.GrpcJsonTranscodingServiceExtensions
static Microsoft.Extensions.DependencyInjection.GrpcJsonTranscodingServiceExtensions.AddJsonTranscoding(this Grpc.AspNetCore.Server.IGrpcServerBuilder! grpcBuilder) -> Grpc.AspNetCore.Server.IGrpcServerBuilder!
static Microsoft.Extensions.DependencyInjection.GrpcJsonTranscodingServiceExtensions.AddJsonTranscoding(this Grpc.AspNetCore.Server.IGrpcServerBuilder! grpcBuilder, System.Action<Microsoft.AspNetCore.Grpc.JsonTranscoding.GrpcJsonTranscodingOptions!>! configureOptions) -> Grpc.AspNetCore.Server.IGrpcServerBuilder!

Просмотреть файл

@ -0,0 +1,78 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Xml.XPath;
using Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for the gRPC JSON transcoding services.
/// </summary>
public static class GrpcSwaggerGenOptionsExtensions
{
/// <summary>
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="xmlDocFactory">A factory method that returns XML Comments as an XPathDocument</param>
public static void IncludeGrpcXmlComments(
this SwaggerGenOptions swaggerGenOptions,
Func<XPathDocument> xmlDocFactory)
{
swaggerGenOptions.IncludeGrpcXmlComments(xmlDocFactory, includeControllerXmlComments: false);
}
/// <summary>
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="xmlDocFactory">A factory method that returns XML Comments as an XPathDocument</param>
/// <param name="includeControllerXmlComments">
/// Flag to indicate if controller XML comments (i.e. summary) should be used to assign Tag descriptions.
/// Don't set this flag if you're customizing the default tag for operations via TagActionsBy.
/// </param>
public static void IncludeGrpcXmlComments(
this SwaggerGenOptions swaggerGenOptions,
Func<XPathDocument> xmlDocFactory,
bool includeControllerXmlComments)
{
var xmlDoc = xmlDocFactory();
swaggerGenOptions.OperationFilter<GrpcXmlCommentsOperationFilter>(xmlDoc);
if (includeControllerXmlComments)
{
swaggerGenOptions.DocumentFilter<GrpcXmlCommentsDocumentFilter>(xmlDoc);
}
}
/// <summary>
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="filePath">An absolute path to the file that contains XML Comments</param>
public static void IncludeGrpcXmlComments(
this SwaggerGenOptions swaggerGenOptions,
string filePath)
{
swaggerGenOptions.IncludeGrpcXmlComments(() => new XPathDocument(filePath));
}
/// <summary>
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
/// </summary>
/// <param name="swaggerGenOptions"></param>
/// <param name="filePath">An absolute path to the file that contains XML Comments</param>
/// <param name="includeControllerXmlComments">
/// Flag to indicate if controller XML comments (i.e. summary) should be used to assign Tag descriptions.
/// Don't set this flag if you're customizing the default tag for operations via TagActionsBy.
/// </param>
public static void IncludeGrpcXmlComments(
this SwaggerGenOptions swaggerGenOptions,
string filePath,
bool includeControllerXmlComments)
{
swaggerGenOptions.IncludeGrpcXmlComments(() => new XPathDocument(filePath), includeControllerXmlComments);
}
}

Просмотреть файл

@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Text.Json;
using Microsoft.AspNetCore.Grpc.Swagger.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension methods for the gRPC JSON transcoding services.
/// </summary>
public static class GrpcSwaggerServiceExtensions
{
/// <summary>
/// Adds gRPC JSON transcoding services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> for adding services.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddGrpcSwagger(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddGrpc().AddJsonTranscoding();
services.TryAddEnumerable(ServiceDescriptor.Transient<IApiDescriptionProvider, GrpcJsonTranscodingDescriptionProvider>());
// Register default description provider in case MVC is not registered
services.TryAddSingleton<IApiDescriptionGroupCollectionProvider>(serviceProvider =>
{
var actionDescriptorCollectionProvider = serviceProvider.GetService<IActionDescriptorCollectionProvider>();
var apiDescriptionProvider = serviceProvider.GetServices<IApiDescriptionProvider>();
return new ApiDescriptionGroupCollectionProvider(
actionDescriptorCollectionProvider ?? new EmptyActionDescriptorCollectionProvider(),
apiDescriptionProvider);
});
// Add or replace contract resolver.
services.Replace(ServiceDescriptor.Transient<ISerializerDataContractResolver>(s =>
{
var serializerOptions = s.GetService<IOptions<JsonOptions>>()?.Value?.JsonSerializerOptions ?? new JsonSerializerOptions();
var innerContractResolver = new JsonSerializerDataContractResolver(serializerOptions);
return new GrpcDataContractResolver(innerContractResolver);
}));
return services;
}
// Dummy type that is only used if MVC is not registered in the app
private class EmptyActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider
{
public ActionDescriptorCollection ActionDescriptors { get; } = new ActionDescriptorCollection(new List<ActionDescriptor>(), 1);
}
}

Просмотреть файл

@ -0,0 +1,143 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Reflection;
using Google.Protobuf;
using Google.Protobuf.Reflection;
using Google.Protobuf.WellKnownTypes;
using Grpc.Shared;
using Swashbuckle.AspNetCore.SwaggerGen;
using Type = System.Type;
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal;
internal class GrpcDataContractResolver : ISerializerDataContractResolver
{
private readonly ISerializerDataContractResolver _innerContractResolver;
private readonly Dictionary<Type, MessageDescriptor> _messageTypeMapping;
private readonly Dictionary<Type, EnumDescriptor> _enumTypeMapping;
public GrpcDataContractResolver(ISerializerDataContractResolver innerContractResolver)
{
_innerContractResolver = innerContractResolver;
_messageTypeMapping = new Dictionary<Type, MessageDescriptor>();
_enumTypeMapping = new Dictionary<Type, EnumDescriptor>();
}
public DataContract GetDataContractForType(Type type)
{
if (!_messageTypeMapping.TryGetValue(type, out var messageDescriptor))
{
if (typeof(IMessage).IsAssignableFrom(type))
{
var property = type.GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static);
messageDescriptor = property?.GetValue(null) as MessageDescriptor;
if (messageDescriptor == null)
{
throw new InvalidOperationException($"Couldn't resolve message descriptor for {type}.");
}
_messageTypeMapping[type] = messageDescriptor;
}
}
if (messageDescriptor != null)
{
return ConvertMessage(messageDescriptor);
}
if (type.IsEnum)
{
if (_enumTypeMapping.TryGetValue(type, out var enumDescriptor))
{
var values = enumDescriptor.Values.Select(v => v.Name).ToList();
return DataContract.ForPrimitive(type, DataType.String, dataFormat: null, value =>
{
var match = enumDescriptor.Values.SingleOrDefault(v => v.Number == (int)value);
var name = match?.Name ?? value.ToString();
return @"""" + name + @"""";
});
}
}
return _innerContractResolver.GetDataContractForType(type);
}
private DataContract ConvertMessage(MessageDescriptor messageDescriptor)
{
if (ServiceDescriptorHelpers.IsWellKnownType(messageDescriptor))
{
if (ServiceDescriptorHelpers.IsWrapperType(messageDescriptor))
{
var field = messageDescriptor.Fields[Int32Value.ValueFieldNumber];
return _innerContractResolver.GetDataContractForType(MessageDescriptorHelpers.ResolveFieldType(field));
}
if (messageDescriptor.FullName == Timestamp.Descriptor.FullName ||
messageDescriptor.FullName == Duration.Descriptor.FullName ||
messageDescriptor.FullName == FieldMask.Descriptor.FullName)
{
return DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.String, dataFormat: null);
}
if (messageDescriptor.FullName == Struct.Descriptor.FullName)
{
return DataContract.ForObject(messageDescriptor.ClrType, Array.Empty<DataProperty>(), extensionDataType: typeof(Value));
}
if (messageDescriptor.FullName == ListValue.Descriptor.FullName)
{
return DataContract.ForArray(messageDescriptor.ClrType, typeof(Value));
}
if (messageDescriptor.FullName == Value.Descriptor.FullName)
{
return DataContract.ForPrimitive(messageDescriptor.ClrType, DataType.Unknown, dataFormat: null);
}
if (messageDescriptor.FullName == Any.Descriptor.FullName)
{
var anyProperties = new List<DataProperty>
{
new DataProperty("@type", typeof(string), isRequired: true)
};
return DataContract.ForObject(messageDescriptor.ClrType, anyProperties, extensionDataType: typeof(Value));
}
}
var properties = new List<DataProperty>();
foreach (var field in messageDescriptor.Fields.InFieldNumberOrder())
{
// Enum type will later be used to call this contract resolver.
// Register the enum type so we know to resolve its names from the descriptor.
if (field.FieldType == FieldType.Enum)
{
_enumTypeMapping.TryAdd(field.EnumType.ClrType, field.EnumType);
}
Type fieldType;
if (field.IsMap)
{
var mapFields = field.MessageType.Fields.InFieldNumberOrder();
var valueType = MessageDescriptorHelpers.ResolveFieldType(mapFields[1]);
fieldType = typeof(IDictionary<,>).MakeGenericType(typeof(string), valueType);
}
else if (field.IsRepeated)
{
fieldType = typeof(IList<>).MakeGenericType(MessageDescriptorHelpers.ResolveFieldType(field));
}
else
{
fieldType = MessageDescriptorHelpers.ResolveFieldType(field);
}
var propertyName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false);
var propertyInfo = messageDescriptor.ClrType.GetProperty(propertyName);
properties.Add(new DataProperty(field.JsonName, fieldType, memberInfo: propertyInfo));
}
var schema = DataContract.ForObject(messageDescriptor.ClrType, properties: properties);
return schema;
}
}

Просмотреть файл

@ -0,0 +1,138 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using Google.Api;
using Google.Protobuf.Reflection;
using Grpc.AspNetCore.Server;
using Grpc.Shared;
using Microsoft.AspNetCore.Grpc.JsonTranscoding;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal;
internal sealed class GrpcJsonTranscodingDescriptionProvider : IApiDescriptionProvider
{
private readonly EndpointDataSource _endpointDataSource;
public GrpcJsonTranscodingDescriptionProvider(EndpointDataSource endpointDataSource)
{
_endpointDataSource = endpointDataSource;
}
// Executes after ASP.NET Core
public int Order => -900;
public void OnProvidersExecuting(ApiDescriptionProviderContext context)
{
var endpoints = _endpointDataSource.Endpoints;
foreach (var endpoint in endpoints)
{
if (endpoint is RouteEndpoint routeEndpoint)
{
var grpcMetadata = endpoint.Metadata.GetMetadata<GrpcJsonTranscodingMetadata>();
if (grpcMetadata != null)
{
var httpRule = grpcMetadata.HttpRule;
var methodDescriptor = grpcMetadata.MethodDescriptor;
if (ServiceDescriptorHelpers.TryResolvePattern(grpcMetadata.HttpRule, out var pattern, out var verb))
{
var apiDescription = CreateApiDescription(routeEndpoint, httpRule, methodDescriptor, pattern, verb);
context.Results.Add(apiDescription);
}
}
}
}
}
private static ApiDescription CreateApiDescription(RouteEndpoint routeEndpoint, HttpRule httpRule, MethodDescriptor methodDescriptor, string pattern, string verb)
{
var apiDescription = new ApiDescription();
apiDescription.HttpMethod = verb;
apiDescription.ActionDescriptor = new ActionDescriptor
{
RouteValues = new Dictionary<string, string?>
{
// Swagger uses this to group endpoints together.
// Group methods together using the service name.
["controller"] = methodDescriptor.Service.FullName
},
EndpointMetadata = routeEndpoint.Metadata.ToList()
};
apiDescription.RelativePath = pattern.TrimStart('/');
apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat { MediaType = "application/json" });
apiDescription.SupportedResponseTypes.Add(new ApiResponseType
{
ApiResponseFormats = { new ApiResponseFormat { MediaType = "application/json" } },
ModelMetadata = new GrpcModelMetadata(ModelMetadataIdentity.ForType(methodDescriptor.OutputType.ClrType)),
StatusCode = 200
});
var explorerSettings = routeEndpoint.Metadata.GetMetadata<ApiExplorerSettingsAttribute>();
if (explorerSettings != null)
{
apiDescription.GroupName = explorerSettings.GroupName;
}
var methodMetadata = routeEndpoint.Metadata.GetMetadata<GrpcMethodMetadata>()!;
var routeParameters = ServiceDescriptorHelpers.ResolveRouteParameterDescriptors(routeEndpoint.RoutePattern, methodDescriptor.InputType);
foreach (var routeParameter in routeParameters)
{
var field = routeParameter.Value.Last();
var parameterName = ServiceDescriptorHelpers.FormatUnderscoreName(field.Name, pascalCase: true, preservePeriod: false);
var propertyInfo = field.ContainingType.ClrType.GetProperty(parameterName);
// If from a property, create model as property to get its XML comments.
var identity = propertyInfo != null
? ModelMetadataIdentity.ForProperty(propertyInfo, MessageDescriptorHelpers.ResolveFieldType(field), field.ContainingType.ClrType)
: ModelMetadataIdentity.ForType(MessageDescriptorHelpers.ResolveFieldType(field));
apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = routeParameter.Key,
ModelMetadata = new GrpcModelMetadata(identity),
Source = BindingSource.Path,
DefaultValue = string.Empty
});
}
var bodyDescriptor = ServiceDescriptorHelpers.ResolveBodyDescriptor(httpRule.Body, methodMetadata.ServiceType, methodDescriptor);
if (bodyDescriptor != null)
{
// If from a property, create model as property to get its XML comments.
var identity = bodyDescriptor.PropertyInfo != null
? ModelMetadataIdentity.ForProperty(bodyDescriptor.PropertyInfo, bodyDescriptor.Descriptor.ClrType, bodyDescriptor.PropertyInfo.DeclaringType!)
: ModelMetadataIdentity.ForType(bodyDescriptor.Descriptor.ClrType);
// Or if from a parameter, create model as parameter to get its XML comments.
var parameterDescriptor = bodyDescriptor.ParameterInfo != null
? new ControllerParameterDescriptor { ParameterInfo = bodyDescriptor.ParameterInfo }
: null;
apiDescription.ParameterDescriptions.Add(new ApiParameterDescription
{
Name = "Input",
ModelMetadata = new GrpcModelMetadata(identity),
Source = BindingSource.Body,
ParameterDescriptor = parameterDescriptor!
});
}
return apiDescription;
}
public void OnProvidersExecuted(ApiDescriptionProviderContext context)
{
// no-op
}
}

Просмотреть файл

@ -0,0 +1,54 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
#nullable disable
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal;
internal class GrpcModelMetadata : ModelMetadata
{
public GrpcModelMetadata(ModelMetadataIdentity identity) : base(identity)
{
IsBindingAllowed = true;
}
public override IReadOnlyDictionary<object, object> AdditionalValues { get; }
public override string BinderModelName { get; }
public override Type BinderType { get; }
public override BindingSource BindingSource { get; }
public override bool ConvertEmptyStringToNull { get; }
public override string DataTypeName { get; }
public override string Description { get; }
public override string DisplayFormatString { get; }
public override string DisplayName { get; }
public override string EditFormatString { get; }
public override ModelMetadata ElementMetadata { get; }
public override IEnumerable<KeyValuePair<EnumGroupAndName, string>> EnumGroupedDisplayNamesAndValues { get; }
public override IReadOnlyDictionary<string, string> EnumNamesAndValues { get; }
public override bool HasNonDefaultEditFormat { get; }
public override bool HideSurroundingHtml { get; }
public override bool HtmlEncode { get; }
public override bool IsBindingAllowed { get; }
public override bool IsBindingRequired { get; }
public override bool IsEnum { get; }
public override bool IsFlagsEnum { get; }
public override bool IsReadOnly { get; }
public override bool IsRequired { get; }
public override ModelBindingMessageProvider ModelBindingMessageProvider { get; }
public override string NullDisplayText { get; }
public override int Order { get; }
public override string Placeholder { get; }
public override ModelPropertyCollection Properties { get; }
public override IPropertyFilterProvider PropertyFilterProvider { get; }
public override Func<object, object> PropertyGetter { get; }
public override Action<object, object> PropertySetter { get; }
public override bool ShowForDisplay { get; }
public override bool ShowForEdit { get; }
public override string SimpleDisplayProperty { get; }
public override string TemplateHint { get; }
public override bool ValidateChildren { get; }
public override IReadOnlyList<object> ValidatorMetadata { get; }
}

Просмотреть файл

@ -0,0 +1,52 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Google.Protobuf.Reflection;
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal;
internal static class MessageDescriptorHelpers
{
public static Type ResolveFieldType(FieldDescriptor field)
{
switch (field.FieldType)
{
case FieldType.Double:
return typeof(double);
case FieldType.Float:
return typeof(float);
case FieldType.Int64:
return typeof(long);
case FieldType.UInt64:
return typeof(ulong);
case FieldType.Int32:
return typeof(int);
case FieldType.Fixed64:
return typeof(long);
case FieldType.Fixed32:
return typeof(int);
case FieldType.Bool:
return typeof(bool);
case FieldType.String:
return typeof(string);
case FieldType.Bytes:
return typeof(string);
case FieldType.UInt32:
return typeof(uint);
case FieldType.SFixed32:
return typeof(int);
case FieldType.SFixed64:
return typeof(long);
case FieldType.SInt32:
return typeof(int);
case FieldType.SInt64:
return typeof(long);
case FieldType.Enum:
return field.EnumType.ClrType;
case FieldType.Message:
return field.MessageType.ClrType;
default:
throw new InvalidOperationException("Unexpected field type: " + field.FieldType);
}
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Globalization;
using System.Linq;
using System.Xml.XPath;
using Grpc.AspNetCore.Server;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments;
internal class GrpcXmlCommentsDocumentFilter : IDocumentFilter
{
private const string MemberXPath = "/doc/members/member[@name='{0}']";
private const string SummaryTag = "summary";
private readonly XPathNavigator _xmlNavigator;
public GrpcXmlCommentsDocumentFilter(XPathDocument xmlDoc)
{
_xmlNavigator = xmlDoc.CreateNavigator();
}
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Get unique services
var nameAndServiceDescriptor = context.ApiDescriptions
.Select(apiDesc => apiDesc.ActionDescriptor)
.Where(actionDesc => actionDesc != null && (actionDesc.EndpointMetadata?.Any(m => m is GrpcMethodMetadata) ?? false))
.GroupBy(actionDesc => actionDesc.RouteValues["controller"]!)
.Select(group => new KeyValuePair<string, ActionDescriptor>(group.Key, group.First()));
foreach (var nameAndType in nameAndServiceDescriptor)
{
var grpcMethodMetadata = nameAndType.Value.EndpointMetadata.OfType<GrpcMethodMetadata>().First();
if (TryAdd(swaggerDoc, nameAndType, grpcMethodMetadata.ServiceType))
{
continue;
}
if (grpcMethodMetadata.ServiceType.BaseType?.DeclaringType is { } staticService)
{
if (TryAdd(swaggerDoc, nameAndType, staticService))
{
continue;
}
}
}
}
private bool TryAdd(OpenApiDocument swaggerDoc, KeyValuePair<string, ActionDescriptor> nameAndType, Type type)
{
var memberName = XmlCommentsNodeNameHelper.GetMemberNameForType(type);
var typeNode = _xmlNavigator.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, MemberXPath, memberName));
if (typeNode != null)
{
var summaryNode = typeNode.SelectSingleNode(SummaryTag);
if (summaryNode != null)
{
if (swaggerDoc.Tags == null)
{
swaggerDoc.Tags = new List<OpenApiTag>();
}
swaggerDoc.Tags.Add(new OpenApiTag
{
Name = nameAndType.Key,
Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml)
});
}
return true;
}
return false;
}
}

Просмотреть файл

@ -0,0 +1,110 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
using System.Reflection;
using System.Xml.XPath;
using Grpc.AspNetCore.Server;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Microsoft.AspNetCore.Grpc.Swagger.Internal.XmlComments;
internal class GrpcXmlCommentsOperationFilter : IOperationFilter
{
private readonly XPathNavigator _xmlNavigator;
public GrpcXmlCommentsOperationFilter(XPathDocument xmlDoc)
{
_xmlNavigator = xmlDoc.CreateNavigator();
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var grpcMetadata = context.ApiDescription.ActionDescriptor.EndpointMetadata.OfType<GrpcMethodMetadata>().FirstOrDefault();
if (grpcMetadata == null)
{
return;
}
var methodInfo = grpcMetadata.ServiceType.GetMethod(grpcMetadata.Method.Name);
if (methodInfo == null)
{
return;
}
// If method is from a constructed generic type, look for comments from the generic type method
var targetMethod = methodInfo.DeclaringType!.IsConstructedGenericType
? methodInfo.GetUnderlyingGenericTypeMethod()
: methodInfo;
if (targetMethod == null)
{
return;
}
// Base service never has response tags.
ApplyServiceTags(operation, targetMethod.DeclaringType!);
if (TryApplyMethodTags(operation, targetMethod))
{
return;
}
if (targetMethod.IsVirtual && targetMethod.GetBaseDefinition() is { } baseMethod)
{
if (TryApplyMethodTags(operation, baseMethod))
{
return;
}
}
}
private void ApplyServiceTags(OpenApiOperation operation, Type controllerType)
{
var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(controllerType);
var responseNodes = _xmlNavigator.Select($"/doc/members/member[@name='{typeMemberName}']/response");
ApplyResponseTags(operation, responseNodes);
}
private bool TryApplyMethodTags(OpenApiOperation operation, MethodInfo methodInfo)
{
var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo);
var methodNode = _xmlNavigator.SelectSingleNode($"/doc/members/member[@name='{methodMemberName}']");
if (methodNode == null)
{
return false;
}
var summaryNode = methodNode.SelectSingleNode("summary");
if (summaryNode != null)
{
operation.Summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml);
}
var remarksNode = methodNode.SelectSingleNode("remarks");
if (remarksNode != null)
{
operation.Description = XmlCommentsTextHelper.Humanize(remarksNode.InnerXml);
}
var responseNodes = methodNode.Select("response");
ApplyResponseTags(operation, responseNodes);
return true;
}
private static void ApplyResponseTags(OpenApiOperation operation, XPathNodeIterator responseNodes)
{
while (responseNodes.MoveNext())
{
var code = responseNodes.Current!.GetAttribute("code", "");
var response = operation.Responses.ContainsKey(code)
? operation.Responses[code]
: operation.Responses[code] = new OpenApiResponse();
response.Description = XmlCommentsTextHelper.Humanize(responseNodes.Current.InnerXml);
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Swagger for gRPC ASP.NET Core</Description>
<PackageTags>gRPC RPC HTTP/2 REST Swagger OpenAPI</PackageTags>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<IsPackable>false</IsPackable>
<IsShipping>false</IsShipping>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.AspNetCore.Grpc.Swagger.Tests" />
<Compile Include="..\Shared\ServiceDescriptorHelpers.cs" Link="Internal\Shared\ServiceDescriptorHelpers.cs" />
<Reference Include="Microsoft.AspNetCore.Grpc.JsonTranscoding" />
<Reference Include="Swashbuckle.AspNetCore" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1 @@
#nullable enable

Просмотреть файл

@ -0,0 +1,8 @@
#nullable enable
Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions
Microsoft.Extensions.DependencyInjection.GrpcSwaggerServiceExtensions
static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, System.Func<System.Xml.XPath.XPathDocument!>! xmlDocFactory) -> void
static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, System.Func<System.Xml.XPath.XPathDocument!>! xmlDocFactory, bool includeControllerXmlComments) -> void
static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, string! filePath) -> void
static Microsoft.Extensions.DependencyInjection.GrpcSwaggerGenOptionsExtensions.IncludeGrpcXmlComments(this Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenOptions! swaggerGenOptions, string! filePath, bool includeControllerXmlComments) -> void
static Microsoft.Extensions.DependencyInjection.GrpcSwaggerServiceExtensions.AddGrpcSwagger(this Microsoft.Extensions.DependencyInjection.IServiceCollection! services) -> Microsoft.Extensions.DependencyInjection.IServiceCollection!

Просмотреть файл

@ -0,0 +1,3 @@
[*.{cs,vb}]
# IDE0073: File header
dotnet_diagnostic.IDE0073.severity = none

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше