Skip to content
This repository has been archived by the owner on Oct 17, 2018. It is now read-only.

Ignore assembly version when activating DataProtection types from string name #223

Merged
merged 3 commits into from
Apr 25, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public static IDataProtectionBuilder AddDataProtection(this IServiceCollection s
throw new ArgumentNullException(nameof(services));
}

services.AddSingleton<IActivator, RC1ForwardingActivator>();
services.TryAddSingleton<IActivator, TypeForwardingActivator>();
services.AddOptions();
AddDataProtectionServices(services);

Expand Down
42 changes: 0 additions & 42 deletions src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs

This file was deleted.

73 changes: 73 additions & 0 deletions src/Microsoft.AspNetCore.DataProtection/TypeForwardingActivator.cs
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, "");
}
}

This file was deleted.

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>
{
}
}
}