Ignore assembly version when activating DataProtection types from string name (#223)
This commit is contained in:
parent
5fe4807c1e
commit
4dad47eeab
|
|
@ -3,8 +3,8 @@
|
|||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Description>Redis storrage support as key store.</Description>
|
||||
<VersionPrefix>0.1.0</VersionPrefix>
|
||||
<Description>Redis storage support as key store.</Description>
|
||||
<VersionPrefix>0.3.0</VersionPrefix>
|
||||
<TargetFrameworks>net46;netstandard1.5</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddSingleton<IActivator, RC1ForwardingActivator>();
|
||||
services.TryAddSingleton<IActivator, TypeForwardingActivator>();
|
||||
services.AddOptions();
|
||||
AddDataProtectionServices(services);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
internal class RC1ForwardingActivator: SimpleActivator
|
||||
{
|
||||
private const string From = "Microsoft.AspNet.DataProtection";
|
||||
private const string To = "Microsoft.AspNetCore.DataProtection";
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RC1ForwardingActivator(IServiceProvider services) : this(services, DataProtectionProviderFactory.GetDefaultLoggerFactory())
|
||||
{
|
||||
}
|
||||
|
||||
public RC1ForwardingActivator(IServiceProvider services, ILoggerFactory loggerFactory) : base(services)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(typeof(RC1ForwardingActivator));
|
||||
}
|
||||
|
||||
public override object CreateInstance(Type expectedBaseType, string implementationTypeName)
|
||||
{
|
||||
if (implementationTypeName.Contains(From))
|
||||
{
|
||||
var forwardedImplementationTypeName = implementationTypeName.Replace(From, To);
|
||||
var type = Type.GetType(forwardedImplementationTypeName, false);
|
||||
if (type != null)
|
||||
{
|
||||
_logger.LogDebug("Forwarded activator type request from {FromType} to {ToType}",
|
||||
implementationTypeName,
|
||||
forwardedImplementationTypeName);
|
||||
|
||||
implementationTypeName = forwardedImplementationTypeName;
|
||||
}
|
||||
}
|
||||
return base.CreateInstance(expectedBaseType, implementationTypeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
internal class TypeForwardingActivator : SimpleActivator
|
||||
{
|
||||
private const string OldNamespace = "Microsoft.AspNet.DataProtection";
|
||||
private const string CurrentNamespace = "Microsoft.AspNetCore.DataProtection";
|
||||
private readonly ILogger _logger;
|
||||
private static readonly Regex _versionPattern = new Regex(@",\s?Version=(\d+\.?)(\d+\.?)?(\d+\.?)?(\d+\.?)?", RegexOptions.Compiled, TimeSpan.FromSeconds(2));
|
||||
|
||||
public TypeForwardingActivator(IServiceProvider services)
|
||||
: this(services, DataProtectionProviderFactory.GetDefaultLoggerFactory())
|
||||
{
|
||||
}
|
||||
|
||||
public TypeForwardingActivator(IServiceProvider services, ILoggerFactory loggerFactory)
|
||||
: base(services)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger(typeof(TypeForwardingActivator));
|
||||
}
|
||||
|
||||
public override object CreateInstance(Type expectedBaseType, string originalTypeName)
|
||||
=> CreateInstance(expectedBaseType, originalTypeName, out var _);
|
||||
|
||||
// for testing
|
||||
internal object CreateInstance(Type expectedBaseType, string originalTypeName, out bool forwarded)
|
||||
{
|
||||
var forwardedTypeName = originalTypeName;
|
||||
var candidate = false;
|
||||
if (originalTypeName.Contains(OldNamespace))
|
||||
{
|
||||
candidate = true;
|
||||
forwardedTypeName = originalTypeName.Replace(OldNamespace, CurrentNamespace);
|
||||
}
|
||||
|
||||
#if NET46
|
||||
if (candidate || forwardedTypeName.Contains(CurrentNamespace))
|
||||
{
|
||||
candidate = true;
|
||||
forwardedTypeName = RemoveVersionFromAssemblyName(forwardedTypeName);
|
||||
}
|
||||
#elif NETSTANDARD1_3
|
||||
#else
|
||||
#error Target framework needs to be updated
|
||||
#endif
|
||||
|
||||
if (candidate)
|
||||
{
|
||||
var type = Type.GetType(forwardedTypeName, false);
|
||||
if (type != null)
|
||||
{
|
||||
_logger.LogDebug("Forwarded activator type request from {FromType} to {ToType}",
|
||||
originalTypeName,
|
||||
forwardedTypeName);
|
||||
forwarded = true;
|
||||
return base.CreateInstance(expectedBaseType, forwardedTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
forwarded = false;
|
||||
return base.CreateInstance(expectedBaseType, originalTypeName);
|
||||
}
|
||||
|
||||
protected string RemoveVersionFromAssemblyName(string forwardedTypeName)
|
||||
=> _versionPattern.Replace(forwardedTypeName, "");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
public class RC1ForwardingActivatorTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateInstance_ForwardsToNewNamespaceIfExists()
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddDataProtection();
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var activator = services.GetActivator();
|
||||
|
||||
// Act
|
||||
var name = "Microsoft.AspNet.DataProtection.RC1ForwardingActivatorTests+ClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test";
|
||||
var instance = activator.CreateInstance<object>(name);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ClassWithParameterlessCtor>(instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateInstance_DoesNotForwardIfClassDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddDataProtection();
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var activator = services.GetActivator();
|
||||
|
||||
// Act & Assert
|
||||
var name = "Microsoft.AspNet.DataProtection.RC1ForwardingActivatorTests+NonExistentClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test";
|
||||
var exception = Assert.ThrowsAny<Exception>(()=> activator.CreateInstance<object>(name));
|
||||
|
||||
Assert.Contains("Microsoft.AspNet.DataProtection.Test", exception.Message);
|
||||
}
|
||||
|
||||
private class ClassWithParameterlessCtor
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,177 @@
|
|||
// 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.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.DataProtection
|
||||
{
|
||||
public class TypeForwardingActivatorTests : MarshalByRefObject
|
||||
{
|
||||
[Fact]
|
||||
public void CreateInstance_ForwardsToNewNamespaceIfExists()
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddDataProtection();
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var activator = services.GetActivator();
|
||||
|
||||
// Act
|
||||
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+ClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test, Version=1.0.0.0";
|
||||
var instance = activator.CreateInstance<object>(name);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<ClassWithParameterlessCtor>(instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateInstance_DoesNotForwardIfClassDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddDataProtection();
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
var activator = services.GetActivator();
|
||||
|
||||
// Act & Assert
|
||||
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+NonExistentClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test";
|
||||
var exception = Assert.ThrowsAny<Exception>(() => activator.CreateInstance<object>(name));
|
||||
|
||||
Assert.Contains("Microsoft.AspNet.DataProtection.Test", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(GenericType<GenericType<ClassWithParameterlessCtor>>))]
|
||||
[InlineData(typeof(GenericType<ClassWithParameterlessCtor>))]
|
||||
[InlineData(typeof(GenericType<GenericType<string>>))]
|
||||
[InlineData(typeof(GenericType<GenericType<string, string>>))]
|
||||
[InlineData(typeof(GenericType<string>))]
|
||||
[InlineData(typeof(GenericType<int>))]
|
||||
[InlineData(typeof(List<ClassWithParameterlessCtor>))]
|
||||
public void CreateInstance_Generics(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var activator = new TypeForwardingActivator(null);
|
||||
var name = type.AssemblyQualifiedName;
|
||||
|
||||
// Act & Assert
|
||||
Assert.IsType(type, activator.CreateInstance<object>(name));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(GenericType<>))]
|
||||
[InlineData(typeof(GenericType<,>))]
|
||||
public void CreateInstance_ThrowsForOpenGenerics(Type type)
|
||||
{
|
||||
// Arrange
|
||||
var activator = new TypeForwardingActivator(null);
|
||||
var name = type.AssemblyQualifiedName;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => activator.CreateInstance<object>(name));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
|
||||
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
|
||||
[InlineData(
|
||||
"Some.Type`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral",
|
||||
"Some.Type`1[[System.Int32, mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Culture=neutral")]
|
||||
[InlineData(
|
||||
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
|
||||
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
|
||||
public void ParsesFullyQualifiedTypeName(string typeName, string expected)
|
||||
{
|
||||
Assert.Equal(expected, new MockTypeForwardingActivator().Parse(typeName));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(typeof(List<string>))]
|
||||
[InlineData(typeof(FactAttribute))]
|
||||
public void CreateInstance_DoesNotForwardingTypesExternalTypes(Type type)
|
||||
{
|
||||
new TypeForwardingActivator(null).CreateInstance(typeof(object), type.AssemblyQualifiedName, out var forwarded);
|
||||
Assert.False(forwarded, "Should not have forwarded types that are not in Microsoft.AspNetCore.DataProjection");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AssemblyVersions))]
|
||||
public void CreateInstance_ForwardsAcrossVersionChanges(Version version)
|
||||
{
|
||||
#if NET46
|
||||
// run this test in an appdomain without testhost's custom assembly resolution hooks
|
||||
var setupInfo = new AppDomainSetup
|
||||
{
|
||||
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
|
||||
};
|
||||
var domain = AppDomain.CreateDomain("TestDomain", null, setupInfo);
|
||||
var wrappedTestClass = (TypeForwardingActivatorTests)domain.CreateInstanceAndUnwrap(GetType().Assembly.FullName, typeof(TypeForwardingActivatorTests).FullName);
|
||||
wrappedTestClass.CreateInstance_ForwardsAcrossVersionChangesImpl(version);
|
||||
#elif NETCOREAPP2_0
|
||||
CreateInstance_ForwardsAcrossVersionChangesImpl(version);
|
||||
#else
|
||||
#error Target framework should be updated
|
||||
#endif
|
||||
}
|
||||
|
||||
private void CreateInstance_ForwardsAcrossVersionChangesImpl(Version newVersion)
|
||||
{
|
||||
var activator = new TypeForwardingActivator(null);
|
||||
|
||||
var typeInfo = typeof(ClassWithParameterlessCtor).GetTypeInfo();
|
||||
var typeName = typeInfo.FullName;
|
||||
var assemblyName = typeInfo.Assembly.GetName();
|
||||
|
||||
assemblyName.Version = newVersion;
|
||||
var newName = $"{typeName}, {assemblyName}";
|
||||
|
||||
Assert.NotEqual(typeInfo.AssemblyQualifiedName, newName);
|
||||
Assert.IsType<ClassWithParameterlessCtor>(activator.CreateInstance(typeof(object), newName, out var forwarded));
|
||||
#if NET46
|
||||
Assert.True(forwarded, "Should have forwarded this type to new version or namespace");
|
||||
#elif NETCOREAPP2_0
|
||||
Assert.False(forwarded, "Should not have forwarded this type to new version or namespace");
|
||||
#else
|
||||
#error Target framework should be updated
|
||||
#endif
|
||||
}
|
||||
|
||||
public static TheoryData<Version> AssemblyVersions
|
||||
{
|
||||
get
|
||||
{
|
||||
var current = typeof(ActivatorTests).Assembly.GetName().Version;
|
||||
return new TheoryData<Version>
|
||||
{
|
||||
new Version(Math.Max(0, current.Major - 1), 0, 0, 0),
|
||||
new Version(current.Major + 1, 0, 0, 0),
|
||||
new Version(current.Major, current.Minor + 1, 0, 0),
|
||||
new Version(current.Major, current.Minor, current.Revision + 1, 0),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class MockTypeForwardingActivator : TypeForwardingActivator
|
||||
{
|
||||
public MockTypeForwardingActivator() : base(null) { }
|
||||
public string Parse(string typeName) => RemoveVersionFromAssemblyName(typeName);
|
||||
}
|
||||
|
||||
private class ClassWithParameterlessCtor
|
||||
{
|
||||
}
|
||||
|
||||
private class GenericType<T>
|
||||
{
|
||||
}
|
||||
|
||||
private class GenericType<T1, T2>
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue