diff --git a/AzureIntegration.sln b/AzureIntegration.sln
index db46b9da9f..0954531e43 100644
--- a/AzureIntegration.sln
+++ b/AzureIntegration.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2002
MinimumVisualStudioVersion = 15.0.26730.03
@@ -50,6 +50,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Web.Xdt.Extension
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.AzureAppServices.SiteExtension.Tests", "test\Microsoft.AspNetCore.AzureAppServices.SiteExtension.Tests\Microsoft.AspNetCore.AzureAppServices.SiteExtension.Tests.csproj", "{491A857A-3529-4375-985D-D748F9F01476}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.ApplicationModelDetection", "src\Microsoft.Extensions.ApplicationModelDetection\Microsoft.Extensions.ApplicationModelDetection.csproj", "{F0CABFE8-A5B1-487B-A451-A486D26742D3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Extensions.ApplicationModelDetection.Tests", "test\Microsoft.Extensions.ApplicationModelDetection.Tests\Microsoft.Extensions.ApplicationModelDetection.Tests.csproj", "{15664836-2B94-4D2D-AC18-6DED01FCCCBD}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -112,6 +116,14 @@ Global
{491A857A-3529-4375-985D-D748F9F01476}.Debug|Any CPU.Build.0 = Debug|Any CPU
{491A857A-3529-4375-985D-D748F9F01476}.Release|Any CPU.ActiveCfg = Release|Any CPU
{491A857A-3529-4375-985D-D748F9F01476}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F0CABFE8-A5B1-487B-A451-A486D26742D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F0CABFE8-A5B1-487B-A451-A486D26742D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F0CABFE8-A5B1-487B-A451-A486D26742D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F0CABFE8-A5B1-487B-A451-A486D26742D3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {15664836-2B94-4D2D-AC18-6DED01FCCCBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {15664836-2B94-4D2D-AC18-6DED01FCCCBD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {15664836-2B94-4D2D-AC18-6DED01FCCCBD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {15664836-2B94-4D2D-AC18-6DED01FCCCBD}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -131,6 +143,8 @@ Global
{1EC31DA1-131D-4257-B001-BE8391E6077E} = {FF9B744E-6C59-40CC-9E41-9D2EBD292435}
{809AEE05-1B28-4E31-8959-776B249BD725} = {CD650B4B-81C2-4A44-AEF2-A251A877C1F0}
{491A857A-3529-4375-985D-D748F9F01476} = {CD650B4B-81C2-4A44-AEF2-A251A877C1F0}
+ {F0CABFE8-A5B1-487B-A451-A486D26742D3} = {FF9B744E-6C59-40CC-9E41-9D2EBD292435}
+ {15664836-2B94-4D2D-AC18-6DED01FCCCBD} = {CD650B4B-81C2-4A44-AEF2-A251A877C1F0}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5743DFE7-1AA5-439D-84AE-A480EA389927}
diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json
index 4e5da217e3..cf7178529c 100644
--- a/NuGetPackageVerifier.json
+++ b/NuGetPackageVerifier.json
@@ -4,7 +4,8 @@
"AdxVerificationCompositeRule"
],
"packages": {
- "Microsoft.AspNetCore.AzureAppServicesIntegration": { }
+ "Microsoft.AspNetCore.AzureAppServicesIntegration": { },
+ "Microsoft.Extensions.ApplicationModelDetection": { }
}
},
"Default": { // Rules to run for packages not listed in any other set.
diff --git a/build/dependencies.props b/build/dependencies.props
index c2ede2cdee..95bacc4edd 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -33,6 +33,8 @@
15.3.0
1.4.0
4.7.49
+ 10.0.1
+ 1.5.0
8.1.4
2.3.0
2.3.0
diff --git a/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetectionResult.cs b/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetectionResult.cs
new file mode 100644
index 0000000000..96e53dc52d
--- /dev/null
+++ b/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetectionResult.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Microsoft.Extensions.ApplicationModelDetection
+{
+ public class AppModelDetectionResult
+ {
+ public RuntimeFramework? Framework { get; set; }
+ public string FrameworkVersion { get; set; }
+ public string AspNetCoreVersion { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetector.cs b/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetector.cs
new file mode 100644
index 0000000000..e42a8f2918
--- /dev/null
+++ b/src/Microsoft.Extensions.ApplicationModelDetection/AppModelDetector.cs
@@ -0,0 +1,250 @@
+// 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.IO;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Xml.Linq;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Extensions.ApplicationModelDetection
+{
+ public class AppModelDetector
+ {
+ // We use Hosting package to detect AspNetCore version
+ // it contains light-up implemenation that we care about and
+ // would have to be used by aspnet core web apps
+ private const string AspNetCoreAssembly = "Microsoft.AspNetCore.Hosting";
+
+ ///
+ /// Reads the following sources
+ /// - web.config to detect dotnet framework kind
+ /// - *.runtimeconfig.json to detect target framework version
+ /// - *.deps.json to detect Asp.Net Core version
+ /// - Microsoft.AspNetCore.Hosting.dll to detect Asp.Net Core version
+ ///
+ /// The application directory
+ /// The instance containing information about application
+ public AppModelDetectionResult Detect(DirectoryInfo directory)
+ {
+ string entryPoint = null;
+
+ // Try reading web.config and resolving framework and app path
+ var webConfig = directory.GetFiles("web.config").FirstOrDefault();
+
+ bool webConfigExists = webConfig != null;
+ bool? usesDotnetExe = null;
+
+ if (webConfigExists &&
+ TryParseWebConfig(webConfig, out var dotnetExe, out entryPoint))
+ {
+ usesDotnetExe = dotnetExe;
+ }
+
+ // If we found entry point let's look for .deps.json
+ // in some cases it exists in desktop too
+ FileInfo depsJson = null;
+ FileInfo runtimeConfig = null;
+
+ if (!string.IsNullOrWhiteSpace(entryPoint))
+ {
+ depsJson = new FileInfo(Path.ChangeExtension(entryPoint, ".deps.json"));
+ runtimeConfig = new FileInfo(Path.ChangeExtension(entryPoint, ".runtimeconfig.json"));
+ }
+
+ if (depsJson == null || !depsJson.Exists)
+ {
+ depsJson = directory.GetFiles("*.deps.json").FirstOrDefault();
+ }
+
+ if (runtimeConfig == null || !runtimeConfig.Exists)
+ {
+ runtimeConfig = directory.GetFiles("*.runtimeconfig.json").FirstOrDefault();
+ }
+
+ string aspNetCoreVersionFromDeps = null;
+ string aspNetCoreVersionFromDll = null;
+
+
+ // Try to detect ASP.NET Core version from .deps.json
+ if (depsJson != null &&
+ depsJson.Exists &&
+ TryParseDependencies(depsJson, out var aspNetCoreVersion))
+ {
+ aspNetCoreVersionFromDeps = aspNetCoreVersion;
+ }
+
+ // Try to detect ASP.NET Core version from .deps.json
+ var aspNetCoreDll = directory.GetFiles(AspNetCoreAssembly + ".dll").FirstOrDefault();
+ if (aspNetCoreDll != null &&
+ TryParseAssembly(aspNetCoreDll, out aspNetCoreVersion))
+ {
+ aspNetCoreVersionFromDll = aspNetCoreVersion;
+ }
+
+ // Try to detect dotnet core runtime version from runtimeconfig.json
+ string runtimeVersionFromRuntimeConfig = null;
+ if (runtimeConfig != null &&
+ runtimeConfig.Exists)
+ {
+ TryParseRuntimeConfig(runtimeConfig, out runtimeVersionFromRuntimeConfig);
+ }
+
+ var result = new AppModelDetectionResult();
+ if (usesDotnetExe == true)
+ {
+ result.Framework = RuntimeFramework.DotNetCore;
+ result.FrameworkVersion = runtimeVersionFromRuntimeConfig;
+ }
+ else
+ {
+ if (depsJson?.Exists == true &&
+ runtimeConfig?.Exists == true)
+ {
+ result.Framework = RuntimeFramework.DotNetCoreStandalone;
+ }
+ else
+ {
+ result.Framework = RuntimeFramework.DotNetFramework;
+ }
+ }
+
+ result.AspNetCoreVersion = aspNetCoreVersionFromDeps ?? aspNetCoreVersionFromDll;
+
+ return result;
+ }
+
+ private bool TryParseAssembly(FileInfo aspNetCoreDll, out string aspNetCoreVersion)
+ {
+ aspNetCoreVersion = null;
+ try
+ {
+ using (var stream = aspNetCoreDll.OpenRead())
+ using (var peReader = new PEReader(stream))
+ {
+ var metadataReader = peReader.GetMetadataReader();
+ var assemblyDefinition = metadataReader.GetAssemblyDefinition();
+ aspNetCoreVersion = assemblyDefinition.Version.ToString();
+ return true;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Search for Microsoft.AspNetCore.Hosting entry in deps.json and get it's version number
+ ///
+ private bool TryParseDependencies(FileInfo depsJson, out string aspnetCoreVersion)
+ {
+ aspnetCoreVersion = null;
+ try
+ {
+ using (var streamReader = depsJson.OpenText())
+ using (var jsonReader = new JsonTextReader(streamReader))
+ {
+ var json = JObject.Load(jsonReader);
+
+ var libraryPrefix = AspNetCoreAssembly+ "/";
+
+ var library = json.Descendants().OfType().FirstOrDefault(property => property.Name.StartsWith(libraryPrefix));
+ if (library != null)
+ {
+ aspnetCoreVersion = library.Name.Substring(libraryPrefix.Length);
+ return true;
+ }
+ }
+ }
+ catch (Exception)
+ {
+ }
+ return false;
+ }
+
+ private bool TryParseRuntimeConfig(FileInfo runtimeConfig, out string frameworkVersion)
+ {
+ frameworkVersion = null;
+ try
+ {
+ using (var streamReader = runtimeConfig.OpenText())
+ using (var jsonReader = new JsonTextReader(streamReader))
+ {
+ var json = JObject.Load(jsonReader);
+ frameworkVersion = (string)json?["runtimeOptions"]
+ ?["framework"]
+ ?["version"];
+
+ return true;
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ private bool TryParseWebConfig(FileInfo webConfig, out bool usesDotnetExe, out string entryPoint)
+ {
+ usesDotnetExe = false;
+ entryPoint = null;
+
+ try
+ {
+ var xdocument = XDocument.Load(webConfig.FullName);
+ var aspNetCoreHandler = xdocument.Root?
+ .Element("system.webServer")
+ .Element("aspNetCore");
+
+ if (aspNetCoreHandler == null)
+ {
+ return false;
+ }
+
+ var processPath = (string) aspNetCoreHandler.Attribute("processPath");
+ var arguments = (string) aspNetCoreHandler.Attribute("arguments");
+
+ if (processPath.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase) ||
+ processPath.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase) &&
+ !string.IsNullOrWhiteSpace(arguments))
+ {
+ usesDotnetExe = true;
+ var entryPointPart = arguments.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();
+
+ if (!string.IsNullOrWhiteSpace(entryPointPart))
+ {
+ try
+ {
+ entryPoint = Path.GetFullPath(Path.Combine(webConfig.DirectoryName, entryPointPart));
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ else
+ {
+ usesDotnetExe = false;
+
+ try
+ {
+ entryPoint = Path.GetFullPath(Path.Combine(webConfig.DirectoryName, processPath));
+ }
+ catch (Exception)
+ {
+ }
+ }
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.ApplicationModelDetection/Microsoft.Extensions.ApplicationModelDetection.csproj b/src/Microsoft.Extensions.ApplicationModelDetection/Microsoft.Extensions.ApplicationModelDetection.csproj
new file mode 100644
index 0000000000..aae6b8bf52
--- /dev/null
+++ b/src/Microsoft.Extensions.ApplicationModelDetection/Microsoft.Extensions.ApplicationModelDetection.csproj
@@ -0,0 +1,15 @@
+
+
+ ASP.NET Core integration with Azure AppServices.
+ netstandard2.0
+ $(NoWarn);CS1591
+ true
+ true
+ aspnetcore;azure;appservices
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.Extensions.ApplicationModelDetection/RuntimeFramework.cs b/src/Microsoft.Extensions.ApplicationModelDetection/RuntimeFramework.cs
new file mode 100644
index 0000000000..b182c79eec
--- /dev/null
+++ b/src/Microsoft.Extensions.ApplicationModelDetection/RuntimeFramework.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Microsoft.Extensions.ApplicationModelDetection
+{
+ public enum RuntimeFramework
+ {
+ DotNetCore,
+ DotNetCoreStandalone,
+ DotNetFramework
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.Extensions.ApplicationModelDetection.Tests/AppModelTests.cs b/test/Microsoft.Extensions.ApplicationModelDetection.Tests/AppModelTests.cs
new file mode 100644
index 0000000000..02e420d2e4
--- /dev/null
+++ b/test/Microsoft.Extensions.ApplicationModelDetection.Tests/AppModelTests.cs
@@ -0,0 +1,229 @@
+// // 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.IO;
+using Microsoft.AspNetCore.Hosting;
+using Xunit;
+
+namespace Microsoft.Extensions.ApplicationModelDetection.Tests
+{
+ public class AppModelTests
+ {
+ private const string EmptyWebConfig = @"";
+
+ [Theory]
+ [InlineData("dotnet")]
+ [InlineData("dotnet.exe")]
+ [InlineData("%HOME%/dotnet")]
+ [InlineData("%HOME%/dotnet.exe")]
+ [InlineData("DoTNeT.ExE")]
+ public void DetectsCoreFrameworkFromWebConfig(string processPath)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config",GenerateWebConfig(processPath, ".\\app.dll")))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCore, result.Framework);
+ }
+ }
+
+ [Theory]
+ [InlineData("app")]
+ [InlineData("app.exe")]
+ [InlineData("%HOME%/app")]
+ [InlineData("%HOME%/app.exe")]
+ public void DetectsFullFrameworkFromWebConfig(string processPath)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig(processPath, ".\\app.dll")))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetFramework, result.Framework);
+ }
+ }
+
+ [Theory]
+ [InlineData("2.0.0")]
+ [InlineData("2.0.0-preview1")]
+ [InlineData("1.1.3")]
+ public void DetectsRuntimeVersionFromRuntimeConfig(string runtimeVersion)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig("dotnet", ".\\app.dll"))
+ .WithFile("app.runtimeconfig.json", @"{
+ ""runtimeOptions"": {
+ ""tfm"": ""netcoreapp2.0"",
+ ""framework"": {
+ ""name"": ""Microsoft.NETCore.App"",
+ ""version"": """+ runtimeVersion + @"""
+ },
+ ""configProperties"": {
+ ""System.GC.Server"": true
+ }
+ }
+}"))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCore, result.Framework);
+ Assert.Equal(runtimeVersion, result.FrameworkVersion);
+ }
+ }
+
+
+ [Theory]
+ [InlineData("2.0.0")]
+ [InlineData("2.0.0-preview1")]
+ [InlineData("1.1.3")]
+ public void DetectsRuntimeVersionFromRuntimeConfigWitoutEntryPoint(string runtimeVersion)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig("dotnet", "%HOME%\\app.dll"))
+ .WithFile("app.runtimeconfig.json", @"{
+ ""runtimeOptions"": {
+ ""tfm"": ""netcoreapp2.0"",
+ ""framework"": {
+ ""name"": ""Microsoft.NETCore.App"",
+ ""version"": """+ runtimeVersion + @"""
+ },
+ ""configProperties"": {
+ ""System.GC.Server"": true
+ }
+ }
+}"))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCore, result.Framework);
+ Assert.Equal(runtimeVersion, result.FrameworkVersion);
+ }
+ }
+
+ [Theory]
+ [InlineData("2.0.0")]
+ [InlineData("2.0.0-preview1")]
+ [InlineData("1.1.3")]
+ public void DetectsAspNetCoreVersionFromDepsFile(string runtimeVersion)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig("dotnet", "app.dll"))
+ .WithFile("app.deps.json", @"{
+ ""targets"": {
+ "".NETCoreApp,Version=v2.7"": {
+ ""Microsoft.AspNetCore.Hosting/" + runtimeVersion + @""": { }
+ }
+ }
+}"))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCore, result.Framework);
+ Assert.Equal(runtimeVersion, result.AspNetCoreVersion);
+ }
+ }
+
+ [Theory]
+ [InlineData("2.0.0")]
+ [InlineData("2.0.0-preview1")]
+ [InlineData("1.1.3")]
+ public void DetectsAspNetCoreVersionFromDepsFileWithoutEntryPoint(string runtimeVersion)
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig("dotnet", "%HOME%\\app.dll"))
+ .WithFile("app.deps.json", @"{
+ ""targets"": {
+ "".NETCoreApp,Version=v2.7"": {
+ ""Microsoft.AspNetCore.Hosting/" + runtimeVersion + @""": { }
+ }
+ }
+}"))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCore, result.Framework);
+ Assert.Equal(runtimeVersion, result.AspNetCoreVersion);
+ }
+ }
+
+ [Fact]
+ public void DetectsFullFrameworkWhenWebConfigExists()
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", EmptyWebConfig))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetFramework, result.Framework);
+ }
+ }
+
+ [Fact]
+ public void DetectsStandalone_WhenBothDepsAndRuntimeConfigExist()
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile("web.config", GenerateWebConfig("app.exe", ""))
+ .WithFile("app.runtimeconfig.json", "{}")
+ .WithFile("app.deps.json", "{}"))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(RuntimeFramework.DotNetCoreStandalone, result.Framework);
+ }
+ }
+
+ [Fact]
+ public void DetectsAspNetCoreVersionFromHostingDll()
+ {
+ using (var temp = new TemporaryDirectory()
+ .WithFile(typeof(WebHostBuilder).Assembly.Location))
+ {
+ var result = new AppModelDetector().Detect(temp.Directory);
+ Assert.Equal(typeof(WebHostBuilder).Assembly.GetName().Version.ToString(), result.AspNetCoreVersion);
+ }
+ }
+
+ private static string GenerateWebConfig(string processPath, string arguments)
+ {
+ return $@"
+
+
+
+
+
+
+
+
+";
+ }
+
+ private class TemporaryDirectory: IDisposable
+ {
+ public TemporaryDirectory()
+ {
+ Directory = new DirectoryInfo(Path.GetTempPath())
+ .CreateSubdirectory(Guid.NewGuid().ToString("N"));
+ }
+
+ public DirectoryInfo Directory { get; }
+
+ public void Dispose()
+ {
+ try
+ {
+ Directory.Delete(true);
+ }
+ catch (IOException)
+ {
+ }
+ }
+
+ public TemporaryDirectory WithFile(string name, string value)
+ {
+ File.WriteAllText(Path.Combine(Directory.FullName, name), value);
+ return this;
+ }
+
+
+ public TemporaryDirectory WithFile(string name)
+ {
+ File.Copy(name, Path.Combine(Directory.FullName, Path.GetFileName(name)));
+ return this;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.Extensions.ApplicationModelDetection.Tests/Microsoft.Extensions.ApplicationModelDetection.Tests.csproj b/test/Microsoft.Extensions.ApplicationModelDetection.Tests/Microsoft.Extensions.ApplicationModelDetection.Tests.csproj
new file mode 100644
index 0000000000..b68825fcc0
--- /dev/null
+++ b/test/Microsoft.Extensions.ApplicationModelDetection.Tests/Microsoft.Extensions.ApplicationModelDetection.Tests.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp2.0;net461
+ netcoreapp2.0
+
+
+
+
+
+
+
\ No newline at end of file