This repository has been archived by the owner on Oct 17, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 87
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ignore assembly version when activating DataProtection types from str…
…ing name (#223)
- Loading branch information
1 parent
5fe4807
commit 4dad47e
Showing
6 changed files
with
253 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 0 additions & 42 deletions
42
src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs
This file was deleted.
Oops, something went wrong.
73 changes: 73 additions & 0 deletions
73
src/Microsoft.AspNetCore.DataProtection/TypeForwardingActivator.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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, ""); | ||
} | ||
} |
49 changes: 0 additions & 49 deletions
49
test/Microsoft.AspNetCore.DataProtection.Test/RC1ForwardingActivatorTests.cs
This file was deleted.
Oops, something went wrong.
177 changes: 177 additions & 0 deletions
177
test/Microsoft.AspNetCore.DataProtection.Test/TypeForwardingActivatorTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
{ | ||
} | ||
} | ||
} |
Common to what?