diff --git a/build/buildorder.props b/build/buildorder.props index 2338a88d73..937df0f56a 100644 --- a/build/buildorder.props +++ b/build/buildorder.props @@ -11,7 +11,6 @@ - diff --git a/build/dependencies.props b/build/dependencies.props index 6b0d8fcf9b..6f676dc4b9 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -36,14 +36,12 @@ 2.2.0 2.2.0 2.2.0 - 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 - 2.2.0 2.2.0 2.2.0 2.2.0 @@ -57,7 +55,6 @@ 2.2.0 2.2.0 2.2.0 - 2.2.0 2.2.0 2.2.0 2.2.0 @@ -85,23 +82,15 @@ 2.2.0 2.2.0 2.2.0 - 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 2.2.0 - 2.2.0 - 2.2.0 - 2.2.0 - 2.2.0 - 2.2.0 - 2.2.0 2.2.0 2.2.0 2.2.0 - 2.2.0 2.2.0 diff --git a/build/external-dependencies.props b/build/external-dependencies.props index 04c60ff7a3..37b2379b42 100644 --- a/build/external-dependencies.props +++ b/build/external-dependencies.props @@ -13,15 +13,12 @@ - - - @@ -35,7 +32,6 @@ - @@ -63,22 +59,14 @@ - - - - - - - - diff --git a/build/repo.props b/build/repo.props index e96da8c9f4..887da3ee09 100644 --- a/build/repo.props +++ b/build/repo.props @@ -78,6 +78,7 @@ $(MSBuildThisFileDirectory)..\eng\ProjectReferences.props + @(_ProjectReferenceProvider->'', '%0A ') diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..98fe46e403 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,5 @@ +Contributor documentation +========================= + +The primary audience for documentation in this folder is contributors to ASP.NET Core. +If you are looking for documentation on to *use* ASP.NET Core, go to . diff --git a/docs/ReferenceResolution.md b/docs/ReferenceResolution.md new file mode 100644 index 0000000000..e0c3dbf6f4 --- /dev/null +++ b/docs/ReferenceResolution.md @@ -0,0 +1,34 @@ +`` resolution +======================== + +Most project files in this repo should use `` instead of `` or ``. +This was done to enable ASP.NET Core's unique requirements without requiring most ASP.NET Core contributors +to understand the complex rules for how versions and references should work. The build system will resolve +Reference items to the correct type and version of references based on our servicing and update rules. + +See [ResolveReferences.targets](/eng/targets/ResolveReferences.targets) for the exact implementation of custom +`` resolutions. + +The requirements that led to this system are: + +* Versions of external dependencies should be consistent. +* Servicing updates of ASP.NET Core should minimize the number of assemblies which need to re-build and re-ship. +* Newer versions of packages should not have lower dependency versions than previous releases. +* Minimize the cascading effect of servicing updates where possible by keeping a consistent baseline of dependencies. + +## Recommendations for writing a .csproj + +* Use `` +* Do not use `` +* Only use `` in test projects +* Name the .csproj file to match the assembly name. +* Run `build.cmd /t:GenerateProjectList` when adding new projects +* Use [eng/tools/BaseLineGenerator/](/eng/tools/BaselineGenerator/README.md) if you need to update baselines. + +## Important files + +* [eng/Baseline.xml](/eng/Baseline.xml) - this contains the 'baseline' of the latest servicing release for this branch. It should be modified and used to update the generated file, Baseline.Designer.props. +* [eng/Dependencies.props](/eng/Dependencies.props) - contains a list of all package references that might be used in the repo. +* [eng/PatchConfig.props](/eng/PatchConfig.props) - lists which assemblies or packages are patching in the current build. +* [eng/ProjectReferences.props](/eng/ProjectReferences.props) - lists which assemblies or packages might be available to be referenced as a local project +* [eng/Versions.props](/eng/Versions.props) - contains a list of versions which may be updated by automation. diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props index 1a501d89e7..e8fc6321d9 100644 --- a/eng/Baseline.Designer.props +++ b/eng/Baseline.Designer.props @@ -20,14 +20,6 @@ 2.2.0 - - - 2.2.0 - - - - 2.2.0 - 2.2.0 @@ -39,6 +31,14 @@ + + + 2.2.0 + + + + 2.2.0 + 2.2.0 @@ -445,6 +445,17 @@ + + + 2.2.0 + + + + + + + + 2.2.0 @@ -464,17 +475,6 @@ - - - 2.2.0 - - - - - - - - 2.2.0 @@ -611,6 +611,30 @@ + + + 2.2.0 + + + + + + + + + + + + + + + + + + + + + 2.2.0 diff --git a/eng/Baseline.xml b/eng/Baseline.xml index cce6ce895c..78c6bb7e7e 100644 --- a/eng/Baseline.xml +++ b/eng/Baseline.xml @@ -1,11 +1,17 @@ + + - @@ -48,8 +54,8 @@ - + @@ -61,6 +67,7 @@ + diff --git a/eng/Dependencies.props b/eng/Dependencies.props index 789d0e20f3..086e54069a 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -1,14 +1,27 @@ - + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + + + - - @@ -19,14 +32,12 @@ - - @@ -41,12 +52,7 @@ - - - - - @@ -60,6 +66,7 @@ + @@ -69,6 +76,7 @@ + @@ -89,13 +97,4 @@ - - - - - - - - - diff --git a/eng/PatchConfig.props b/eng/PatchConfig.props index 62e4561ae5..b804cb9c45 100644 --- a/eng/PatchConfig.props +++ b/eng/PatchConfig.props @@ -1,3 +1,13 @@ + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props index 230f8a5417..f244f4666e 100644 --- a/eng/ProjectReferences.props +++ b/eng/ProjectReferences.props @@ -1,7 +1,11 @@ - + - + @@ -13,6 +17,7 @@ + @@ -44,12 +49,12 @@ - + @@ -61,12 +66,12 @@ + + - - - - + + diff --git a/eng/Versions.props b/eng/Versions.props new file mode 100644 index 0000000000..8adc6dcbbd --- /dev/null +++ b/eng/Versions.props @@ -0,0 +1,8 @@ + + diff --git a/eng/targets/CSharp.Common.props b/eng/targets/CSharp.Common.props index fe72087c07..29c167c722 100644 --- a/eng/targets/CSharp.Common.props +++ b/eng/targets/CSharp.Common.props @@ -15,4 +15,13 @@ + + + + + + + + + diff --git a/eng/targets/ResolveReferences.targets b/eng/targets/ResolveReferences.targets index 675b0efa18..965da850bc 100644 --- a/eng/targets/ResolveReferences.targets +++ b/eng/targets/ResolveReferences.targets @@ -1,3 +1,21 @@ + @@ -31,17 +49,23 @@ + <_ImplicitPackageReference Include="@(PackageReference->WithMetadataValue('IsImplicitlyDefined', 'true'))" /> + <_ExplicitPackageReference Include="@(PackageReference)" Exclude="@(_ImplicitPackageReference)" /> + <_ExplicitPackageReference Remove="Internal.AspNetCore.Sdk" /> <_ExplicitPackageReference Remove="Microsoft.NETFramework.ReferenceAssemblies" /> - + <_UnusedProjectReferenceProvider Include="@(ProjectReferenceProvider)" Exclude="@(Reference)" /> - + <_ProjectReferenceByAssemblyName Condition="'$(UseProjectReferences)' == 'true'" Include="@(ProjectReferenceProvider)" - Exclude="@(UnusedProjectReferenceProvider)" /> + Exclude="@(_UnusedProjectReferenceProvider)" /> @@ -52,12 +76,18 @@ + <_ReferenceTemp Remove="@(_ReferenceTemp)" /> + + @@ -118,6 +149,7 @@ Text="Could not resolve this reference. Could not locate the package or project for "%(Reference.Identity)"" /> + <_TargetFramework Remove="@(_TargetFramework)" /> diff --git a/src/AuthSamples/build/dependencies.props b/src/AuthSamples/build/dependencies.props index 83cb185cd8..8385592af4 100644 --- a/src/AuthSamples/build/dependencies.props +++ b/src/AuthSamples/build/dependencies.props @@ -54,8 +54,6 @@ 2.2.0-preview3-35425 2.2.0-preview3-35425 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 3.14.2 5.2.0 2.0.9 diff --git a/src/AzureIntegration/Directory.Build.props b/src/AzureIntegration/Directory.Build.props index cf33763ee4..f1021ec41a 100644 --- a/src/AzureIntegration/Directory.Build.props +++ b/src/AzureIntegration/Directory.Build.props @@ -15,6 +15,7 @@ $(MSBuildThisFileDirectory)..\..\eng\AspNetCore.snk true true + $(MSBuildThisFileDirectory)..\Shared\ diff --git a/src/AzureIntegration/build/dependencies.props b/src/AzureIntegration/build/dependencies.props index 4825e01b5e..78f9cabac4 100644 --- a/src/AzureIntegration/build/dependencies.props +++ b/src/AzureIntegration/build/dependencies.props @@ -1,51 +1,50 @@ - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - 2.2.0-preview2-20181004.6 - 2.2.0-preview2-20181004.6 - 2.1.1 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.1.0-preview2-30187 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 0.6.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 1.1.3 - 1.0.1 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.0.9 - 2.1.3 - 2.2.0-preview3-27001-02 - 2.2.0-preview3-35425 - 15.6.1 - 1.4.0 - 4.7.49 - 2.0.3 - 11.0.2 - 1.6.0 - 8.1.4 - 2.3.1 - 2.4.0 - - - - + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + 2.2.0-preview2-20181004.6 + 2.2.0-preview2-20181004.6 + 2.1.1 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.1.0-preview2-30187 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 0.6.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 1.1.3 + 1.0.1 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.2.0-preview3-35425 + 2.0.9 + 2.1.3 + 2.2.0-preview3-27001-02 + 2.2.0-preview3-35425 + 15.6.1 + 1.4.0 + 4.7.49 + 2.0.3 + 11.0.2 + 1.6.0 + 8.1.4 + 2.3.1 + 2.4.0 + + + + diff --git a/src/AzureIntegration/test/Microsoft.AspNetCore.AzureAppServices.FunctionalTests/Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj b/src/AzureIntegration/test/Microsoft.AspNetCore.AzureAppServices.FunctionalTests/Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj index 6bf10bc884..dd2aa41bb2 100644 --- a/src/AzureIntegration/test/Microsoft.AspNetCore.AzureAppServices.FunctionalTests/Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj +++ b/src/AzureIntegration/test/Microsoft.AspNetCore.AzureAppServices.FunctionalTests/Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj @@ -9,13 +9,13 @@ + - diff --git a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj index 021b3fde2c..e1c44678f6 100644 --- a/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj +++ b/src/DataProtection/Abstractions/src/Microsoft.AspNetCore.DataProtection.Abstractions.csproj @@ -12,10 +12,7 @@ Microsoft.AspNetCore.DataProtection.IDataProtector - - - - + diff --git a/src/DefaultBuilder/DefaultBuilder.sln b/src/DefaultBuilder/DefaultBuilder.sln new file mode 100644 index 0000000000..30d7765447 --- /dev/null +++ b/src/DefaultBuilder/DefaultBuilder.sln @@ -0,0 +1,205 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{C19108F8-667B-4CF9-B227-CDD2290224BC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Tests", "test\Microsoft.AspNetCore.Tests\Microsoft.AspNetCore.Tests.csproj", "{1CD49F15-D381-4C7E-8E12-A85E7753B110}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.FunctionalTests", "test\Microsoft.AspNetCore.FunctionalTests\Microsoft.AspNetCore.FunctionalTests.csproj", "{766C394B-ABBB-4624-A071-C806C0A2CD3E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "testassets\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{BE8D7353-692B-4B5B-ADFD-32632AE758E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderOfTApp", "testassets\CreateDefaultBuilderOfTApp\CreateDefaultBuilderOfTApp.csproj", "{AE1F0124-996E-476A-9331-FB789F3D0577}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjectionApp", "testassets\DependencyInjectionApp\DependencyInjectionApp.csproj", "{03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRequestDelegateUrlApp", "testassets\StartRequestDelegateUrlApp\StartRequestDelegateUrlApp.csproj", "{4B69520E-CB30-4B20-BCA7-9378EAC322A6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRouteBuilderUrlApp", "testassets\StartRouteBuilderUrlApp\StartRouteBuilderUrlApp.csproj", "{AC847245-BFC3-4BEB-915C-FCD932359A5F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartWithIApplicationBuilderUrlApp", "testassets\StartWithIApplicationBuilderUrlApp\StartWithIApplicationBuilderUrlApp.csproj", "{92F27C24-44CA-4C2B-867E-1A4D776B03E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore", "src\Microsoft.AspNetCore.csproj", "{BEB88AF7-67EB-4754-A5CD-89C0388974C9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_dependencies", "_dependencies", "{31D4AC03-410F-476C-A0C8-E9E9490289B1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.IntegrationTesting", "..\Hosting\Server.IntegrationTesting\src\Microsoft.AspNetCore.Server.IntegrationTesting.csproj", "{BF5885C7-F975-4652-9C11-093781FC16C5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.StaticFiles", "..\Middleware\StaticFiles\src\Microsoft.AspNetCore.StaticFiles.csproj", "{D46E4E5D-0846-4574-A8BA-30D040A6254D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|x64.Build.0 = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Debug|x86.Build.0 = Debug|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|Any CPU.Build.0 = Release|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|x64.ActiveCfg = Release|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|x64.Build.0 = Release|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|x86.ActiveCfg = Release|Any CPU + {C19108F8-667B-4CF9-B227-CDD2290224BC}.Release|x86.Build.0 = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|x64.ActiveCfg = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|x64.Build.0 = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|x86.ActiveCfg = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Debug|x86.Build.0 = Debug|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|Any CPU.Build.0 = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|x64.ActiveCfg = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|x64.Build.0 = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|x86.ActiveCfg = Release|Any CPU + {1CD49F15-D381-4C7E-8E12-A85E7753B110}.Release|x86.Build.0 = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|x64.ActiveCfg = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|x64.Build.0 = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|x86.ActiveCfg = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Debug|x86.Build.0 = Debug|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|Any CPU.Build.0 = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x64.ActiveCfg = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x64.Build.0 = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x86.ActiveCfg = Release|Any CPU + {766C394B-ABBB-4624-A071-C806C0A2CD3E}.Release|x86.Build.0 = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x64.Build.0 = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Debug|x86.Build.0 = Debug|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|Any CPU.Build.0 = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.ActiveCfg = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x64.Build.0 = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.ActiveCfg = Release|Any CPU + {BE8D7353-692B-4B5B-ADFD-32632AE758E3}.Release|x86.Build.0 = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|x64.ActiveCfg = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|x64.Build.0 = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|x86.ActiveCfg = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Debug|x86.Build.0 = Debug|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|Any CPU.Build.0 = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|x64.ActiveCfg = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|x64.Build.0 = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|x86.ActiveCfg = Release|Any CPU + {AE1F0124-996E-476A-9331-FB789F3D0577}.Release|x86.Build.0 = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|x64.ActiveCfg = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|x64.Build.0 = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|x86.ActiveCfg = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Debug|x86.Build.0 = Debug|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|Any CPU.Build.0 = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|x64.ActiveCfg = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|x64.Build.0 = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|x86.ActiveCfg = Release|Any CPU + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6}.Release|x86.Build.0 = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|x64.ActiveCfg = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|x64.Build.0 = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Debug|x86.Build.0 = Debug|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|Any CPU.Build.0 = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|x64.ActiveCfg = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|x64.Build.0 = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|x86.ActiveCfg = Release|Any CPU + {4B69520E-CB30-4B20-BCA7-9378EAC322A6}.Release|x86.Build.0 = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|x64.ActiveCfg = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|x64.Build.0 = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|x86.ActiveCfg = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Debug|x86.Build.0 = Debug|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|Any CPU.Build.0 = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|x64.ActiveCfg = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|x64.Build.0 = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|x86.ActiveCfg = Release|Any CPU + {AC847245-BFC3-4BEB-915C-FCD932359A5F}.Release|x86.Build.0 = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|x64.Build.0 = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Debug|x86.Build.0 = Debug|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|Any CPU.Build.0 = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|x64.ActiveCfg = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|x64.Build.0 = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|x86.ActiveCfg = Release|Any CPU + {92F27C24-44CA-4C2B-867E-1A4D776B03E0}.Release|x86.Build.0 = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|x64.ActiveCfg = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|x64.Build.0 = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Debug|x86.Build.0 = Debug|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|Any CPU.Build.0 = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|x64.ActiveCfg = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|x64.Build.0 = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|x86.ActiveCfg = Release|Any CPU + {BEB88AF7-67EB-4754-A5CD-89C0388974C9}.Release|x86.Build.0 = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|x64.Build.0 = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Debug|x86.Build.0 = Debug|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|Any CPU.Build.0 = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|x64.ActiveCfg = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|x64.Build.0 = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|x86.ActiveCfg = Release|Any CPU + {BF5885C7-F975-4652-9C11-093781FC16C5}.Release|x86.Build.0 = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|x64.Build.0 = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Debug|x86.Build.0 = Debug|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|Any CPU.Build.0 = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|x64.ActiveCfg = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|x64.Build.0 = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|x86.ActiveCfg = Release|Any CPU + {D46E4E5D-0846-4574-A8BA-30D040A6254D}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BE8D7353-692B-4B5B-ADFD-32632AE758E3} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {AE1F0124-996E-476A-9331-FB789F3D0577} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {03C1BD3F-6F6D-4D8F-9BD9-205D059723B6} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {4B69520E-CB30-4B20-BCA7-9378EAC322A6} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {AC847245-BFC3-4BEB-915C-FCD932359A5F} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {92F27C24-44CA-4C2B-867E-1A4D776B03E0} = {3C38AE63-E4F4-4BCA-97C1-F0A96A06BA88} + {BF5885C7-F975-4652-9C11-093781FC16C5} = {31D4AC03-410F-476C-A0C8-E9E9490289B1} + {D46E4E5D-0846-4574-A8BA-30D040A6254D} = {31D4AC03-410F-476C-A0C8-E9E9490289B1} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3A0191FF-7538-4406-BD4A-4883C0E1B72C} + EndGlobalSection +EndGlobal diff --git a/src/MetaPackages/build/Key.snk b/src/DefaultBuilder/build/Key.snk similarity index 100% rename from src/MetaPackages/build/Key.snk rename to src/DefaultBuilder/build/Key.snk diff --git a/src/MetaPackages/samples/SampleApp/Program.cs b/src/DefaultBuilder/samples/SampleApp/Program.cs similarity index 100% rename from src/MetaPackages/samples/SampleApp/Program.cs rename to src/DefaultBuilder/samples/SampleApp/Program.cs diff --git a/src/MetaPackages/samples/SampleApp/SampleApp.csproj b/src/DefaultBuilder/samples/SampleApp/SampleApp.csproj similarity index 52% rename from src/MetaPackages/samples/SampleApp/SampleApp.csproj rename to src/DefaultBuilder/samples/SampleApp/SampleApp.csproj index 64ab5e297e..682f5ae029 100644 --- a/src/MetaPackages/samples/SampleApp/SampleApp.csproj +++ b/src/DefaultBuilder/samples/SampleApp/SampleApp.csproj @@ -1,4 +1,4 @@ - + netcoreapp2.2;net461 @@ -10,7 +10,7 @@ - - + + diff --git a/src/MetaPackages/samples/SampleApp/Startup.cs b/src/DefaultBuilder/samples/SampleApp/Startup.cs similarity index 100% rename from src/MetaPackages/samples/SampleApp/Startup.cs rename to src/DefaultBuilder/samples/SampleApp/Startup.cs diff --git a/src/MetaPackages/samples/SampleApp/appsettings.json b/src/DefaultBuilder/samples/SampleApp/appsettings.json similarity index 100% rename from src/MetaPackages/samples/SampleApp/appsettings.json rename to src/DefaultBuilder/samples/SampleApp/appsettings.json diff --git a/src/MetaPackages/samples/SampleApp/wwwroot/htmlpage.html b/src/DefaultBuilder/samples/SampleApp/wwwroot/htmlpage.html similarity index 100% rename from src/MetaPackages/samples/SampleApp/wwwroot/htmlpage.html rename to src/DefaultBuilder/samples/SampleApp/wwwroot/htmlpage.html diff --git a/src/MetaPackages/src/Microsoft.AspNetCore/HostFilteringStartupFilter.cs b/src/DefaultBuilder/src/HostFilteringStartupFilter.cs similarity index 100% rename from src/MetaPackages/src/Microsoft.AspNetCore/HostFilteringStartupFilter.cs rename to src/DefaultBuilder/src/HostFilteringStartupFilter.cs diff --git a/src/DefaultBuilder/src/Microsoft.AspNetCore.csproj b/src/DefaultBuilder/src/Microsoft.AspNetCore.csproj new file mode 100644 index 0000000000..6fd6d7b704 --- /dev/null +++ b/src/DefaultBuilder/src/Microsoft.AspNetCore.csproj @@ -0,0 +1,32 @@ + + + + netstandard2.0 + aspnetcore + Microsoft.AspNetCore + true + false + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MetaPackages/src/Microsoft.AspNetCore/WebHost.cs b/src/DefaultBuilder/src/WebHost.cs similarity index 100% rename from src/MetaPackages/src/Microsoft.AspNetCore/WebHost.cs rename to src/DefaultBuilder/src/WebHost.cs diff --git a/src/MetaPackages/src/Microsoft.AspNetCore/baseline.netcore.json b/src/DefaultBuilder/src/baseline.netcore.json similarity index 100% rename from src/MetaPackages/src/Microsoft.AspNetCore/baseline.netcore.json rename to src/DefaultBuilder/src/baseline.netcore.json diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj new file mode 100644 index 0000000000..a6bf62fe99 --- /dev/null +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj @@ -0,0 +1,22 @@ + + + + $(StandardTestTfms) + + + true + + + + + + + + + + + + diff --git a/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs similarity index 99% rename from src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs rename to src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs index d2e71f5778..68ffd317a6 100644 --- a/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/WebHostFunctionalTests.cs @@ -240,10 +240,10 @@ namespace Microsoft.AspNetCore.Tests var directoryInfo = new DirectoryInfo(applicationBasePath); do { - var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "MetaPackages.sln")); + var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, "DefaultBuilder.sln")); if (solutionFileInfo.Exists) { - return Path.GetFullPath(Path.Combine(directoryInfo.FullName, "test", "TestSites")); + return Path.GetFullPath(Path.Combine(directoryInfo.FullName, "testassets")); } directoryInfo = directoryInfo.Parent; diff --git a/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx b/src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx similarity index 100% rename from src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx rename to src/DefaultBuilder/test/Microsoft.AspNetCore.FunctionalTests/testCert.pfx diff --git a/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj new file mode 100644 index 0000000000..9fe73037b0 --- /dev/null +++ b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj @@ -0,0 +1,11 @@ + + + + $(StandardTestTfms) + + + + + + + diff --git a/src/MetaPackages/test/Microsoft.AspNetCore.Tests/WebHostTests.cs b/src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs similarity index 100% rename from src/MetaPackages/test/Microsoft.AspNetCore.Tests/WebHostTests.cs rename to src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/WebHostTests.cs diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj b/src/DefaultBuilder/testassets/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj similarity index 74% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj rename to src/DefaultBuilder/testassets/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj index 9fc88e0995..1d4a68ab59 100644 --- a/src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj +++ b/src/DefaultBuilder/testassets/CreateDefaultBuilderApp/CreateDefaultBuilderApp.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/Program.cs b/src/DefaultBuilder/testassets/CreateDefaultBuilderApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/Program.cs rename to src/DefaultBuilder/testassets/CreateDefaultBuilderApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/appsettings.Development.json b/src/DefaultBuilder/testassets/CreateDefaultBuilderApp/appsettings.Development.json similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/appsettings.Development.json rename to src/DefaultBuilder/testassets/CreateDefaultBuilderApp/appsettings.Development.json diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/appsettings.json b/src/DefaultBuilder/testassets/CreateDefaultBuilderApp/appsettings.json similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderApp/appsettings.json rename to src/DefaultBuilder/testassets/CreateDefaultBuilderApp/appsettings.json diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj b/src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj similarity index 74% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj rename to src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj index beca85599c..0ff43da236 100644 --- a/src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj +++ b/src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/CreateDefaultBuilderOfTApp.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/Program.cs b/src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/Program.cs rename to src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/appsettings.Development.json b/src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/appsettings.Development.json similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/appsettings.Development.json rename to src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/appsettings.Development.json diff --git a/src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/appsettings.json b/src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/appsettings.json similarity index 100% rename from src/MetaPackages/test/TestSites/CreateDefaultBuilderOfTApp/appsettings.json rename to src/DefaultBuilder/testassets/CreateDefaultBuilderOfTApp/appsettings.json diff --git a/src/MetaPackages/test/TestSites/DependencyInjectionApp/DependencyInjectionApp.csproj b/src/DefaultBuilder/testassets/DependencyInjectionApp/DependencyInjectionApp.csproj similarity index 67% rename from src/MetaPackages/test/TestSites/DependencyInjectionApp/DependencyInjectionApp.csproj rename to src/DefaultBuilder/testassets/DependencyInjectionApp/DependencyInjectionApp.csproj index f52140e4d0..ad14446c68 100644 --- a/src/MetaPackages/test/TestSites/DependencyInjectionApp/DependencyInjectionApp.csproj +++ b/src/DefaultBuilder/testassets/DependencyInjectionApp/DependencyInjectionApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/MetaPackages/test/TestSites/DependencyInjectionApp/Program.cs b/src/DefaultBuilder/testassets/DependencyInjectionApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/DependencyInjectionApp/Program.cs rename to src/DefaultBuilder/testassets/DependencyInjectionApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/StartRequestDelegateUrlApp/Program.cs b/src/DefaultBuilder/testassets/StartRequestDelegateUrlApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/StartRequestDelegateUrlApp/Program.cs rename to src/DefaultBuilder/testassets/StartRequestDelegateUrlApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj b/src/DefaultBuilder/testassets/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj similarity index 67% rename from src/MetaPackages/test/TestSites/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj rename to src/DefaultBuilder/testassets/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj index f52140e4d0..ad14446c68 100644 --- a/src/MetaPackages/test/TestSites/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj +++ b/src/DefaultBuilder/testassets/StartRequestDelegateUrlApp/StartRequestDelegateUrlApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/MetaPackages/test/TestSites/StartRouteBuilderUrlApp/Program.cs b/src/DefaultBuilder/testassets/StartRouteBuilderUrlApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/StartRouteBuilderUrlApp/Program.cs rename to src/DefaultBuilder/testassets/StartRouteBuilderUrlApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj b/src/DefaultBuilder/testassets/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj similarity index 67% rename from src/MetaPackages/test/TestSites/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj rename to src/DefaultBuilder/testassets/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj index f52140e4d0..ad14446c68 100644 --- a/src/MetaPackages/test/TestSites/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj +++ b/src/DefaultBuilder/testassets/StartRouteBuilderUrlApp/StartRouteBuilderUrlApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/MetaPackages/test/TestSites/StartWithIApplicationBuilderUrlApp/Program.cs b/src/DefaultBuilder/testassets/StartWithIApplicationBuilderUrlApp/Program.cs similarity index 100% rename from src/MetaPackages/test/TestSites/StartWithIApplicationBuilderUrlApp/Program.cs rename to src/DefaultBuilder/testassets/StartWithIApplicationBuilderUrlApp/Program.cs diff --git a/src/MetaPackages/test/TestSites/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj b/src/DefaultBuilder/testassets/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj similarity index 67% rename from src/MetaPackages/test/TestSites/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj rename to src/DefaultBuilder/testassets/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj index f52140e4d0..ad14446c68 100644 --- a/src/MetaPackages/test/TestSites/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj +++ b/src/DefaultBuilder/testassets/StartWithIApplicationBuilderUrlApp/StartWithIApplicationBuilderUrlApp.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj index 3708e92927..f6f11fd3b3 100644 --- a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj +++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj @@ -8,10 +8,13 @@ aspnetcore;json;jsonpatch + + + + - diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj index 627b36bbc2..fbefc75eef 100644 --- a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj +++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj @@ -8,6 +8,11 @@ aspnetcore;hosting + + + + + @@ -20,8 +25,6 @@ - - diff --git a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj index 50970c8d86..1fa4ce9d7a 100644 --- a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj +++ b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj @@ -17,10 +17,8 @@ + - - - @@ -31,7 +29,6 @@ - diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj index 4344d0ae8e..e9d04e1ebc 100644 --- a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj +++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj @@ -9,10 +9,13 @@ aspnetcore + + + + - diff --git a/src/Http/Routing.Abstractions/src/Microsoft.AspNetCore.Routing.Abstractions.csproj b/src/Http/Routing.Abstractions/src/Microsoft.AspNetCore.Routing.Abstractions.csproj index edb292bb22..ff154fe535 100644 --- a/src/Http/Routing.Abstractions/src/Microsoft.AspNetCore.Routing.Abstractions.csproj +++ b/src/Http/Routing.Abstractions/src/Microsoft.AspNetCore.Routing.Abstractions.csproj @@ -11,8 +11,11 @@ Microsoft.AspNetCore.Routing.RouteData aspnetcore;routing + + + + - diff --git a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj index 1e297bdf93..99717d310b 100644 --- a/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj +++ b/src/Http/Routing/src/Microsoft.AspNetCore.Routing.csproj @@ -24,6 +24,10 @@ Microsoft.AspNetCore.Routing.RouteCollection IL_EMIT_SAVE_ASSEMBLIES;$(DefineConstants) + + + + @@ -31,7 +35,6 @@ Microsoft.AspNetCore.Routing.RouteCollection - diff --git a/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj b/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj index 3c7d2d8255..fdc9592cc8 100644 --- a/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj +++ b/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj @@ -10,7 +10,10 @@ - + + + + diff --git a/src/Identity/Directory.Build.props b/src/Identity/Directory.Build.props index 1c9369f941..5a4e9d7eef 100644 --- a/src/Identity/Directory.Build.props +++ b/src/Identity/Directory.Build.props @@ -17,6 +17,7 @@ true false + $(MSBuildThisFileDirectory)..\Shared\ diff --git a/src/Identity/build/dependencies.props b/src/Identity/build/dependencies.props index e9b3e6f8bc..de5fbc39ce 100644 --- a/src/Identity/build/dependencies.props +++ b/src/Identity/build/dependencies.props @@ -58,8 +58,6 @@ 2.2.0-preview3-35425 2.2.0-preview3-35425 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 2.0.9 2.1.3 2.2.0-preview3-27001-02 diff --git a/src/MetaPackages/.gitignore b/src/MetaPackages/.gitignore deleted file mode 100644 index 3af0091ea3..0000000000 --- a/src/MetaPackages/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -[Oo]bj/ -[Bb]in/ -TestResults/ -.nuget/ -_ReSharper.*/ -packages/ -artifacts/ -PublishProfiles/ -*.user -*.suo -*.cache -*.docstates -_ReSharper.* -nuget.exe -*net45.csproj -*net451.csproj -*k10.csproj -*.psess -*.vsp -*.pidb -*.userprefs -*DS_Store -*.ncrunchsolution -*.*sdf -*.ipch -*.sln.ide -project.lock.json -.vs -.vscode/ -.build/ -.testPublish/ -global.json - -# Dependencies from pre-requisite builds -.deps/ -.rw/ -.ro/ diff --git a/src/MetaPackages/Directory.Build.props b/src/MetaPackages/Directory.Build.props deleted file mode 100644 index 7c614d7224..0000000000 --- a/src/MetaPackages/Directory.Build.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - Microsoft ASP.NET Core - https://github.com/aspnet/AspnetCore - git - $(MSBuildThisFileDirectory) - false - $(MSBuildThisFileDirectory)..\..\eng\AspNetCore.snk - true - true - - diff --git a/src/MetaPackages/Directory.Build.targets b/src/MetaPackages/Directory.Build.targets deleted file mode 100644 index 78626b773e..0000000000 --- a/src/MetaPackages/Directory.Build.targets +++ /dev/null @@ -1,10 +0,0 @@ - - - $(MicrosoftNETCoreApp20PackageVersion) - $(MicrosoftNETCoreApp21PackageVersion) - $(MicrosoftNETCoreApp22PackageVersion) - $(NETStandardLibrary20PackageVersion) - - 99.9 - - diff --git a/src/MetaPackages/MetaPackages.sln b/src/MetaPackages/MetaPackages.sln deleted file mode 100644 index 659427dfcb..0000000000 --- a/src/MetaPackages/MetaPackages.sln +++ /dev/null @@ -1,116 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27005.2 -MinimumVisualStudioVersion = 15.0.26730.03 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED834E68-51C3-4ADE-ACC8-6BA6D4207C09}" - ProjectSection(SolutionItems) = preProject - src\Directory.Build.props = src\Directory.Build.props - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore", "src\Microsoft.AspNetCore\Microsoft.AspNetCore.csproj", "{6F3D43F7-9546-4B41-AF04-CF4708B62051}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{97D53BEB-A511-4FBE-B784-AB407D9A219F}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - Directory.Build.targets = Directory.Build.targets - NuGet.config = NuGet.config - version.xml = version.xml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{F92CB7A1-C38E-408C-A7EC-A5C040D041E1}" - ProjectSection(SolutionItems) = preProject - build\dependencies.props = build\dependencies.props - build\repo.targets = build\repo.targets - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{192F583C-C4CA-43E5-B31C-D21B7806E274}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{AF5BB04E-92F7-4737-8B98-F86F6244FAB2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.FunctionalTests", "test\Microsoft.AspNetCore.FunctionalTests\Microsoft.AspNetCore.FunctionalTests.csproj", "{C72A756A-D29D-44C7-83D4-821DBE82DBCA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestSites", "TestSites", "{EC22261D-0DE1-47DE-8F7C-072675D6F5B4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRouteBuilderUrlApp", "test\TestSites\StartRouteBuilderUrlApp\StartRouteBuilderUrlApp.csproj", "{AB42054B-1801-4FEE-B5C3-8529C6D7BFDA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartWithIApplicationBuilderUrlApp", "test\TestSites\StartWithIApplicationBuilderUrlApp\StartWithIApplicationBuilderUrlApp.csproj", "{3A85FA52-F601-422E-A42E-9F187DB28492}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StartRequestDelegateUrlApp", "test\TestSites\StartRequestDelegateUrlApp\StartRequestDelegateUrlApp.csproj", "{401C741B-6C7C-4E08-9F09-C3D43D22C0DE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CreateDefaultBuilderApp", "test\TestSites\CreateDefaultBuilderApp\CreateDefaultBuilderApp.csproj", "{79CF58CE-B020-45D8-BDB5-2D8036BEAD14}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DependencyInjectionApp", "test\TestSites\DependencyInjectionApp\DependencyInjectionApp.csproj", "{65FE2E38-4529-4C93-A7B0-CF12DD7A70C3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreateDefaultBuilderOfTApp", "test\TestSites\CreateDefaultBuilderOfTApp\CreateDefaultBuilderOfTApp.csproj", "{A922B5AC-836B-44F4-83F1-3CB9EB08A3F8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Tests", "test\Microsoft.AspNetCore.Tests\Microsoft.AspNetCore.Tests.csproj", "{BD08F027-3BB9-427B-9367-19534B7376B3}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6F3D43F7-9546-4B41-AF04-CF4708B62051}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F3D43F7-9546-4B41-AF04-CF4708B62051}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F3D43F7-9546-4B41-AF04-CF4708B62051}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F3D43F7-9546-4B41-AF04-CF4708B62051}.Release|Any CPU.Build.0 = Release|Any CPU - {AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF5BB04E-92F7-4737-8B98-F86F6244FAB2}.Release|Any CPU.Build.0 = Release|Any CPU - {C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C72A756A-D29D-44C7-83D4-821DBE82DBCA}.Release|Any CPU.Build.0 = Release|Any CPU - {AB42054B-1801-4FEE-B5C3-8529C6D7BFDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB42054B-1801-4FEE-B5C3-8529C6D7BFDA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB42054B-1801-4FEE-B5C3-8529C6D7BFDA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB42054B-1801-4FEE-B5C3-8529C6D7BFDA}.Release|Any CPU.Build.0 = Release|Any CPU - {3A85FA52-F601-422E-A42E-9F187DB28492}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A85FA52-F601-422E-A42E-9F187DB28492}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A85FA52-F601-422E-A42E-9F187DB28492}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A85FA52-F601-422E-A42E-9F187DB28492}.Release|Any CPU.Build.0 = Release|Any CPU - {401C741B-6C7C-4E08-9F09-C3D43D22C0DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {401C741B-6C7C-4E08-9F09-C3D43D22C0DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {401C741B-6C7C-4E08-9F09-C3D43D22C0DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {401C741B-6C7C-4E08-9F09-C3D43D22C0DE}.Release|Any CPU.Build.0 = Release|Any CPU - {79CF58CE-B020-45D8-BDB5-2D8036BEAD14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {79CF58CE-B020-45D8-BDB5-2D8036BEAD14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79CF58CE-B020-45D8-BDB5-2D8036BEAD14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {79CF58CE-B020-45D8-BDB5-2D8036BEAD14}.Release|Any CPU.Build.0 = Release|Any CPU - {65FE2E38-4529-4C93-A7B0-CF12DD7A70C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {65FE2E38-4529-4C93-A7B0-CF12DD7A70C3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {65FE2E38-4529-4C93-A7B0-CF12DD7A70C3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {65FE2E38-4529-4C93-A7B0-CF12DD7A70C3}.Release|Any CPU.Build.0 = Release|Any CPU - {A922B5AC-836B-44F4-83F1-3CB9EB08A3F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A922B5AC-836B-44F4-83F1-3CB9EB08A3F8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A922B5AC-836B-44F4-83F1-3CB9EB08A3F8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A922B5AC-836B-44F4-83F1-3CB9EB08A3F8}.Release|Any CPU.Build.0 = Release|Any CPU - {BD08F027-3BB9-427B-9367-19534B7376B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BD08F027-3BB9-427B-9367-19534B7376B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BD08F027-3BB9-427B-9367-19534B7376B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BD08F027-3BB9-427B-9367-19534B7376B3}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {6F3D43F7-9546-4B41-AF04-CF4708B62051} = {ED834E68-51C3-4ADE-ACC8-6BA6D4207C09} - {AF5BB04E-92F7-4737-8B98-F86F6244FAB2} = {192F583C-C4CA-43E5-B31C-D21B7806E274} - {C72A756A-D29D-44C7-83D4-821DBE82DBCA} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C} - {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C} - {AB42054B-1801-4FEE-B5C3-8529C6D7BFDA} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {3A85FA52-F601-422E-A42E-9F187DB28492} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {401C741B-6C7C-4E08-9F09-C3D43D22C0DE} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {79CF58CE-B020-45D8-BDB5-2D8036BEAD14} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {65FE2E38-4529-4C93-A7B0-CF12DD7A70C3} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {A922B5AC-836B-44F4-83F1-3CB9EB08A3F8} = {EC22261D-0DE1-47DE-8F7C-072675D6F5B4} - {BD08F027-3BB9-427B-9367-19534B7376B3} = {9E49B5B9-9E72-42FB-B684-90CA1B1BCF9C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {A666E9B0-125B-4975-B35B-09A6D68A5047} - EndGlobalSection -EndGlobal diff --git a/src/MetaPackages/NuGetPackageVerifier.json b/src/MetaPackages/NuGetPackageVerifier.json deleted file mode 100644 index b153ab1515..0000000000 --- a/src/MetaPackages/NuGetPackageVerifier.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "Default": { - "rules": [ - "DefaultCompositeRule" - ] - } -} \ No newline at end of file diff --git a/src/MetaPackages/README.md b/src/MetaPackages/README.md deleted file mode 100644 index d35e5f0866..0000000000 --- a/src/MetaPackages/README.md +++ /dev/null @@ -1,7 +0,0 @@ -Meta packages -======== - -This repo contains NuGet meta packages that help quickly reference sets of common packages. - -This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. - diff --git a/src/MetaPackages/build/dependencies.props b/src/MetaPackages/build/dependencies.props deleted file mode 100644 index 8d12964ca3..0000000000 --- a/src/MetaPackages/build/dependencies.props +++ /dev/null @@ -1,41 +0,0 @@ - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - - 2.2.0-preview2-20181004.6 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 0.6.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.2.0-preview3-35425 - 2.0.9 - 2.1.3 - 2.2.0-preview3-27001-02 - 15.6.1 - 4.7.49 - 2.0.3 - 2.3.1 - 2.4.0 - - - - diff --git a/src/MetaPackages/build/repo.props b/src/MetaPackages/build/repo.props deleted file mode 100644 index e3e0fa123b..0000000000 --- a/src/MetaPackages/build/repo.props +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/MetaPackages/build/sources.props b/src/MetaPackages/build/sources.props deleted file mode 100644 index 9215df9751..0000000000 --- a/src/MetaPackages/build/sources.props +++ /dev/null @@ -1,17 +0,0 @@ - - - - - $(DotNetRestoreSources) - - $(RestoreSources); - https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; - https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; - https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; - - - $(RestoreSources); - https://api.nuget.org/v3/index.json; - - - diff --git a/src/MetaPackages/samples/SampleApp/Properties/launchSettings.json b/src/MetaPackages/samples/SampleApp/Properties/launchSettings.json deleted file mode 100644 index f0370cf22b..0000000000 --- a/src/MetaPackages/samples/SampleApp/Properties/launchSettings.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:53432/", - "sslPort": 0 - } - }, - "profiles": { - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "SampleApp": { - "commandName": "Project", - "launchBrowser": true, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "applicationUrl": "http://localhost:53433" - } - } -} diff --git a/src/MetaPackages/src/Directory.Build.props b/src/MetaPackages/src/Directory.Build.props deleted file mode 100644 index 410f24daa9..0000000000 --- a/src/MetaPackages/src/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/MetaPackages/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj b/src/MetaPackages/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj deleted file mode 100644 index ea9a676d87..0000000000 --- a/src/MetaPackages/src/Microsoft.AspNetCore/Microsoft.AspNetCore.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - netstandard2.0 - aspnetcore - Microsoft.AspNetCore - true - false - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/MetaPackages/test/Directory.Build.props b/src/MetaPackages/test/Directory.Build.props deleted file mode 100644 index b8557e9f98..0000000000 --- a/src/MetaPackages/test/Directory.Build.props +++ /dev/null @@ -1,14 +0,0 @@ - - - - - netcoreapp2.2 - $(DeveloperBuildTestTfms) - - $(StandardTestTfms);net461 - - - - - - diff --git a/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj b/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj deleted file mode 100644 index 67422e13d6..0000000000 --- a/src/MetaPackages/test/Microsoft.AspNetCore.FunctionalTests/Microsoft.AspNetCore.FunctionalTests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - $(StandardTestTfms) - - - true - - - - - - - - - - - - - - - - - - - diff --git a/src/MetaPackages/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj b/src/MetaPackages/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj deleted file mode 100644 index 1b8156a1ca..0000000000 --- a/src/MetaPackages/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - $(StandardTestTfms) - - - - - - - - - - - - - - diff --git a/src/MetaPackages/version.props b/src/MetaPackages/version.props deleted file mode 100644 index 9b41b556d7..0000000000 --- a/src/MetaPackages/version.props +++ /dev/null @@ -1,12 +0,0 @@ - - - 2.2.0 - rtm - $(VersionPrefix) - $(VersionPrefix)-$(VersionSuffix)-final - t000 - a- - $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) - $(VersionSuffix)-$(BuildNumber) - - diff --git a/src/Middleware/Diagnostics.EntityFrameworkCore/src/Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj b/src/Middleware/Diagnostics.EntityFrameworkCore/src/Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj index f418a50148..23e9b782f1 100644 --- a/src/Middleware/Diagnostics.EntityFrameworkCore/src/Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj +++ b/src/Middleware/Diagnostics.EntityFrameworkCore/src/Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.csproj @@ -10,12 +10,12 @@ + - diff --git a/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj b/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj index a5ae405dcd..9bdc1cea77 100644 --- a/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj +++ b/src/Middleware/Diagnostics/src/Microsoft.AspNetCore.Diagnostics.csproj @@ -10,6 +10,8 @@ + + @@ -20,8 +22,6 @@ - - diff --git a/src/Mvc/Directory.Build.props b/src/Mvc/Directory.Build.props index 6c951ddaca..83f51a1fe7 100644 --- a/src/Mvc/Directory.Build.props +++ b/src/Mvc/Directory.Build.props @@ -16,6 +16,7 @@ true true true + $(MSBuildThisFileDirectory)..\Shared\ diff --git a/src/Mvc/build/dependencies.props b/src/Mvc/build/dependencies.props index 063add026b..89f7f61250 100644 --- a/src/Mvc/build/dependencies.props +++ b/src/Mvc/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -64,11 +64,9 @@ 2.2.0-rtm-35519 1.7.0 2.2.0-rtm-35519 - 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 - 2.2.0-rtm-35519 2.2.0-rtm-35519 2.1.0 2.2.0-rtm-35519 @@ -83,16 +81,11 @@ 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 - 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 - 2.2.0-rtm-35519 - 2.2.0-rtm-35519 - 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 - 2.2.0-rtm-35519 2.0.9 2.1.3 2.2.0-preview3-27014-02 diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj index 34f0f5e6a1..1b3f5091ad 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -10,13 +10,16 @@ Microsoft.AspNetCore.Mvc.IActionResult aspnetcore;aspnetcoremvc + + + + - diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj index 7fe1a42493..161671b41e 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj @@ -17,7 +17,12 @@ Microsoft.AspNetCore.Mvc.RouteAttribute - + + + + + + @@ -41,12 +46,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute - - - - - diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj index 45749b70ec..b203286a9b 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj @@ -8,10 +8,13 @@ aspnetcore;aspnetcoremvc;json + + + + - diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj index 72cb71f8c2..f6b6300835 100644 --- a/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj @@ -10,13 +10,16 @@ System.Web.Http.ApiController aspnetcore;aspnetcoremvc;aspnetwebapi + + + + - diff --git a/src/Razor/build/dependencies.props b/src/Razor/build/dependencies.props index b76ea87d30..683fa6e686 100644 --- a/src/Razor/build/dependencies.props +++ b/src/Razor/build/dependencies.props @@ -14,7 +14,6 @@ 2.8.0 2.8.0 2.2.0-rtm-181106-13 - 2.2.0-rtm-181106-13 2.1.0 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 @@ -62,7 +61,6 @@ 2.3.1 2.4.0 - - + diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.Runtime/Microsoft.AspNetCore.Razor.Runtime.csproj b/src/Razor/src/Microsoft.AspNetCore.Razor.Runtime/Microsoft.AspNetCore.Razor.Runtime.csproj index b58a7f9c3f..40cb287307 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.Runtime/Microsoft.AspNetCore.Razor.Runtime.csproj +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.Runtime/Microsoft.AspNetCore.Razor.Runtime.csproj @@ -6,13 +6,16 @@ $(PackageTags);taghelper;taghelpers + + + + - diff --git a/src/Security/build/dependencies.props b/src/Security/build/dependencies.props index 0d1a80ebb6..a732028250 100644 --- a/src/Security/build/dependencies.props +++ b/src/Security/build/dependencies.props @@ -28,7 +28,6 @@ 2.2.0-preview3-35425 2.2.0-preview3-35425 2.2.0-preview3-35425 - 2.2.0-preview3-35425 2.2.0-preview3-35425 3.14.2 5.2.0 diff --git a/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj b/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj index 7e3ce4eb39..ce5b4c6f93 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authentication/Microsoft.AspNetCore.Authentication.csproj @@ -8,6 +8,10 @@ aspnetcore;authentication;security + + + + @@ -15,7 +19,6 @@ - diff --git a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj index 16e4aa2622..9516b08388 100644 --- a/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj +++ b/src/Security/src/Microsoft.AspNetCore.Authorization.Policy/Microsoft.AspNetCore.Authorization.Policy.csproj @@ -8,13 +8,16 @@ aspnetcore;authorization + + + + - diff --git a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs index 6f63d5ca22..7333a1ce71 100644 --- a/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs +++ b/src/Servers/HttpSys/test/FunctionalTests/ResponseCachingTests.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests public ResponseCachingTests() { - _absoluteFilePath = Directory.GetFiles(Directory.GetCurrentDirectory()).First(); + _absoluteFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Microsoft.AspNetCore.Server.HttpSys.dll"); _fileLength = new FileInfo(_absoluteFilePath).Length; } diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj index 0eeffec460..e612ba5cc8 100644 --- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj +++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj @@ -11,10 +11,10 @@ + - diff --git a/src/Shared/CertificateGeneration/CertificateManager.cs b/src/Shared/CertificateGeneration/CertificateManager.cs new file mode 100644 index 0000000000..952cf7c36d --- /dev/null +++ b/src/Shared/CertificateGeneration/CertificateManager.cs @@ -0,0 +1,951 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal class CertificateManager + { + public const string AspNetHttpsOid = "1.3.6.1.4.1.311.84.1.1"; + public const string AspNetHttpsOidFriendlyName = "ASP.NET Core HTTPS development certificate"; + + private const string ServerAuthenticationEnhancedKeyUsageOid = "1.3.6.1.5.5.7.3.1"; + private const string ServerAuthenticationEnhancedKeyUsageOidFriendlyName = "Server Authentication"; + + private const string LocalhostHttpsDnsName = "localhost"; + private const string LocalhostHttpsDistinguishedName = "CN=" + LocalhostHttpsDnsName; + + public const int RSAMinimumKeySizeInBits = 2048; + + private static readonly TimeSpan MaxRegexTimeout = TimeSpan.FromMinutes(1); + private const string CertificateSubjectRegex = "CN=(.*[^,]+).*"; + private const string MacOSSystemKeyChain = "/Library/Keychains/System.keychain"; + private static readonly string MacOSUserKeyChain = Environment.GetEnvironmentVariable("HOME") + "/Library/Keychains/login.keychain-db"; + private const string MacOSFindCertificateCommandLine = "security"; +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 + private static readonly string MacOSFindCertificateCommandLineArgumentsFormat = "find-certificate -c {0} -a -Z -p " + MacOSSystemKeyChain; +#endif + private const string MacOSFindCertificateOutputRegex = "SHA-1 hash: ([0-9A-Z]+)"; + private const string MacOSRemoveCertificateTrustCommandLine = "sudo"; + private const string MacOSRemoveCertificateTrustCommandLineArgumentsFormat = "security remove-trusted-cert -d {0}"; + private const string MacOSDeleteCertificateCommandLine = "sudo"; + private const string MacOSDeleteCertificateCommandLineArgumentsFormat = "security delete-certificate -Z {0} {1}"; + private const string MacOSTrustCertificateCommandLine = "sudo"; +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 + private static readonly string MacOSTrustCertificateCommandLineArguments = "security add-trusted-cert -d -r trustRoot -k " + MacOSSystemKeyChain + " "; +#endif + private const int UserCancelledErrorCode = 1223; + + public IList ListCertificates( + CertificatePurpose purpose, + StoreName storeName, + StoreLocation location, + bool isValid, + bool requireExportable = true, + DiagnosticInformation diagnostics = null) + { + diagnostics?.Debug($"Listing '{purpose.ToString()}' certificates on '{location}\\{storeName}'."); + var certificates = new List(); + try + { + using (var store = new X509Store(storeName, location)) + { + store.Open(OpenFlags.ReadOnly); + certificates.AddRange(store.Certificates.OfType()); + IEnumerable matchingCertificates = certificates; + switch (purpose) + { + case CertificatePurpose.All: + matchingCertificates = matchingCertificates + .Where(c => HasOid(c, AspNetHttpsOid)); + break; + case CertificatePurpose.HTTPS: + matchingCertificates = matchingCertificates + .Where(c => HasOid(c, AspNetHttpsOid)); + break; + default: + break; + } + + diagnostics?.Debug(diagnostics.DescribeCertificates(matchingCertificates)); + if (isValid) + { + // Ensure the certificate hasn't expired, has a private key and its exportable + // (for container/unix scenarios). + diagnostics?.Debug("Checking certificates for validity."); + var now = DateTimeOffset.Now; + var validCertificates = matchingCertificates + .Where(c => c.NotBefore <= now && + now <= c.NotAfter && + (!requireExportable || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || IsExportable(c))) + .ToArray(); + + var invalidCertificates = matchingCertificates.Except(validCertificates); + + diagnostics?.Debug("Listing valid certificates"); + diagnostics?.Debug(diagnostics.DescribeCertificates(validCertificates)); + diagnostics?.Debug("Listing invalid certificates"); + diagnostics?.Debug(diagnostics.DescribeCertificates(invalidCertificates)); + + matchingCertificates = validCertificates; + } + + // We need to enumerate the certificates early to prevent dispoisng issues. + matchingCertificates = matchingCertificates.ToList(); + + var certificatesToDispose = certificates.Except(matchingCertificates); + DisposeCertificates(certificatesToDispose); + + store.Close(); + + return (IList)matchingCertificates; + } + } + catch + { + DisposeCertificates(certificates); + certificates.Clear(); + return certificates; + } + + bool HasOid(X509Certificate2 certificate, string oid) => + certificate.Extensions.OfType() + .Any(e => string.Equals(oid, e.Oid.Value, StringComparison.Ordinal)); +#if !XPLAT + bool IsExportable(X509Certificate2 c) => + ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && + rsaPrivateKey.CspKeyContainerInfo.Exportable) || + (c.GetRSAPrivateKey() is RSACng cngPrivateKey && + cngPrivateKey.Key.ExportPolicy == CngExportPolicies.AllowExport)); +#else + // Only check for RSA CryptoServiceProvider and do not fail in XPlat tooling as + // System.Security.Cryptography.Cng is not part of the shared framework and we don't + // want to bring the dependency in on CLI scenarios. This functionality will be used + // on CLI scenarios as part of the first run experience, so checking the exportability + // of the certificate is not important. + bool IsExportable(X509Certificate2 c) => + ((c.GetRSAPrivateKey() is RSACryptoServiceProvider rsaPrivateKey && + rsaPrivateKey.CspKeyContainerInfo.Exportable) || !(c.GetRSAPrivateKey() is RSACryptoServiceProvider)); +#endif + } + + private static void DisposeCertificates(IEnumerable disposables) + { + foreach (var disposable in disposables) + { + try + { + disposable.Dispose(); + } + catch + { + } + } + } + +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 + + public X509Certificate2 CreateAspNetCoreHttpsDevelopmentCertificate(DateTimeOffset notBefore, DateTimeOffset notAfter, string subjectOverride, DiagnosticInformation diagnostics = null) + { + var subject = new X500DistinguishedName(subjectOverride ?? LocalhostHttpsDistinguishedName); + var extensions = new List(); + var sanBuilder = new SubjectAlternativeNameBuilder(); + sanBuilder.AddDnsName(LocalhostHttpsDnsName); + + var keyUsage = new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true); + var enhancedKeyUsage = new X509EnhancedKeyUsageExtension( + new OidCollection() { + new Oid( + ServerAuthenticationEnhancedKeyUsageOid, + ServerAuthenticationEnhancedKeyUsageOidFriendlyName) + }, + critical: true); + + var basicConstraints = new X509BasicConstraintsExtension( + certificateAuthority: false, + hasPathLengthConstraint: false, + pathLengthConstraint: 0, + critical: true); + + var aspNetHttpsExtension = new X509Extension( + new AsnEncodedData( + new Oid(AspNetHttpsOid, AspNetHttpsOidFriendlyName), + Encoding.ASCII.GetBytes(AspNetHttpsOidFriendlyName)), + critical: false); + + extensions.Add(basicConstraints); + extensions.Add(keyUsage); + extensions.Add(enhancedKeyUsage); + extensions.Add(sanBuilder.Build(critical: true)); + extensions.Add(aspNetHttpsExtension); + + var certificate = CreateSelfSignedCertificate(subject, extensions, notBefore, notAfter); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + certificate.FriendlyName = AspNetHttpsOidFriendlyName; + } + + return certificate; + } + + public X509Certificate2 CreateSelfSignedCertificate( + X500DistinguishedName subject, + IEnumerable extensions, + DateTimeOffset notBefore, + DateTimeOffset notAfter) + { + var key = CreateKeyMaterial(RSAMinimumKeySizeInBits); + + var request = new CertificateRequest(subject, key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + foreach (var extension in extensions) + { + request.CertificateExtensions.Add(extension); + } + + return request.CreateSelfSigned(notBefore, notAfter); + + RSA CreateKeyMaterial(int minimumKeySize) + { + var rsa = RSA.Create(minimumKeySize); + if (rsa.KeySize < minimumKeySize) + { + throw new InvalidOperationException($"Failed to create a key with a size of {minimumKeySize} bits"); + } + + return rsa; + } + } + + public X509Certificate2 SaveCertificateInStore(X509Certificate2 certificate, StoreName name, StoreLocation location, DiagnosticInformation diagnostics = null) + { + diagnostics?.Debug("Saving the certificate into the certificate store."); + var imported = certificate; + if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + // On non OSX systems we need to export the certificate and import it so that the transient + // key that we generated gets persisted. + var export = certificate.Export(X509ContentType.Pkcs12, ""); + imported = new X509Certificate2(export, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + Array.Clear(export, 0, export.Length); + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + imported.FriendlyName = certificate.FriendlyName; + } + + using (var store = new X509Store(name, location)) + { + store.Open(OpenFlags.ReadWrite); + store.Add(imported); + store.Close(); + }; + + return imported; + } + + public void ExportCertificate(X509Certificate2 certificate, string path, bool includePrivateKey, string password, DiagnosticInformation diagnostics = null) + { + diagnostics?.Debug( + $"Exporting certificate to '{path}'", + includePrivateKey ? "The certificate will contain the private key" : "The certificate will not contain the private key"); + if (includePrivateKey && password == null) + { + diagnostics?.Debug("No password was provided for the certificate."); + } + + var targetDirectoryPath = Path.GetDirectoryName(path); + if (targetDirectoryPath != "") + { + diagnostics?.Debug($"Ensuring that the directory for the target exported certificate path exists '{targetDirectoryPath}'"); + Directory.CreateDirectory(targetDirectoryPath); + } + + byte[] bytes; + if (includePrivateKey) + { + try + { + diagnostics?.Debug($"Exporting the certificate including the private key."); + bytes = certificate.Export(X509ContentType.Pkcs12, password); + } + catch (Exception e) + { + diagnostics?.Error($"Failed to export the certificate with the private key", e); + throw; + } + } + else + { + try + { + diagnostics?.Debug($"Exporting the certificate without the private key."); + bytes = certificate.Export(X509ContentType.Cert); + } + catch (Exception ex) + { + diagnostics?.Error($"Failed to export the certificate without the private key", ex); + throw; + } + } + try + { + diagnostics?.Debug($"Writing exported certificate to path '{path}'."); + File.WriteAllBytes(path, bytes); + } + catch (Exception ex) + { + diagnostics?.Error("Failed writing the certificate to the target path", ex); + throw; + } + finally + { + Array.Clear(bytes, 0, bytes.Length); + } + } + + public void TrustCertificate(X509Certificate2 certificate, DiagnosticInformation diagnostics = null) + { + // Strip certificate of the private key if any. + var publicCertificate = new X509Certificate2(certificate.Export(X509ContentType.Cert)); + + if (!IsTrusted(publicCertificate)) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + diagnostics?.Debug("Trusting the certificate on Windows."); + TrustCertificateOnWindows(certificate, publicCertificate, diagnostics); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + diagnostics?.Debug("Trusting the certificate on MAC."); + TrustCertificateOnMac(publicCertificate, diagnostics); + } + } + } + + private void TrustCertificateOnMac(X509Certificate2 publicCertificate, DiagnosticInformation diagnostics) + { + var tmpFile = Path.GetTempFileName(); + try + { + ExportCertificate(publicCertificate, tmpFile, includePrivateKey: false, password: null); + diagnostics?.Debug("Running the trust command on Mac OS"); + using (var process = Process.Start(MacOSTrustCertificateCommandLine, MacOSTrustCertificateCommandLineArguments + tmpFile)) + { + process.WaitForExit(); + if (process.ExitCode != 0) + { + throw new InvalidOperationException("There was an error trusting the certificate."); + } + } + } + finally + { + try + { + if (File.Exists(tmpFile)) + { + File.Delete(tmpFile); + } + } + catch + { + // We don't care if we can't delete the temp file. + } + } + } + + private static void TrustCertificateOnWindows(X509Certificate2 certificate, X509Certificate2 publicCertificate, DiagnosticInformation diagnostics = null) + { + publicCertificate.FriendlyName = certificate.FriendlyName; + + using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + var existing = store.Certificates.Find(X509FindType.FindByThumbprint, publicCertificate.Thumbprint, validOnly: false); + if (existing.Count > 0) + { + diagnostics?.Debug("Certificate already trusted. Skipping trust step."); + DisposeCertificates(existing.OfType()); + return; + } + + try + { + diagnostics?.Debug("Adding certificate to the store."); + store.Add(publicCertificate); + } + catch (CryptographicException exception) when (exception.HResult == UserCancelledErrorCode) + { + diagnostics?.Debug("User cancelled the trust prompt."); + throw new UserCancelledTrustException(); + } + store.Close(); + }; + } + + public bool IsTrusted(X509Certificate2 certificate) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: true, requireExportable: false) + .Any(c => c.Thumbprint == certificate.Thumbprint); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var subjectMatch = Regex.Match(certificate.Subject, CertificateSubjectRegex, RegexOptions.Singleline, MaxRegexTimeout); + if (!subjectMatch.Success) + { + throw new InvalidOperationException($"Can't determine the subject for the certificate with subject '{certificate.Subject}'."); + } + var subject = subjectMatch.Groups[1].Value; + using (var checkTrustProcess = Process.Start(new ProcessStartInfo( + MacOSFindCertificateCommandLine, + string.Format(MacOSFindCertificateCommandLineArgumentsFormat, subject)) + { + RedirectStandardOutput = true + })) + { + var output = checkTrustProcess.StandardOutput.ReadToEnd(); + checkTrustProcess.WaitForExit(); + var matches = Regex.Matches(output, MacOSFindCertificateOutputRegex, RegexOptions.Multiline, MaxRegexTimeout); + var hashes = matches.OfType().Select(m => m.Groups[1].Value).ToList(); + return hashes.Any(h => string.Equals(h, certificate.Thumbprint, StringComparison.Ordinal)); + } + } + else + { + return false; + } + } + + public void CleanupHttpsCertificates(string subject = LocalhostHttpsDistinguishedName) + { + CleanupCertificates(CertificatePurpose.HTTPS, subject); + } + + public void CleanupCertificates(CertificatePurpose purpose, string subject) + { + // On OS X we don't have a good way to manage trusted certificates in the system keychain + // so we do everything by invoking the native toolchain. + // This has some limitations, like for example not being able to identify our custom OID extension. For that + // matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates. + // To do this, we list the certificates that we can identify on the current user personal store and we invoke + // the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates, + // we remove the certificates from the local user store to finish up the cleanup. + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false); + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, RemoveLocations.All); + } + } + + public DiagnosticInformation CleanupHttpsCertificates2(string subject = LocalhostHttpsDistinguishedName) + { + return CleanupCertificates2(CertificatePurpose.HTTPS, subject); + } + + public DiagnosticInformation CleanupCertificates2(CertificatePurpose purpose, string subject) + { + var diagnostics = new DiagnosticInformation(); + // On OS X we don't have a good way to manage trusted certificates in the system keychain + // so we do everything by invoking the native toolchain. + // This has some limitations, like for example not being able to identify our custom OID extension. For that + // matter, when we are cleaning up certificates on the machine, we start by removing the trusted certificates. + // To do this, we list the certificates that we can identify on the current user personal store and we invoke + // the native toolchain to remove them from the sytem keychain. Once we have removed the trusted certificates, + // we remove the certificates from the local user store to finish up the cleanup. + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false, requireExportable: true, diagnostics); + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, RemoveLocations.All, diagnostics); + } + + return diagnostics; + } + + public void RemoveAllCertificates(CertificatePurpose purpose, StoreName storeName, StoreLocation storeLocation, string subject = null) + { + var certificates = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? + ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: false) : + ListCertificates(purpose, storeName, storeLocation, isValid: false); + var certificatesWithName = subject == null ? certificates : certificates.Where(c => c.Subject == subject); + + var removeLocation = storeName == StoreName.My ? RemoveLocations.Local : RemoveLocations.Trusted; + + foreach (var certificate in certificates) + { + RemoveCertificate(certificate, removeLocation); + } + + DisposeCertificates(certificates); + } + + private void RemoveCertificate(X509Certificate2 certificate, RemoveLocations locations, DiagnosticInformation diagnostics = null) + { + switch (locations) + { + case RemoveLocations.Undefined: + throw new InvalidOperationException($"'{nameof(RemoveLocations.Undefined)}' is not a valid location."); + case RemoveLocations.Local: + RemoveCertificateFromUserStore(certificate, diagnostics); + break; + case RemoveLocations.Trusted when !RuntimeInformation.IsOSPlatform(OSPlatform.Linux): + RemoveCertificateFromTrustedRoots(certificate, diagnostics); + break; + case RemoveLocations.All: + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + RemoveCertificateFromTrustedRoots(certificate, diagnostics); + } + RemoveCertificateFromUserStore(certificate, diagnostics); + break; + default: + throw new InvalidOperationException("Invalid location."); + } + } + + private static void RemoveCertificateFromUserStore(X509Certificate2 certificate, DiagnosticInformation diagnostics) + { + diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'."); + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + var matching = store.Certificates + .OfType() + .Single(c => c.SerialNumber == certificate.SerialNumber); + + store.Remove(matching); + store.Close(); + } + } + + private void RemoveCertificateFromTrustedRoots(X509Certificate2 certificate, DiagnosticInformation diagnostics) + { + diagnostics?.Debug($"Trying to remove certificate with thumbprint '{certificate.Thumbprint}' from certificate store '{StoreLocation.CurrentUser}\\{StoreName.Root}'."); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadWrite); + var matching = store.Certificates + .OfType() + .SingleOrDefault(c => c.SerialNumber == certificate.SerialNumber); + + if (matching != null) + { + store.Remove(matching); + } + + store.Close(); + } + } + else + { + if (IsTrusted(certificate)) // On OSX this check just ensures its on the system keychain + { + try + { + diagnostics?.Debug("Trying to remove the certificate trust rule."); + RemoveCertificateTrustRule(certificate); + } + catch + { + diagnostics?.Debug("Failed to remove the certificate trust rule."); + // We don't care if we fail to remove the trust rule if + // for some reason the certificate became untrusted. + // The delete command will fail if the certificate is + // trusted. + } + RemoveCertificateFromKeyChain(MacOSSystemKeyChain, certificate); + } + else + { + diagnostics?.Debug("The certificate was not trusted."); + } + } + } + + private static void RemoveCertificateTrustRule(X509Certificate2 certificate) + { + var certificatePath = Path.GetTempFileName(); + try + { + var certBytes = certificate.Export(X509ContentType.Cert); + File.WriteAllBytes(certificatePath, certBytes); + var processInfo = new ProcessStartInfo( + MacOSRemoveCertificateTrustCommandLine, + string.Format( + MacOSRemoveCertificateTrustCommandLineArgumentsFormat, + certificatePath + )); + using (var process = Process.Start(processInfo)) + { + process.WaitForExit(); + } + } + finally + { + try + { + if (File.Exists(certificatePath)) + { + File.Delete(certificatePath); + } + } + catch + { + // We don't care about failing to do clean-up on a temp file. + } + } + } + + private static void RemoveCertificateFromKeyChain(string keyChain, X509Certificate2 certificate) + { + var processInfo = new ProcessStartInfo( + MacOSDeleteCertificateCommandLine, + string.Format( + MacOSDeleteCertificateCommandLineArgumentsFormat, + certificate.Thumbprint.ToUpperInvariant(), + keyChain + )) + { + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + using (var process = Process.Start(processInfo)) + { + var output = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); + process.WaitForExit(); + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($@"There was an error removing the certificate with thumbprint '{certificate.Thumbprint}'. + +{output}"); + } + } + } + + public EnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subject = LocalhostHttpsDistinguishedName) + { + return EnsureValidCertificateExists(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); + } + + public EnsureCertificateResult EnsureValidCertificateExists( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + CertificatePurpose purpose, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subjectOverride = null) + { + if (purpose == CertificatePurpose.All) + { + throw new ArgumentException("The certificate must have a specific purpose."); + } + + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true).Concat( + ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true)); + + certificates = subjectOverride == null ? certificates : certificates.Where(c => c.Subject == subjectOverride); + + var result = EnsureCertificateResult.Succeeded; + + X509Certificate2 certificate = null; + if (certificates.Count() > 0) + { + certificate = certificates.FirstOrDefault(); + result = EnsureCertificateResult.ValidCertificatePresent; + } + else + { + try + { + switch (purpose) + { + case CertificatePurpose.All: + throw new InvalidOperationException("The certificate must have a specific purpose."); + case CertificatePurpose.HTTPS: + certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subjectOverride); + break; + default: + throw new InvalidOperationException("The certificate must have a purpose."); + } + } + catch + { + return EnsureCertificateResult.ErrorCreatingTheCertificate; + } + + try + { + certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser); + } + catch + { + return EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; + } + } + if (path != null) + { + try + { + ExportCertificate(certificate, path, includePrivateKey, password); + } + catch + { + return EnsureCertificateResult.ErrorExportingTheCertificate; + } + } + + if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust) + { + try + { + TrustCertificate(certificate); + } + catch (UserCancelledTrustException) + { + return EnsureCertificateResult.UserCancelledTrustStep; + } + catch + { + return EnsureCertificateResult.FailedToTrustTheCertificate; + } + } + + return result; + } + + // This is just to avoid breaking changes across repos. + // Will be renamed back to EnsureAspNetCoreHttpsDevelopmentCertificate once updates are made elsewhere. + public DetailedEnsureCertificateResult EnsureAspNetCoreHttpsDevelopmentCertificate2( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + string path = null, + bool trust = false, + bool includePrivateKey = false, + string password = null, + string subject = LocalhostHttpsDistinguishedName) + { + return EnsureValidCertificateExists2(notBefore, notAfter, CertificatePurpose.HTTPS, path, trust, includePrivateKey, password, subject); + } + + public DetailedEnsureCertificateResult EnsureValidCertificateExists2( + DateTimeOffset notBefore, + DateTimeOffset notAfter, + CertificatePurpose purpose, + string path, + bool trust, + bool includePrivateKey, + string password, + string subject) + { + if (purpose == CertificatePurpose.All) + { + throw new ArgumentException("The certificate must have a specific purpose."); + } + + var result = new DetailedEnsureCertificateResult(); + + var certificates = ListCertificates(purpose, StoreName.My, StoreLocation.CurrentUser, isValid: true, requireExportable: true, result.Diagnostics).Concat( + ListCertificates(purpose, StoreName.My, StoreLocation.LocalMachine, isValid: true, requireExportable: true, result.Diagnostics)); + + var filteredCertificates = subject == null ? certificates : certificates.Where(c => c.Subject == subject); + if (subject != null) + { + var excludedCertificates = certificates.Except(filteredCertificates); + + result.Diagnostics.Debug($"Filtering found certificates to those with a subject equal to '{subject}'"); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(filteredCertificates)); + result.Diagnostics.Debug($"Listing certificates excluded from consideration."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(excludedCertificates)); + } + else + { + result.Diagnostics.Debug("Skipped filtering certificates by subject."); + } + + certificates = filteredCertificates; + + result.ResultCode = EnsureCertificateResult.Succeeded; + + X509Certificate2 certificate = null; + if (certificates.Count() > 0) + { + result.Diagnostics.Debug("Found valid certificates present on the machine."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificates)); + certificate = certificates.First(); + result.Diagnostics.Debug("Selected certificate"); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate)); + result.ResultCode = EnsureCertificateResult.ValidCertificatePresent; + } + else + { + result.Diagnostics.Debug("No valid certificates present on this machine. Trying to create one."); + try + { + switch (purpose) + { + case CertificatePurpose.All: + throw new InvalidOperationException("The certificate must have a specific purpose."); + case CertificatePurpose.HTTPS: + certificate = CreateAspNetCoreHttpsDevelopmentCertificate(notBefore, notAfter, subject, result.Diagnostics); + break; + default: + throw new InvalidOperationException("The certificate must have a purpose."); + } + } + catch (Exception e) + { + result.Diagnostics.Error("Error creating the certificate.", e); + result.ResultCode = EnsureCertificateResult.ErrorCreatingTheCertificate; + return result; + } + + try + { + certificate = SaveCertificateInStore(certificate, StoreName.My, StoreLocation.CurrentUser, result.Diagnostics); + } + catch (Exception e) + { + result.Diagnostics.Error($"Error saving the certificate in the certificate store '{StoreLocation.CurrentUser}\\{StoreName.My}'.", e); + result.ResultCode = EnsureCertificateResult.ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore; + return result; + } + } + if (path != null) + { + result.Diagnostics.Debug("Trying to export the certificate."); + result.Diagnostics.Debug(result.Diagnostics.DescribeCertificates(certificate)); + try + { + ExportCertificate(certificate, path, includePrivateKey, password, result.Diagnostics); + } + catch (Exception e) + { + result.Diagnostics.Error("An error ocurred exporting the certificate.", e); + result.ResultCode = EnsureCertificateResult.ErrorExportingTheCertificate; + return result; + } + } + + if ((RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) && trust) + { + try + { + result.Diagnostics.Debug("Trying to export the certificate."); + TrustCertificate(certificate, result.Diagnostics); + } + catch (UserCancelledTrustException) + { + result.Diagnostics.Error("The user cancelled trusting the certificate.", null); + result.ResultCode = EnsureCertificateResult.UserCancelledTrustStep; + return result; + } + catch (Exception e) + { + result.Diagnostics.Error("There was an error trusting the certificate.", e); + result.ResultCode = EnsureCertificateResult.FailedToTrustTheCertificate; + return result; + } + } + + return result; + } + + private class UserCancelledTrustException : Exception + { + } + + private enum RemoveLocations + { + Undefined, + Local, + Trusted, + All + } + + internal class DetailedEnsureCertificateResult + { + public EnsureCertificateResult ResultCode { get; set; } + public DiagnosticInformation Diagnostics { get; set; } = new DiagnosticInformation(); + } +#endif + + internal class DiagnosticInformation + { + public IList Messages { get; } = new List(); + + public IList Exceptions { get; } = new List(); + + internal void Debug(params string[] messages) + { + foreach (var message in messages) + { + Messages.Add(message); + } + } + + internal string[] DescribeCertificates(params X509Certificate2[] certificates) + { + return DescribeCertificates(certificates.AsEnumerable()); + } + + internal string[] DescribeCertificates(IEnumerable certificates) + { + var result = new List(); + result.Add($"'{certificates.Count()}' found matching the criteria."); + result.Add($"SUBJECT - THUMBPRINT - NOT BEFORE - EXPIRES - HAS PRIVATE KEY"); + foreach (var certificate in certificates) + { + result.Add(DescribeCertificate(certificate)); + } + + return result.ToArray(); + } + + private static string DescribeCertificate(X509Certificate2 certificate) => + $"{certificate.Subject} - {certificate.Thumbprint} - {certificate.NotBefore} - {certificate.NotAfter} - {certificate.HasPrivateKey}"; + + internal void Error(string preamble, Exception e) + { + Messages.Add(preamble); + if (Exceptions.Count > 0 && Exceptions[Exceptions.Count - 1] == e) + { + return; + } + + var ex = e; + while (ex != null) + { + Messages.Add("Exception message: " + ex.Message); + ex = ex.InnerException; + } + + } + } + } +} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/CertificatePurpose.cs b/src/Shared/CertificateGeneration/CertificatePurpose.cs new file mode 100644 index 0000000000..7b3231f80d --- /dev/null +++ b/src/Shared/CertificateGeneration/CertificatePurpose.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal enum CertificatePurpose + { + All, + HTTPS + } +} \ No newline at end of file diff --git a/src/Shared/CertificateGeneration/EnsureCertificateResult.cs b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs new file mode 100644 index 0000000000..84c495249d --- /dev/null +++ b/src/Shared/CertificateGeneration/EnsureCertificateResult.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 + +namespace Microsoft.AspNetCore.Certificates.Generation +{ + internal enum EnsureCertificateResult + { + Succeeded = 1, + ValidCertificatePresent, + ErrorCreatingTheCertificate, + ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore, + ErrorExportingTheCertificate, + FailedToTrustTheCertificate, + UserCancelledTrustStep + } +} + +#endif \ No newline at end of file diff --git a/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs new file mode 100644 index 0000000000..f234c2edbc --- /dev/null +++ b/src/Shared/ClosedGenericMatcher/ClosedGenericMatcher.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper related to generic interface definitions and implementing classes. + /// + internal static class ClosedGenericMatcher + { + /// + /// Determine whether is or implements a closed generic + /// created from . + /// + /// The of interest. + /// The open generic to match. Usually an interface. + /// + /// The closed generic created from that + /// is or implements. null if the two s have no such + /// relationship. + /// + /// + /// This method will return if is + /// typeof(KeyValuePair{,}), and is + /// typeof(KeyValuePair{string, object}). + /// + public static Type ExtractGenericInterface(Type queryType, Type interfaceType) + { + if (queryType == null) + { + throw new ArgumentNullException(nameof(queryType)); + } + + if (interfaceType == null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + if (IsGenericInstantiation(queryType, interfaceType)) + { + // queryType matches (i.e. is a closed generic type created from) the open generic type. + return queryType; + } + + // Otherwise check all interfaces the type implements for a match. + // - If multiple different generic instantiations exists, we want the most derived one. + // - If that doesn't break the tie, then we sort alphabetically so that it's deterministic. + // + // We do this by looking at interfaces on the type, and recursing to the base type + // if we don't find any matches. + return GetGenericInstantiation(queryType, interfaceType); + } + + private static bool IsGenericInstantiation(Type candidate, Type interfaceType) + { + return + candidate.GetTypeInfo().IsGenericType && + candidate.GetGenericTypeDefinition() == interfaceType; + } + + private static Type GetGenericInstantiation(Type queryType, Type interfaceType) + { + Type bestMatch = null; + var interfaces = queryType.GetInterfaces(); + foreach (var @interface in interfaces) + { + if (IsGenericInstantiation(@interface, interfaceType)) + { + if (bestMatch == null) + { + bestMatch = @interface; + } + else if (StringComparer.Ordinal.Compare(@interface.FullName, bestMatch.FullName) < 0) + { + bestMatch = @interface; + } + else + { + // There are two matches at this level of the class hierarchy, but @interface is after + // bestMatch in the sort order. + } + } + } + + if (bestMatch != null) + { + return bestMatch; + } + + // BaseType will be null for object and interfaces, which means we've reached 'bottom'. + var baseType = queryType?.GetTypeInfo().BaseType; + if (baseType == null) + { + return null; + } + else + { + return GetGenericInstantiation(baseType, interfaceType); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs new file mode 100644 index 0000000000..1408059ad9 --- /dev/null +++ b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionary.cs @@ -0,0 +1,155 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal class CopyOnWriteDictionary : IDictionary + { + private readonly IDictionary _sourceDictionary; + private readonly IEqualityComparer _comparer; + private IDictionary _innerDictionary; + + public CopyOnWriteDictionary( + IDictionary sourceDictionary, + IEqualityComparer comparer) + { + if (sourceDictionary == null) + { + throw new ArgumentNullException(nameof(sourceDictionary)); + } + + if (comparer == null) + { + throw new ArgumentNullException(nameof(comparer)); + } + + _sourceDictionary = sourceDictionary; + _comparer = comparer; + } + + private IDictionary ReadDictionary + { + get + { + return _innerDictionary ?? _sourceDictionary; + } + } + + private IDictionary WriteDictionary + { + get + { + if (_innerDictionary == null) + { + _innerDictionary = new Dictionary(_sourceDictionary, + _comparer); + } + + return _innerDictionary; + } + } + + public virtual ICollection Keys + { + get + { + return ReadDictionary.Keys; + } + } + + public virtual ICollection Values + { + get + { + return ReadDictionary.Values; + } + } + + public virtual int Count + { + get + { + return ReadDictionary.Count; + } + } + + public virtual bool IsReadOnly + { + get + { + return false; + } + } + + public virtual TValue this[TKey key] + { + get + { + return ReadDictionary[key]; + } + set + { + WriteDictionary[key] = value; + } + } + + public virtual bool ContainsKey(TKey key) + { + return ReadDictionary.ContainsKey(key); + } + + public virtual void Add(TKey key, TValue value) + { + WriteDictionary.Add(key, value); + } + + public virtual bool Remove(TKey key) + { + return WriteDictionary.Remove(key); + } + + public virtual bool TryGetValue(TKey key, out TValue value) + { + return ReadDictionary.TryGetValue(key, out value); + } + + public virtual void Add(KeyValuePair item) + { + WriteDictionary.Add(item); + } + + public virtual void Clear() + { + WriteDictionary.Clear(); + } + + public virtual bool Contains(KeyValuePair item) + { + return ReadDictionary.Contains(item); + } + + public virtual void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ReadDictionary.CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return WriteDictionary.Remove(item); + } + + public virtual IEnumerator> GetEnumerator() + { + return ReadDictionary.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs new file mode 100644 index 0000000000..7cd935e940 --- /dev/null +++ b/src/Shared/CopyOnWriteDictionary/CopyOnWriteDictionaryHolder.cs @@ -0,0 +1,166 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.Internal +{ + internal struct CopyOnWriteDictionaryHolder + { + private readonly Dictionary _source; + private Dictionary _copy; + + public CopyOnWriteDictionaryHolder(Dictionary source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + _source = source; + _copy = null; + } + + public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder source) + { + _source = source._copy ?? source._source; + _copy = null; + } + + public bool HasBeenCopied => _copy != null; + + public Dictionary ReadDictionary + { + get + { + if (_copy != null) + { + return _copy; + } + else if (_source != null) + { + return _source; + } + else + { + // Default-Constructor case + _copy = new Dictionary(); + return _copy; + } + } + } + + public Dictionary WriteDictionary + { + get + { + if (_copy == null && _source == null) + { + // Default-Constructor case + _copy = new Dictionary(); + } + else if (_copy == null) + { + _copy = new Dictionary(_source, _source.Comparer); + } + + return _copy; + } + } + + public Dictionary.KeyCollection Keys + { + get + { + return ReadDictionary.Keys; + } + } + + public Dictionary.ValueCollection Values + { + get + { + return ReadDictionary.Values; + } + } + + public int Count + { + get + { + return ReadDictionary.Count; + } + } + + public bool IsReadOnly + { + get + { + return false; + } + } + + public TValue this[TKey key] + { + get + { + return ReadDictionary[key]; + } + set + { + WriteDictionary[key] = value; + } + } + + public bool ContainsKey(TKey key) + { + return ReadDictionary.ContainsKey(key); + } + + public void Add(TKey key, TValue value) + { + WriteDictionary.Add(key, value); + } + + public bool Remove(TKey key) + { + return WriteDictionary.Remove(key); + } + + public bool TryGetValue(TKey key, out TValue value) + { + return ReadDictionary.TryGetValue(key, out value); + } + + public void Add(KeyValuePair item) + { + ((ICollection>)WriteDictionary).Add(item); + } + + public void Clear() + { + WriteDictionary.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return ((ICollection>)ReadDictionary).Contains(item); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((ICollection>)ReadDictionary).CopyTo(array, arrayIndex); + } + + public bool Remove(KeyValuePair item) + { + return ((ICollection>)WriteDictionary).Remove(item); + } + + public Dictionary.Enumerator GetEnumerator() + { + return ReadDictionary.GetEnumerator(); + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs new file mode 100644 index 0000000000..431b83a6e5 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/AwaitableInfo.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal struct AwaitableInfo + { + public Type AwaiterType { get; } + public PropertyInfo AwaiterIsCompletedProperty { get; } + public MethodInfo AwaiterGetResultMethod { get; } + public MethodInfo AwaiterOnCompletedMethod { get; } + public MethodInfo AwaiterUnsafeOnCompletedMethod { get; } + public Type ResultType { get; } + public MethodInfo GetAwaiterMethod { get; } + + public AwaitableInfo( + Type awaiterType, + PropertyInfo awaiterIsCompletedProperty, + MethodInfo awaiterGetResultMethod, + MethodInfo awaiterOnCompletedMethod, + MethodInfo awaiterUnsafeOnCompletedMethod, + Type resultType, + MethodInfo getAwaiterMethod) + { + AwaiterType = awaiterType; + AwaiterIsCompletedProperty = awaiterIsCompletedProperty; + AwaiterGetResultMethod = awaiterGetResultMethod; + AwaiterOnCompletedMethod = awaiterOnCompletedMethod; + AwaiterUnsafeOnCompletedMethod = awaiterUnsafeOnCompletedMethod; + ResultType = resultType; + GetAwaiterMethod = getAwaiterMethod; + } + + public static bool IsTypeAwaitable(Type type, out AwaitableInfo awaitableInfo) + { + // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 + + // Awaitable must have method matching "object GetAwaiter()" + var getAwaiterMethod = type.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetAwaiter", StringComparison.OrdinalIgnoreCase) + && m.GetParameters().Length == 0 + && m.ReturnType != null); + if (getAwaiterMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + var awaiterType = getAwaiterMethod.ReturnType; + + // Awaiter must have property matching "bool IsCompleted { get; }" + var isCompletedProperty = awaiterType.GetRuntimeProperties().FirstOrDefault(p => + p.Name.Equals("IsCompleted", StringComparison.OrdinalIgnoreCase) + && p.PropertyType == typeof(bool) + && p.GetMethod != null); + if (isCompletedProperty == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // Awaiter must implement INotifyCompletion + var awaiterInterfaces = awaiterType.GetInterfaces(); + var implementsINotifyCompletion = awaiterInterfaces.Any(t => t == typeof(INotifyCompletion)); + if (!implementsINotifyCompletion) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + // INotifyCompletion supplies a method matching "void OnCompleted(Action action)" + var iNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(INotifyCompletion)); + var onCompletedMethod = iNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("OnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + + // Awaiter optionally implements ICriticalNotifyCompletion + var implementsICriticalNotifyCompletion = awaiterInterfaces.Any(t => t == typeof(ICriticalNotifyCompletion)); + MethodInfo unsafeOnCompletedMethod; + if (implementsICriticalNotifyCompletion) + { + // ICriticalNotifyCompletion supplies a method matching "void UnsafeOnCompleted(Action action)" + var iCriticalNotifyCompletionMap = awaiterType + .GetTypeInfo() + .GetRuntimeInterfaceMap(typeof(ICriticalNotifyCompletion)); + unsafeOnCompletedMethod = iCriticalNotifyCompletionMap.InterfaceMethods.Single(m => + m.Name.Equals("UnsafeOnCompleted", StringComparison.OrdinalIgnoreCase) + && m.ReturnType == typeof(void) + && m.GetParameters().Length == 1 + && m.GetParameters()[0].ParameterType == typeof(Action)); + } + else + { + unsafeOnCompletedMethod = null; + } + + // Awaiter must have method matching "void GetResult" or "T GetResult()" + var getResultMethod = awaiterType.GetRuntimeMethods().FirstOrDefault(m => + m.Name.Equals("GetResult") + && m.GetParameters().Length == 0); + if (getResultMethod == null) + { + awaitableInfo = default(AwaitableInfo); + return false; + } + + awaitableInfo = new AwaitableInfo( + awaiterType, + isCompletedProperty, + getResultMethod, + onCompletedMethod, + unsafeOnCompletedMethod, + getResultMethod.ReturnType, + getAwaiterMethod); + return true; + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs new file mode 100644 index 0000000000..4e48ef09a1 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/CoercedAwaitableInfo.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; + +namespace Microsoft.Extensions.Internal +{ + internal struct CoercedAwaitableInfo + { + public AwaitableInfo AwaitableInfo { get; } + public Expression CoercerExpression { get; } + public Type CoercerResultType { get; } + public bool RequiresCoercion => CoercerExpression != null; + + public CoercedAwaitableInfo(AwaitableInfo awaitableInfo) + { + AwaitableInfo = awaitableInfo; + CoercerExpression = null; + CoercerResultType = null; + } + + public CoercedAwaitableInfo(Expression coercerExpression, Type coercerResultType, AwaitableInfo coercedAwaitableInfo) + { + CoercerExpression = coercerExpression; + CoercerResultType = coercerResultType; + AwaitableInfo = coercedAwaitableInfo; + } + + public static bool IsTypeAwaitable(Type type, out CoercedAwaitableInfo info) + { + if (AwaitableInfo.IsTypeAwaitable(type, out var directlyAwaitableInfo)) + { + info = new CoercedAwaitableInfo(directlyAwaitableInfo); + return true; + } + + // It's not directly awaitable, but maybe we can coerce it. + // Currently we support coercing FSharpAsync. + if (ObjectMethodExecutorFSharpSupport.TryBuildCoercerFromFSharpAsyncToAwaitable(type, + out var coercerExpression, + out var coercerResultType)) + { + if (AwaitableInfo.IsTypeAwaitable(coercerResultType, out var coercedAwaitableInfo)) + { + info = new CoercedAwaitableInfo(coercerExpression, coercerResultType, coercedAwaitableInfo); + return true; + } + } + + info = default(CoercedAwaitableInfo); + return false; + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs new file mode 100644 index 0000000000..f8e5b70f0d --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutor.cs @@ -0,0 +1,340 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class ObjectMethodExecutor + { + private readonly object[] _parameterDefaultValues; + private readonly MethodExecutorAsync _executorAsync; + private readonly MethodExecutor _executor; + + private static readonly ConstructorInfo _objectMethodExecutorAwaitableConstructor = + typeof(ObjectMethodExecutorAwaitable).GetConstructor(new[] { + typeof(object), // customAwaitable + typeof(Func), // getAwaiterMethod + typeof(Func), // isCompletedMethod + typeof(Func), // getResultMethod + typeof(Action), // onCompletedMethod + typeof(Action) // unsafeOnCompletedMethod + }); + + private ObjectMethodExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + MethodInfo = methodInfo; + MethodParameters = methodInfo.GetParameters(); + TargetTypeInfo = targetTypeInfo; + MethodReturnType = methodInfo.ReturnType; + + var isAwaitable = CoercedAwaitableInfo.IsTypeAwaitable(MethodReturnType, out var coercedAwaitableInfo); + + IsMethodAsync = isAwaitable; + AsyncResultType = isAwaitable ? coercedAwaitableInfo.AwaitableInfo.ResultType : null; + + // Upstream code may prefer to use the sync-executor even for async methods, because if it knows + // that the result is a specific Task where T is known, then it can directly cast to that type + // and await it without the extra heap allocations involved in the _executorAsync code path. + _executor = GetExecutor(methodInfo, targetTypeInfo); + + if (IsMethodAsync) + { + _executorAsync = GetExecutorAsync(methodInfo, targetTypeInfo, coercedAwaitableInfo); + } + + _parameterDefaultValues = parameterDefaultValues; + } + + private delegate ObjectMethodExecutorAwaitable MethodExecutorAsync(object target, object[] parameters); + + private delegate object MethodExecutor(object target, object[] parameters); + + private delegate void VoidMethodExecutor(object target, object[] parameters); + + public MethodInfo MethodInfo { get; } + + public ParameterInfo[] MethodParameters { get; } + + public TypeInfo TargetTypeInfo { get; } + + public Type AsyncResultType { get; } + + // This field is made internal set because it is set in unit tests. + public Type MethodReturnType { get; internal set; } + + public bool IsMethodAsync { get; } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, null); + } + + public static ObjectMethodExecutor Create(MethodInfo methodInfo, TypeInfo targetTypeInfo, object[] parameterDefaultValues) + { + if (parameterDefaultValues == null) + { + throw new ArgumentNullException(nameof(parameterDefaultValues)); + } + + return new ObjectMethodExecutor(methodInfo, targetTypeInfo, parameterDefaultValues); + } + + /// + /// Executes the configured method on . This can be used whether or not + /// the configured method is asynchronous. + /// + /// + /// Even if the target method is asynchronous, it's desirable to invoke it using Execute rather than + /// ExecuteAsync if you know at compile time what the return type is, because then you can directly + /// "await" that value (via a cast), and then the generated code will be able to reference the + /// resulting awaitable as a value-typed variable. If you use ExecuteAsync instead, the generated + /// code will have to treat the resulting awaitable as a boxed object, because it doesn't know at + /// compile time what type it would be. + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// The method return value. + public object Execute(object target, object[] parameters) + { + return _executor(target, parameters); + } + + /// + /// Executes the configured method on . This can only be used if the configured + /// method is asynchronous. + /// + /// + /// If you don't know at compile time the type of the method's returned awaitable, you can use ExecuteAsync, + /// which supplies an awaitable-of-object. This always works, but can incur several extra heap allocations + /// as compared with using Execute and then using "await" on the result value typecasted to the known + /// awaitable type. The possible extra heap allocations are for: + /// + /// 1. The custom awaitable (though usually there's a heap allocation for this anyway, since normally + /// it's a reference type, and you normally create a new instance per call). + /// 2. The custom awaiter (whether or not it's a value type, since if it's not, you need a new instance + /// of it, and if it is, it will have to be boxed so the calling code can reference it as an object). + /// 3. The async result value, if it's a value type (it has to be boxed as an object, since the calling + /// code doesn't know what type it's going to be). + /// + /// The object whose method is to be executed. + /// Parameters to pass to the method. + /// An object that you can "await" to get the method return value. + public ObjectMethodExecutorAwaitable ExecuteAsync(object target, object[] parameters) + { + return _executorAsync(target, parameters); + } + + public object GetDefaultValueForParameter(int index) + { + if (_parameterDefaultValues == null) + { + throw new InvalidOperationException($"Cannot call {nameof(GetDefaultValueForParameter)}, because no parameter default values were supplied."); + } + + if (index < 0 || index > MethodParameters.Length - 1) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _parameterDefaultValues[index]; + } + + private static MethodExecutor GetExecutor(MethodInfo methodInfo, TypeInfo targetTypeInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // methodCall is "((Ttarget) target) method((T0) parameters[0], (T1) parameters[1], ...)" + // Create function + if (methodCall.Type == typeof(void)) + { + var lambda = Expression.Lambda(methodCall, targetParameter, parametersParameter); + var voidExecutor = lambda.Compile(); + return WrapVoidMethod(voidExecutor); + } + else + { + // must coerce methodCall to match ActionExecutor signature + var castMethodCall = Expression.Convert(methodCall, typeof(object)); + var lambda = Expression.Lambda(castMethodCall, targetParameter, parametersParameter); + return lambda.Compile(); + } + } + + private static MethodExecutor WrapVoidMethod(VoidMethodExecutor executor) + { + return delegate (object target, object[] parameters) + { + executor(target, parameters); + return null; + }; + } + + private static MethodExecutorAsync GetExecutorAsync( + MethodInfo methodInfo, + TypeInfo targetTypeInfo, + CoercedAwaitableInfo coercedAwaitableInfo) + { + // Parameters to executor + var targetParameter = Expression.Parameter(typeof(object), "target"); + var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + + // Build parameter list + var parameters = new List(); + var paramInfos = methodInfo.GetParameters(); + for (int i = 0; i < paramInfos.Length; i++) + { + var paramInfo = paramInfos[i]; + var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); + var valueCast = Expression.Convert(valueObj, paramInfo.ParameterType); + + // valueCast is "(Ti) parameters[i]" + parameters.Add(valueCast); + } + + // Call method + var instanceCast = Expression.Convert(targetParameter, targetTypeInfo.AsType()); + var methodCall = Expression.Call(instanceCast, methodInfo, parameters); + + // Using the method return value, construct an ObjectMethodExecutorAwaitable based on + // the info we have about its implementation of the awaitable pattern. Note that all + // the funcs/actions we construct here are precompiled, so that only one instance of + // each is preserved throughout the lifetime of the ObjectMethodExecutor. + + // var getAwaiterFunc = (object awaitable) => + // (object)((CustomAwaitableType)awaitable).GetAwaiter(); + var customAwaitableParam = Expression.Parameter(typeof(object), "awaitable"); + var awaitableInfo = coercedAwaitableInfo.AwaitableInfo; + var postCoercionMethodReturnType = coercedAwaitableInfo.CoercerResultType ?? methodInfo.ReturnType; + var getAwaiterFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(customAwaitableParam, postCoercionMethodReturnType), + awaitableInfo.GetAwaiterMethod), + typeof(object)), + customAwaitableParam).Compile(); + + // var isCompletedFunc = (object awaiter) => + // ((CustomAwaiterType)awaiter).IsCompleted; + var isCompletedParam = Expression.Parameter(typeof(object), "awaiter"); + var isCompletedFunc = Expression.Lambda>( + Expression.MakeMemberAccess( + Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterIsCompletedProperty), + isCompletedParam).Compile(); + + var getResultParam = Expression.Parameter(typeof(object), "awaiter"); + Func getResultFunc; + if (awaitableInfo.ResultType == typeof(void)) + { + // var getResultFunc = (object awaiter) => + // { + // ((CustomAwaiterType)awaiter).GetResult(); // We need to invoke this to surface any exceptions + // return (object)null; + // }; + getResultFunc = Expression.Lambda>( + Expression.Block( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + Expression.Constant(null) + ), + getResultParam).Compile(); + } + else + { + // var getResultFunc = (object awaiter) => + // (object)((CustomAwaiterType)awaiter).GetResult(); + getResultFunc = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(getResultParam, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterGetResultMethod), + typeof(object)), + getResultParam).Compile(); + } + + // var onCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).OnCompleted(continuation); + // }; + var onCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var onCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + var onCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterOnCompletedMethod, + onCompletedParam2), + onCompletedParam1, + onCompletedParam2).Compile(); + + Action unsafeOnCompletedFunc = null; + if (awaitableInfo.AwaiterUnsafeOnCompletedMethod != null) + { + // var unsafeOnCompletedFunc = (object awaiter, Action continuation) => { + // ((CustomAwaiterType)awaiter).UnsafeOnCompleted(continuation); + // }; + var unsafeOnCompletedParam1 = Expression.Parameter(typeof(object), "awaiter"); + var unsafeOnCompletedParam2 = Expression.Parameter(typeof(Action), "continuation"); + unsafeOnCompletedFunc = Expression.Lambda>( + Expression.Call( + Expression.Convert(unsafeOnCompletedParam1, awaitableInfo.AwaiterType), + awaitableInfo.AwaiterUnsafeOnCompletedMethod, + unsafeOnCompletedParam2), + unsafeOnCompletedParam1, + unsafeOnCompletedParam2).Compile(); + } + + // If we need to pass the method call result through a coercer function to get an + // awaitable, then do so. + var coercedMethodCall = coercedAwaitableInfo.RequiresCoercion + ? Expression.Invoke(coercedAwaitableInfo.CoercerExpression, methodCall) + : (Expression)methodCall; + + // return new ObjectMethodExecutorAwaitable( + // (object)coercedMethodCall, + // getAwaiterFunc, + // isCompletedFunc, + // getResultFunc, + // onCompletedFunc, + // unsafeOnCompletedFunc); + var returnValueExpression = Expression.New( + _objectMethodExecutorAwaitableConstructor, + Expression.Convert(coercedMethodCall, typeof(object)), + Expression.Constant(getAwaiterFunc), + Expression.Constant(isCompletedFunc), + Expression.Constant(getResultFunc), + Expression.Constant(onCompletedFunc), + Expression.Constant(unsafeOnCompletedFunc, typeof(Action))); + + var lambda = Expression.Lambda(returnValueExpression, targetParameter, parametersParameter); + return lambda.Compile(); + } + } +} diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs new file mode 100644 index 0000000000..7509b86b2b --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorAwaitable.cs @@ -0,0 +1,114 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Provides a common awaitable structure that can + /// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an + /// application-defined custom awaitable. + /// + internal struct ObjectMethodExecutorAwaitable + { + private readonly object _customAwaitable; + private readonly Func _getAwaiterMethod; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + // Perf note: since we're requiring the customAwaitable to be supplied here as an object, + // this will trigger a further allocation if it was a value type (i.e., to box it). We can't + // fix this by making the customAwaitable type generic, because the calling code typically + // does not know the type of the awaitable/awaiter at compile-time anyway. + // + // However, we could fix it by not passing the customAwaitable here at all, and instead + // passing a func that maps directly from the target object (e.g., controller instance), + // target method (e.g., action method info), and params array to the custom awaiter in the + // GetAwaiter() method below. In effect, by delaying the actual method call until the + // upstream code calls GetAwaiter on this ObjectMethodExecutorAwaitable instance. + // This optimization is not currently implemented because: + // [1] It would make no difference when the awaitable was an object type, which is + // by far the most common scenario (e.g., System.Task). + // [2] It would be complex - we'd need some kind of object pool to track all the parameter + // arrays until we needed to use them in GetAwaiter(). + // We can reconsider this in the future if there's a need to optimize for ValueTask + // or other value-typed awaitables. + + public ObjectMethodExecutorAwaitable( + object customAwaitable, + Func getAwaiterMethod, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaitable = customAwaitable; + _getAwaiterMethod = getAwaiterMethod; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public Awaiter GetAwaiter() + { + var customAwaiter = _getAwaiterMethod(_customAwaitable); + return new Awaiter(customAwaiter, _isCompletedMethod, _getResultMethod, _onCompletedMethod, _unsafeOnCompletedMethod); + } + + public struct Awaiter : ICriticalNotifyCompletion + { + private readonly object _customAwaiter; + private readonly Func _isCompletedMethod; + private readonly Func _getResultMethod; + private readonly Action _onCompletedMethod; + private readonly Action _unsafeOnCompletedMethod; + + public Awaiter( + object customAwaiter, + Func isCompletedMethod, + Func getResultMethod, + Action onCompletedMethod, + Action unsafeOnCompletedMethod) + { + _customAwaiter = customAwaiter; + _isCompletedMethod = isCompletedMethod; + _getResultMethod = getResultMethod; + _onCompletedMethod = onCompletedMethod; + _unsafeOnCompletedMethod = unsafeOnCompletedMethod; + } + + public bool IsCompleted => _isCompletedMethod(_customAwaiter); + + public object GetResult() => _getResultMethod(_customAwaiter); + + public void OnCompleted(Action continuation) + { + _onCompletedMethod(_customAwaiter, continuation); + } + + public void UnsafeOnCompleted(Action continuation) + { + // If the underlying awaitable implements ICriticalNotifyCompletion, use its UnsafeOnCompleted. + // If not, fall back on using its OnCompleted. + // + // Why this is safe: + // - Implementing ICriticalNotifyCompletion is a way of saying the caller can choose whether it + // needs the execution context to be preserved (which it signals by calling OnCompleted), or + // that it doesn't (which it signals by calling UnsafeOnCompleted). Obviously it's faster *not* + // to preserve and restore the context, so we prefer that where possible. + // - If a caller doesn't need the execution context to be preserved and hence calls UnsafeOnCompleted, + // there's no harm in preserving it anyway - it's just a bit of wasted cost. That's what will happen + // if a caller sees that the proxy implements ICriticalNotifyCompletion but the proxy chooses to + // pass the call on to the underlying awaitable's OnCompleted method. + + var underlyingMethodToUse = _unsafeOnCompletedMethod ?? _onCompletedMethod; + underlyingMethodToUse(_customAwaiter, continuation); + } + } + } +} \ No newline at end of file diff --git a/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs new file mode 100644 index 0000000000..2198c0ce45 --- /dev/null +++ b/src/Shared/ObjectMethodExecutor/ObjectMethodExecutorFSharpSupport.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying + /// an for mapping instances of that type to a C# awaitable. + /// + /// + /// The main design goal here is to avoid taking a compile-time dependency on + /// FSharp.Core.dll, because non-F# applications wouldn't use it. So all the references + /// to FSharp types have to be constructed dynamically at runtime. + /// + internal static class ObjectMethodExecutorFSharpSupport + { + private static object _fsharpValuesCacheLock = new object(); + private static Assembly _fsharpCoreAssembly; + private static MethodInfo _fsharpAsyncStartAsTaskGenericMethod; + private static PropertyInfo _fsharpOptionOfTaskCreationOptionsNoneProperty; + private static PropertyInfo _fsharpOptionOfCancellationTokenNoneProperty; + + public static bool TryBuildCoercerFromFSharpAsyncToAwaitable( + Type possibleFSharpAsyncType, + out Expression coerceToAwaitableExpression, + out Type awaitableType) + { + var methodReturnGenericType = possibleFSharpAsyncType.IsGenericType + ? possibleFSharpAsyncType.GetGenericTypeDefinition() + : null; + + if (!IsFSharpAsyncOpenGenericType(methodReturnGenericType)) + { + coerceToAwaitableExpression = null; + awaitableType = null; + return false; + } + + var awaiterResultType = possibleFSharpAsyncType.GetGenericArguments().Single(); + awaitableType = typeof(Task<>).MakeGenericType(awaiterResultType); + + // coerceToAwaitableExpression = (object fsharpAsync) => + // { + // return (object)FSharpAsync.StartAsTask( + // (Microsoft.FSharp.Control.FSharpAsync)fsharpAsync, + // FSharpOption.None, + // FSharpOption.None); + // }; + var startAsTaskClosedMethod = _fsharpAsyncStartAsTaskGenericMethod + .MakeGenericMethod(awaiterResultType); + var coerceToAwaitableParam = Expression.Parameter(typeof(object)); + coerceToAwaitableExpression = Expression.Lambda( + Expression.Convert( + Expression.Call( + startAsTaskClosedMethod, + Expression.Convert(coerceToAwaitableParam, possibleFSharpAsyncType), + Expression.MakeMemberAccess(null, _fsharpOptionOfTaskCreationOptionsNoneProperty), + Expression.MakeMemberAccess(null, _fsharpOptionOfCancellationTokenNoneProperty)), + typeof(object)), + coerceToAwaitableParam); + + return true; + } + + private static bool IsFSharpAsyncOpenGenericType(Type possibleFSharpAsyncGenericType) + { + var typeFullName = possibleFSharpAsyncGenericType?.FullName; + if (!string.Equals(typeFullName, "Microsoft.FSharp.Control.FSharpAsync`1", StringComparison.Ordinal)) + { + return false; + } + + lock (_fsharpValuesCacheLock) + { + if (_fsharpCoreAssembly != null) + { + // Since we've already found the real FSharpAsync.Core assembly, we just have + // to check that the supplied FSharpAsync`1 type is the one from that assembly. + return possibleFSharpAsyncGenericType.Assembly == _fsharpCoreAssembly; + } + else + { + // We'll keep trying to find the FSharp types/values each time any type called + // FSharpAsync`1 is supplied. + return TryPopulateFSharpValueCaches(possibleFSharpAsyncGenericType); + } + } + } + + private static bool TryPopulateFSharpValueCaches(Type possibleFSharpAsyncGenericType) + { + var assembly = possibleFSharpAsyncGenericType.Assembly; + var fsharpOptionType = assembly.GetType("Microsoft.FSharp.Core.FSharpOption`1"); + var fsharpAsyncType = assembly.GetType("Microsoft.FSharp.Control.FSharpAsync"); + + if (fsharpOptionType == null || fsharpAsyncType == null) + { + return false; + } + + // Get a reference to FSharpOption.None + var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType + .MakeGenericType(typeof(TaskCreationOptions)); + _fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpOption.None + var fsharpOptionOfCancellationTokenType = fsharpOptionType + .MakeGenericType(typeof(CancellationToken)); + _fsharpOptionOfCancellationTokenNoneProperty = fsharpOptionOfCancellationTokenType + .GetTypeInfo() + .GetRuntimeProperty("None"); + + // Get a reference to FSharpAsync.StartAsTask<> + var fsharpAsyncMethods = fsharpAsyncType + .GetRuntimeMethods() + .Where(m => m.Name.Equals("StartAsTask", StringComparison.Ordinal)); + foreach (var candidateMethodInfo in fsharpAsyncMethods) + { + var parameters = candidateMethodInfo.GetParameters(); + if (parameters.Length == 3 + && TypesHaveSameIdentity(parameters[0].ParameterType, possibleFSharpAsyncGenericType) + && parameters[1].ParameterType == fsharpOptionOfTaskCreationOptionsType + && parameters[2].ParameterType == fsharpOptionOfCancellationTokenType) + { + // This really does look like the correct method (and hence assembly). + _fsharpAsyncStartAsTaskGenericMethod = candidateMethodInfo; + _fsharpCoreAssembly = assembly; + break; + } + } + + return _fsharpCoreAssembly != null; + } + + private static bool TypesHaveSameIdentity(Type type1, Type type2) + { + return type1.Assembly == type2.Assembly + && string.Equals(type1.Namespace, type2.Namespace, StringComparison.Ordinal) + && string.Equals(type1.Name, type2.Name, StringComparison.Ordinal); + } + } +} diff --git a/src/Shared/Process/ProcessExtensions.cs b/src/Shared/Process/ProcessExtensions.cs new file mode 100644 index 0000000000..c6cbd1f970 --- /dev/null +++ b/src/Shared/Process/ProcessExtensions.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +namespace Microsoft.Extensions.Internal +{ + internal static class ProcessExtensions + { + private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); + + public static void KillTree(this Process process) => process.KillTree(_defaultTimeout); + + public static void KillTree(this Process process, TimeSpan timeout) + { + var pid = process.Id; + if (_isWindows) + { + RunProcessAndWaitForExit( + "taskkill", + $"/T /F /PID {pid}", + timeout, + out var _); + } + else + { + var children = new HashSet(); + GetAllChildIdsUnix(pid, children, timeout); + foreach (var childId in children) + { + KillProcessUnix(childId, timeout); + } + KillProcessUnix(pid, timeout); + } + } + + private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) + { + RunProcessAndWaitForExit( + "pgrep", + $"-P {parentId}", + timeout, + out var stdout); + + if (!string.IsNullOrEmpty(stdout)) + { + using (var reader = new StringReader(stdout)) + { + while (true) + { + var text = reader.ReadLine(); + if (text == null) + { + return; + } + + if (int.TryParse(text, out var id)) + { + children.Add(id); + // Recursively get the children + GetAllChildIdsUnix(id, children, timeout); + } + } + } + } + } + + private static void KillProcessUnix(int processId, TimeSpan timeout) + { + RunProcessAndWaitForExit( + "kill", + $"-TERM {processId}", + timeout, + out var stdout); + } + + private static void RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) + { + var startInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + }; + + var process = Process.Start(startInfo); + + stdout = null; + if (process.WaitForExit((int)timeout.TotalMilliseconds)) + { + stdout = process.StandardOutput.ReadToEnd(); + } + else + { + process.Kill(); + } + } + } +} diff --git a/src/Shared/PropertyActivator/PropertyActivator.cs b/src/Shared/PropertyActivator/PropertyActivator.cs new file mode 100644 index 0000000000..925f6a76ae --- /dev/null +++ b/src/Shared/PropertyActivator/PropertyActivator.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; + +namespace Microsoft.Extensions.Internal +{ + internal class PropertyActivator + { + private readonly Func _valueAccessor; + private readonly Action _fastPropertySetter; + + public PropertyActivator( + PropertyInfo propertyInfo, + Func valueAccessor) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + if (valueAccessor == null) + { + throw new ArgumentNullException(nameof(valueAccessor)); + } + + PropertyInfo = propertyInfo; + _valueAccessor = valueAccessor; + _fastPropertySetter = PropertyHelper.MakeFastPropertySetter(propertyInfo); + } + + public PropertyInfo PropertyInfo { get; private set; } + + public object Activate(object instance, TContext context) + { + if (instance == null) + { + throw new ArgumentNullException(nameof(instance)); + } + + var value = _valueAccessor(context); + _fastPropertySetter(instance, value); + return value; + } + + public static PropertyActivator[] GetPropertiesToActivate( + Type type, + Type activateAttributeType, + Func> createActivateInfo) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (activateAttributeType == null) + { + throw new ArgumentNullException(nameof(activateAttributeType)); + } + + if (createActivateInfo == null) + { + throw new ArgumentNullException(nameof(createActivateInfo)); + } + + return GetPropertiesToActivate(type, activateAttributeType, createActivateInfo, includeNonPublic: false); + } + + public static PropertyActivator[] GetPropertiesToActivate( + Type type, + Type activateAttributeType, + Func> createActivateInfo, + bool includeNonPublic) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (activateAttributeType == null) + { + throw new ArgumentNullException(nameof(activateAttributeType)); + } + + if (createActivateInfo == null) + { + throw new ArgumentNullException(nameof(createActivateInfo)); + } + + var properties = type.GetRuntimeProperties() + .Where((property) => + { + return + property.IsDefined(activateAttributeType) && + property.GetIndexParameters().Length == 0 && + property.SetMethod != null && + !property.SetMethod.IsStatic; + }); + + if (!includeNonPublic) + { + properties = properties.Where(property => property.SetMethod.IsPublic); + } + + return properties.Select(createActivateInfo).ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Shared/PropertyHelper/PropertyHelper.cs b/src/Shared/PropertyHelper/PropertyHelper.cs new file mode 100644 index 0000000000..f6aad151e5 --- /dev/null +++ b/src/Shared/PropertyHelper/PropertyHelper.cs @@ -0,0 +1,551 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Microsoft.Extensions.Internal +{ + internal class PropertyHelper + { + // Delegate type for a by-ref property getter + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter)); + + private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference)); + + private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter)); + + private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference)); + + private static readonly MethodInfo CallPropertySetterOpenGenericMethod = + typeof(PropertyHelper).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter)); + + // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. + private static readonly ConcurrentDictionary PropertiesCache = + new ConcurrentDictionary(); + + private static readonly ConcurrentDictionary VisiblePropertiesCache = + new ConcurrentDictionary(); + + // We need to be able to check if a type is a 'ref struct' - but we need to be able to compile + // for platforms where the attribute is not defined, like net46. So we can fetch the attribute + // by late binding. If the attribute isn't defined, then we assume we won't encounter any + // 'ref struct' types. + private static readonly Type IsByRefLikeAttribute = Type.GetType("System.Runtime.CompilerServices.IsByRefLikeAttribute", throwOnError: false); + + private Action _valueSetter; + private Func _valueGetter; + + /// + /// Initializes a fast . + /// This constructor does not cache the helper. For caching, use . + /// + public PropertyHelper(PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + Property = property; + Name = property.Name; + } + + /// + /// Gets the backing . + /// + public PropertyInfo Property { get; } + + /// + /// Gets (or sets in derived types) the property name. + /// + public virtual string Name { get; protected set; } + + /// + /// Gets the property value getter. + /// + public Func ValueGetter + { + get + { + if (_valueGetter == null) + { + _valueGetter = MakeFastPropertyGetter(Property); + } + + return _valueGetter; + } + } + + /// + /// Gets the property value setter. + /// + public Action ValueSetter + { + get + { + if (_valueSetter == null) + { + _valueSetter = MakeFastPropertySetter(Property); + } + + return _valueSetter; + } + } + + /// + /// Returns the property value for the specified . + /// + /// The object whose property value will be returned. + /// The property value. + public object GetValue(object instance) + { + return ValueGetter(instance); + } + + /// + /// Sets the property value for the specified . + /// + /// The object whose property value will be set. + /// The property value. + public void SetValue(object instance, object value) + { + ValueSetter(instance, value); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// underlying type. + /// + /// The type info to extract property accessors for. + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetProperties(TypeInfo typeInfo) + { + return GetProperties(typeInfo.AsType()); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// specified type. + /// + /// The type to extract property accessors for. + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetProperties(Type type) + { + return GetProperties(type, CreateInstance, PropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type info to extract property accessors for. + /// + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo) + { + return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type to extract property accessors for. + /// + /// A cached array of all public properties of the specified type. + /// + public static PropertyHelper[] GetVisibleProperties(Type type) + { + return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallPropertyGetterOpenGenericMethod, + CallPropertyGetterByReferenceOpenGenericMethod); + } + + /// + /// Creates a single fast property getter which is safe for a null input object. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallNullSafePropertyGetterOpenGenericMethod, + CallNullSafePropertyGetterByReferenceOpenGenericMethod); + } + + private static Func MakeFastPropertyGetter( + PropertyInfo propertyInfo, + MethodInfo propertyGetterWrapperMethod, + MethodInfo propertyGetterByRefWrapperMethod) + { + Debug.Assert(propertyInfo != null); + + // Must be a generic method with a Func<,> parameter + Debug.Assert(propertyGetterWrapperMethod != null); + Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); + + // Must be a generic method with a ByRefFunc<,> parameter + Debug.Assert(propertyGetterByRefWrapperMethod != null); + Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); + + var getMethod = propertyInfo.GetMethod; + Debug.Assert(getMethod != null); + Debug.Assert(!getMethod.IsStatic); + Debug.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + if (getMethod.DeclaringType.GetTypeInfo().IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + return MakeFastPropertyGetter( + typeof(ByRefFunc<,>), + getMethod, + propertyGetterByRefWrapperMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + return MakeFastPropertyGetter( + typeof(Func<,>), + getMethod, + propertyGetterWrapperMethod); + } + } + + private static Func MakeFastPropertyGetter( + Type openGenericDelegateType, + MethodInfo propertyGetMethod, + MethodInfo openGenericWrapperMethod) + { + var typeInput = propertyGetMethod.DeclaringType; + var typeOutput = propertyGetMethod.ReturnType; + + var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); + var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); + + var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); + var accessorDelegate = wrapperDelegateMethod.CreateDelegate( + typeof(Func), + propertyGetterDelegate); + + return (Func)accessorDelegate; + } + + /// + /// Creates a single fast property setter for reference types. The result is not cached. + /// + /// propertyInfo to extract the setter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. This only works for reference types. + /// + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType); + + var setMethod = propertyInfo.SetMethod; + Debug.Assert(setMethod != null); + Debug.Assert(!setMethod.IsStatic); + Debug.Assert(setMethod.ReturnType == typeof(void)); + var parameters = setMethod.GetParameters(); + Debug.Assert(parameters.Length == 1); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + var typeInput = setMethod.DeclaringType; + var parameterType = parameters[0].ParameterType; + + // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } + var propertySetterAsAction = + setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); + var callPropertySetterClosedGenericMethod = + CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); + var callPropertySetterDelegate = + callPropertySetterClosedGenericMethod.CreateDelegate( + typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; + } + + /// + /// Given an object, adds each instance property with a public get method as a key and its + /// associated value to a dictionary. + /// + /// If the object is already an instance, then a copy + /// is returned. + /// + /// + /// The implementation of PropertyHelper will cache the property accessors per-type. This is + /// faster when the same type is used multiple times with ObjectToDictionary. + /// + public static IDictionary ObjectToDictionary(object value) + { + var dictionary = value as IDictionary; + if (dictionary != null) + { + return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } + + dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (value != null) + { + foreach (var helper in GetProperties(value.GetType())) + { + dictionary[helper.Name] = helper.GetValue(value); + } + } + + return dictionary; + } + + private static PropertyHelper CreateInstance(PropertyInfo property) + { + return new PropertyHelper(property); + } + + // Called via reflection + private static object CallPropertyGetter( + Func getter, + object target) + { + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallPropertyGetterByReference( + ByRefFunc getter, + object target) + { + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + // Called via reflection + private static object CallNullSafePropertyGetter( + Func getter, + object target) + { + if (target == null) + { + return null; + } + + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallNullSafePropertyGetterByReference( + ByRefFunc getter, + object target) + { + if (target == null) + { + return null; + } + + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + private static void CallPropertySetter( + Action setter, + object target, + object value) + { + setter((TDeclaringType)target, (TValue)value); + } + + protected static PropertyHelper[] GetVisibleProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary allPropertiesCache, + ConcurrentDictionary visiblePropertiesCache) + { + PropertyHelper[] result; + if (visiblePropertiesCache.TryGetValue(type, out result)) + { + return result; + } + + // The simple and common case, this is normal POCO object - no need to allocate. + var allPropertiesDefinedOnType = true; + var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); + foreach (var propertyHelper in allProperties) + { + if (propertyHelper.Property.DeclaringType != type) + { + allPropertiesDefinedOnType = false; + break; + } + } + + if (allPropertiesDefinedOnType) + { + result = allProperties; + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + // There's some inherited properties here, so we need to check for hiding via 'new'. + var filteredProperties = new List(allProperties.Length); + foreach (var propertyHelper in allProperties) + { + var declaringType = propertyHelper.Property.DeclaringType; + if (declaringType == type) + { + filteredProperties.Add(propertyHelper); + continue; + } + + // If this property was declared on a base type then look for the definition closest to the + // the type to see if we should include it. + var ignoreProperty = false; + + // Walk up the hierarchy until we find the type that actually declares this + // PropertyInfo. + var currentTypeInfo = type.GetTypeInfo(); + var declaringTypeInfo = declaringType.GetTypeInfo(); + while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo) + { + // We've found a 'more proximal' public definition + var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name); + if (declaredProperty != null) + { + ignoreProperty = true; + break; + } + + currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo(); + } + + if (!ignoreProperty) + { + filteredProperties.Add(propertyHelper); + } + } + + result = filteredProperties.ToArray(); + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + protected static PropertyHelper[] GetProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary cache) + { + // Unwrap nullable types. This means Nullable.Value and Nullable.HasValue will not be + // part of the sequence of properties returned by this method. + type = Nullable.GetUnderlyingType(type) ?? type; + + PropertyHelper[] helpers; + if (!cache.TryGetValue(type, out helpers)) + { + // We avoid loading indexed properties using the Where statement. + var properties = type.GetRuntimeProperties().Where(IsInterestingProperty); + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsInterface) + { + // Reflection does not return information about inherited properties on the interface itself. + properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( + interfaceType => interfaceType.GetRuntimeProperties().Where(IsInterestingProperty))); + } + + helpers = properties.Select(p => createPropertyHelper(p)).ToArray(); + cache.TryAdd(type, helpers); + } + + return helpers; + } + + + private static bool IsInterestingProperty(PropertyInfo property) + { + // For improving application startup time, do not use GetIndexParameters() api early in this check as it + // creates a copy of parameter array and also we would like to check for the presence of a get method + // and short circuit asap. + return + property.GetMethod != null && + property.GetMethod.IsPublic && + !property.GetMethod.IsStatic && + + // PropertyHelper can't work with ref structs. + !IsRefStructProperty(property) && + + // Indexed properties are not useful (or valid) for grabbing properties off an object. + property.GetMethod.GetParameters().Length == 0; + } + + // PropertyHelper can't really interact with ref-struct properties since they can't be + // boxed and can't be used as generic types. We just ignore them. + // + // see: https://github.com/aspnet/Mvc/issues/8545 + private static bool IsRefStructProperty(PropertyInfo property) + { + return + IsByRefLikeAttribute != null && + property.PropertyType.IsValueType && + property.PropertyType.IsDefined(IsByRefLikeAttribute); + } + } +} diff --git a/src/Shared/RazorViews/AttributeValue.cs b/src/Shared/RazorViews/AttributeValue.cs new file mode 100644 index 0000000000..7a066a7040 --- /dev/null +++ b/src/Shared/RazorViews/AttributeValue.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.Extensions.RazorViews +{ + internal class AttributeValue + { + public AttributeValue(string prefix, object value, bool literal) + { + Prefix = prefix; + Value = value; + Literal = literal; + } + + public string Prefix { get; } + + public object Value { get; } + + public bool Literal { get; } + + public static AttributeValue FromTuple(Tuple value) + { + return new AttributeValue(value.Item1, value.Item2, value.Item3); + } + + public static AttributeValue FromTuple(Tuple value) + { + return new AttributeValue(value.Item1, value.Item2, value.Item3); + } + + public static implicit operator AttributeValue(Tuple value) + { + return FromTuple(value); + } + } +} \ No newline at end of file diff --git a/src/Shared/RazorViews/BaseView.cs b/src/Shared/RazorViews/BaseView.cs new file mode 100644 index 0000000000..a171d8d1f2 --- /dev/null +++ b/src/Shared/RazorViews/BaseView.cs @@ -0,0 +1,279 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.Extensions.RazorViews +{ + /// + /// Infrastructure + /// + internal abstract class BaseView + { + private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private readonly Stack _textWriterStack = new Stack(); + + /// + /// The request context + /// + protected HttpContext Context { get; private set; } + + /// + /// The request + /// + protected HttpRequest Request { get; private set; } + + /// + /// The response + /// + protected HttpResponse Response { get; private set; } + + /// + /// The output stream + /// + protected TextWriter Output { get; private set; } + + /// + /// Html encoder used to encode content. + /// + protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default; + + /// + /// Url encoder used to encode content. + /// + protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default; + + /// + /// JavaScript encoder used to encode content. + /// + protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default; + + /// + /// Execute an individual request + /// + /// + public async Task ExecuteAsync(HttpContext context) + { + Context = context; + Request = Context.Request; + Response = Context.Response; + Output = new StreamWriter(Response.Body, UTF8NoBOM, 4096, leaveOpen: true); + await ExecuteAsync(); + Output.Dispose(); + } + + /// + /// Execute an individual request + /// + public abstract Task ExecuteAsync(); + + protected virtual void PushWriter(TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + _textWriterStack.Push(Output); + Output = writer; + } + + protected virtual TextWriter PopWriter() + { + Output = _textWriterStack.Pop(); + return Output; + } + + /// + /// Write the given value without HTML encoding directly to . + /// + /// The to write. + protected void WriteLiteral(object value) + { + WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture)); + } + + /// + /// Write the given value without HTML encoding directly to . + /// + /// The to write. + protected void WriteLiteral(string value) + { + if (!string.IsNullOrEmpty(value)) + { + Output.Write(value); + } + } + + private List AttributeValues { get; set; } + + protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno) + { + if (AttributeValues == null) + { + AttributeValues = new List(); + } + + AttributeValues.Add(value.ToString()); + } + + private string AttributeEnding { get; set; } + + protected void BeginWriteAttribute(string name, string begining, int startPosition, string ending, int endPosition, int thingy) + { + Debug.Assert(string.IsNullOrEmpty(AttributeEnding)); + + Output.Write(begining); + AttributeEnding = ending; + } + + protected void EndWriteAttribute() + { + Debug.Assert(!string.IsNullOrEmpty(AttributeEnding)); + + var attributes = string.Join(" ", AttributeValues); + Output.Write(attributes); + AttributeValues = null; + + Output.Write(AttributeEnding); + AttributeEnding = null; + } + + /// + /// Writes the given attribute to the given writer + /// + /// The name of the attribute to write + /// The value of the prefix + /// The value of the suffix + /// The s to write. + protected void WriteAttribute( + string name, + string leader, + string trailer, + params AttributeValue[] values) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (leader == null) + { + throw new ArgumentNullException(nameof(leader)); + } + + if (trailer == null) + { + throw new ArgumentNullException(nameof(trailer)); + } + + WriteLiteral(leader); + foreach (var value in values) + { + WriteLiteral(value.Prefix); + + // The special cases here are that the value we're writing might already be a string, or that the + // value might be a bool. If the value is the bool 'true' we want to write the attribute name + // instead of the string 'true'. If the value is the bool 'false' we don't want to write anything. + // Otherwise the value is another object (perhaps an HtmlString) and we'll ask it to format itself. + string stringValue; + if (value.Value is bool) + { + if ((bool)value.Value) + { + stringValue = name; + } + else + { + continue; + } + } + else + { + stringValue = value.Value as string; + } + + // Call the WriteTo(string) overload when possible + if (value.Literal && stringValue != null) + { + WriteLiteral(stringValue); + } + else if (value.Literal) + { + WriteLiteral(value.Value); + } + else if (stringValue != null) + { + Write(stringValue); + } + else + { + Write(value.Value); + } + } + WriteLiteral(trailer); + } + + /// + /// is invoked + /// + /// The to invoke + protected void Write(HelperResult result) + { + Write(result); + } + + /// + /// Writes the specified to . + /// + /// The to write. + /// + /// is invoked for types. + /// For all other types, the encoded result of is written to + /// . + /// + protected void Write(object value) + { + if (value is HelperResult helperResult) + { + helperResult.WriteTo(Output); + } + else + { + Write(Convert.ToString(value, CultureInfo.InvariantCulture)); + } + } + + /// + /// Writes the specified with HTML encoding to . + /// + /// The to write. + protected void Write(string value) + { + WriteLiteral(HtmlEncoder.Encode(value)); + } + + protected string HtmlEncodeAndReplaceLineBreaks(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + // Split on line breaks before passing it through the encoder. + return string.Join("
" + Environment.NewLine, + input.Split(new[] { "\r\n" }, StringSplitOptions.None) + .SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None)) + .Select(HtmlEncoder.Encode)); + } + } +} \ No newline at end of file diff --git a/src/Shared/RazorViews/HelperResult.cs b/src/Shared/RazorViews/HelperResult.cs new file mode 100644 index 0000000000..c79944aae6 --- /dev/null +++ b/src/Shared/RazorViews/HelperResult.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +namespace Microsoft.Extensions.RazorViews +{ + /// + /// Represents a deferred write operation in a . + /// + internal class HelperResult + { + /// + /// Creates a new instance of . + /// + /// The delegate to invoke when is called. + public HelperResult(Action action) + { + WriteAction = action; + } + + public Action WriteAction { get; } + + /// + /// Method invoked to produce content from the . + /// + /// The instance to write to. + public void WriteTo(TextWriter writer) + { + WriteAction(writer); + } + } +} \ No newline at end of file diff --git a/src/Shared/SecurityHelper/SecurityHelper.cs b/src/Shared/SecurityHelper/SecurityHelper.cs new file mode 100644 index 0000000000..408ef6b224 --- /dev/null +++ b/src/Shared/SecurityHelper/SecurityHelper.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Security.Claims; + +namespace Microsoft.Extensions.Internal +{ + /// + /// Helper code used when implementing authentication middleware + /// + internal static class SecurityHelper + { + /// + /// Add all ClaimsIdentities from an additional ClaimPrincipal to the ClaimsPrincipal + /// Merges a new claims principal, placing all new identities first, and eliminating + /// any empty unauthenticated identities from context.User + /// + /// The containing existing . + /// The containing to be added. + public static ClaimsPrincipal MergeUserPrincipal(ClaimsPrincipal existingPrincipal, ClaimsPrincipal additionalPrincipal) + { + var newPrincipal = new ClaimsPrincipal(); + + // New principal identities go first + if (additionalPrincipal != null) + { + newPrincipal.AddIdentities(additionalPrincipal.Identities); + } + + // Then add any existing non empty or authenticated identities + if (existingPrincipal != null) + { + newPrincipal.AddIdentities(existingPrincipal.Identities.Where(i => i.IsAuthenticated || i.Claims.Any())); + } + return newPrincipal; + } + } +} diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs new file mode 100644 index 0000000000..8862611136 --- /dev/null +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetails.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + /// + /// Contains details for individual exception messages. + /// + internal class ExceptionDetails + { + /// + /// An individual exception + /// + public Exception Error { get; set; } + + /// + /// The generated stack frames + /// + public IEnumerable StackFrames { get; set; } + + /// + /// Gets or sets the summary message. + /// + public string ErrorMessage { get; set; } + } +} diff --git a/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs new file mode 100644 index 0000000000..2d1dd20710 --- /dev/null +++ b/src/Shared/StackTrace/ExceptionDetails/ExceptionDetailsProvider.cs @@ -0,0 +1,170 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class ExceptionDetailsProvider + { + private readonly IFileProvider _fileProvider; + private readonly int _sourceCodeLineCount; + + public ExceptionDetailsProvider(IFileProvider fileProvider, int sourceCodeLineCount) + { + _fileProvider = fileProvider; + _sourceCodeLineCount = sourceCodeLineCount; + } + + public IEnumerable GetDetails(Exception exception) + { + var exceptions = FlattenAndReverseExceptionTree(exception); + + foreach (var ex in exceptions) + { + yield return new ExceptionDetails + { + Error = ex, + StackFrames = StackTraceHelper.GetFrames(ex) + .Select(frame => GetStackFrameSourceCodeInfo( + frame.MethodDisplayInfo.ToString(), + frame.FilePath, + frame.LineNumber)) + }; + } + } + + private static IEnumerable FlattenAndReverseExceptionTree(Exception ex) + { + // ReflectionTypeLoadException is special because the details are in + // the LoaderExceptions property + var typeLoadException = ex as ReflectionTypeLoadException; + if (typeLoadException != null) + { + var typeLoadExceptions = new List(); + foreach (var loadException in typeLoadException.LoaderExceptions) + { + typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException)); + } + + typeLoadExceptions.Add(ex); + return typeLoadExceptions; + } + + var list = new List(); + if (ex is AggregateException aggregateException) + { + list.Add(ex); + foreach (var innerException in aggregateException.Flatten().InnerExceptions) + { + list.Add(innerException); + } + } + + else + { + while (ex != null) + { + list.Add(ex); + ex = ex.InnerException; + } + list.Reverse(); + } + + return list; + } + + // make it internal to enable unit testing + internal StackFrameSourceCodeInfo GetStackFrameSourceCodeInfo(string method, string filePath, int lineNumber) + { + var stackFrame = new StackFrameSourceCodeInfo + { + Function = method, + File = filePath, + Line = lineNumber + }; + + if (string.IsNullOrEmpty(stackFrame.File)) + { + return stackFrame; + } + + IEnumerable lines = null; + if (File.Exists(stackFrame.File)) + { + lines = File.ReadLines(stackFrame.File); + } + else + { + // Handle relative paths and embedded files + var fileInfo = _fileProvider.GetFileInfo(stackFrame.File); + if (fileInfo.Exists) + { + // ReadLines doesn't accept a stream. Use ReadLines as its more efficient + // relative to reading lines via stream reader + if (!string.IsNullOrEmpty(fileInfo.PhysicalPath)) + { + lines = File.ReadLines(fileInfo.PhysicalPath); + } + else + { + lines = ReadLines(fileInfo); + } + } + } + + if (lines != null) + { + ReadFrameContent(stackFrame, lines, stackFrame.Line, stackFrame.Line); + } + + return stackFrame; + } + + // make it internal to enable unit testing + internal void ReadFrameContent( + StackFrameSourceCodeInfo frame, + IEnumerable allLines, + int errorStartLineNumberInFile, + int errorEndLineNumberInFile) + { + // Get the line boundaries in the file to be read and read all these lines at once into an array. + var preErrorLineNumberInFile = Math.Max(errorStartLineNumberInFile - _sourceCodeLineCount, 1); + var postErrorLineNumberInFile = errorEndLineNumberInFile + _sourceCodeLineCount; + var codeBlock = allLines + .Skip(preErrorLineNumberInFile - 1) + .Take(postErrorLineNumberInFile - preErrorLineNumberInFile + 1) + .ToArray(); + + var numOfErrorLines = (errorEndLineNumberInFile - errorStartLineNumberInFile) + 1; + var errorStartLineNumberInArray = errorStartLineNumberInFile - preErrorLineNumberInFile; + + frame.PreContextLine = preErrorLineNumberInFile; + frame.PreContextCode = codeBlock.Take(errorStartLineNumberInArray).ToArray(); + frame.ContextCode = codeBlock + .Skip(errorStartLineNumberInArray) + .Take(numOfErrorLines) + .ToArray(); + frame.PostContextCode = codeBlock + .Skip(errorStartLineNumberInArray + numOfErrorLines) + .ToArray(); + } + + private static IEnumerable ReadLines(IFileInfo fileInfo) + { + using (var reader = new StreamReader(fileInfo.CreateReadStream())) + { + string line; + while ((line = reader.ReadLine()) != null) + { + yield return line; + } + } + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs new file mode 100644 index 0000000000..b1c0ccc188 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/MethodDisplayInfo.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class MethodDisplayInfo + { + public string DeclaringTypeName { get; set; } + + public string Name { get; set; } + + public string GenericArguments { get; set; } + + public string SubMethod { get; set; } + + public IEnumerable Parameters { get; set; } + + public override string ToString() + { + var builder = new StringBuilder(); + if (!string.IsNullOrEmpty(DeclaringTypeName)) + { + builder + .Append(DeclaringTypeName) + .Append("."); + } + + builder.Append(Name); + builder.Append(GenericArguments); + + builder.Append("("); + builder.Append(string.Join(", ", Parameters.Select(p => p.ToString()))); + builder.Append(")"); + + if (!string.IsNullOrEmpty(SubMethod)) + { + builder.Append("+"); + builder.Append(SubMethod); + builder.Append("()"); + } + + return builder.ToString(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs b/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs new file mode 100644 index 0000000000..1199a8386d --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/ParameterDisplayInfo.cs @@ -0,0 +1,33 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Text; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class ParameterDisplayInfo + { + public string Name { get; set; } + + public string Type { get; set; } + + public string Prefix { get; set; } + + public override string ToString() + { + var builder = new StringBuilder(); + if (!string.IsNullOrEmpty(Prefix)) + { + builder + .Append(Prefix) + .Append(" "); + } + + builder.Append(Type); + builder.Append(" "); + builder.Append(Name); + + return builder.ToString(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs new file mode 100644 index 0000000000..ff6a4947f8 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/PortablePdbReader.cs @@ -0,0 +1,135 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Reflection.Metadata; +using System.Reflection.Metadata.Ecma335; +using System.Reflection.PortableExecutable; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class PortablePdbReader : IDisposable + { + private readonly Dictionary _cache = + new Dictionary(StringComparer.Ordinal); + + public void PopulateStackFrame(StackFrameInfo frameInfo, MethodBase method, int IlOffset) + { + if (method.Module.Assembly.IsDynamic) + { + return; + } + + var metadataReader = GetMetadataReader(method.Module.Assembly.Location); + + if (metadataReader == null) + { + return; + } + + var methodToken = MetadataTokens.Handle(method.MetadataToken); + + Debug.Assert(methodToken.Kind == HandleKind.MethodDefinition); + + var handle = ((MethodDefinitionHandle)methodToken).ToDebugInformationHandle(); + + if (!handle.IsNil) + { + var methodDebugInfo = metadataReader.GetMethodDebugInformation(handle); + var sequencePoints = methodDebugInfo.GetSequencePoints(); + SequencePoint? bestPointSoFar = null; + + foreach (var point in sequencePoints) + { + if (point.Offset > IlOffset) + { + break; + } + + if (point.StartLine != SequencePoint.HiddenLine) + { + bestPointSoFar = point; + } + } + + if (bestPointSoFar.HasValue) + { + frameInfo.LineNumber = bestPointSoFar.Value.StartLine; + frameInfo.FilePath = metadataReader.GetString(metadataReader.GetDocument(bestPointSoFar.Value.Document).Name); + } + } + } + + private MetadataReader GetMetadataReader(string assemblyPath) + { + MetadataReaderProvider provider = null; + if (!_cache.TryGetValue(assemblyPath, out provider)) + { + var pdbPath = GetPdbPath(assemblyPath); + + if (!string.IsNullOrEmpty(pdbPath) && File.Exists(pdbPath) && IsPortable(pdbPath)) + { + var pdbStream = File.OpenRead(pdbPath); + provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream); + } + + _cache[assemblyPath] = provider; + } + + return provider?.GetMetadataReader(); + } + + private static string GetPdbPath(string assemblyPath) + { + if (string.IsNullOrEmpty(assemblyPath)) + { + return null; + } + + if (File.Exists(assemblyPath)) + { + var peStream = File.OpenRead(assemblyPath); + + using (var peReader = new PEReader(peStream)) + { + foreach (var entry in peReader.ReadDebugDirectory()) + { + if (entry.Type == DebugDirectoryEntryType.CodeView) + { + var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); + var peDirectory = Path.GetDirectoryName(assemblyPath); + return Path.Combine(peDirectory, Path.GetFileName(codeViewData.Path)); + } + } + } + } + + return null; + } + + private static bool IsPortable(string pdbPath) + { + using (var pdbStream = File.OpenRead(pdbPath)) + { + return pdbStream.ReadByte() == 'B' && + pdbStream.ReadByte() == 'S' && + pdbStream.ReadByte() == 'J' && + pdbStream.ReadByte() == 'B'; + } + } + + public void Dispose() + { + foreach (var entry in _cache) + { + entry.Value?.Dispose(); + } + + _cache.Clear(); + } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs new file mode 100644 index 0000000000..ffd91f213c --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackFrameInfo.cs @@ -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. + +using System.Diagnostics; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class StackFrameInfo + { + public int LineNumber { get; set; } + + public string FilePath { get; set; } + + public StackFrame StackFrame { get; set; } + + public MethodDisplayInfo MethodDisplayInfo { get; set; } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs b/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs new file mode 100644 index 0000000000..2932e083b1 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackFrameSourceCodeInfo.cs @@ -0,0 +1,54 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + /// + /// Contains the source code where the exception occurred. + /// + internal class StackFrameSourceCodeInfo + { + /// + /// Function containing instruction + /// + public string Function { get; set; } + + /// + /// File containing the instruction + /// + public string File { get; set; } + + /// + /// The line number of the instruction + /// + public int Line { get; set; } + + /// + /// The line preceding the frame line + /// + public int PreContextLine { get; set; } + + /// + /// Lines of code before the actual error line(s). + /// + public IEnumerable PreContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Line(s) of code responsible for the error. + /// + public IEnumerable ContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Lines of code after the actual error line(s). + /// + public IEnumerable PostContextCode { get; set; } = Enumerable.Empty(); + + /// + /// Specific error details for this stack frame. + /// + public string ErrorDetails { get; set; } + } +} diff --git a/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs new file mode 100644 index 0000000000..5ce9a40903 --- /dev/null +++ b/src/Shared/StackTrace/StackFrame/StackTraceHelper.cs @@ -0,0 +1,261 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using Microsoft.Extensions.Internal; + +namespace Microsoft.Extensions.StackTrace.Sources +{ + internal class StackTraceHelper + { + public static IList GetFrames(Exception exception) + { + var frames = new List(); + + if (exception == null) + { + return frames; + } + + using (var portablePdbReader = new PortablePdbReader()) + { + var needFileInfo = true; + var stackTrace = new System.Diagnostics.StackTrace(exception, needFileInfo); + var stackFrames = stackTrace.GetFrames(); + + if (stackFrames == null) + { + return frames; + } + + for (var i = 0; i < stackFrames.Length; i++) + { + var frame = stackFrames[i]; + var method = frame.GetMethod(); + + // Always show last stackFrame + if (!ShowInStackTrace(method) && i < stackFrames.Length - 1) + { + continue; + } + + var stackFrame = new StackFrameInfo + { + StackFrame = frame, + FilePath = frame.GetFileName(), + LineNumber = frame.GetFileLineNumber(), + MethodDisplayInfo = GetMethodDisplayString(frame.GetMethod()), + }; + + if (string.IsNullOrEmpty(stackFrame.FilePath)) + { + // .NET Framework and older versions of mono don't support portable PDBs + // so we read it manually to get file name and line information + portablePdbReader.PopulateStackFrame(stackFrame, method, frame.GetILOffset()); + } + + frames.Add(stackFrame); + } + + return frames; + } + } + + internal static MethodDisplayInfo GetMethodDisplayString(MethodBase method) + { + // Special case: no method available + if (method == null) + { + return null; + } + + var methodDisplayInfo = new MethodDisplayInfo(); + + // Type name + var type = method.DeclaringType; + + var methodName = method.Name; + + if (type != null && type.IsDefined(typeof(CompilerGeneratedAttribute)) && + (typeof(IAsyncStateMachine).IsAssignableFrom(type) || typeof(IEnumerator).IsAssignableFrom(type))) + { + // Convert StateMachine methods to correct overload +MoveNext() + if (TryResolveStateMachineMethod(ref method, out type)) + { + methodDisplayInfo.SubMethod = methodName; + } + } + // ResolveStateMachineMethod may have set declaringType to null + if (type != null) + { + methodDisplayInfo.DeclaringTypeName = TypeNameHelper.GetTypeDisplayName(type, includeGenericParameterNames: true); + } + + // Method name + methodDisplayInfo.Name = method.Name; + if (method.IsGenericMethod) + { + var genericArguments = string.Join(", ", method.GetGenericArguments() + .Select(arg => TypeNameHelper.GetTypeDisplayName(arg, fullName: false, includeGenericParameterNames: true))); + methodDisplayInfo.GenericArguments += "<" + genericArguments + ">"; + } + + // Method parameters + methodDisplayInfo.Parameters = method.GetParameters().Select(parameter => + { + var parameterType = parameter.ParameterType; + + var prefix = string.Empty; + if (parameter.IsOut) + { + prefix = "out"; + } + else if (parameterType != null && parameterType.IsByRef) + { + prefix = "ref"; + } + + var parameterTypeString = "?"; + if (parameterType != null) + { + if (parameterType.IsByRef) + { + parameterType = parameterType.GetElementType(); + } + + parameterTypeString = TypeNameHelper.GetTypeDisplayName(parameterType, fullName: false, includeGenericParameterNames: true); + } + + return new ParameterDisplayInfo + { + Prefix = prefix, + Name = parameter.Name, + Type = parameterTypeString, + }; + }); + + return methodDisplayInfo; + } + + private static bool ShowInStackTrace(MethodBase method) + { + Debug.Assert(method != null); + + // Don't show any methods marked with the StackTraceHiddenAttribute + // https://github.com/dotnet/coreclr/pull/14652 + if (HasStackTraceHiddenAttribute(method)) + { + return false; + } + + + var type = method.DeclaringType; + if (type == null) + { + return true; + } + + if (HasStackTraceHiddenAttribute(type)) + { + return false; + } + + // Fallbacks for runtime pre-StackTraceHiddenAttribute + if (type == typeof(ExceptionDispatchInfo) && method.Name == "Throw") + { + return false; + } + else if (type == typeof(TaskAwaiter) || + type == typeof(TaskAwaiter<>) || + type == typeof(ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) || + type == typeof(ConfiguredTaskAwaitable<>.ConfiguredTaskAwaiter)) + { + switch (method.Name) + { + case "HandleNonSuccessAndDebuggerNotification": + case "ThrowForNonSuccess": + case "ValidateEnd": + case "GetResult": + return false; + } + } + + return true; + } + + private static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType) + { + Debug.Assert(method != null); + Debug.Assert(method.DeclaringType != null); + + declaringType = method.DeclaringType; + + var parentType = declaringType.DeclaringType; + if (parentType == null) + { + return false; + } + + var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly); + if (methods == null) + { + return false; + } + + foreach (var candidateMethod in methods) + { + var attributes = candidateMethod.GetCustomAttributes(); + if (attributes == null) + { + continue; + } + + foreach (var asma in attributes) + { + if (asma.StateMachineType == declaringType) + { + method = candidateMethod; + declaringType = candidateMethod.DeclaringType; + // Mark the iterator as changed; so it gets the + annotation of the original method + // async statemachines resolve directly to their builder methods so aren't marked as changed + return asma is IteratorStateMachineAttribute; + } + } + } + + return false; + } + + private static bool HasStackTraceHiddenAttribute(MemberInfo memberInfo) + { + IList attributes; + try + { + // Accessing MembmerInfo.GetCustomAttributesData throws for some types (such as types in dynamically generated assemblies). + // We'll skip looking up StackTraceHiddenAttributes on such types. + attributes = memberInfo.GetCustomAttributesData(); + } + catch + { + return false; + } + + for (var i = 0; i < attributes.Count; i++) + { + if (attributes[i].AttributeType.Name == "StackTraceHiddenAttribute") + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Shared/WebEncoders/Properties/EncoderResources.cs b/src/Shared/WebEncoders/Properties/EncoderResources.cs new file mode 100644 index 0000000000..3474ae82c5 --- /dev/null +++ b/src/Shared/WebEncoders/Properties/EncoderResources.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Globalization; + +namespace Microsoft.Extensions.WebEncoders.Sources +{ + // TODO using a resx file. project.json, unfortunately, fails to embed resx files when there are also compile items + // in the contentFiles section. Revisit once we convert repos to MSBuild + internal static class EncoderResources + { + /// + /// Invalid {0}, {1} or {2} length. + /// + internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length."; + + /// + /// Malformed input: {0} is an invalid input length. + /// + internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length."; + + /// + /// Invalid {0}, {1} or {2} length. + /// + internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2) + { + return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2); + } + + /// + /// Malformed input: {0} is an invalid input length. + /// + internal static string FormatWebEncoders_MalformedInput(object p0) + { + return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0); + } + } +} diff --git a/src/Shared/WebEncoders/WebEncoders.cs b/src/Shared/WebEncoders/WebEncoders.cs new file mode 100644 index 0000000000..17068ae67a --- /dev/null +++ b/src/Shared/WebEncoders/WebEncoders.cs @@ -0,0 +1,388 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Globalization; +using Microsoft.Extensions.WebEncoders.Sources; + +#if WebEncoders_In_WebUtilities +namespace Microsoft.AspNetCore.WebUtilities +#else +namespace Microsoft.Extensions.Internal +#endif +{ + /// + /// Contains utility APIs to assist with common encoding and decoding operations. + /// +#if WebEncoders_In_WebUtilities + public +#else + internal +#endif + static class WebEncoders + { + private static readonly byte[] EmptyBytes = new byte[0]; + + /// + /// Decodes a base64url-encoded string. + /// + /// The base64url-encoded input to decode. + /// The base64url-decoded form of the input. + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + return Base64UrlDecode(input, offset: 0, count: input.Length); + } + + /// + /// Decodes a base64url-encoded substring of a given string. + /// + /// A string containing the base64url-encoded input to decode. + /// The position in at which decoding should begin. + /// The number of characters in to decode. + /// The base64url-decoded form of the input. + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input, int offset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + + // Special-case empty input + if (count == 0) + { + return EmptyBytes; + } + + // Create array large enough for the Base64 characters, not just shorter Base64-URL-encoded form. + var buffer = new char[GetArraySizeRequiredToDecode(count)]; + + return Base64UrlDecode(input, offset, buffer, bufferOffset: 0, count: count); + } + + /// + /// Decodes a base64url-encoded into a byte[]. + /// + /// A string containing the base64url-encoded input to decode. + /// The position in at which decoding should begin. + /// + /// Scratch buffer to hold the s to decode. Array must be large enough to hold + /// and characters as well as Base64 padding + /// characters. Content is not preserved. + /// + /// + /// The offset into at which to begin writing the s to decode. + /// + /// The number of characters in to decode. + /// The base64url-decoded form of the . + /// + /// The input must not contain any whitespace or padding characters. + /// Throws if the input is malformed. + /// + public static byte[] Base64UrlDecode(string input, int offset, char[] buffer, int bufferOffset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + if (bufferOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(bufferOffset)); + } + + if (count == 0) + { + return EmptyBytes; + } + + // Assumption: input is base64url encoded without padding and contains no whitespace. + + var paddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); + var arraySizeRequired = checked(count + paddingCharsToAdd); + Debug.Assert(arraySizeRequired % 4 == 0, "Invariant: Array length must be a multiple of 4."); + + if (buffer.Length - bufferOffset < arraySizeRequired) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(bufferOffset), + nameof(input)), + nameof(count)); + } + + // Copy input into buffer, fixing up '-' -> '+' and '_' -> '/'. + var i = bufferOffset; + for (var j = offset; i - bufferOffset < count; i++, j++) + { + var ch = input[j]; + if (ch == '-') + { + buffer[i] = '+'; + } + else if (ch == '_') + { + buffer[i] = '/'; + } + else + { + buffer[i] = ch; + } + } + + // Add the padding characters back. + for (; paddingCharsToAdd > 0; i++, paddingCharsToAdd--) + { + buffer[i] = '='; + } + + // Decode. + // If the caller provided invalid base64 chars, they'll be caught here. + return Convert.FromBase64CharArray(buffer, bufferOffset, arraySizeRequired); + } + + /// + /// Gets the minimum char[] size required for decoding of characters + /// with the method. + /// + /// The number of characters to decode. + /// + /// The minimum char[] size required for decoding of characters. + /// + public static int GetArraySizeRequiredToDecode(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + if (count == 0) + { + return 0; + } + + var numPaddingCharsToAdd = GetNumBase64PaddingCharsToAddForDecode(count); + + return checked(count + numPaddingCharsToAdd); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + return Base64UrlEncode(input, offset: 0, count: input.Length); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// The number of bytes from to encode. + /// The base64url-encoded form of . + public static string Base64UrlEncode(byte[] input, int offset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + + // Special-case empty input + if (count == 0) + { + return string.Empty; + } + + var buffer = new char[GetArraySizeRequiredToEncode(count)]; + var numBase64Chars = Base64UrlEncode(input, offset, buffer, outputOffset: 0, count: count); + + return new String(buffer, startIndex: 0, length: numBase64Chars); + } + + /// + /// Encodes using base64url encoding. + /// + /// The binary input to encode. + /// The offset into at which to begin encoding. + /// + /// Buffer to receive the base64url-encoded form of . Array must be large enough to + /// hold characters and the full base64-encoded form of + /// , including padding characters. + /// + /// + /// The offset into at which to begin writing the base64url-encoded form of + /// . + /// + /// The number of bytes from to encode. + /// + /// The number of characters written to , less any padding characters. + /// + public static int Base64UrlEncode(byte[] input, int offset, char[] output, int outputOffset, int count) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + ValidateParameters(input.Length, nameof(input), offset, count); + if (outputOffset < 0) + { + throw new ArgumentOutOfRangeException(nameof(outputOffset)); + } + + var arraySizeRequired = GetArraySizeRequiredToEncode(count); + if (output.Length - outputOffset < arraySizeRequired) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(outputOffset), + nameof(output)), + nameof(count)); + } + + // Special-case empty input. + if (count == 0) + { + return 0; + } + + // Use base64url encoding with no padding characters. See RFC 4648, Sec. 5. + + // Start with default Base64 encoding. + var numBase64Chars = Convert.ToBase64CharArray(input, offset, count, output, outputOffset); + + // Fix up '+' -> '-' and '/' -> '_'. Drop padding characters. + for (var i = outputOffset; i - outputOffset < numBase64Chars; i++) + { + var ch = output[i]; + if (ch == '+') + { + output[i] = '-'; + } + else if (ch == '/') + { + output[i] = '_'; + } + else if (ch == '=') + { + // We've reached a padding character; truncate the remainder. + return i - outputOffset; + } + } + + return numBase64Chars; + } + + /// + /// Get the minimum output char[] size required for encoding + /// s with the method. + /// + /// The number of characters to encode. + /// + /// The minimum output char[] size required for encoding s. + /// + public static int GetArraySizeRequiredToEncode(int count) + { + var numWholeOrPartialInputBlocks = checked(count + 2) / 3; + return checked(numWholeOrPartialInputBlocks * 4); + } + + private static int GetNumBase64PaddingCharsInString(string str) + { + // Assumption: input contains a well-formed base64 string with no whitespace. + + // base64 guaranteed have 0 - 2 padding characters. + if (str[str.Length - 1] == '=') + { + if (str[str.Length - 2] == '=') + { + return 2; + } + return 1; + } + return 0; + } + + private static int GetNumBase64PaddingCharsToAddForDecode(int inputLength) + { + switch (inputLength % 4) + { + case 0: + return 0; + case 2: + return 2; + case 3: + return 1; + default: + throw new FormatException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_MalformedInput, + inputLength)); + } + } + + private static void ValidateParameters(int bufferLength, string inputName, int offset, int count) + { + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + if (bufferLength - offset < count) + { + throw new ArgumentException( + string.Format( + CultureInfo.CurrentCulture, + EncoderResources.WebEncoders_InvalidCountOffsetOrLength, + nameof(count), + nameof(offset), + inputName), + nameof(count)); + } + } + } +} diff --git a/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs b/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs new file mode 100644 index 0000000000..e71a792692 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ClosedGenericMatcherTest.cs @@ -0,0 +1,360 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class ClosedGenericMatcherTest + { + // queryType, interfaceType, expectedResult + public static TheoryData ExtractGenericInterfaceDataSet + { + get + { + return new TheoryData + { + // Closed generic types that match given open generic type. + { + typeof(IEnumerable), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IReadOnlyList), + typeof(IReadOnlyList<>), + typeof(IReadOnlyList) + }, + { + typeof(KeyValuePair), + typeof(KeyValuePair<,>), + typeof(KeyValuePair) + }, + // Closed generic interfaces that implement sub-interface of given open generic type. + { + typeof(ICollection), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IReadOnlyList), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + { + typeof(IDictionary), + typeof(IEnumerable<>), + typeof(IEnumerable>) + }, + // Class that implements closed generic based on given open generic interface. + { + typeof(BaseClass), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(BaseClass), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(BaseClass), + typeof(ICollection<>), + typeof(ICollection>) + }, + // Derived class that implements closed generic based on given open generic interface. + { + typeof(DerivedClass), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(DerivedClass), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(DerivedClass), + typeof(ICollection<>), + typeof(ICollection>) + }, + // Derived class that also implements another interface. + { + typeof(DerivedClassWithComparable), + typeof(IDictionary<,>), + typeof(IDictionary) + }, + { + typeof(DerivedClassWithComparable), + typeof(IEquatable<>), + typeof(IEquatable) + }, + { + typeof(DerivedClassWithComparable), + typeof(ICollection<>), + typeof(ICollection>) + }, + { + typeof(DerivedClassWithComparable), + typeof(IComparable<>), + typeof(IComparable) + }, + // Derived class using system implementation. + { + typeof(DerivedClassFromSystemImplementation), + typeof(ICollection<>), + typeof(ICollection) + }, + { + typeof(DerivedClassFromSystemImplementation), + typeof(IReadOnlyList<>), + typeof(IReadOnlyList) + }, + { + typeof(DerivedClassFromSystemImplementation), + typeof(IEnumerable<>), + typeof(IEnumerable) + }, + // Not given an open generic type. + { + typeof(IEnumerable), + typeof(IEnumerable), + null + }, + { + typeof(IEnumerable), + typeof(IEnumerable), + null + }, + { + typeof(IReadOnlyList), + typeof(BaseClass), + null + }, + { + typeof(KeyValuePair<,>), + typeof(KeyValuePair), + null + }, + // Not a match. + { + typeof(IEnumerable), + typeof(IReadOnlyList<>), + null + }, + { + typeof(IList), + typeof(IReadOnlyList<>), + null + }, + { + typeof(IDictionary), + typeof(KeyValuePair<,>), + null + }, + }; + } + } + + [Theory] + [MemberData(nameof(ExtractGenericInterfaceDataSet))] + public void ExtractGenericInterface_ReturnsExpectedType( + Type queryType, + Type interfaceType, + Type expectedResult) + { + // Arrange & Act + var result = ClosedGenericMatcher.ExtractGenericInterface(queryType, interfaceType); + + // Assert + Assert.Equal(expectedResult, result); + } + + // IEnumerable is preferred because it is defined on the more-derived type. + [Fact] + public void ExtractGenericInterface_MultipleDefinitionsInherited() + { + // Arrange + var type = typeof(TwoIEnumerableImplementationsInherited); + + // Act + var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); + + // Sort + Assert.Equal(typeof(IEnumerable), result); + } + + // IEnumerable is preferred because we sort by Ordinal on the full name. + [Fact] + public void ExtractGenericInterface_MultipleDefinitionsOnSameType() + { + // Arrange + var type = typeof(TwoIEnumerableImplementationsOnSameClass); + + // Act + var result = ClosedGenericMatcher.ExtractGenericInterface(type, typeof(IEnumerable<>)); + + // Sort + Assert.Equal(typeof(IEnumerable), result); + } + + private class TwoIEnumerableImplementationsOnSameClass : IEnumerable, IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + private class TwoIEnumerableImplementationsInherited : List, IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + } + + private class BaseClass : IDictionary, IEquatable + { + object IDictionary.this[string key] + { + get + { + throw new NotImplementedException(); + } + + set + { + throw new NotImplementedException(); + } + } + + int ICollection>.Count + { + get + { + throw new NotImplementedException(); + } + } + + bool ICollection>.IsReadOnly + { + get + { + throw new NotImplementedException(); + } + } + + ICollection IDictionary.Keys + { + get + { + throw new NotImplementedException(); + } + } + + ICollection IDictionary.Values + { + get + { + throw new NotImplementedException(); + } + } + + public bool Equals(BaseClass other) + { + throw new NotImplementedException(); + } + + void ICollection>.Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + void IDictionary.Add(string key, object value) + { + throw new NotImplementedException(); + } + + void ICollection>.Clear() + { + throw new NotImplementedException(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + bool IDictionary.ContainsKey(string key) + { + throw new NotImplementedException(); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + throw new NotImplementedException(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + throw new NotImplementedException(); + } + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + bool IDictionary.Remove(string key) + { + throw new NotImplementedException(); + } + + bool IDictionary.TryGetValue(string key, out object value) + { + throw new NotImplementedException(); + } + } + + private class DerivedClass : BaseClass + { + } + + private class DerivedClassWithComparable : DerivedClass, IComparable + { + public int CompareTo(DerivedClassWithComparable other) + { + throw new NotImplementedException(); + } + } + + private class DerivedClassFromSystemImplementation : Collection + { + } + } +} \ No newline at end of file diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs new file mode 100644 index 0000000000..9a0951eb27 --- /dev/null +++ b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryHolderTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CopyOnWriteDictionaryHolderTest + { + [Fact] + public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "test-key", "test-value" }, + { "key2", "key2-value" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act and Assert + Assert.Equal("key2-value", holder["key2"]); + Assert.Equal(2, holder.Count); + Assert.Equal(new string[] { "test-key", "key2" }, holder.Keys.ToArray()); + Assert.Equal(new object[] { "test-value", "key2-value" }, holder.Values.ToArray()); + Assert.True(holder.ContainsKey("test-key")); + + object value; + Assert.False(holder.TryGetValue("different-key", out value)); + + Assert.False(holder.HasBeenCopied); + Assert.Same(source, holder.ReadDictionary); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act + holder["key2"] = "value3"; + + // Assert + Assert.Equal("value2", source["key2"]); + Assert.Equal(2, holder.Count); + Assert.Equal("value1", holder["key1"]); + Assert.Equal("value3", holder["key2"]); + + Assert.True(holder.HasBeenCopied); + Assert.NotSame(source, holder.ReadDictionary); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceValueIsAdded() + { + // Arrange + var source = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "key1", "value1" }, + { "key2", "value2" } + }; + + var holder = new CopyOnWriteDictionaryHolder(source); + + // Act + holder.Add("key3", "value3"); + holder.Remove("key1"); + + // Assert + Assert.Equal(2, source.Count); + Assert.Equal("value1", source["key1"]); + Assert.Equal(2, holder.Count); + Assert.Equal("value2", holder["KeY2"]); + Assert.Equal("value3", holder["key3"]); + + Assert.True(holder.HasBeenCopied); + Assert.NotSame(source, holder.ReadDictionary); + } + } +} diff --git a/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs new file mode 100644 index 0000000000..c1b54036d4 --- /dev/null +++ b/src/Shared/test/Shared.Tests/CopyOnWriteDictionaryTest.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Moq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class CopyOnWriteDictionaryTest + { + [Fact] + public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() + { + // Arrange + var values = new List(); + var enumerator = Mock.Of>>(); + var sourceDictionary = new Mock>(MockBehavior.Strict); + sourceDictionary + .SetupGet(d => d.Count) + .Returns(100) + .Verifiable(); + sourceDictionary + .SetupGet(d => d.Values) + .Returns(values) + .Verifiable(); + sourceDictionary + .Setup(d => d.ContainsKey("test-key")) + .Returns(value: true) + .Verifiable(); + sourceDictionary + .Setup(d => d.GetEnumerator()) + .Returns(enumerator) + .Verifiable(); + sourceDictionary + .Setup(d => d["key2"]) + .Returns("key2-value") + .Verifiable(); + object value; + sourceDictionary.Setup(d => d.TryGetValue("different-key", out value)) + .Returns(false) + .Verifiable(); + + var copyOnWriteDictionary = new CopyOnWriteDictionary(sourceDictionary.Object, + StringComparer.OrdinalIgnoreCase); + + // Act and Assert + Assert.Equal("key2-value", copyOnWriteDictionary["key2"]); + Assert.Equal(100, copyOnWriteDictionary.Count); + Assert.Same(values, copyOnWriteDictionary.Values); + Assert.True(copyOnWriteDictionary.ContainsKey("test-key")); + Assert.Same(enumerator, copyOnWriteDictionary.GetEnumerator()); + Assert.False(copyOnWriteDictionary.TryGetValue("different-key", out value)); + sourceDictionary.Verify(); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() + { + // Arrange + var values = new List(); + var sourceDictionary = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + var copyOnWriteDictionary = new CopyOnWriteDictionary( + sourceDictionary, + StringComparer.OrdinalIgnoreCase); + + // Act + copyOnWriteDictionary["key2"] = "value3"; + + // Assert + Assert.Equal("value2", sourceDictionary["key2"]); + Assert.Equal(2, copyOnWriteDictionary.Count); + Assert.Equal("value1", copyOnWriteDictionary["key1"]); + Assert.Equal("value3", copyOnWriteDictionary["key2"]); + } + + [Fact] + public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceDictionaryIsModified() + { + // Arrange + var values = new List(); + var sourceDictionary = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" } + }; + var copyOnWriteDictionary = new CopyOnWriteDictionary( + sourceDictionary, + StringComparer.OrdinalIgnoreCase); + + // Act + copyOnWriteDictionary.Add("key3", "value3"); + copyOnWriteDictionary.Remove("key1"); + + + // Assert + Assert.Equal(2, sourceDictionary.Count); + Assert.Equal("value1", sourceDictionary["key1"]); + Assert.Equal(2, copyOnWriteDictionary.Count); + Assert.Equal("value2", copyOnWriteDictionary["KeY2"]); + Assert.Equal("value3", copyOnWriteDictionary["key3"]); + } + } +} diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj new file mode 100644 index 0000000000..48b2099db7 --- /dev/null +++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.1;net461 + portable + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs new file mode 100644 index 0000000000..1c26ef1de1 --- /dev/null +++ b/src/Shared/test/Shared.Tests/ObjectMethodExecutorTest.cs @@ -0,0 +1,634 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.FSharp.Control; +using Microsoft.FSharp.Core; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class ObjectMethodExecutorTest + { + private TestObject _targetObject = new TestObject(); + private TypeInfo targetTypeInfo = typeof(TestObject).GetTypeInfo(); + + [Fact] + public void ExecuteValueMethod() + { + var executor = GetExecutorForMethod("ValueMethod"); + var result = executor.Execute( + _targetObject, + new object[] { 10, 20 }); + Assert.False(executor.IsMethodAsync); + Assert.Equal(30, (int)result); + } + + [Fact] + public void ExecuteVoidValueMethod() + { + var executor = GetExecutorForMethod("VoidValueMethod"); + var result = executor.Execute( + _targetObject, + new object[] { 10 }); + Assert.False(executor.IsMethodAsync); + Assert.Null(result); + } + + [Fact] + public void ExecuteValueMethodWithReturnType() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnType"); + var result = executor.Execute( + _targetObject, + new object[] { 10 }); + var resultObject = Assert.IsType(result); + Assert.False(executor.IsMethodAsync); + Assert.Equal("Hello", resultObject.value); + } + + [Fact] + public void ExecuteValueMethodUpdateValue() + { + var executor = GetExecutorForMethod("ValueMethodUpdateValue"); + var parameter = new TestObject(); + var result = executor.Execute( + _targetObject, + new object[] { parameter }); + var resultObject = Assert.IsType(result); + Assert.False(executor.IsMethodAsync); + Assert.Equal("HelloWorld", resultObject.value); + } + + [Fact] + public void ExecuteValueMethodWithReturnTypeThrowsException() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsException"); + var parameter = new TestObject(); + Assert.False(executor.IsMethodAsync); + Assert.Throws( + () => executor.Execute( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public async Task ExecuteValueMethodAsync() + { + var executor = GetExecutorForMethod("ValueMethodAsync"); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { 10, 20 }); + Assert.True(executor.IsMethodAsync); + Assert.Equal(30, (int)result); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnTypeAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeAsync"); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { 10 }); + var resultObject = Assert.IsType(result); + Assert.True(executor.IsMethodAsync); + Assert.Equal("Hello", resultObject.value); + } + + [Fact] + public async Task ExecuteValueMethodUpdateValueAsync() + { + var executor = GetExecutorForMethod("ValueMethodUpdateValueAsync"); + var parameter = new TestObject(); + var result = await executor.ExecuteAsync( + _targetObject, + new object[] { parameter }); + var resultObject = Assert.IsType(result); + Assert.True(executor.IsMethodAsync); + Assert.Equal("HelloWorld", resultObject.value); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnTypeThrowsExceptionAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnTypeThrowsExceptionAsync"); + var parameter = new TestObject(); + Assert.True(executor.IsMethodAsync); + await Assert.ThrowsAsync( + async () => await executor.ExecuteAsync( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public async Task ExecuteValueMethodWithReturnVoidThrowsExceptionAsync() + { + var executor = GetExecutorForMethod("ValueMethodWithReturnVoidThrowsExceptionAsync"); + var parameter = new TestObject(); + Assert.True(executor.IsMethodAsync); + await Assert.ThrowsAsync( + async () => await executor.ExecuteAsync( + _targetObject, + new object[] { parameter })); + } + + [Fact] + public void GetDefaultValueForParameters_ReturnsSuppliedValues() + { + var suppliedDefaultValues = new object[] { 123, "test value" }; + var executor = GetExecutorForMethod("MethodWithMultipleParameters", suppliedDefaultValues); + Assert.Equal(suppliedDefaultValues[0], executor.GetDefaultValueForParameter(0)); + Assert.Equal(suppliedDefaultValues[1], executor.GetDefaultValueForParameter(1)); + Assert.Throws(() => executor.GetDefaultValueForParameter(2)); + } + + [Fact] + public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied() + { + var executor = GetExecutorForMethod("MethodWithMultipleParameters"); + Assert.Throws(() => executor.GetDefaultValueForParameter(0)); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); + + // Act + var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { "Hello", 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(TestObject), executor.AsyncResultType); + Assert.NotNull(result); + Assert.Equal("Hello 123", result.value); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); + + // Act + var result = await (TestAwaitable)executor.Execute(_targetObject, new object[] { 123, 456 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.Equal(579, result); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "Hello", 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(TestObject), executor.AsyncResultType); + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal("Hello 123", ((TestObject)result).value); + } + + [Fact] + public async void TargetMethodReturningCustomAwaitableOfValueType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableOfValueTypeAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123, 456 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.NotNull(result); + Assert.IsType(result); + Assert.Equal(579, (int)result); + } + + [Fact] + public async void TargetMethodReturningAwaitableOfVoidType_CanInvokeViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("VoidValueMethodAsync"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(void), executor.AsyncResultType); + Assert.Null(result); + } + + [Fact] + public async void TargetMethodReturningAwaitableWithICriticalNotifyCompletion_UsesUnsafeOnCompleted() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableWithICriticalNotifyCompletion"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[0]); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("Used UnsafeOnCompleted", (string)result); + } + + [Fact] + public async void TargetMethodReturningAwaitableWithoutICriticalNotifyCompletion_UsesOnCompleted() + { + // Arrange + var executor = GetExecutorForMethod("CustomAwaitableWithoutICriticalNotifyCompletion"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[0]); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("Used OnCompleted", (string)result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfValueType"); + + // Act + var result = await (ValueTask)executor.Execute(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.Equal(123, result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); + + // Act + var result = await (ValueTask)executor.Execute(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfValueType_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfValueType"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { 123 }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(int), executor.AsyncResultType); + Assert.NotNull(result); + Assert.Equal(123, (int)result); + } + + [Fact] + public async void TargetMethodReturningValueTaskOfReferenceType_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("ValueTaskOfReferenceType"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncMethod"); + + // Act + var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); + var result = await FSharpAsync.StartAsTask(fsharpAsync, + FSharpOption.None, + FSharpOption.None); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecute() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); + + // Act + var fsharpAsync = (FSharpAsync)executor.Execute(_targetObject, new object[] { "test result" }); + var resultTask = FSharpAsync.StartAsTask(fsharpAsync, + FSharpOption.None, + FSharpOption.None); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + + var exception = await Assert.ThrowsAsync(async () => await resultTask); + Assert.IsType(exception.InnerException); + Assert.Equal("Test exception", exception.InnerException.Message); + } + + [Fact] + public async void TargetMethodReturningFSharpAsync_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncMethod"); + + // Act + var result = await executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + Assert.Equal("test result", result); + } + + [Fact] + public async void TargetMethodReturningFailingFSharpAsync_CanBeInvokedViaExecuteAsync() + { + // Arrange + var executor = GetExecutorForMethod("FSharpAsyncFailureMethod"); + + // Act + var resultTask = executor.ExecuteAsync(_targetObject, new object[] { "test result" }); + + // Assert + Assert.True(executor.IsMethodAsync); + Assert.Same(typeof(string), executor.AsyncResultType); + + var exception = await Assert.ThrowsAsync(async () => await resultTask); + Assert.IsType(exception.InnerException); + Assert.Equal("Test exception", exception.InnerException.Message); + } + + private ObjectMethodExecutor GetExecutorForMethod(string methodName) + { + var method = typeof(TestObject).GetMethod(methodName); + return ObjectMethodExecutor.Create(method, targetTypeInfo); + } + + private ObjectMethodExecutor GetExecutorForMethod(string methodName, object[] parameterDefaultValues) + { + var method = typeof(TestObject).GetMethod(methodName); + return ObjectMethodExecutor.Create(method, targetTypeInfo, parameterDefaultValues); + } + + public class TestObject + { + public string value; + public int ValueMethod(int i, int j) + { + return i + j; + } + + public void VoidValueMethod(int i) + { + + } + + public TestObject ValueMethodWithReturnType(int i) + { + return new TestObject() { value = "Hello" }; ; + } + + public TestObject ValueMethodWithReturnTypeThrowsException(TestObject i) + { + throw new NotImplementedException("Not Implemented Exception"); + } + + public TestObject ValueMethodUpdateValue(TestObject parameter) + { + parameter.value = "HelloWorld"; + return parameter; + } + + public Task ValueMethodAsync(int i, int j) + { + return Task.FromResult(i + j); + } + + public async Task VoidValueMethodAsync(int i) + { + await ValueMethodAsync(3, 4); + } + public Task ValueMethodWithReturnTypeAsync(int i) + { + return Task.FromResult(new TestObject() { value = "Hello" }); + } + + public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i) + { + await Task.CompletedTask; + throw new NotImplementedException("Not Implemented Exception"); + } + + public async Task ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i) + { + await Task.CompletedTask; + throw new NotImplementedException("Not Implemented Exception"); + } + + public Task ValueMethodUpdateValueAsync(TestObject parameter) + { + parameter.value = "HelloWorld"; + return Task.FromResult(parameter); + } + + public TestAwaitable CustomAwaitableOfReferenceTypeAsync( + string input1, + int input2) + { + return new TestAwaitable(new TestObject + { + value = $"{input1} {input2}" + }); + } + + public TestAwaitable CustomAwaitableOfValueTypeAsync( + int input1, + int input2) + { + return new TestAwaitable(input1 + input2); + } + + public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion() + { + return new TestAwaitableWithICriticalNotifyCompletion(); + } + + public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion() + { + return new TestAwaitableWithoutICriticalNotifyCompletion(); + } + + public ValueTask ValueTaskOfValueType(int result) + { + return new ValueTask(result); + } + + public ValueTask ValueTaskOfReferenceType(string result) + { + return new ValueTask(result); + } + + public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam) + { + } + + public FSharpAsync FSharpAsyncMethod(string parameter) + { + return FSharpAsync.AwaitTask(Task.FromResult(parameter)); + } + + public FSharpAsync FSharpAsyncFailureMethod(string parameter) + { + return FSharpAsync.AwaitTask( + Task.FromException(new InvalidOperationException("Test exception"))); + } + } + + public class TestAwaitable + { + private T _result; + private bool _isCompleted; + private List _onCompletedCallbacks = new List(); + + public TestAwaitable(T result) + { + _result = result; + + // Simulate a brief delay before completion + ThreadPool.QueueUserWorkItem(_ => + { + Thread.Sleep(100); + SetCompleted(); + }); + } + + private void SetCompleted() + { + _isCompleted = true; + + foreach (var callback in _onCompletedCallbacks) + { + callback(); + } + } + + public TestAwaiter GetAwaiter() + { + return new TestAwaiter(this); + } + + public struct TestAwaiter : INotifyCompletion + { + private TestAwaitable _owner; + + public TestAwaiter(TestAwaitable owner) : this() + { + _owner = owner; + } + + public bool IsCompleted => _owner._isCompleted; + + public void OnCompleted(Action continuation) + { + if (_owner._isCompleted) + { + continuation(); + } + else + { + _owner._onCompletedCallbacks.Add(continuation); + } + } + + public T GetResult() + { + return _owner._result; + } + } + } + + public class TestAwaitableWithICriticalNotifyCompletion + { + public TestAwaiterWithICriticalNotifyCompletion GetAwaiter() + => new TestAwaiterWithICriticalNotifyCompletion(); + } + + public class TestAwaitableWithoutICriticalNotifyCompletion + { + public TestAwaiterWithoutICriticalNotifyCompletion GetAwaiter() + => new TestAwaiterWithoutICriticalNotifyCompletion(); + } + + public class TestAwaiterWithICriticalNotifyCompletion + : CompletionTrackingAwaiterBase, ICriticalNotifyCompletion + { + } + + public class TestAwaiterWithoutICriticalNotifyCompletion + : CompletionTrackingAwaiterBase, INotifyCompletion + { + } + + public class CompletionTrackingAwaiterBase + { + private string _result; + + public bool IsCompleted { get; private set; } + + public string GetResult() => _result; + + public void OnCompleted(Action continuation) + { + _result = "Used OnCompleted"; + IsCompleted = true; + continuation(); + } + + public void UnsafeOnCompleted(Action continuation) + { + _result = "Used UnsafeOnCompleted"; + IsCompleted = true; + continuation(); + } + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs new file mode 100644 index 0000000000..a5cb1605b3 --- /dev/null +++ b/src/Shared/test/Shared.Tests/PropertyActivatorTest.cs @@ -0,0 +1,187 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class PropertyActivatorTest + { + [Fact] + public void Activate_InvokesValueAccessorWithExpectedValue() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("IntProperty"); + var invokedWith = -1; + var activator = new PropertyActivator( + property, + valueAccessor: (val) => + { + invokedWith = val; + return val; + }); + + // Act + activator.Activate(instance, 123); + + // Assert + Assert.Equal(123, invokedWith); + } + + [Fact] + public void Activate_SetsPropertyValue() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("IntProperty"); + var activator = new PropertyActivator(property, valueAccessor: (val) => val + 1); + + // Act + activator.Activate(instance, 123); + + // Assert + Assert.Equal(124, instance.IntProperty); + } + + [Fact] + public void GetPropertiesToActivate_RestrictsActivatableProperties() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("ActivatableProperty"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + type: typeof(TestClass), + activateAttributeType: typeof(TestActivateAttribute), + createActivateInfo: + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val + 1)); + + // Assert + Assert.Collection( + propertiesToActivate, + (activator) => + { + Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); + }); + } + + [Fact] + public void GetPropertiesToActivate_CanCreateCustomPropertyActivators() + { + // Arrange + var instance = new TestClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("IntProperty"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + type: typeof(TestClass), + activateAttributeType: typeof(TestActivateAttribute), + createActivateInfo: + (propertyInfo) => new PropertyActivator(expectedPropertyInfo, valueAccessor: (val) => val + 1)); + + // Assert + Assert.Collection( + propertiesToActivate, + (activator) => + { + Assert.Equal(expectedPropertyInfo, activator.PropertyInfo); + }); + } + + [Fact] + public void GetPropertiesToActivate_ExcludesNonPublic() + { + // Arrange + var instance = new TestClassWithPropertyVisiblity(); + var typeInfo = instance.GetType().GetTypeInfo(); + var expectedPropertyInfo = typeInfo.GetDeclaredProperty("Public"); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + typeof(TestClassWithPropertyVisiblity), + typeof(TestActivateAttribute), + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val)); + + // Assert + Assert.Single(propertiesToActivate); + Assert.Single(propertiesToActivate, p => p.PropertyInfo == expectedPropertyInfo); + } + + [Fact] + public void GetPropertiesToActivate_IncludesNonPublic() + { + // Arrange + var instance = new TestClassWithPropertyVisiblity(); + var typeInfo = instance.GetType().GetTypeInfo(); + + // Act + var propertiesToActivate = PropertyActivator.GetPropertiesToActivate( + typeof(TestClassWithPropertyVisiblity), + typeof(TestActivateAttribute), + (propertyInfo) => new PropertyActivator(propertyInfo, valueAccessor: (val) => val), + includeNonPublic: true); + + // Assert + Assert.Equal(5, propertiesToActivate.Length); + } + + private class TestClass + { + public int IntProperty { get; set; } + + [TestActivate] + public int ActivatableProperty { get; set; } + + [TestActivate] + public int NoSetterActivatableProperty { get; } + + [TestActivate] + public int this[int something] // Not activatable + { + get + { + return 0; + } + } + + [TestActivate] + public static int StaticActivatablProperty { get; set; } + } + + private class TestClassWithPropertyVisiblity + { + [TestActivate] + public int Public { get; set; } + + [TestActivate] + protected int Protected { get; set; } + + [TestActivate] + internal int Internal { get; set; } + + [TestActivate] + protected internal int ProtectedInternal {get; set; } + + [TestActivate] + private int Private { get; set; } + } + + [AttributeUsage(AttributeTargets.Property)] + private class TestActivateAttribute : Attribute + { + } + + private class ActivationInfo + { + public string Name { get; set; } + } + } +} diff --git a/src/Shared/test/Shared.Tests/PropertyHelperTest.cs b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs new file mode 100644 index 0000000000..1c43dc880b --- /dev/null +++ b/src/Shared/test/Shared.Tests/PropertyHelperTest.cs @@ -0,0 +1,863 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class PropertyHelperTest + { + [Fact] + public void PropertyHelper_ReturnsNameCorrectly() + { + // Arrange + var anonymous = new { foo = "bar" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("foo", property.Name); + Assert.Equal("foo", helper.Name); + } + + [Fact] + public void PropertyHelper_ReturnsValueCorrectly() + { + // Arrange + var anonymous = new { bar = "baz" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("bar", helper.Name); + Assert.Equal("baz", helper.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ReturnsGetterDelegate() + { + // Arrange + var anonymous = new { bar = "baz" }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.NotNull(helper.ValueGetter); + Assert.Equal("baz", helper.ValueGetter(anonymous)); + } + + [Fact] + public void SetValue_SetsPropertyValue() + { + // Arrange + var expected = "new value"; + var instance = new BaseClass { PropA = "old value" }; + var helper = PropertyHelper.GetProperties( + instance.GetType()).First(prop => prop.Name == "PropA"); + + // Act + helper.SetValue(instance, expected); + + // Assert + Assert.Equal(expected, instance.PropA); + } + + [Fact] + public void PropertyHelper_ReturnsSetterDelegate() + { + // Arrange + var expected = "new value"; + var instance = new BaseClass { PropA = "old value" }; + var helper = PropertyHelper.GetProperties( + instance.GetType()).First(prop => prop.Name == "PropA"); + + // Act and Assert + Assert.NotNull(helper.ValueSetter); + helper.ValueSetter(instance, expected); + + // Assert + Assert.Equal(expected, instance.PropA); + } + + [Fact] + public void PropertyHelper_ReturnsValueCorrectly_ForValueTypes() + { + // Arrange + var anonymous = new { foo = 32 }; + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; + + // Act + var helper = new PropertyHelper(property); + + // Assert + Assert.Equal("foo", helper.Name); + Assert.Equal(32, helper.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ReturnsCachedPropertyHelper() + { + // Arrange + var anonymous = new { foo = "bar" }; + + // Act + var helpers1 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); + var helpers2 = PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()); + + // Assert + Assert.Single(helpers1); + Assert.Same(helpers1, helpers2); + Assert.Same(helpers1[0], helpers2[0]); + } + + [Fact] + public void PropertyHelper_DoesNotChangeUnderscores() + { + // Arrange + var anonymous = new { bar_baz2 = "foo" }; + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("bar_baz2", helper.Name); + } + + [Fact] + public void PropertyHelper_DoesNotFindPrivateProperties() + { + // Arrange + var anonymous = new PrivateProperties(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop1", helper.Name); + } + + [Fact] + public void PropertyHelper_DoesNotFindStaticProperties() + { + // Arrange + var anonymous = new Static(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop5", helper.Name); + } + +#if NETSTANDARD || NETCOREAPP + [Fact] + public void PropertyHelper_RefStructProperties() + { + // Arrange + var obj = new RefStructProperties(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(obj.GetType().GetTypeInfo())); + Assert.Equal("Prop5", helper.Name); + } +#elif NET46 || NET461 +#else +#error Unknown TFM - update the set of TFMs where we test for ref structs +#endif + + [Fact] + public void PropertyHelper_DoesNotFindSetOnlyProperties() + { + // Arrange + var anonymous = new SetOnly(); + + // Act + Assert + var helper = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo())); + Assert.Equal("Prop6", helper.Name); + } + + [Theory] + [InlineData(typeof(int?))] + [InlineData(typeof(DayOfWeek?))] + public void PropertyHelper_WorksForNullablePrimitiveAndEnumTypes(Type nullableType) + { + // Act + var properties = PropertyHelper.GetProperties(nullableType); + + // Assert + Assert.Empty(properties); + } + + [Fact] + public void PropertyHelper_UnwrapsNullableTypes() + { + // Arrange + var myType = typeof(MyStruct?); + + // Act + var properties = PropertyHelper.GetProperties(myType); + + // Assert + var property = Assert.Single(properties); + Assert.Equal("Foo", property.Name); + } + + [Fact] + public void PropertyHelper_WorksForStruct() + { + // Arrange + var anonymous = new MyProperties(); + + anonymous.IntProp = 3; + anonymous.StringProp = "Five"; + + // Act + Assert + var helper1 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "IntProp")); + var helper2 = Assert.Single(PropertyHelper.GetProperties(anonymous.GetType().GetTypeInfo()).Where(prop => prop.Name == "StringProp")); + Assert.Equal(3, helper1.GetValue(anonymous)); + Assert.Equal("Five", helper2.GetValue(anonymous)); + } + + [Fact] + public void PropertyHelper_ForDerivedClass() + { + // Arrange + var derived = new DerivedClass { PropA = "propAValue", PropB = "propBValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("propAValue", propAHelper.GetValue(derived)); + Assert.Equal("propBValue", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForDerivedClass_WithNew() + { + // Arrange + var derived = new DerivedClassWithNew { PropA = "propAValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("propAValue", propAHelper.GetValue(derived)); + Assert.Equal("Newed", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForDerived_WithVirtual() + { + // Arrange + var derived = new DerivedClassWithOverride { PropA = "propAValue", PropB = "propBValue" }; + + // Act + var helpers = PropertyHelper.GetProperties(derived.GetType().GetTypeInfo()).ToArray(); + + // Assert + Assert.NotNull(helpers); + Assert.Equal(2, helpers.Length); + + var propAHelper = Assert.Single(helpers.Where(h => h.Name == "PropA")); + var propBHelper = Assert.Single(helpers.Where(h => h.Name == "PropB")); + + Assert.Equal("OverridenpropAValue", propAHelper.GetValue(derived)); + Assert.Equal("propBValue", propBHelper.GetValue(derived)); + } + + [Fact] + public void PropertyHelper_ForInterface_ReturnsExpectedProperties() + { + // Arrange + var expectedNames = new[] { "Count", "IsReadOnly" }; + + // Act + var helpers = PropertyHelper.GetProperties(typeof(ICollection)); + + // Assert + Assert.Collection( + helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), + helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }); + } + + [Fact] + public void PropertyHelper_ForDerivedInterface_ReturnsAllProperties() + { + // Arrange + var expectedNames = new[] { "Count", "IsReadOnly", "Keys", "Values" }; + + // Act + var helpers = PropertyHelper.GetProperties(typeof(IDictionary)); + + // Assert + Assert.Collection( + helpers.OrderBy(helper => helper.Name, StringComparer.Ordinal), + helper => { Assert.Equal(expectedNames[0], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[1], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[2], helper.Name, StringComparer.Ordinal); }, + helper => { Assert.Equal(expectedNames[3], helper.Name, StringComparer.Ordinal); }); + } + + [Fact] + public void GetProperties_ExcludesIndexersAndPropertiesWithoutPublicGetters() + { + // Arrange + var type = typeof(DerivedClassWithNonReadableProperties); + + // Act + var result = PropertyHelper.GetProperties(type).ToArray(); + + // Assert + Assert.Equal(3, result.Length); + Assert.Equal("Visible", result[0].Name); + Assert.Equal("PropA", result[1].Name); + Assert.Equal("PropB", result[2].Name); + } + + [Fact] + public void GetVisibleProperties_NoHiddenProperty() + { + // Arrange + var type = typeof(string); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + var property = Assert.Single(result); + Assert.Equal("Length", property.Name); + Assert.Equal(typeof(int), property.Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty() + { + // Arrange + var type = typeof(DerivedHiddenProperty); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(string), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty_TwoLevels() + { + // Arrange + var type = typeof(DerivedHiddenProperty2); + + // Act + var result = PropertyHelper.GetVisibleProperties(type).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(Guid), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_NoHiddenPropertyWithTypeInfoInput() + { + // Arrange + var type = typeof(string); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + var property = Assert.Single(result); + Assert.Equal("Length", property.Name); + Assert.Equal(typeof(int), property.Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenPropertyWithTypeInfoInput() + { + // Arrange + var type = typeof(DerivedHiddenProperty); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(string), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void GetVisibleProperties_HiddenProperty_TwoLevelsWithTypeInfoInput() + { + // Arrange + var type = typeof(DerivedHiddenProperty2); + + // Act + var result = PropertyHelper.GetVisibleProperties(type.GetTypeInfo()).ToArray(); + + // Assert + Assert.Equal(2, result.Length); + Assert.Equal("Id", result[0].Name); + Assert.Equal(typeof(Guid), result[0].Property.PropertyType); + Assert.Equal("Name", result[1].Name); + Assert.Equal(typeof(string), result[1].Property.PropertyType); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForPublicAndNobPublicProperties() + { + // Arrange + var instance = new BaseClass(); + var typeInfo = instance.GetType().GetTypeInfo(); + var publicProperty = typeInfo.GetDeclaredProperty("PropA"); + var protectedProperty = typeInfo.GetDeclaredProperty("PropProtected"); + var publicPropertySetter = PropertyHelper.MakeFastPropertySetter(publicProperty); + var protectedPropertySetter = PropertyHelper.MakeFastPropertySetter(protectedProperty); + + // Act + publicPropertySetter(instance, "TestPublic"); + protectedPropertySetter(instance, "TestProtected"); + + // Assert + Assert.Equal("TestPublic", instance.PropA); + Assert.Equal("TestProtected", instance.GetPropProtected()); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForOverridenProperties() + { + // Arrange + var instance = new DerivedClassWithOverride(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("PropA"); + var propertySetter = PropertyHelper.MakeFastPropertySetter(property); + + // Act + propertySetter(instance, "Test value"); + + // Assert + Assert.Equal("OverridenTest value", instance.PropA); + } + + [Fact] + public void MakeFastPropertySetter_SetsPropertyValues_ForNewedProperties() + { + // Arrange + var instance = new DerivedClassWithNew(); + var typeInfo = instance.GetType().GetTypeInfo(); + var property = typeInfo.GetDeclaredProperty("PropB"); + var propertySetter = PropertyHelper.MakeFastPropertySetter(property); + + // Act + propertySetter(instance, "Test value"); + + // Assert + Assert.Equal("NewedTest value", instance.PropB); + } + + [Fact] + public void MakeFastPropertyGetter_ReferenceType_ForNullObject_Throws() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); + + // Act & Assert + Assert.Throws(() => accessor(null)); + } + + [Fact] + public void MakeFastPropertyGetter_ValueType_ForNullObject_Throws() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeFastPropertyGetter(property.Property); + + // Act & Assert + Assert.Throws(() => accessor(null)); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ReferenceType_Success() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(new BaseClass() { PropA = "Hi" }); + + // Assert + Assert.Equal("Hi", value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ValueType_Success() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(new MyProperties() { StringProp = "Hi" }); + + // Assert + Assert.Equal("Hi", value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ReferenceType_ForNullObject_ReturnsNull() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(BaseClass)) + .Single(p => p.Name == nameof(BaseClass.PropA)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(null); + + // Assert + Assert.Null(value); + } + + [Fact] + public void MakeNullSafeFastPropertyGetter_ValueType_ForNullObject_ReturnsNull() + { + // Arrange + var property = PropertyHelper + .GetProperties(typeof(MyProperties)) + .Single(p => p.Name == nameof(MyProperties.StringProp)); + + var accessor = PropertyHelper.MakeNullSafeFastPropertyGetter(property.Property); + + // Act + var value = accessor(null); + + // Assert + Assert.Null(value); + } + + public static TheoryData> IgnoreCaseTestData + { + get + { + return new TheoryData> + { + { + new + { + selected = true, + SeLeCtEd = false + }, + new KeyValuePair("selected", false) + }, + { + new + { + SeLeCtEd = false, + selected = true + }, + new KeyValuePair("SeLeCtEd", true) + }, + { + new + { + SelECTeD = false, + SeLECTED = true + }, + new KeyValuePair("SelECTeD", true) + } + }; + } + } + + [Theory] + [MemberData(nameof(IgnoreCaseTestData))] + public void ObjectToDictionary_IgnoresPropertyCase(object testObject, + KeyValuePair expectedEntry) + { + // Act + var result = PropertyHelper.ObjectToDictionary(testObject); + + // Assert + var entry = Assert.Single(result); + Assert.Equal(expectedEntry, entry); + } + + [Fact] + public void ObjectToDictionary_WithNullObject_ReturnsEmptyDictionary() + { + // Arrange + object value = null; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(0, dictValues.Count); + } + + [Fact] + public void ObjectToDictionary_WithPlainObjectType_ReturnsEmptyDictionary() + { + // Arrange + var value = new object(); + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(0, dictValues.Count); + } + + [Fact] + public void ObjectToDictionary_WithPrimitiveType_LooksUpPublicProperties() + { + // Arrange + var value = "test"; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(1, dictValues.Count); + Assert.Equal(4, dictValues["Length"]); + } + + [Fact] + public void ObjectToDictionary_WithAnonymousType_LooksUpProperties() + { + // Arrange + var value = new { test = "value", other = 1 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(2, dictValues.Count); + Assert.Equal("value", dictValues["test"]); + Assert.Equal(1, dictValues["other"]); + } + + [Fact] + public void ObjectToDictionary_ReturnsCaseInsensitiveDictionary() + { + // Arrange + var value = new { TEST = "value", oThEr = 1 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(2, dictValues.Count); + Assert.Equal("value", dictValues["test"]); + Assert.Equal(1, dictValues["other"]); + } + + [Fact] + public void ObjectToDictionary_ReturnsInheritedProperties() + { + // Arrange + var value = new ThreeDPoint() { X = 5, Y = 10, Z = 17 }; + + // Act + var dictValues = PropertyHelper.ObjectToDictionary(value); + + // Assert + Assert.NotNull(dictValues); + Assert.Equal(3, dictValues.Count); + Assert.Equal(5, dictValues["X"]); + Assert.Equal(10, dictValues["Y"]); + Assert.Equal(17, dictValues["Z"]); + } + + private class Point + { + public int X { get; set; } + public int Y { get; set; } + } + + private class ThreeDPoint : Point + { + public int Z { get; set; } + } + + private class Static + { + public static int Prop2 { get; set; } + public int Prop5 { get; set; } + } + +#if NETSTANDARD || NETCOREAPP + private class RefStructProperties + { + public Span Span => throw new NotImplementedException(); + public MyRefStruct UserDefined => throw new NotImplementedException(); + + public int Prop5 { get; set; } + } + + private readonly ref struct MyRefStruct + { + } +#elif NET46 || NET461 +#else +#error Unknown TFM - update the set of TFMs where we test for ref structs +#endif + private struct MyProperties + { + public int IntProp { get; set; } + public string StringProp { get; set; } + } + + private class SetOnly + { + public int Prop2 { set { } } + public int Prop6 { get; set; } + } + + private class PrivateProperties + { + public int Prop1 { get; set; } + protected int Prop2 { get; set; } + private int Prop3 { get; set; } + } + + public class BaseClass + { + public string PropA { get; set; } + + protected string PropProtected { get; set; } + + public string GetPropProtected() + { + return PropProtected; + } + } + + public class DerivedClass : BaseClass + { + public string PropB { get; set; } + } + + public class BaseClassWithVirtual + { + public virtual string PropA { get; set; } + public string PropB { get; set; } + } + + public class DerivedClassWithNew : BaseClassWithVirtual + { + private string _value = "Newed"; + + public new string PropB + { + get { return _value; } + set { _value = "Newed" + value; } + } + } + + public class DerivedClassWithOverride : BaseClassWithVirtual + { + private string _value = "Overriden"; + + public override string PropA + { + get { return _value; } + set { _value = "Overriden" + value; } + } + } + + private class DerivedClassWithNonReadableProperties : BaseClassWithVirtual + { + public string this[int index] + { + get { return string.Empty; } + set { } + } + + public int Visible { get; set; } + + private string NotVisible { get; set; } + + public string NotVisible2 { private get; set; } + + public string NotVisible3 + { + set { } + } + + public static string NotVisible4 { get; set; } + } + + private struct MyStruct + { + public string Foo { get; set; } + } + + private class BaseHiddenProperty + { + public int Id { get; set; } + } + + private class DerivedHiddenProperty : BaseHiddenProperty + { + public new string Id { get; set; } + + public string Name { get; set; } + } + + private class DerivedHiddenProperty2 : DerivedHiddenProperty + { + public new Guid Id { get; set; } + + public new string Name { get; private set; } + } + } +} diff --git a/src/Shared/test/Shared.Tests/SecurityHelperTests.cs b/src/Shared/test/Shared.Tests/SecurityHelperTests.cs new file mode 100644 index 0000000000..8e7515ad36 --- /dev/null +++ b/src/Shared/test/Shared.Tests/SecurityHelperTests.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Security.Claims; +using System.Security.Principal; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class SecurityHelperTests + { + [Fact] + public void AddingToAnonymousIdentityDoesNotKeepAnonymousIdentity() + { + var user = SecurityHelper.MergeUserPrincipal(new ClaimsPrincipal(), new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), new string[0])); + + Assert.NotNull(user); + Assert.Equal("Alpha", user.Identity.AuthenticationType); + Assert.Equal("Test1", user.Identity.Name); + Assert.IsAssignableFrom(user); + Assert.IsAssignableFrom(user.Identity); + Assert.Single(user.Identities); + } + + [Fact] + public void AddingExistingIdentityChangesDefaultButPreservesPrior() + { + ClaimsPrincipal user = new GenericPrincipal(new GenericIdentity("Test1", "Alpha"), null); + + Assert.Equal("Alpha", user.Identity.AuthenticationType); + Assert.Equal("Test1", user.Identity.Name); + + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test2", "Beta"), new string[0])); + + Assert.Equal("Beta", user.Identity.AuthenticationType); + Assert.Equal("Test2", user.Identity.Name); + + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); + + Assert.Equal("Gamma", user.Identity.AuthenticationType); + Assert.Equal("Test3", user.Identity.Name); + + Assert.Equal(3, user.Identities.Count()); + Assert.Equal("Test3", user.Identities.Skip(0).First().Name); + Assert.Equal("Test2", user.Identities.Skip(1).First().Name); + Assert.Equal("Test1", user.Identities.Skip(2).First().Name); + } + + [Fact] + public void AddingPreservesNewIdentitiesAndDropsEmpty() + { + var existingPrincipal = new ClaimsPrincipal(new ClaimsIdentity()); + var identityNoAuthTypeWithClaim = new ClaimsIdentity(); + identityNoAuthTypeWithClaim.AddClaim(new Claim("identityNoAuthTypeWithClaim", "yes")); + existingPrincipal.AddIdentity(identityNoAuthTypeWithClaim); + var identityEmptyWithAuthType = new ClaimsIdentity("empty"); + existingPrincipal.AddIdentity(identityEmptyWithAuthType); + + Assert.False(existingPrincipal.Identity.IsAuthenticated); + + var newPrincipal = new ClaimsPrincipal(); + var newEmptyIdentity = new ClaimsIdentity(); + var identityTwo = new ClaimsIdentity("yep"); + newPrincipal.AddIdentity(newEmptyIdentity); + newPrincipal.AddIdentity(identityTwo); + + var user = SecurityHelper.MergeUserPrincipal(existingPrincipal, newPrincipal); + + // Preserve newPrincipal order + Assert.False(user.Identity.IsAuthenticated); + Assert.Null(user.Identity.Name); + + Assert.Equal(4, user.Identities.Count()); + Assert.Equal(newEmptyIdentity, user.Identities.Skip(0).First()); + Assert.Equal(identityTwo, user.Identities.Skip(1).First()); + Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); + Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); + + // This merge should drop newEmptyIdentity since its empty + user = SecurityHelper.MergeUserPrincipal(user, new GenericPrincipal(new GenericIdentity("Test3", "Gamma"), new string[0])); + + Assert.Equal("Gamma", user.Identity.AuthenticationType); + Assert.Equal("Test3", user.Identity.Name); + + Assert.Equal(4, user.Identities.Count()); + Assert.Equal("Test3", user.Identities.Skip(0).First().Name); + Assert.Equal(identityTwo, user.Identities.Skip(1).First()); + Assert.Equal(identityNoAuthTypeWithClaim, user.Identities.Skip(2).First()); + Assert.Equal(identityEmptyWithAuthType, user.Identities.Skip(3).First()); + } + } +} diff --git a/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs new file mode 100644 index 0000000000..657a310b6e --- /dev/null +++ b/src/Shared/test/Shared.Tests/StackTraceHelperTest.cs @@ -0,0 +1,345 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.Extensions.StackTrace.Sources; +using ThrowingLibrary; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class StackTraceHelperTest + { + [Fact] + public void StackTraceHelper_IncludesLineNumbersForFiles() + { + // Arrange + Exception exception = null; + try + { + // Throwing an exception in the current assembly always seems to populate the full stack + // trace regardless of symbol type. Crossing assembly boundaries ensures PortablePdbReader gets used + // on desktop. + Thrower.Throw(); + } + catch (Exception ex) + { + exception = ex; + } + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + Assert.Collection(stackFrames, + frame => + { + Assert.Contains("Thrower.cs", frame.FilePath); + Assert.Equal(17, frame.LineNumber); + }, + frame => + { + Assert.Contains("StackTraceHelperTest.cs", frame.FilePath); + }); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForGenericMethods() + { + // Arrange + var exception = Record.Exception(() => GenericMethod(null)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.GenericMethod(T val)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithOutParameters() + { + // Arrange + var exception = Record.Exception(() => MethodWithOutParameter(out var value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithOutParameter(out int value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericOutParameters() + { + // Arrange + var exception = Record.Exception(() => MethodWithGenericOutParameter("Test", out int value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericOutParameter(string a, out TVal value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithRefParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithRefParameter(ref value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithRefParameter(ref int value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithGenericRefParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithGenericRefParameter(ref value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithGenericRefParameter(ref TVal value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsWithNullableParameters() + { + // Arrange + var value = 0; + var exception = Record.Exception(() => MethodWithNullableParameter(value)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.MethodWithNullableParameter(Nullable value)", methods[0]); + } + + [Fact] + public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes() + { + // Arrange + var exception = Record.Exception(() => new GenericClass().Throw(0)); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.Throw(T parameter)", methods[0]); + } + + [Fact] + public void StackTraceHelper_ProducesReadableOutput() + { + // Arrange + var expectedCallStack = new List() + { + "Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()", + "string.Join(string separator, IEnumerable values)", + "Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass.GenericMethod(ref V value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(TValue value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.Method(string value)", + "Microsoft.Extensions.Internal.StackTraceHelperTest.StackTraceHelper_ProducesReadableOutput()", + }; + + Exception exception = null; + try + { + Method("test"); + } + catch (Exception ex) + { + exception = ex; + } + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + var methodNames = stackFrames.Select(stackFrame => stackFrame.MethodDisplayInfo.ToString()).ToArray(); + + // Assert + Assert.Equal(expectedCallStack, methodNames); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeInstanceMethodsOnTypesWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => InvokeMethodOnTypeWithStackTraceHiddenAttribute()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeStaticMethodsOnTypesWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute()", methods[1]); + } + + [Fact] + public void StackTraceHelper_DoesNotIncludeMethodsWithStackTraceHiddenAttribute() + { + // Arrange + var exception = Record.Exception(() => new TypeWithMethodWithStackTraceHiddenAttribute().Throw()); + + // Act + var stackFrames = StackTraceHelper.GetFrames(exception); + + // Assert + var methods = stackFrames.Select(frame => frame.MethodDisplayInfo.ToString()).ToArray(); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest.ThrowCore()", methods[0]); + Assert.Equal("Microsoft.Extensions.Internal.StackTraceHelperTest+TypeWithMethodWithStackTraceHiddenAttribute.Throw()", methods[1]); + } + + [Fact] + public void GetFrames_DoesNotFailForDynamicallyGeneratedAssemblies() + { + // Arrange + var action = (Action)Expression.Lambda( + Expression.Throw( + Expression.New(typeof(Exception)))).Compile(); + var exception = Record.Exception(action); + + // Act + var frames = StackTraceHelper.GetFrames(exception).ToArray(); + + // Assert + var frame = frames[0]; + Assert.Null(frame.FilePath); + Assert.Equal($"lambda_method(Closure )", frame.MethodDisplayInfo.ToString()); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + async Task MethodAsync(int value) + { + await Task.Delay(0); + return GenericClass.GenericMethod(ref value); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + async Task MethodAsync(TValue value) + { + return await MethodAsync(1); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + string Method(string value) + { + return MethodAsync(value).GetAwaiter().GetResult(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + static IEnumerable Iterator() + { + yield return "Success"; + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithOutParameter(out int value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithGenericOutParameter(string a, out TVal value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithRefParameter(ref int value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithGenericRefParameter(ref TVal value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void MethodWithNullableParameter(int? value) => throw new Exception(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void InvokeMethodOnTypeWithStackTraceHiddenAttribute() => new TypeWithStackTraceHiddenAttribute().Throw(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + void InvokeStaticMethodOnTypeWithStackTraceHiddenAttribute() => TypeWithStackTraceHiddenAttribute.ThrowStatic(); + + class GenericClass + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static string GenericMethod(ref V value) + { + var returnVal = ""; + for (var i = 0; i < 10; i++) + { + returnVal += string.Join(", ", Iterator()); + } + return returnVal; + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw(T parameter) => throw new Exception(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private void GenericMethod(T val) where T : class => throw new Exception(); + + private class StackTraceHiddenAttribute : Attribute + { + } + + [StackTraceHidden] + private class TypeWithStackTraceHiddenAttribute + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw() => ThrowCore(); + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public static void ThrowStatic() => ThrowCore(); + } + + private class TypeWithMethodWithStackTraceHiddenAttribute + { + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + [StackTraceHidden] + public void MethodWithStackTraceHiddenAttribute() + { + ThrowCore(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + public void Throw() => MethodWithStackTraceHiddenAttribute(); + } + + [MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)] + private static void ThrowCore() => throw new Exception(); + } +} diff --git a/src/Shared/test/Shared.Tests/WebEncodersTests.cs b/src/Shared/test/Shared.Tests/WebEncodersTests.cs new file mode 100644 index 0000000000..5c71403fd6 --- /dev/null +++ b/src/Shared/test/Shared.Tests/WebEncodersTests.cs @@ -0,0 +1,113 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Xunit; + +namespace Microsoft.Extensions.Internal +{ + public class WebEncodersTests + { + [Theory] + [InlineData("", 1, 0)] + [InlineData("", 0, 1)] + [InlineData("0123456789", 9, 2)] + [InlineData("0123456789", Int32.MaxValue, 2)] + [InlineData("0123456789", 9, -1)] + public void Base64UrlDecode_BadOffsets(string input, int offset, int count) + { + // Act & assert + Assert.ThrowsAny(() => + { + var retVal = WebEncoders.Base64UrlDecode(input, offset, count); + }); + } + + [Theory] + [InlineData("x")] + [InlineData("(x)")] + public void Base64UrlDecode_MalformedInput(string input) + { + // Act & assert + Assert.Throws(() => + { + var retVal = WebEncoders.Base64UrlDecode(input); + }); + } + + [Theory] + [InlineData("", "")] + [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] + [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] + [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] + public void Base64UrlEncode_And_Decode(string base64Input, string expectedBase64Url) + { + // Arrange + byte[] input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); + + // Act & assert - 1 + string actualBase64Url = WebEncoders.Base64UrlEncode(input, 3, input.Length - 5); // also helps test offsets + Assert.Equal(expectedBase64Url, actualBase64Url); + + // Act & assert - 2 + // Verify that values round-trip + byte[] roundTripped = WebEncoders.Base64UrlDecode("xx" + actualBase64Url + "yyy", 2, actualBase64Url.Length); // also helps test offsets + string roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); + Assert.Equal(roundTrippedAsBase64, base64Input); + } + + [Theory] + [InlineData("", "")] + [InlineData("123456qwerty++//X+/x", "123456qwerty--__X-_x")] + [InlineData("123456qwerty++//X+/xxw==", "123456qwerty--__X-_xxw")] + [InlineData("123456qwerty++//X+/xxw0=", "123456qwerty--__X-_xxw0")] + public void Base64UrlEncode_And_Decode_WithBufferOffsets(string base64Input, string expectedBase64Url) + { + // Arrange + var input = new byte[3].Concat(Convert.FromBase64String(base64Input)).Concat(new byte[2]).ToArray(); + var buffer = new char[30]; + var output = new char[30]; + for (var i = 0; i < buffer.Length; i++) + { + buffer[i] = '^'; + output[i] = '^'; + } + + // Act 1 + var numEncodedChars = + WebEncoders.Base64UrlEncode(input, offset: 3, output: output, outputOffset: 4, count: input.Length - 5); + + // Assert 1 + var encodedString = new string(output, startIndex: 4, length: numEncodedChars); + Assert.Equal(expectedBase64Url, encodedString); + + // Act 2 + var roundTripInput = new string(output); + var roundTripped = + WebEncoders.Base64UrlDecode(roundTripInput, offset: 4, buffer: buffer, bufferOffset: 5, count: numEncodedChars); + + // Assert 2, verify that values round-trip + var roundTrippedAsBase64 = Convert.ToBase64String(roundTripped); + Assert.Equal(roundTrippedAsBase64, base64Input); + } + + [Theory] + [InlineData(0, 1, 0)] + [InlineData(0, 0, 1)] + [InlineData(10, 9, 2)] + [InlineData(10, Int32.MaxValue, 2)] + [InlineData(10, 9, -1)] + public void Base64UrlEncode_BadOffsets(int inputLength, int offset, int count) + { + // Arrange + byte[] input = new byte[inputLength]; + + // Act & assert + Assert.ThrowsAny(() => + { + var retVal = WebEncoders.Base64UrlEncode(input, offset, count); + }); + } + } +} diff --git a/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs b/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs new file mode 100644 index 0000000000..babe2387c6 --- /dev/null +++ b/src/Shared/test/testassets/ThrowingLibrary/Thrower.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; + +namespace ThrowingLibrary +{ + // Throwing an exception in the current assembly always seems to populate the full stack + // trace regardless of symbol type. This type exists to simulate an exception thrown + // across assemblies which is the typical use case for StackTraceHelper. + public static class Thrower + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void Throw() + { + throw new DivideByZeroException(); + } + } +} diff --git a/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj new file mode 100644 index 0000000000..2b2900911a --- /dev/null +++ b/src/Shared/test/testassets/ThrowingLibrary/ThrowingLibrary.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.0 + portable + false + + + diff --git a/src/SignalR/Directory.Build.props b/src/SignalR/Directory.Build.props index 5eadbd4ae2..c99c9cd91a 100644 --- a/src/SignalR/Directory.Build.props +++ b/src/SignalR/Directory.Build.props @@ -16,5 +16,6 @@ true true latest + $(MSBuildThisFileDirectory)..\Shared\ diff --git a/src/SignalR/build/dependencies.props b/src/SignalR/build/dependencies.props index 361fce7a5f..34fc6c4be9 100644 --- a/src/SignalR/build/dependencies.props +++ b/src/SignalR/build/dependencies.props @@ -38,7 +38,6 @@ 2.2.0-rtm-35661 2.2.0-rtm-35661 2.2.0-rtm-35661 - 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 @@ -53,9 +52,7 @@ 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 - 2.2.0-rtm-181106-13 2.2.0-rtm-181106-13 - 2.2.0-rtm-181106-13 2.2.0-rtm-27105-02 15.6.1 4.10.0 diff --git a/src/SignalR/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj b/src/SignalR/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj index 1cc7e47c44..53b5b2e1ef 100644 --- a/src/SignalR/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj +++ b/src/SignalR/src/Microsoft.AspNetCore.Http.Connections/Microsoft.AspNetCore.Http.Connections.csproj @@ -15,6 +15,11 @@ + + + + + @@ -25,8 +30,6 @@ - - diff --git a/src/SignalR/src/Microsoft.AspNetCore.SignalR.Core/Microsoft.AspNetCore.SignalR.Core.csproj b/src/SignalR/src/Microsoft.AspNetCore.SignalR.Core/Microsoft.AspNetCore.SignalR.Core.csproj index d63b5aed42..728a93a055 100644 --- a/src/SignalR/src/Microsoft.AspNetCore.SignalR.Core/Microsoft.AspNetCore.SignalR.Core.csproj +++ b/src/SignalR/src/Microsoft.AspNetCore.SignalR.Core/Microsoft.AspNetCore.SignalR.Core.csproj @@ -6,6 +6,11 @@ Microsoft.AspNetCore.SignalR + + + + + @@ -15,8 +20,6 @@ - - diff --git a/src/Templating/Directory.Build.props b/src/Templating/Directory.Build.props index d7eb964138..356908672a 100644 --- a/src/Templating/Directory.Build.props +++ b/src/Templating/Directory.Build.props @@ -13,5 +13,6 @@ https://github.com/aspnet/AspNetCore git true + $(MSBuildThisFileDirectory)..\Shared\ diff --git a/src/Templating/build/dependencies.props b/src/Templating/build/dependencies.props index 0fff6800e6..008697bb69 100644 --- a/src/Templating/build/dependencies.props +++ b/src/Templating/build/dependencies.props @@ -13,7 +13,6 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 @@ -33,7 +32,6 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-preview3-35359 2.0.9 2.1.3 2.2.0-preview3-26927-02 diff --git a/src/Templating/test/Templates.Test/Templates.Test.csproj b/src/Templating/test/Templates.Test/Templates.Test.csproj index 10f46cdf09..abd2a02d88 100644 --- a/src/Templating/test/Templates.Test/Templates.Test.csproj +++ b/src/Templating/test/Templates.Test/Templates.Test.csproj @@ -6,15 +6,15 @@ + + - - diff --git a/src/Tools/Directory.Build.props b/src/Tools/Directory.Build.props index 6b35802689..0cbe48018a 100644 --- a/src/Tools/Directory.Build.props +++ b/src/Tools/Directory.Build.props @@ -4,6 +4,7 @@ $(RepositoryRoot)obj\$(MSBuildProjectName)\ $(RepositoryRoot)bin\$(MSBuildProjectName)\ + $(MSBuildThisFileDirectory)Shared\ diff --git a/src/Tools/FirstRunCertGenerator/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj b/src/Tools/FirstRunCertGenerator/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj index c5b21cb478..5796c9fb86 100644 --- a/src/Tools/FirstRunCertGenerator/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj +++ b/src/Tools/FirstRunCertGenerator/src/Microsoft.AspNetCore.DeveloperCertificates.XPlat.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Tools/FirstRunCertGenerator/src/Properties/AssemblyInfo.cs b/src/Tools/FirstRunCertGenerator/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..0f02f95e4a --- /dev/null +++ b/src/Tools/FirstRunCertGenerator/src/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs new file mode 100644 index 0000000000..b11f92ee54 --- /dev/null +++ b/src/Tools/FirstRunCertGenerator/test/CertificateManagerTests.cs @@ -0,0 +1,288 @@ +// 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. + +#if NETCOREAPP2_2 + +using System; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Certificates.Generation.Tests +{ + public class CertificateManagerTests + { + public CertificateManagerTests(ITestOutputHelper output) + { + Output = output; + } + + public const string TestCertificateSubject = "CN=aspnet.test"; + + public ITestOutputHelper Output { get; } + + [Fact(Skip = "True")] + public void EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates() + { + try + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + // Act + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.Succeeded, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); + Assert.NotNull(exportedCertificate); + Assert.False(exportedCertificate.HasPrivateKey); + + var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false); + var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject); + Assert.True(httpsCertificate.HasPrivateKey); + Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); + Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer); + Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName); + Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value); + + Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore); + Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter); + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509BasicConstraintsExtension basicConstraints && + basicConstraints.Critical == true && + basicConstraints.CertificateAuthority == false && + basicConstraints.HasPathLengthConstraint == false && + basicConstraints.PathLengthConstraint == 0); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509KeyUsageExtension keyUsage && + keyUsage.Critical == true && + keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && + enhancedKeyUsage.Critical == true && + enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && + keyUsage.Value == "1.3.6.1.5.5.7.3.1"); + + // Subject alternative name + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == true && + e.Oid.Value == "2.5.29.17"); + + // ASP.NET HTTPS Development certificate extension + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == false && + e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && + Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate"); + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + + } + catch (Exception e) + { + Output.WriteLine(e.Message); + ListCertificates(Output); + throw; + } + } + + [Fact] + public void EnsureCreateHttpsCertificate2_CreatesACertificate_WhenThereAreNoHttpsCertificates() + { + try + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_CreatesACertificate_WhenThereAreNoHttpsCertificates) + ".cer"; + var manager = new CertificateManager(); + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + // Act + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate2(now, now.AddYears(1), CertificateName, trust: false, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.Succeeded, result.ResultCode); + Assert.NotNull(result.Diagnostics); + Assert.NotEmpty(result.Diagnostics.Messages); + Assert.Empty(result.Diagnostics.Exceptions); + + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName)); + Assert.NotNull(exportedCertificate); + Assert.False(exportedCertificate.HasPrivateKey); + + var httpsCertificates = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false); + var httpsCertificate = Assert.Single(httpsCertificates, c => c.Subject == TestCertificateSubject); + Assert.True(httpsCertificate.HasPrivateKey); + Assert.Equal(TestCertificateSubject, httpsCertificate.Subject); + Assert.Equal(TestCertificateSubject, httpsCertificate.Issuer); + Assert.Equal("sha256RSA", httpsCertificate.SignatureAlgorithm.FriendlyName); + Assert.Equal("1.2.840.113549.1.1.11", httpsCertificate.SignatureAlgorithm.Value); + + Assert.Equal(now.LocalDateTime, httpsCertificate.NotBefore); + Assert.Equal(now.AddYears(1).LocalDateTime, httpsCertificate.NotAfter); + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509BasicConstraintsExtension basicConstraints && + basicConstraints.Critical == true && + basicConstraints.CertificateAuthority == false && + basicConstraints.HasPathLengthConstraint == false && + basicConstraints.PathLengthConstraint == 0); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509KeyUsageExtension keyUsage && + keyUsage.Critical == true && + keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment); + + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage && + enhancedKeyUsage.Critical == true && + enhancedKeyUsage.EnhancedKeyUsages.OfType().Single() is Oid keyUsage && + keyUsage.Value == "1.3.6.1.5.5.7.3.1"); + + // Subject alternative name + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == true && + e.Oid.Value == "2.5.29.17"); + + // ASP.NET HTTPS Development certificate extension + Assert.Contains( + httpsCertificate.Extensions.OfType(), + e => e.Critical == false && + e.Oid.Value == "1.3.6.1.4.1.311.84.1.1" && + Encoding.ASCII.GetString(e.RawData) == "ASP.NET Core HTTPS development certificate"); + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + + } + catch (Exception e) + { + Output.WriteLine(e.Message); + ListCertificates(Output); + throw; + } + } + + private void ListCertificates(ITestOutputHelper output) + { + using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser)) + { + store.Open(OpenFlags.ReadOnly); + var certificates = store.Certificates; + foreach (var certificate in certificates) + { + Output.WriteLine($"Certificate: '{Convert.ToBase64String(certificate.Export(X509ContentType.Cert))}'."); + certificate.Dispose(); + } + + store.Close(); + } + } + + [Fact(Skip = "true")] + public void EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates() + { + // Arrange + const string CertificateName = nameof(EnsureCreateHttpsCertificate_DoesNotCreateACertificate_WhenThereIsAnExistingHttpsCertificates) + ".pfx"; + var certificatePassword = Guid.NewGuid().ToString(); + + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: false, subject: TestCertificateSubject); + + var httpsCertificate = manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Single(c => c.Subject == TestCertificateSubject); + + // Act + var result = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), CertificateName, trust: false, includePrivateKey: true, password: certificatePassword, subject: TestCertificateSubject); + + // Assert + Assert.Equal(EnsureCertificateResult.ValidCertificatePresent, result); + Assert.True(File.Exists(CertificateName)); + + var exportedCertificate = new X509Certificate2(File.ReadAllBytes(CertificateName), certificatePassword); + Assert.NotNull(exportedCertificate); + Assert.True(exportedCertificate.HasPrivateKey); + + + Assert.Equal(httpsCertificate.GetCertHashString(), exportedCertificate.GetCertHashString()); + } + + [Fact(Skip = "Requires user interaction")] + public void EnsureAspNetCoreHttpsDevelopmentCertificate_ReturnsCorrectResult_WhenUserCancelsTrustStepOnWindows() + { + var manager = new CertificateManager(); + + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, TestCertificateSubject); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + manager.RemoveAllCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, TestCertificateSubject); + } + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + var trustFailed = manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); + + Assert.Equal(EnsureCertificateResult.UserCancelledTrustStep, trustFailed); + } + + [Fact(Skip = "Requires user interaction")] + public void EnsureAspNetCoreHttpsDevelopmentCertificate_CanRemoveCertificates() + { + var manager = new CertificateManager(); + + DateTimeOffset now = DateTimeOffset.UtcNow; + now = new DateTimeOffset(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0, now.Offset); + manager.EnsureAspNetCoreHttpsDevelopmentCertificate(now, now.AddYears(1), path: null, trust: true, subject: TestCertificateSubject); + + manager.CleanupHttpsCertificates(TestCertificateSubject); + + Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Empty(manager.ListCertificates(CertificatePurpose.HTTPS, StoreName.Root, StoreLocation.CurrentUser, isValid: false).Where(c => c.Subject == TestCertificateSubject)); + } + } + } +} + +#endif diff --git a/src/Tools/FirstRunCertGenerator/test/Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests.csproj b/src/Tools/FirstRunCertGenerator/test/Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests.csproj new file mode 100644 index 0000000000..dc5df79a40 --- /dev/null +++ b/src/Tools/FirstRunCertGenerator/test/Microsoft.AspNetCore.DeveloperCertificates.XPlat.Tests.csproj @@ -0,0 +1,11 @@ + + + + netcoreapp2.2 + + + + + + + diff --git a/src/Tools/shared/src/CliContext.cs b/src/Tools/Shared/CommandLine/CliContext.cs similarity index 100% rename from src/Tools/shared/src/CliContext.cs rename to src/Tools/Shared/CommandLine/CliContext.cs diff --git a/src/Tools/shared/src/CommandLineApplicationExtensions.cs b/src/Tools/Shared/CommandLine/CommandLineApplicationExtensions.cs similarity index 100% rename from src/Tools/shared/src/CommandLineApplicationExtensions.cs rename to src/Tools/Shared/CommandLine/CommandLineApplicationExtensions.cs diff --git a/src/Tools/shared/src/ConsoleReporter.cs b/src/Tools/Shared/CommandLine/ConsoleReporter.cs similarity index 100% rename from src/Tools/shared/src/ConsoleReporter.cs rename to src/Tools/Shared/CommandLine/ConsoleReporter.cs diff --git a/src/Tools/shared/src/DebugHelper.cs b/src/Tools/Shared/CommandLine/DebugHelper.cs similarity index 100% rename from src/Tools/shared/src/DebugHelper.cs rename to src/Tools/Shared/CommandLine/DebugHelper.cs diff --git a/src/Tools/shared/src/Ensure.cs b/src/Tools/Shared/CommandLine/Ensure.cs similarity index 100% rename from src/Tools/shared/src/Ensure.cs rename to src/Tools/Shared/CommandLine/Ensure.cs diff --git a/src/Tools/shared/src/IConsole.cs b/src/Tools/Shared/CommandLine/IConsole.cs similarity index 100% rename from src/Tools/shared/src/IConsole.cs rename to src/Tools/Shared/CommandLine/IConsole.cs diff --git a/src/Tools/shared/src/IReporter.cs b/src/Tools/Shared/CommandLine/IReporter.cs similarity index 100% rename from src/Tools/shared/src/IReporter.cs rename to src/Tools/Shared/CommandLine/IReporter.cs diff --git a/src/Tools/shared/src/NullReporter.cs b/src/Tools/Shared/CommandLine/NullReporter.cs similarity index 100% rename from src/Tools/shared/src/NullReporter.cs rename to src/Tools/Shared/CommandLine/NullReporter.cs diff --git a/src/Tools/shared/src/PhysicalConsole.cs b/src/Tools/Shared/CommandLine/PhysicalConsole.cs similarity index 100% rename from src/Tools/shared/src/PhysicalConsole.cs rename to src/Tools/Shared/CommandLine/PhysicalConsole.cs diff --git a/src/Tools/shared/test/TestConsole.cs b/src/Tools/Shared/TestHelpers/TestConsole.cs similarity index 100% rename from src/Tools/shared/test/TestConsole.cs rename to src/Tools/Shared/TestHelpers/TestConsole.cs diff --git a/src/Tools/shared/test/TestReporter.cs b/src/Tools/Shared/TestHelpers/TestReporter.cs similarity index 100% rename from src/Tools/shared/test/TestReporter.cs rename to src/Tools/Shared/TestHelpers/TestReporter.cs diff --git a/src/Tools/dotnet-dev-certs/src/dotnet-dev-certs.csproj b/src/Tools/dotnet-dev-certs/src/dotnet-dev-certs.csproj index a0fe307a72..15e8245b52 100644 --- a/src/Tools/dotnet-dev-certs/src/dotnet-dev-certs.csproj +++ b/src/Tools/dotnet-dev-certs/src/dotnet-dev-certs.csproj @@ -12,19 +12,11 @@ - - - - - - - - - + + - diff --git a/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj b/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj index b4db8f100f..468deea1ec 100644 --- a/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj +++ b/src/Tools/dotnet-sql-cache/src/dotnet-sql-cache.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj index 46feeeb1a6..a5dbdecd1c 100644 --- a/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj +++ b/src/Tools/dotnet-user-secrets/src/dotnet-user-secrets.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj index 56c964c016..82e940a061 100644 --- a/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj +++ b/src/Tools/dotnet-user-secrets/test/dotnet-user-secrets.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Tools/dotnet-watch/src/dotnet-watch.csproj b/src/Tools/dotnet-watch/src/dotnet-watch.csproj index 09f60754c3..c3df24638f 100644 --- a/src/Tools/dotnet-watch/src/dotnet-watch.csproj +++ b/src/Tools/dotnet-watch/src/dotnet-watch.csproj @@ -12,13 +12,13 @@ - + + - diff --git a/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj b/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj index ecb1cdf907..2e97def40f 100644 --- a/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj +++ b/src/Tools/dotnet-watch/test/dotnet-watch.Tests.csproj @@ -7,7 +7,7 @@ - +