diff --git a/Directory.Build.props b/Directory.Build.props
index 2cbdaff55666..f1609e9bd7b0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -202,6 +202,7 @@
false
$(MSBuildThisFileDirectory)src\Mvc\Mvc.Testing\src\Microsoft.AspNetCore.Mvc.Testing.targets
+ <_MvcTestingTasksAssembly>$(ArtifactsBinDir)\Microsoft.AspNetCore.Mvc.Testing.Tasks\$(Configuration)\netstandard2.0\Microsoft.AspNetCore.Mvc.Testing.Tasks.dll
true
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index a02777218241..eed651e65e5b 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -116,6 +116,7 @@
+
diff --git a/src/Mvc/Mvc.Testing.Tasks/src/GenerateMvcTestManifestTask.cs b/src/Mvc/Mvc.Testing.Tasks/src/GenerateMvcTestManifestTask.cs
new file mode 100644
index 000000000000..2a945f01e212
--- /dev/null
+++ b/src/Mvc/Mvc.Testing.Tasks/src/GenerateMvcTestManifestTask.cs
@@ -0,0 +1,56 @@
+// 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.Reflection;
+using System.Runtime.Serialization.Json;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.AspNetCore.Mvc.Testing.Tasks
+{
+ ///
+ /// Generate a JSON file mapping assemblies to content root paths.
+ ///
+ public class GenerateMvcTestManifestTask : Task
+ {
+ ///
+ /// The path to output the manifest file to.
+ ///
+ [Required]
+ public string ManifestPath { get; set; }
+
+ ///
+ /// A list of content root paths and assembly names to generate the
+ /// manifest from.
+ ///
+ [Required]
+ public ITaskItem[] Projects { get; set; }
+
+ ///
+ public override bool Execute()
+ {
+ using var fileStream = File.Create(ManifestPath);
+ var output = new Dictionary();
+
+ foreach (var project in Projects)
+ {
+ var contentRoot = project.GetMetadata("ContentRoot");
+ var assemblyName = project.GetMetadata("Identity");
+ output[assemblyName] = contentRoot;
+ }
+
+ var serializer = new DataContractJsonSerializer(typeof(Dictionary), new DataContractJsonSerializerSettings
+ {
+ UseSimpleDictionaryFormat = true
+ });
+ using var writer = JsonReaderWriterFactory.CreateJsonWriter(fileStream, Encoding.UTF8, ownsStream: false, indent: true);
+ serializer.WriteObject(writer, output);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Mvc/Mvc.Testing.Tasks/src/Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj b/src/Mvc/Mvc.Testing.Tasks/src/Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj
new file mode 100644
index 000000000000..d572bd29be69
--- /dev/null
+++ b/src/Mvc/Mvc.Testing.Tasks/src/Microsoft.AspNetCore.Mvc.Testing.Tasks.csproj
@@ -0,0 +1,16 @@
+
+
+ Build tasks for functional tests.
+ netstandard2.0
+
+ false
+ false
+ false
+ false
+
+
+
+
+
+
+
diff --git a/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.csproj b/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.csproj
index b39ab0349b5e..045d8ab0adf9 100644
--- a/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.csproj
+++ b/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.csproj
@@ -16,6 +16,15 @@
+
+
+
+
diff --git a/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.targets b/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.targets
index 8e9f7093e19c..a3e40e41f865 100644
--- a/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.targets
+++ b/src/Mvc/Mvc.Testing/src/Microsoft.AspNetCore.Mvc.Testing.targets
@@ -1,5 +1,10 @@
+
+ <_MvcTestingTasksAssembly Condition="$(_MvcTestingTasksAssembly) == ''">$(MSBuildThisFileDirectory)..\tasks\Microsoft.AspNetCore.Mvc.Testing.dll
+
+
+