Merge branch 'release/2.1' into release/2.2
\n\nCommit migrated from 0a705e33a5
This commit is contained in:
commit
93b7e57a30
|
|
@ -1,951 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<X509Certificate2> 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<X509Certificate2>();
|
||||
try
|
||||
{
|
||||
using (var store = new X509Store(storeName, location))
|
||||
{
|
||||
store.Open(OpenFlags.ReadOnly);
|
||||
certificates.AddRange(store.Certificates.OfType<X509Certificate2>());
|
||||
IEnumerable<X509Certificate2> 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<X509Certificate2>)matchingCertificates;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DisposeCertificates(certificates);
|
||||
certificates.Clear();
|
||||
return certificates;
|
||||
}
|
||||
|
||||
bool HasOid(X509Certificate2 certificate, string oid) =>
|
||||
certificate.Extensions.OfType<X509Extension>()
|
||||
.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<X509Certificate2> 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<X509Extension>();
|
||||
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<X509Extension> 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<X509Certificate2>());
|
||||
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<Match>().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<X509Certificate2>()
|
||||
.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<X509Certificate2>()
|
||||
.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<string> Messages { get; } = new List<string>();
|
||||
|
||||
public IList<Exception> Exceptions { get; } = new List<Exception>();
|
||||
|
||||
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<X509Certificate2> certificates)
|
||||
{
|
||||
var result = new List<string>();
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Certificates.Generation
|
||||
{
|
||||
internal enum CertificatePurpose
|
||||
{
|
||||
All,
|
||||
HTTPS
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
<Project>
|
||||
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\Directory.Build.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>Microsoft.AspNetCore.Certificates.Generation.Sources</PackageId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#if NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2
|
||||
|
||||
namespace Microsoft.AspNetCore.Certificates.Generation
|
||||
{
|
||||
internal enum EnsureCertificateResult
|
||||
{
|
||||
Succeeded = 1,
|
||||
ValidCertificatePresent,
|
||||
ErrorCreatingTheCertificate,
|
||||
ErrorSavingTheCertificateIntoTheCurrentUserPersonalStore,
|
||||
ErrorExportingTheCertificate,
|
||||
FailedToTrustTheCertificate,
|
||||
UserCancelledTrustStep
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper related to generic interface definitions and implementing classes.
|
||||
/// </summary>
|
||||
internal static class ClosedGenericMatcher
|
||||
{
|
||||
/// <summary>
|
||||
/// Determine whether <paramref name="queryType"/> is or implements a closed generic <see cref="Type"/>
|
||||
/// created from <paramref name="interfaceType"/>.
|
||||
/// </summary>
|
||||
/// <param name="queryType">The <see cref="Type"/> of interest.</param>
|
||||
/// <param name="interfaceType">The open generic <see cref="Type"/> to match. Usually an interface.</param>
|
||||
/// <returns>
|
||||
/// The closed generic <see cref="Type"/> created from <paramref name="interfaceType"/> that
|
||||
/// <paramref name="queryType"/> is or implements. <c>null</c> if the two <see cref="Type"/>s have no such
|
||||
/// relationship.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method will return <paramref name="queryType"/> if <paramref name="interfaceType"/> is
|
||||
/// <c>typeof(KeyValuePair{,})</c>, and <paramref name="queryType"/> is
|
||||
/// <c>typeof(KeyValuePair{string, object})</c>.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal class CopyOnWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly IDictionary<TKey, TValue> _sourceDictionary;
|
||||
private readonly IEqualityComparer<TKey> _comparer;
|
||||
private IDictionary<TKey, TValue> _innerDictionary;
|
||||
|
||||
public CopyOnWriteDictionary(
|
||||
IDictionary<TKey, TValue> sourceDictionary,
|
||||
IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
if (sourceDictionary == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(sourceDictionary));
|
||||
}
|
||||
|
||||
if (comparer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(comparer));
|
||||
}
|
||||
|
||||
_sourceDictionary = sourceDictionary;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
private IDictionary<TKey, TValue> ReadDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return _innerDictionary ?? _sourceDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
private IDictionary<TKey, TValue> WriteDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_innerDictionary == null)
|
||||
{
|
||||
_innerDictionary = new Dictionary<TKey, TValue>(_sourceDictionary,
|
||||
_comparer);
|
||||
}
|
||||
|
||||
return _innerDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ICollection<TKey> Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual ICollection<TValue> 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<TKey, TValue> item)
|
||||
{
|
||||
WriteDictionary.Add(item);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
WriteDictionary.Clear();
|
||||
}
|
||||
|
||||
public virtual bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ReadDictionary.Contains(item);
|
||||
}
|
||||
|
||||
public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
ReadDictionary.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return WriteDictionary.Remove(item);
|
||||
}
|
||||
|
||||
public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return ReadDictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal struct CopyOnWriteDictionaryHolder<TKey, TValue>
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue> _source;
|
||||
private Dictionary<TKey, TValue> _copy;
|
||||
|
||||
public CopyOnWriteDictionaryHolder(Dictionary<TKey, TValue> source)
|
||||
{
|
||||
if (source == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
_source = source;
|
||||
_copy = null;
|
||||
}
|
||||
|
||||
public CopyOnWriteDictionaryHolder(CopyOnWriteDictionaryHolder<TKey, TValue> source)
|
||||
{
|
||||
_source = source._copy ?? source._source;
|
||||
_copy = null;
|
||||
}
|
||||
|
||||
public bool HasBeenCopied => _copy != null;
|
||||
|
||||
public Dictionary<TKey, TValue> ReadDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_copy != null)
|
||||
{
|
||||
return _copy;
|
||||
}
|
||||
else if (_source != null)
|
||||
{
|
||||
return _source;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default-Constructor case
|
||||
_copy = new Dictionary<TKey, TValue>();
|
||||
return _copy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue> WriteDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_copy == null && _source == null)
|
||||
{
|
||||
// Default-Constructor case
|
||||
_copy = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
else if (_copy == null)
|
||||
{
|
||||
_copy = new Dictionary<TKey, TValue>(_source, _source.Comparer);
|
||||
}
|
||||
|
||||
return _copy;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.KeyCollection Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
return ReadDictionary.Keys;
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.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<TKey, TValue> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
WriteDictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)ReadDictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)WriteDictionary).Remove(item);
|
||||
}
|
||||
|
||||
public Dictionary<TKey, TValue>.Enumerator GetEnumerator()
|
||||
{
|
||||
return ReadDictionary.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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<T>.
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,340 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<object, object>), // getAwaiterMethod
|
||||
typeof(Func<object, bool>), // isCompletedMethod
|
||||
typeof(Func<object, object>), // getResultMethod
|
||||
typeof(Action<object, Action>), // onCompletedMethod
|
||||
typeof(Action<object, 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<T> 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the configured method on <paramref name="target"/>. This can be used whether or not
|
||||
/// the configured method is asynchronous.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
/// <param name="target">The object whose method is to be executed.</param>
|
||||
/// <param name="parameters">Parameters to pass to the method.</param>
|
||||
/// <returns>The method return value.</returns>
|
||||
public object Execute(object target, object[] parameters)
|
||||
{
|
||||
return _executor(target, parameters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the configured method on <paramref name="target"/>. This can only be used if the configured
|
||||
/// method is asynchronous.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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).
|
||||
/// </remarks>
|
||||
/// <param name="target">The object whose method is to be executed.</param>
|
||||
/// <param name="parameters">Parameters to pass to the method.</param>
|
||||
/// <returns>An object that you can "await" to get the method return value.</returns>
|
||||
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<Expression>();
|
||||
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<VoidMethodExecutor>(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<MethodExecutor>(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<Expression>();
|
||||
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<Func<object, object>>(
|
||||
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<Func<object, bool>>(
|
||||
Expression.MakeMemberAccess(
|
||||
Expression.Convert(isCompletedParam, awaitableInfo.AwaiterType),
|
||||
awaitableInfo.AwaiterIsCompletedProperty),
|
||||
isCompletedParam).Compile();
|
||||
|
||||
var getResultParam = Expression.Parameter(typeof(object), "awaiter");
|
||||
Func<object, object> 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<Func<object, object>>(
|
||||
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<Func<object, object>>(
|
||||
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<Action<object, Action>>(
|
||||
Expression.Call(
|
||||
Expression.Convert(onCompletedParam1, awaitableInfo.AwaiterType),
|
||||
awaitableInfo.AwaiterOnCompletedMethod,
|
||||
onCompletedParam2),
|
||||
onCompletedParam1,
|
||||
onCompletedParam2).Compile();
|
||||
|
||||
Action<object, 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<Action<object, Action>>(
|
||||
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<object, Action>)));
|
||||
|
||||
var lambda = Expression.Lambda<MethodExecutorAsync>(returnValueExpression, targetParameter, parametersParameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a common awaitable structure that <see cref="ObjectMethodExecutor.ExecuteAsync"/> can
|
||||
/// return, regardless of whether the underlying value is a System.Task, an FSharpAsync, or an
|
||||
/// application-defined custom awaitable.
|
||||
/// </summary>
|
||||
internal struct ObjectMethodExecutorAwaitable
|
||||
{
|
||||
private readonly object _customAwaitable;
|
||||
private readonly Func<object, object> _getAwaiterMethod;
|
||||
private readonly Func<object, bool> _isCompletedMethod;
|
||||
private readonly Func<object, object> _getResultMethod;
|
||||
private readonly Action<object, Action> _onCompletedMethod;
|
||||
private readonly Action<object, 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<T>).
|
||||
// [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<T>
|
||||
// or other value-typed awaitables.
|
||||
|
||||
public ObjectMethodExecutorAwaitable(
|
||||
object customAwaitable,
|
||||
Func<object, object> getAwaiterMethod,
|
||||
Func<object, bool> isCompletedMethod,
|
||||
Func<object, object> getResultMethod,
|
||||
Action<object, Action> onCompletedMethod,
|
||||
Action<object, 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<object, bool> _isCompletedMethod;
|
||||
private readonly Func<object, object> _getResultMethod;
|
||||
private readonly Action<object, Action> _onCompletedMethod;
|
||||
private readonly Action<object, Action> _unsafeOnCompletedMethod;
|
||||
|
||||
public Awaiter(
|
||||
object customAwaiter,
|
||||
Func<object, bool> isCompletedMethod,
|
||||
Func<object, object> getResultMethod,
|
||||
Action<object, Action> onCompletedMethod,
|
||||
Action<object, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper for detecting whether a given type is FSharpAsync`1, and if so, supplying
|
||||
/// an <see cref="Expression"/> for mapping instances of that type to a C# awaitable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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<TResult>(
|
||||
// (Microsoft.FSharp.Control.FSharpAsync<TResult>)fsharpAsync,
|
||||
// FSharpOption<TaskCreationOptions>.None,
|
||||
// FSharpOption<CancellationToken>.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<TaskCreationOptions>.None
|
||||
var fsharpOptionOfTaskCreationOptionsType = fsharpOptionType
|
||||
.MakeGenericType(typeof(TaskCreationOptions));
|
||||
_fsharpOptionOfTaskCreationOptionsNoneProperty = fsharpOptionOfTaskCreationOptionsType
|
||||
.GetTypeInfo()
|
||||
.GetRuntimeProperty("None");
|
||||
|
||||
// Get a reference to FSharpOption<CancellationToken>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<int>();
|
||||
GetAllChildIdsUnix(pid, children, timeout);
|
||||
foreach (var childId in children)
|
||||
{
|
||||
KillProcessUnix(childId, timeout);
|
||||
}
|
||||
KillProcessUnix(pid, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAllChildIdsUnix(int parentId, ISet<int> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
internal class PropertyActivator<TContext>
|
||||
{
|
||||
private readonly Func<TContext, object> _valueAccessor;
|
||||
private readonly Action<object, object> _fastPropertySetter;
|
||||
|
||||
public PropertyActivator(
|
||||
PropertyInfo propertyInfo,
|
||||
Func<TContext, object> 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<TContext>[] GetPropertiesToActivate(
|
||||
Type type,
|
||||
Type activateAttributeType,
|
||||
Func<PropertyInfo, PropertyActivator<TContext>> 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<TContext>[] GetPropertiesToActivate(
|
||||
Type type,
|
||||
Type activateAttributeType,
|
||||
Func<PropertyInfo, PropertyActivator<TContext>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,551 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.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<TDeclaringType, TValue>(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<Type, PropertyHelper[]> PropertiesCache =
|
||||
new ConcurrentDictionary<Type, PropertyHelper[]>();
|
||||
|
||||
private static readonly ConcurrentDictionary<Type, PropertyHelper[]> VisiblePropertiesCache =
|
||||
new ConcurrentDictionary<Type, PropertyHelper[]>();
|
||||
|
||||
// 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<object, object> _valueSetter;
|
||||
private Func<object, object> _valueGetter;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a fast <see cref="PropertyHelper"/>.
|
||||
/// This constructor does not cache the helper. For caching, use <see cref="GetProperties(Type)"/>.
|
||||
/// </summary>
|
||||
public PropertyHelper(PropertyInfo property)
|
||||
{
|
||||
if (property == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(property));
|
||||
}
|
||||
|
||||
Property = property;
|
||||
Name = property.Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backing <see cref="PropertyInfo"/>.
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets (or sets in derived types) the property name.
|
||||
/// </summary>
|
||||
public virtual string Name { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value getter.
|
||||
/// </summary>
|
||||
public Func<object, object> ValueGetter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueGetter == null)
|
||||
{
|
||||
_valueGetter = MakeFastPropertyGetter(Property);
|
||||
}
|
||||
|
||||
return _valueGetter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value setter.
|
||||
/// </summary>
|
||||
public Action<object, object> ValueSetter
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_valueSetter == null)
|
||||
{
|
||||
_valueSetter = MakeFastPropertySetter(Property);
|
||||
}
|
||||
|
||||
return _valueSetter;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the property value for the specified <paramref name="instance"/>.
|
||||
/// </summary>
|
||||
/// <param name="instance">The object whose property value will be returned.</param>
|
||||
/// <returns>The property value.</returns>
|
||||
public object GetValue(object instance)
|
||||
{
|
||||
return ValueGetter(instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value for the specified <paramref name="instance" />.
|
||||
/// </summary>
|
||||
/// <param name="instance">The object whose property value will be set.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
public void SetValue(object instance, object value)
|
||||
{
|
||||
ValueSetter(instance, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and caches fast property helpers that expose getters for every public get property on the
|
||||
/// underlying type.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The type info to extract property accessors for.</param>
|
||||
/// <returns>A cached array of all public properties of the specified type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetProperties(TypeInfo typeInfo)
|
||||
{
|
||||
return GetProperties(typeInfo.AsType());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and caches fast property helpers that expose getters for every public get property on the
|
||||
/// specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to extract property accessors for.</param>
|
||||
/// <returns>A cached array of all public properties of the specified type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetProperties(Type type)
|
||||
{
|
||||
return GetProperties(type, CreateInstance, PropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates and caches fast property helpers that expose getters for every non-hidden get property
|
||||
/// on the specified type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been
|
||||
/// hidden by definitions using the <c>new</c> keyword.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The type info to extract property accessors for.</param>
|
||||
/// <returns>
|
||||
/// A cached array of all public properties of the specified type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetVisibleProperties(TypeInfo typeInfo)
|
||||
{
|
||||
return GetVisibleProperties(typeInfo.AsType(), CreateInstance, PropertiesCache, VisiblePropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Creates and caches fast property helpers that expose getters for every non-hidden get property
|
||||
/// on the specified type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="M:GetVisibleProperties"/> excludes properties defined on base types that have been
|
||||
/// hidden by definitions using the <c>new</c> keyword.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="type">The type to extract property accessors for.</param>
|
||||
/// <returns>
|
||||
/// A cached array of all public properties of the specified type.
|
||||
/// </returns>
|
||||
public static PropertyHelper[] GetVisibleProperties(Type type)
|
||||
{
|
||||
return GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property getter. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed.
|
||||
/// </remarks>
|
||||
public static Func<object, object> MakeFastPropertyGetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
|
||||
return MakeFastPropertyGetter(
|
||||
propertyInfo,
|
||||
CallPropertyGetterOpenGenericMethod,
|
||||
CallPropertyGetterByReferenceOpenGenericMethod);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property getter which is safe for a null input object. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the getter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed.
|
||||
/// </remarks>
|
||||
public static Func<object, object> MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo)
|
||||
{
|
||||
Debug.Assert(propertyInfo != null);
|
||||
|
||||
return MakeFastPropertyGetter(
|
||||
propertyInfo,
|
||||
CallNullSafePropertyGetterOpenGenericMethod,
|
||||
CallNullSafePropertyGetterByReferenceOpenGenericMethod);
|
||||
}
|
||||
|
||||
private static Func<object, object> 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<object, object> 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<object, object>),
|
||||
propertyGetterDelegate);
|
||||
|
||||
return (Func<object, object>)accessorDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a single fast property setter for reference types. The result is not cached.
|
||||
/// </summary>
|
||||
/// <param name="propertyInfo">propertyInfo to extract the setter for.</param>
|
||||
/// <returns>a fast getter.</returns>
|
||||
/// <remarks>
|
||||
/// This method is more memory efficient than a dynamically compiled lambda, and about the
|
||||
/// same speed. This only works for reference types.
|
||||
/// </remarks>
|
||||
public static Action<object, object> 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<object, object>), propertySetterAsAction);
|
||||
|
||||
return (Action<object, object>)callPropertySetterDelegate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="IDictionary{String, Object}"/> instance, then a copy
|
||||
/// is returned.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The implementation of PropertyHelper will cache the property accessors per-type. This is
|
||||
/// faster when the same type is used multiple times with ObjectToDictionary.
|
||||
/// </remarks>
|
||||
public static IDictionary<string, object> ObjectToDictionary(object value)
|
||||
{
|
||||
var dictionary = value as IDictionary<string, object>;
|
||||
if (dictionary != null)
|
||||
{
|
||||
return new Dictionary<string, object>(dictionary, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
dictionary = new Dictionary<string, object>(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<TDeclaringType, TValue>(
|
||||
Func<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
return getter((TDeclaringType)target);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallPropertyGetterByReference<TDeclaringType, TValue>(
|
||||
ByRefFunc<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
var unboxed = (TDeclaringType)target;
|
||||
return getter(ref unboxed);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallNullSafePropertyGetter<TDeclaringType, TValue>(
|
||||
Func<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return getter((TDeclaringType)target);
|
||||
}
|
||||
|
||||
// Called via reflection
|
||||
private static object CallNullSafePropertyGetterByReference<TDeclaringType, TValue>(
|
||||
ByRefFunc<TDeclaringType, TValue> getter,
|
||||
object target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var unboxed = (TDeclaringType)target;
|
||||
return getter(ref unboxed);
|
||||
}
|
||||
|
||||
private static void CallPropertySetter<TDeclaringType, TValue>(
|
||||
Action<TDeclaringType, TValue> setter,
|
||||
object target,
|
||||
object value)
|
||||
{
|
||||
setter((TDeclaringType)target, (TValue)value);
|
||||
}
|
||||
|
||||
protected static PropertyHelper[] GetVisibleProperties(
|
||||
Type type,
|
||||
Func<PropertyInfo, PropertyHelper> createPropertyHelper,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> allPropertiesCache,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> 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<PropertyHelper>(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<PropertyInfo, PropertyHelper> createPropertyHelper,
|
||||
ConcurrentDictionary<Type, PropertyHelper[]> cache)
|
||||
{
|
||||
// Unwrap nullable types. This means Nullable<T>.Value and Nullable<T>.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.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<string, object, bool> value)
|
||||
{
|
||||
return new AttributeValue(value.Item1, value.Item2, value.Item3);
|
||||
}
|
||||
|
||||
public static AttributeValue FromTuple(Tuple<string, string, bool> value)
|
||||
{
|
||||
return new AttributeValue(value.Item1, value.Item2, value.Item3);
|
||||
}
|
||||
|
||||
public static implicit operator AttributeValue(Tuple<string, object, bool> value)
|
||||
{
|
||||
return FromTuple(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,279 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Infrastructure
|
||||
/// </summary>
|
||||
internal abstract class BaseView
|
||||
{
|
||||
private static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
|
||||
private readonly Stack<TextWriter> _textWriterStack = new Stack<TextWriter>();
|
||||
|
||||
/// <summary>
|
||||
/// The request context
|
||||
/// </summary>
|
||||
protected HttpContext Context { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The request
|
||||
/// </summary>
|
||||
protected HttpRequest Request { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The response
|
||||
/// </summary>
|
||||
protected HttpResponse Response { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The output stream
|
||||
/// </summary>
|
||||
protected TextWriter Output { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Html encoder used to encode content.
|
||||
/// </summary>
|
||||
protected HtmlEncoder HtmlEncoder { get; set; } = HtmlEncoder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Url encoder used to encode content.
|
||||
/// </summary>
|
||||
protected UrlEncoder UrlEncoder { get; set; } = UrlEncoder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// JavaScript encoder used to encode content.
|
||||
/// </summary>
|
||||
protected JavaScriptEncoder JavaScriptEncoder { get; set; } = JavaScriptEncoder.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Execute an individual request
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute an individual request
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
protected void WriteLiteral(object value)
|
||||
{
|
||||
WriteLiteral(Convert.ToString(value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the given value without HTML encoding directly to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="string"/> to write.</param>
|
||||
protected void WriteLiteral(string value)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
Output.Write(value);
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> AttributeValues { get; set; }
|
||||
|
||||
protected void WriteAttributeValue(string thingy, int startPostion, object value, int endValue, int dealyo, bool yesno)
|
||||
{
|
||||
if (AttributeValues == null)
|
||||
{
|
||||
AttributeValues = new List<string>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the given attribute to the given writer
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the attribute to write</param>
|
||||
/// <param name="leader">The value of the prefix</param>
|
||||
/// <param name="trailer">The value of the suffix</param>
|
||||
/// <param name="values">The <see cref="AttributeValue"/>s to write.</param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked
|
||||
/// </summary>
|
||||
/// <param name="result">The <see cref="HelperResult"/> to invoke</param>
|
||||
protected void Write(HelperResult result)
|
||||
{
|
||||
Write(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="object"/> to write.</param>
|
||||
/// <remarks>
|
||||
/// <see cref="HelperResult.WriteTo(TextWriter)"/> is invoked for <see cref="HelperResult"/> types.
|
||||
/// For all other types, the encoded result of <see cref="object.ToString"/> is written to
|
||||
/// <see cref="Output"/>.
|
||||
/// </remarks>
|
||||
protected void Write(object value)
|
||||
{
|
||||
if (value is HelperResult helperResult)
|
||||
{
|
||||
helperResult.WriteTo(Output);
|
||||
}
|
||||
else
|
||||
{
|
||||
Write(Convert.ToString(value, CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The <see cref="string"/> to write.</param>
|
||||
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("<br />" + Environment.NewLine,
|
||||
input.Split(new[] { "\r\n" }, StringSplitOptions.None)
|
||||
.SelectMany(s => s.Split(new[] { '\r', '\n' }, StringSplitOptions.None))
|
||||
.Select(HtmlEncoder.Encode));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Extensions.RazorViews
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a deferred write operation in a <see cref="BaseView"/>.
|
||||
/// </summary>
|
||||
internal class HelperResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HelperResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The delegate to invoke when <see cref="WriteTo(TextWriter)"/> is called.</param>
|
||||
public HelperResult(Action<TextWriter> action)
|
||||
{
|
||||
WriteAction = action;
|
||||
}
|
||||
|
||||
public Action<TextWriter> WriteAction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Method invoked to produce content from the <see cref="HelperResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="writer">The <see cref="TextWriter"/> instance to write to.</param>
|
||||
public void WriteTo(TextWriter writer)
|
||||
{
|
||||
WriteAction(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper code used when implementing authentication middleware
|
||||
/// </summary>
|
||||
internal static class SecurityHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
/// <param name="existingPrincipal">The <see cref="ClaimsPrincipal"/> containing existing <see cref="ClaimsIdentity"/>.</param>
|
||||
/// <param name="additionalPrincipal">The <see cref="ClaimsPrincipal"/> containing <see cref="ClaimsIdentity"/> to be added.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.StackTrace.Sources
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains details for individual exception messages.
|
||||
/// </summary>
|
||||
internal class ExceptionDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// An individual exception
|
||||
/// </summary>
|
||||
public Exception Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The generated stack frames
|
||||
/// </summary>
|
||||
public IEnumerable<StackFrameSourceCodeInfo> StackFrames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the summary message.
|
||||
/// </summary>
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using 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<ExceptionDetails> 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<Exception> 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<Exception>();
|
||||
foreach (var loadException in typeLoadException.LoaderExceptions)
|
||||
{
|
||||
typeLoadExceptions.AddRange(FlattenAndReverseExceptionTree(loadException));
|
||||
}
|
||||
|
||||
typeLoadExceptions.Add(ex);
|
||||
return typeLoadExceptions;
|
||||
}
|
||||
|
||||
var list = new List<Exception>();
|
||||
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<string> 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<string> 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<string> ReadLines(IFileInfo fileInfo)
|
||||
{
|
||||
using (var reader = new StreamReader(fileInfo.CreateReadStream()))
|
||||
{
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
yield return line;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.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<ParameterDisplayInfo> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<string, MetadataReaderProvider> _cache =
|
||||
new Dictionary<string, MetadataReaderProvider>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Extensions.StackTrace.Sources
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the source code where the exception occurred.
|
||||
/// </summary>
|
||||
internal class StackFrameSourceCodeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Function containing instruction
|
||||
/// </summary>
|
||||
public string Function { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File containing the instruction
|
||||
/// </summary>
|
||||
public string File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The line number of the instruction
|
||||
/// </summary>
|
||||
public int Line { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The line preceding the frame line
|
||||
/// </summary>
|
||||
public int PreContextLine { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Lines of code before the actual error line(s).
|
||||
/// </summary>
|
||||
public IEnumerable<string> PreContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Line(s) of code responsible for the error.
|
||||
/// </summary>
|
||||
public IEnumerable<string> ContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Lines of code after the actual error line(s).
|
||||
/// </summary>
|
||||
public IEnumerable<string> PostContextCode { get; set; } = Enumerable.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Specific error details for this stack frame.
|
||||
/// </summary>
|
||||
public string ErrorDetails { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
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<StackFrameInfo> GetFrames(Exception exception)
|
||||
{
|
||||
var frames = new List<StackFrameInfo>();
|
||||
|
||||
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<StateMachineAttribute>();
|
||||
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<CustomAttributeData> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.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
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid {0}, {1} or {2} length.
|
||||
/// </summary>
|
||||
internal static readonly string WebEncoders_InvalidCountOffsetOrLength = "Invalid {0}, {1} or {2} length.";
|
||||
|
||||
/// <summary>
|
||||
/// Malformed input: {0} is an invalid input length.
|
||||
/// </summary>
|
||||
internal static readonly string WebEncoders_MalformedInput = "Malformed input: {0} is an invalid input length.";
|
||||
|
||||
/// <summary>
|
||||
/// Invalid {0}, {1} or {2} length.
|
||||
/// </summary>
|
||||
internal static string FormatWebEncoders_InvalidCountOffsetOrLength(object p0, object p1, object p2)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, WebEncoders_InvalidCountOffsetOrLength, p0, p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Malformed input: {0} is an invalid input length.
|
||||
/// </summary>
|
||||
internal static string FormatWebEncoders_MalformedInput(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, WebEncoders_MalformedInput, p0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,388 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.WebEncoders.Sources;
|
||||
|
||||
#if WebEncoders_In_WebUtilities
|
||||
namespace Microsoft.AspNetCore.WebUtilities
|
||||
#else
|
||||
namespace Microsoft.Extensions.Internal
|
||||
#endif
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains utility APIs to assist with common encoding and decoding operations.
|
||||
/// </summary>
|
||||
#if WebEncoders_In_WebUtilities
|
||||
public
|
||||
#else
|
||||
internal
|
||||
#endif
|
||||
static class WebEncoders
|
||||
{
|
||||
private static readonly byte[] EmptyBytes = new byte[0];
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a base64url-encoded string.
|
||||
/// </summary>
|
||||
/// <param name="input">The base64url-encoded input to decode.</param>
|
||||
/// <returns>The base64url-decoded form of the input.</returns>
|
||||
/// <remarks>
|
||||
/// The input must not contain any whitespace or padding characters.
|
||||
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||
/// </remarks>
|
||||
public static byte[] Base64UrlDecode(string input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
return Base64UrlDecode(input, offset: 0, count: input.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a base64url-encoded substring of a given string.
|
||||
/// </summary>
|
||||
/// <param name="input">A string containing the base64url-encoded input to decode.</param>
|
||||
/// <param name="offset">The position in <paramref name="input"/> at which decoding should begin.</param>
|
||||
/// <param name="count">The number of characters in <paramref name="input"/> to decode.</param>
|
||||
/// <returns>The base64url-decoded form of the input.</returns>
|
||||
/// <remarks>
|
||||
/// The input must not contain any whitespace or padding characters.
|
||||
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes a base64url-encoded <paramref name="input"/> into a <c>byte[]</c>.
|
||||
/// </summary>
|
||||
/// <param name="input">A string containing the base64url-encoded input to decode.</param>
|
||||
/// <param name="offset">The position in <paramref name="input"/> at which decoding should begin.</param>
|
||||
/// <param name="buffer">
|
||||
/// Scratch buffer to hold the <see cref="char"/>s to decode. Array must be large enough to hold
|
||||
/// <paramref name="bufferOffset"/> and <paramref name="count"/> characters as well as Base64 padding
|
||||
/// characters. Content is not preserved.
|
||||
/// </param>
|
||||
/// <param name="bufferOffset">
|
||||
/// The offset into <paramref name="buffer"/> at which to begin writing the <see cref="char"/>s to decode.
|
||||
/// </param>
|
||||
/// <param name="count">The number of characters in <paramref name="input"/> to decode.</param>
|
||||
/// <returns>The base64url-decoded form of the <paramref name="input"/>.</returns>
|
||||
/// <remarks>
|
||||
/// The input must not contain any whitespace or padding characters.
|
||||
/// Throws <see cref="FormatException"/> if the input is malformed.
|
||||
/// </remarks>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters
|
||||
/// with the <see cref="Base64UrlDecode(string, int, char[], int, int)"/> method.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of characters to decode.</param>
|
||||
/// <returns>
|
||||
/// The minimum <c>char[]</c> size required for decoding of <paramref name="count"/> characters.
|
||||
/// </returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||
/// </summary>
|
||||
/// <param name="input">The binary input to encode.</param>
|
||||
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
|
||||
public static string Base64UrlEncode(byte[] input)
|
||||
{
|
||||
if (input == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
return Base64UrlEncode(input, offset: 0, count: input.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||
/// </summary>
|
||||
/// <param name="input">The binary input to encode.</param>
|
||||
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
|
||||
/// <param name="count">The number of bytes from <paramref name="input"/> to encode.</param>
|
||||
/// <returns>The base64url-encoded form of <paramref name="input"/>.</returns>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encodes <paramref name="input"/> using base64url encoding.
|
||||
/// </summary>
|
||||
/// <param name="input">The binary input to encode.</param>
|
||||
/// <param name="offset">The offset into <paramref name="input"/> at which to begin encoding.</param>
|
||||
/// <param name="output">
|
||||
/// Buffer to receive the base64url-encoded form of <paramref name="input"/>. Array must be large enough to
|
||||
/// hold <paramref name="outputOffset"/> characters and the full base64-encoded form of
|
||||
/// <paramref name="input"/>, including padding characters.
|
||||
/// </param>
|
||||
/// <param name="outputOffset">
|
||||
/// The offset into <paramref name="output"/> at which to begin writing the base64url-encoded form of
|
||||
/// <paramref name="input"/>.
|
||||
/// </param>
|
||||
/// <param name="count">The number of <c>byte</c>s from <paramref name="input"/> to encode.</param>
|
||||
/// <returns>
|
||||
/// The number of characters written to <paramref name="output"/>, less any padding characters.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the minimum output <c>char[]</c> size required for encoding <paramref name="count"/>
|
||||
/// <see cref="byte"/>s with the <see cref="Base64UrlEncode(byte[], int, char[], int, int)"/> method.
|
||||
/// </summary>
|
||||
/// <param name="count">The number of characters to encode.</param>
|
||||
/// <returns>
|
||||
/// The minimum output <c>char[]</c> size required for encoding <paramref name="count"/> <see cref="byte"/>s.
|
||||
/// </returns>
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
#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<X509Extension>(),
|
||||
e => e is X509BasicConstraintsExtension basicConstraints &&
|
||||
basicConstraints.Critical == true &&
|
||||
basicConstraints.CertificateAuthority == false &&
|
||||
basicConstraints.HasPathLengthConstraint == false &&
|
||||
basicConstraints.PathLengthConstraint == 0);
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509KeyUsageExtension keyUsage &&
|
||||
keyUsage.Critical == true &&
|
||||
keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment);
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage &&
|
||||
enhancedKeyUsage.Critical == true &&
|
||||
enhancedKeyUsage.EnhancedKeyUsages.OfType<Oid>().Single() is Oid keyUsage &&
|
||||
keyUsage.Value == "1.3.6.1.5.5.7.3.1");
|
||||
|
||||
// Subject alternative name
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e.Critical == true &&
|
||||
e.Oid.Value == "2.5.29.17");
|
||||
|
||||
// ASP.NET HTTPS Development certificate extension
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
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<X509Extension>(),
|
||||
e => e is X509BasicConstraintsExtension basicConstraints &&
|
||||
basicConstraints.Critical == true &&
|
||||
basicConstraints.CertificateAuthority == false &&
|
||||
basicConstraints.HasPathLengthConstraint == false &&
|
||||
basicConstraints.PathLengthConstraint == 0);
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509KeyUsageExtension keyUsage &&
|
||||
keyUsage.Critical == true &&
|
||||
keyUsage.KeyUsages == X509KeyUsageFlags.KeyEncipherment);
|
||||
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e is X509EnhancedKeyUsageExtension enhancedKeyUsage &&
|
||||
enhancedKeyUsage.Critical == true &&
|
||||
enhancedKeyUsage.EnhancedKeyUsages.OfType<Oid>().Single() is Oid keyUsage &&
|
||||
keyUsage.Value == "1.3.6.1.5.5.7.3.1");
|
||||
|
||||
// Subject alternative name
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
e => e.Critical == true &&
|
||||
e.Oid.Value == "2.5.29.17");
|
||||
|
||||
// ASP.NET HTTPS Development certificate extension
|
||||
Assert.Contains(
|
||||
httpsCertificate.Extensions.OfType<X509Extension>(),
|
||||
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
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
public class ClosedGenericMatcherTest
|
||||
{
|
||||
// queryType, interfaceType, expectedResult
|
||||
public static TheoryData<Type, Type, Type> ExtractGenericInterfaceDataSet
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<Type, Type, Type>
|
||||
{
|
||||
// Closed generic types that match given open generic type.
|
||||
{
|
||||
typeof(IEnumerable<BaseClass>),
|
||||
typeof(IEnumerable<>),
|
||||
typeof(IEnumerable<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(IReadOnlyList<int>),
|
||||
typeof(IReadOnlyList<>),
|
||||
typeof(IReadOnlyList<int>)
|
||||
},
|
||||
{
|
||||
typeof(KeyValuePair<string, object>),
|
||||
typeof(KeyValuePair<,>),
|
||||
typeof(KeyValuePair<string, object>)
|
||||
},
|
||||
// Closed generic interfaces that implement sub-interface of given open generic type.
|
||||
{
|
||||
typeof(ICollection<BaseClass>),
|
||||
typeof(IEnumerable<>),
|
||||
typeof(IEnumerable<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(IReadOnlyList<int>),
|
||||
typeof(IEnumerable<>),
|
||||
typeof(IEnumerable<int>)
|
||||
},
|
||||
{
|
||||
typeof(IDictionary<string, object>),
|
||||
typeof(IEnumerable<>),
|
||||
typeof(IEnumerable<KeyValuePair<string, object>>)
|
||||
},
|
||||
// Class that implements closed generic based on given open generic interface.
|
||||
{
|
||||
typeof(BaseClass),
|
||||
typeof(IDictionary<,>),
|
||||
typeof(IDictionary<string, object>)
|
||||
},
|
||||
{
|
||||
typeof(BaseClass),
|
||||
typeof(IEquatable<>),
|
||||
typeof(IEquatable<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(BaseClass),
|
||||
typeof(ICollection<>),
|
||||
typeof(ICollection<KeyValuePair<string, object>>)
|
||||
},
|
||||
// Derived class that implements closed generic based on given open generic interface.
|
||||
{
|
||||
typeof(DerivedClass),
|
||||
typeof(IDictionary<,>),
|
||||
typeof(IDictionary<string, object>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClass),
|
||||
typeof(IEquatable<>),
|
||||
typeof(IEquatable<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClass),
|
||||
typeof(ICollection<>),
|
||||
typeof(ICollection<KeyValuePair<string, object>>)
|
||||
},
|
||||
// Derived class that also implements another interface.
|
||||
{
|
||||
typeof(DerivedClassWithComparable),
|
||||
typeof(IDictionary<,>),
|
||||
typeof(IDictionary<string, object>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClassWithComparable),
|
||||
typeof(IEquatable<>),
|
||||
typeof(IEquatable<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClassWithComparable),
|
||||
typeof(ICollection<>),
|
||||
typeof(ICollection<KeyValuePair<string, object>>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClassWithComparable),
|
||||
typeof(IComparable<>),
|
||||
typeof(IComparable<DerivedClassWithComparable>)
|
||||
},
|
||||
// Derived class using system implementation.
|
||||
{
|
||||
typeof(DerivedClassFromSystemImplementation),
|
||||
typeof(ICollection<>),
|
||||
typeof(ICollection<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClassFromSystemImplementation),
|
||||
typeof(IReadOnlyList<>),
|
||||
typeof(IReadOnlyList<BaseClass>)
|
||||
},
|
||||
{
|
||||
typeof(DerivedClassFromSystemImplementation),
|
||||
typeof(IEnumerable<>),
|
||||
typeof(IEnumerable<BaseClass>)
|
||||
},
|
||||
// Not given an open generic type.
|
||||
{
|
||||
typeof(IEnumerable<BaseClass>),
|
||||
typeof(IEnumerable<BaseClass>),
|
||||
null
|
||||
},
|
||||
{
|
||||
typeof(IEnumerable<BaseClass>),
|
||||
typeof(IEnumerable),
|
||||
null
|
||||
},
|
||||
{
|
||||
typeof(IReadOnlyList<int>),
|
||||
typeof(BaseClass),
|
||||
null
|
||||
},
|
||||
{
|
||||
typeof(KeyValuePair<,>),
|
||||
typeof(KeyValuePair<string, object>),
|
||||
null
|
||||
},
|
||||
// Not a match.
|
||||
{
|
||||
typeof(IEnumerable<BaseClass>),
|
||||
typeof(IReadOnlyList<>),
|
||||
null
|
||||
},
|
||||
{
|
||||
typeof(IList<int>),
|
||||
typeof(IReadOnlyList<>),
|
||||
null
|
||||
},
|
||||
{
|
||||
typeof(IDictionary<string, object>),
|
||||
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<int> 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<int>), result);
|
||||
}
|
||||
|
||||
// IEnumerable<int> 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<int>), result);
|
||||
}
|
||||
|
||||
private class TwoIEnumerableImplementationsOnSameClass : IEnumerable<string>, IEnumerable<int>
|
||||
{
|
||||
IEnumerator<int> IEnumerable<int>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator<string> IEnumerable<string>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class TwoIEnumerableImplementationsInherited : List<int>, IEnumerable<string>
|
||||
{
|
||||
IEnumerator<string> IEnumerable<string>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class BaseClass : IDictionary<string, object>, IEquatable<BaseClass>
|
||||
{
|
||||
object IDictionary<string, object>.this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
int ICollection<KeyValuePair<string, object>>.Count
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<string> IDictionary<string, object>.Keys
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
ICollection<object> IDictionary<string, object>.Values
|
||||
{
|
||||
get
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(BaseClass other)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void IDictionary<string, object>.Add(string key, object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, object>>.Clear()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, object>.ContainsKey(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, object>.Remove(string key)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
bool IDictionary<string, object>.TryGetValue(string key, out object value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedClass : BaseClass
|
||||
{
|
||||
}
|
||||
|
||||
private class DerivedClassWithComparable : DerivedClass, IComparable<DerivedClassWithComparable>
|
||||
{
|
||||
public int CompareTo(DerivedClassWithComparable other)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private class DerivedClassFromSystemImplementation : Collection<BaseClass>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
public class CopyOnWriteDictionaryHolderTest
|
||||
{
|
||||
[Fact]
|
||||
public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed()
|
||||
{
|
||||
// Arrange
|
||||
var source = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "test-key", "test-value" },
|
||||
{ "key2", "key2-value" }
|
||||
};
|
||||
|
||||
var holder = new CopyOnWriteDictionaryHolder<string, object>(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<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", "value2" }
|
||||
};
|
||||
|
||||
var holder = new CopyOnWriteDictionaryHolder<string, object>(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<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", "value2" }
|
||||
};
|
||||
|
||||
var holder = new CopyOnWriteDictionaryHolder<string, object>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Extensions.Internal
|
||||
{
|
||||
public class CopyOnWriteDictionaryTest
|
||||
{
|
||||
[Fact]
|
||||
public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed()
|
||||
{
|
||||
// Arrange
|
||||
var values = new List<object>();
|
||||
var enumerator = Mock.Of<IEnumerator<KeyValuePair<string, object>>>();
|
||||
var sourceDictionary = new Mock<IDictionary<string, object>>(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<string, object>(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<object>();
|
||||
var sourceDictionary = new Dictionary<string, object>
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", "value2" }
|
||||
};
|
||||
var copyOnWriteDictionary = new CopyOnWriteDictionary<string, object>(
|
||||
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<object>();
|
||||
var sourceDictionary = new Dictionary<string, object>
|
||||
{
|
||||
{ "key1", "value1" },
|
||||
{ "key2", "value2" }
|
||||
};
|
||||
var copyOnWriteDictionary = new CopyOnWriteDictionary<string, object>(
|
||||
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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
|
||||
<DebugType>portable</DebugType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
|
@ -10,19 +9,11 @@
|
|||
<Compile Include="..\src\**\*.cs"
|
||||
Exclude="
|
||||
..\src\BenchmarkRunner\**\*.cs;
|
||||
..\src\RazorViews\**\*.cs;
|
||||
..\src\StackTrace\ExceptionDetails\**\*.cs;
|
||||
" />
|
||||
<EmbeddedResource Include="..\src\**\*.resx" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\testassets\ThrowingLibrary\ThrowingLibrary.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="FSharp.Core" />
|
||||
<Reference Include="System.Reflection.Metadata" />
|
||||
<Reference Include="System.Threading.Tasks.Extensions" />
|
||||
<Reference Include="System.Security.Cryptography.Cng" />
|
||||
<Reference Include="System.Runtime.CompilerServices.Unsafe" />
|
||||
|
|
|
|||
|
|
@ -1,634 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.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<TestObject>(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<TestObject>(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<NotImplementedException>(
|
||||
() => 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<TestObject>(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<TestObject>(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<NotImplementedException>(
|
||||
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<NotImplementedException>(
|
||||
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<ArgumentOutOfRangeException>(() => executor.GetDefaultValueForParameter(2));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetDefaultValueForParameters_ThrowsIfNoneWereSupplied()
|
||||
{
|
||||
var executor = GetExecutorForMethod("MethodWithMultipleParameters");
|
||||
Assert.Throws<InvalidOperationException>(() => executor.GetDefaultValueForParameter(0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void TargetMethodReturningCustomAwaitableOfReferenceType_CanInvokeViaExecute()
|
||||
{
|
||||
// Arrange
|
||||
var executor = GetExecutorForMethod("CustomAwaitableOfReferenceTypeAsync");
|
||||
|
||||
// Act
|
||||
var result = await (TestAwaitable<TestObject>)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<int>)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<TestObject>(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<int>(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<int>)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<string>)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<string>)executor.Execute(_targetObject, new object[] { "test result" });
|
||||
var result = await FSharpAsync.StartAsTask(fsharpAsync,
|
||||
FSharpOption<TaskCreationOptions>.None,
|
||||
FSharpOption<CancellationToken>.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<string>)executor.Execute(_targetObject, new object[] { "test result" });
|
||||
var resultTask = FSharpAsync.StartAsTask(fsharpAsync,
|
||||
FSharpOption<TaskCreationOptions>.None,
|
||||
FSharpOption<CancellationToken>.None);
|
||||
|
||||
// Assert
|
||||
Assert.True(executor.IsMethodAsync);
|
||||
Assert.Same(typeof(string), executor.AsyncResultType);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<AggregateException>(async () => await resultTask);
|
||||
Assert.IsType<InvalidOperationException>(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<AggregateException>(async () => await resultTask);
|
||||
Assert.IsType<InvalidOperationException>(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<int> ValueMethodAsync(int i, int j)
|
||||
{
|
||||
return Task.FromResult<int>(i + j);
|
||||
}
|
||||
|
||||
public async Task VoidValueMethodAsync(int i)
|
||||
{
|
||||
await ValueMethodAsync(3, 4);
|
||||
}
|
||||
public Task<TestObject> ValueMethodWithReturnTypeAsync(int i)
|
||||
{
|
||||
return Task.FromResult<TestObject>(new TestObject() { value = "Hello" });
|
||||
}
|
||||
|
||||
public async Task ValueMethodWithReturnVoidThrowsExceptionAsync(TestObject i)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("Not Implemented Exception");
|
||||
}
|
||||
|
||||
public async Task<TestObject> ValueMethodWithReturnTypeThrowsExceptionAsync(TestObject i)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
throw new NotImplementedException("Not Implemented Exception");
|
||||
}
|
||||
|
||||
public Task<TestObject> ValueMethodUpdateValueAsync(TestObject parameter)
|
||||
{
|
||||
parameter.value = "HelloWorld";
|
||||
return Task.FromResult<TestObject>(parameter);
|
||||
}
|
||||
|
||||
public TestAwaitable<TestObject> CustomAwaitableOfReferenceTypeAsync(
|
||||
string input1,
|
||||
int input2)
|
||||
{
|
||||
return new TestAwaitable<TestObject>(new TestObject
|
||||
{
|
||||
value = $"{input1} {input2}"
|
||||
});
|
||||
}
|
||||
|
||||
public TestAwaitable<int> CustomAwaitableOfValueTypeAsync(
|
||||
int input1,
|
||||
int input2)
|
||||
{
|
||||
return new TestAwaitable<int>(input1 + input2);
|
||||
}
|
||||
|
||||
public TestAwaitableWithICriticalNotifyCompletion CustomAwaitableWithICriticalNotifyCompletion()
|
||||
{
|
||||
return new TestAwaitableWithICriticalNotifyCompletion();
|
||||
}
|
||||
|
||||
public TestAwaitableWithoutICriticalNotifyCompletion CustomAwaitableWithoutICriticalNotifyCompletion()
|
||||
{
|
||||
return new TestAwaitableWithoutICriticalNotifyCompletion();
|
||||
}
|
||||
|
||||
public ValueTask<int> ValueTaskOfValueType(int result)
|
||||
{
|
||||
return new ValueTask<int>(result);
|
||||
}
|
||||
|
||||
public ValueTask<string> ValueTaskOfReferenceType(string result)
|
||||
{
|
||||
return new ValueTask<string>(result);
|
||||
}
|
||||
|
||||
public void MethodWithMultipleParameters(int valueTypeParam, string referenceTypeParam)
|
||||
{
|
||||
}
|
||||
|
||||
public FSharpAsync<string> FSharpAsyncMethod(string parameter)
|
||||
{
|
||||
return FSharpAsync.AwaitTask(Task.FromResult(parameter));
|
||||
}
|
||||
|
||||
public FSharpAsync<string> FSharpAsyncFailureMethod(string parameter)
|
||||
{
|
||||
return FSharpAsync.AwaitTask(
|
||||
Task.FromException<string>(new InvalidOperationException("Test exception")));
|
||||
}
|
||||
}
|
||||
|
||||
public class TestAwaitable<T>
|
||||
{
|
||||
private T _result;
|
||||
private bool _isCompleted;
|
||||
private List<Action> _onCompletedCallbacks = new List<Action>();
|
||||
|
||||
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<T> _owner;
|
||||
|
||||
public TestAwaiter(TestAwaitable<T> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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<int>(
|
||||
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<int>(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<int>.GetPropertiesToActivate(
|
||||
type: typeof(TestClass),
|
||||
activateAttributeType: typeof(TestActivateAttribute),
|
||||
createActivateInfo:
|
||||
(propertyInfo) => new PropertyActivator<int>(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<int>.GetPropertiesToActivate(
|
||||
type: typeof(TestClass),
|
||||
activateAttributeType: typeof(TestActivateAttribute),
|
||||
createActivateInfo:
|
||||
(propertyInfo) => new PropertyActivator<int>(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<int>.GetPropertiesToActivate(
|
||||
typeof(TestClassWithPropertyVisiblity),
|
||||
typeof(TestActivateAttribute),
|
||||
(propertyInfo) => new PropertyActivator<int>(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<int>.GetPropertiesToActivate(
|
||||
typeof(TestClassWithPropertyVisiblity),
|
||||
typeof(TestActivateAttribute),
|
||||
(propertyInfo) => new PropertyActivator<int>(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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,863 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<string>));
|
||||
|
||||
// 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<string, string>));
|
||||
|
||||
// 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<NullReferenceException>(() => 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<NullReferenceException>(() => 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<object, KeyValuePair<string, object>> IgnoreCaseTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
return new TheoryData<object, KeyValuePair<string, object>>
|
||||
{
|
||||
{
|
||||
new
|
||||
{
|
||||
selected = true,
|
||||
SeLeCtEd = false
|
||||
},
|
||||
new KeyValuePair<string, object>("selected", false)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
SeLeCtEd = false,
|
||||
selected = true
|
||||
},
|
||||
new KeyValuePair<string, object>("SeLeCtEd", true)
|
||||
},
|
||||
{
|
||||
new
|
||||
{
|
||||
SelECTeD = false,
|
||||
SeLECTED = true
|
||||
},
|
||||
new KeyValuePair<string, object>("SelECTeD", true)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(IgnoreCaseTestData))]
|
||||
public void ObjectToDictionary_IgnoresPropertyCase(object testObject,
|
||||
KeyValuePair<string, object> 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<bool> 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
NOTE:
|
||||
1. The tests for 'ExceptionDetailProvider' and 'StackTraceHelper' in project 'Microsoft.Extensions.StackTrace.Sources' are located in Diagnostics
|
||||
repo. This is because they refer to some packages from FileSystem repo which causes a circular reference and breaks the
|
||||
build.
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.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<ClaimsPrincipal>(user);
|
||||
Assert.IsAssignableFrom<ClaimsIdentity>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,345 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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<string>(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>(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<TVal>(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<TVal>(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<int> value)", methods[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StackTraceHelper_PrettyPrintsStackTraceForMethodsOnGenericTypes()
|
||||
{
|
||||
// Arrange
|
||||
var exception = Record.Exception(() => new GenericClass<int>().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<T>.Throw(T parameter)", methods[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StackTraceHelper_ProducesReadableOutput()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCallStack = new List<string>()
|
||||
{
|
||||
"Microsoft.Extensions.Internal.StackTraceHelperTest.Iterator()+MoveNext()",
|
||||
"string.Join(string separator, IEnumerable<string> values)",
|
||||
"Microsoft.Extensions.Internal.StackTraceHelperTest+GenericClass<T>.GenericMethod<V>(ref V value)",
|
||||
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync(int value)",
|
||||
"Microsoft.Extensions.Internal.StackTraceHelperTest.MethodAsync<TValue>(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<string> MethodAsync(int value)
|
||||
{
|
||||
await Task.Delay(0);
|
||||
return GenericClass<byte>.GenericMethod(ref value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
|
||||
async Task<string> MethodAsync<TValue>(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<string> 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<TVal>(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<TVal>(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<T>
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
|
||||
public static string GenericMethod<V>(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>(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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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<ArgumentException>(() =>
|
||||
{
|
||||
var retVal = WebEncoders.Base64UrlDecode(input, offset, count);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("x")]
|
||||
[InlineData("(x)")]
|
||||
public void Base64UrlDecode_MalformedInput(string input)
|
||||
{
|
||||
// Act & assert
|
||||
Assert.Throws<FormatException>(() =>
|
||||
{
|
||||
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<ArgumentException>(() =>
|
||||
{
|
||||
var retVal = WebEncoders.Base64UrlEncode(input, offset, count);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<DebugType>portable</DebugType>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
Loading…
Reference in New Issue