diff --git a/Directory.Packages.props b/Directory.Packages.props
index 62322f922..568b995c9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -98,7 +98,6 @@
-
diff --git a/samples/AppConfig/AppConfigFramework/Web.config b/samples/AppConfig/AppConfigFramework/Web.config
index aaf3035c4..1ec4f8dcf 100644
--- a/samples/AppConfig/AppConfigFramework/Web.config
+++ b/samples/AppConfig/AppConfigFramework/Web.config
@@ -23,7 +23,6 @@
-
@@ -154,4 +153,4 @@
-
\ No newline at end of file
+
diff --git a/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Web.config b/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Web.config
index 0bb3f0c6d..5956cb031 100644
--- a/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Web.config
+++ b/samples/AuthRemoteIdentity/AuthRemoteIdentityFramework/Web.config
@@ -28,7 +28,6 @@
-
diff --git a/samples/MachineKey/MachineKeyFramework/Global.asax.cs b/samples/MachineKey/MachineKeyFramework/Global.asax.cs
index dbdc90089..47762a063 100644
--- a/samples/MachineKey/MachineKeyFramework/Global.asax.cs
+++ b/samples/MachineKey/MachineKeyFramework/Global.asax.cs
@@ -14,6 +14,7 @@ protected void Application_Start()
{
HttpApplicationHost.RegisterHost(builder =>
{
+ builder.AddSystemWebDependencyInjection();
builder.AddServiceDefaults();
builder.AddDataProtection()
.SetApplicationName(MachineKeyExampleHandler.AppName)
diff --git a/samples/MachineKey/MachineKeyFramework/MachineKeyFramework.csproj b/samples/MachineKey/MachineKeyFramework/MachineKeyFramework.csproj
index 1b6276f64..1f71bca8d 100644
--- a/samples/MachineKey/MachineKeyFramework/MachineKeyFramework.csproj
+++ b/samples/MachineKey/MachineKeyFramework/MachineKeyFramework.csproj
@@ -2,9 +2,6 @@
net481
-
-
-
diff --git a/samples/MachineKey/MachineKeyFramework/Web.config b/samples/MachineKey/MachineKeyFramework/Web.config
index 0ef39e937..aace065ca 100644
--- a/samples/MachineKey/MachineKeyFramework/Web.config
+++ b/samples/MachineKey/MachineKeyFramework/Web.config
@@ -7,7 +7,6 @@
-
diff --git a/samples/SessionRemote/SessionRemoteFramework/Web.config b/samples/SessionRemote/SessionRemoteFramework/Web.config
index 62820678e..4b0b1eb8e 100644
--- a/samples/SessionRemote/SessionRemoteFramework/Web.config
+++ b/samples/SessionRemote/SessionRemoteFramework/Web.config
@@ -15,7 +15,6 @@
-
@@ -146,4 +145,4 @@
-
\ No newline at end of file
+
diff --git a/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/CompatibilityDataProtector.cs b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/CompatibilityDataProtector.cs
new file mode 100644
index 000000000..0d440dffe
--- /dev/null
+++ b/src/Microsoft.AspNetCore.SystemWebAdapters.FrameworkServices/CompatibilityDataProtector.cs
@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.ComponentModel;
+using System.Configuration;
+using System.Security.Cryptography;
+using System.Web;
+using Microsoft.AspNetCore.DataProtection;
+using Microsoft.AspNetCore.SystemWebAdapters.Hosting;
+
+namespace Microsoft.AspNetCore.DataProtection.SystemWeb;
+
+[EditorBrowsable(EditorBrowsableState.Never)]
+public class CompatibilityDataProtector : DataProtector
+{
+ [ThreadStatic]
+ private static bool _suppressPrimaryPurpose;
+
+ private readonly Lazy _lazyProtector;
+ private readonly Lazy _lazyProtectorSuppressedPrimaryPurpose;
+
+ public CompatibilityDataProtector(string applicationName, string primaryPurpose, string[] specificPurposes)
+ : base("application-name", "primary-purpose", null) // we feed dummy values to the base ctor
+ {
+ // We don't want to evaluate the IDataProtectionProvider factory quite yet,
+ // as we'd rather defer failures to the call to Protect so that we can bubble
+ // up a good error message to the developer.
+
+ _lazyProtector = new Lazy(() => GetDataProtectionProvider().CreateProtector(primaryPurpose, specificPurposes));
+
+ // System.Web always provides "User.MachineKey.Protect" as the primary purpose for calls
+ // to MachineKey.Protect. Only in this case should we allow suppressing the primary
+ // purpose, as then we can easily map calls to MachineKey.Protect(userData, purposes)
+ // into calls to provider.GetProtector(purposes).Protect(userData).
+ if (primaryPurpose == "User.MachineKey.Protect")
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = new Lazy(() => GetDataProtectionProvider().CreateProtector(specificPurposes));
+ }
+ else
+ {
+ _lazyProtectorSuppressedPrimaryPurpose = _lazyProtector;
+ }
+ }
+
+ // We take care of flowing purposes ourselves.
+ protected override bool PrependHashedPurposeToPlaintext => false;
+
+ // Retrieves the appropriate protector (potentially with a suppressed primary purpose) for this operation.
+ private IDataProtector Protector => ((_suppressPrimaryPurpose) ? _lazyProtectorSuppressedPrimaryPurpose : _lazyProtector).Value;
+
+ protected virtual IDataProtectionProvider GetDataProtectionProvider()
+ => HttpApplicationHost.Current.Services.GetDataProtectionProvider();
+
+ public override bool IsReprotectRequired(byte[] encryptedData)
+ {
+ // Nobody ever calls this.
+ return false;
+ }
+
+ protected override byte[] ProviderProtect(byte[] userData)
+ {
+ try
+ {
+ return Protector.Protect(userData);
+ }
+ catch (Exception ex)
+ {
+ // System.Web special-cases ConfigurationException errors and allows them to bubble
+ // up to the developer without being homogenized. Since a call to Protect should
+ // never fail, any exceptions here really do imply a misconfiguration.
+
+#pragma warning disable CS0618 // Type or member is obsolete
+ throw new ConfigurationException("DataProtection failed to protect", ex);
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+ }
+
+ protected override byte[] ProviderUnprotect(byte[] encryptedData)
+ {
+ return Protector.Unprotect(encryptedData);
+ }
+
+ ///
+ /// Invokes a delegate where calls to
+ /// and will ignore the primary
+ /// purpose and instead use only the sub-purposes.
+ ///
+ public static byte[] RunWithSuppressedPrimaryPurpose(Func