Implement a .Design package and some testing for MSBuild

This is the first step in adding support for Razor compilation at build
time. Additional steps will complete this by adding tag helper
discovery, code generation and finally a call to CsC.

I want to get this in now to get the package into the build system and
to lay down the general infrastructure for testing.

---

The strategy for testing here is to use checked in projects that have
everything Razor needs to compile code. We already have shims for the
APIs Razor uses by default in this repo and I'm using them in the
project.

The test infrastructure creates a temporary directory, copies the
project, and fixes up a few small things to cooperate with outputs we've
already built so that the test can use the new versions of Razor bits
built from this repo.

We can then call various MSBuild targets and verify the files on disk. I
envision tests that verify incrementalism as well as the basic E2E.

We will test the E2E in general in other places, since it's part of the
new default experience. This repo will test things at a higher level of
detail, but in slightly artifical scenarios.
This commit is contained in:
Ryan Nowak 2017-12-13 08:01:59 -08:00
parent 1c9c05b64d
commit 1962989ffc
29 changed files with 754 additions and 1 deletions

View File

@ -78,6 +78,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test", "test\Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test\Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj", "{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Razor.Design", "src\Microsoft.AspNetCore.Razor.Design\Microsoft.AspNetCore.Razor.Design.csproj", "{5257B25D-330A-4DCF-ACED-B4709CFBF916}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Razor.Design.Test", "test\Microsoft.AspNetCore.Razor.Design.Test\Microsoft.AspNetCore.Razor.Design.Test.csproj", "{1D90F276-E1CA-4FDF-A173-EB889E7D3150}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -316,6 +320,22 @@ Global
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.Release|Any CPU.Build.0 = Release|Any CPU
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.Release|Any CPU.Build.0 = Release|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{5257B25D-330A-4DCF-ACED-B4709CFBF916}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.DebugNoVSIX|Any CPU.ActiveCfg = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.DebugNoVSIX|Any CPU.Build.0 = Debug|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.Release|Any CPU.Build.0 = Release|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.ReleaseNoVSIX|Any CPU.ActiveCfg = Release|Any CPU
{1D90F276-E1CA-4FDF-A173-EB889E7D3150}.ReleaseNoVSIX|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -350,6 +370,8 @@ Global
{FAF9986F-E086-4513-9D52-F7BF5FFCF31D} = {C0CC1E1F-1559-44DE-93A8-63259CEA2AAB}
{95B18DEE-8B45-4CF0-B9F8-CCBAF3B5251A} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{B8A3E4CA-D54A-441F-A3BF-E00F060CA042} = {92463391-81BE-462B-AC3C-78C6C760741F}
{5257B25D-330A-4DCF-ACED-B4709CFBF916} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED}
{1D90F276-E1CA-4FDF-A173-EB889E7D3150} = {92463391-81BE-462B-AC3C-78C6C760741F}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0035341D-175A-4D05-95E6-F1C2785A1E26}

View File

@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Razor is a markup syntax for adding server-side logic to web pages. This package contains MSBuild support for Razor.</Description>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,2 @@
{
}

View File

@ -0,0 +1,10 @@
<Project>
<!--
Properties supporting Razor MSBuild integration
-->
<PropertyGroup>
<RazorGenerateDependsOn></RazorGenerateDependsOn>
<RazorCompileDependsOn></RazorCompileDependsOn>
</PropertyGroup>
</Project>

View File

@ -0,0 +1,11 @@
<Project>
<!--
Targets supporting Razor MSBuild integration
-->
<Target Name="RazorGenerate" DependsOnTargets="$(RazorGenerateDependsOn)">
</Target>
<Target Name="RazorCompile" DependsOnTargets="$(RazorCompileDependsOn)">
</Target>
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="..\build\Microsoft.AspNetCore.Razor.Design.props" />
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<Import Project="..\build\Microsoft.AspNetCore.Razor.Design.targets" />
</Project>

View File

@ -0,0 +1,46 @@
// 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.Text;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
internal class Assert : Xunit.Assert
{
public static void BuildPassed(MSBuildResult result)
{
NotNull(result);
if (result.ExitCode != 0)
{
throw new BuildFailedException(result);
}
}
private class BuildFailedException : Xunit.Sdk.XunitException
{
public BuildFailedException(MSBuildResult result)
{
Result = result;
}
public MSBuildResult Result { get; }
public override string Message
{
get
{
var message = new StringBuilder();
message.Append("Build failed: ");
message.Append(Result.FileName);
message.Append(" ");
message.Append(Result.Arguments);
message.AppendLine();
message.AppendLine();
message.Append(Result.Output);
return message.ToString();
}
}
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class CleanProjectIntegrationTest : MSBuildIntegrationTestBase
{
[Fact]
[InitializeTestProject("SimpleMvc")]
public async Task CleanProject_RunBuild()
{
var result = await DotnetMSBuild("Restore;Build"); // Equivalent to dotnet build
Assert.BuildPassed(result);
}
}
}

View File

@ -0,0 +1,42 @@
// 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.Reflection;
using Xunit.Sdk;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public class InitializeTestProjectAttribute : BeforeAfterTestAttribute
{
private readonly string _projectName;
public InitializeTestProjectAttribute(string projectName)
{
_projectName = projectName;
}
public override void Before(MethodInfo methodUnderTest)
{
if (!typeof(MSBuildIntegrationTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
{
throw new InvalidOperationException($"This should be used on a class derived from {typeof(MSBuildIntegrationTestBase)}");
}
MSBuildIntegrationTestBase.Project = ProjectDirectory.Create(_projectName);
}
public override void After(MethodInfo methodUnderTest)
{
if (!typeof(MSBuildIntegrationTestBase).GetTypeInfo().IsAssignableFrom(methodUnderTest.DeclaringType.GetTypeInfo()))
{
throw new InvalidOperationException($"This should be used on a class derived from {typeof(MSBuildIntegrationTestBase)}");
}
MSBuildIntegrationTestBase.Project.Dispose();
MSBuildIntegrationTestBase.Project = null;
}
}
}

View File

@ -0,0 +1,55 @@
// 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.Diagnostics;
#if NET461
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
#else
using System.Threading;
#endif
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
public abstract class MSBuildIntegrationTestBase
{
#if NET461
#elif NETCOREAPP2_0 || NETCOREAPP2_1
private static readonly AsyncLocal<ProjectDirectory> _project = new AsyncLocal<ProjectDirectory>();
#else
#error TFM not supported
#endif
protected MSBuildIntegrationTestBase()
{
}
// Used by the test framework to set the project that we're working with
internal static ProjectDirectory Project
{
#if NET461
get
{
var handle = (ObjectHandle)CallContext.LogicalGetData("MSBuildIntegrationTestBase_Project");
return (ProjectDirectory)handle.Unwrap();
}
set
{
CallContext.LogicalSetData("MSBuildIntegrationTestBase_Project", new ObjectHandle(value));
}
#elif NETCOREAPP2_0 || NETCOREAPP2_1
get { return _project.Value; }
set { _project.Value = value; }
#else
#error TFM not supported
#endif
}
internal Task<MSBuildResult> DotnetMSBuild(string target)
{
return MSBuildProcessManager.RunProcessAsync($"/t:{target}", Project.DirectoryPath);
}
}
}

View File

@ -0,0 +1,82 @@
// 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.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
internal static class MSBuildProcessManager
{
public static Task<MSBuildResult> RunProcessAsync(string arguments, string workingDirectory, TimeSpan? timeout = null)
{
timeout = timeout ?? TimeSpan.FromSeconds(30);
var process = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "dotnet",
Arguments = "msbuild " + arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
},
EnableRaisingEvents = true,
};
var completionSource = new TaskCompletionSource<MSBuildResult>();
var output = new StringBuilder();
process.Exited += Process_Exited;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.OutputDataReceived += Process_OutputDataReceived;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
var timeoutTask = Task.Delay(timeout.Value).ContinueWith((t) =>
{
// Don't timeout during debug sessions
while (Debugger.IsAttached)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
}
if (process.HasExited)
{
// This will happen on success, the 'real' task has already completed so this value will
// never be visible.
return (MSBuildResult)null;
}
// This is a timeout.
process.Kill();
throw new TimeoutException($"command '${process.StartInfo.FileName} {process.StartInfo.Arguments}' timed out after {timeout}.");
});
return Task.WhenAny<MSBuildResult>(completionSource.Task, timeoutTask).Unwrap();
void Process_Exited(object sender, EventArgs e)
{
var result = new MSBuildResult(process.StartInfo.FileName, process.StartInfo.Arguments, process.ExitCode, output.ToString());
completionSource.SetResult(result);
}
void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
output.AppendLine(e.Data);
}
void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
output.AppendLine(e.Data);
}
}
}
}

View File

@ -0,0 +1,24 @@
// 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.AspNetCore.Razor.Design.IntegrationTests
{
internal class MSBuildResult
{
public MSBuildResult(string fileName, string arguments, int exitCode, string output)
{
FileName = fileName;
Arguments = arguments;
ExitCode = exitCode;
Output = output;
}
public string Arguments { get; }
public string FileName { get; }
public int ExitCode { get; }
public string Output { get; }
}
}

View File

@ -0,0 +1,116 @@
// 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.Threading;
using Microsoft.AspNetCore.Testing;
namespace Microsoft.AspNetCore.Razor.Design.IntegrationTests
{
internal class ProjectDirectory : IDisposable
{
public static ProjectDirectory Create(string projectName)
{
var destinationPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(destinationPath);
try
{
if (Directory.EnumerateFiles(destinationPath).Any())
{
throw new InvalidOperationException($"{destinationPath} should be empty");
}
var solutionRoot = TestPathUtilities.GetSolutionRootDirectory("Razor");
if (solutionRoot == null)
{
throw new InvalidOperationException("Could not find solution root.");
}
var projectRoot = Path.Combine(solutionRoot, "test", "testapps", projectName);
if (!Directory.Exists(projectRoot))
{
throw new InvalidOperationException($"Could not find project at '{projectRoot}'");
}
CopyDirectory(new DirectoryInfo(projectRoot), new DirectoryInfo(destinationPath));
foreach (var project in Directory.EnumerateFiles(destinationPath, "*.csproj"))
{
RewriteCsproj(projectRoot, project);
}
return new ProjectDirectory(destinationPath);
}
catch
{
CleanupDirectory(destinationPath);
throw;
}
void CopyDirectory(DirectoryInfo source, DirectoryInfo destination)
{
foreach (var directory in source.EnumerateDirectories())
{
if (directory.Name == "bin" || directory.Name == "obj")
{
// Just in case someone has opened the project in an IDE or built it. We don't want to copy
// these folders.
continue;
}
var created = destination.CreateSubdirectory(directory.Name);
CopyDirectory(directory, created);
}
foreach (var file in source.EnumerateFiles())
{
file.CopyTo(Path.Combine(destination.FullName, file.Name));
}
}
void RewriteCsproj(string originalProjectRoot, string filePath)
{
// We need to replace $(OriginalProjectRoot) with the path to the original directory
// that way relative references will resolve.
var text = File.ReadAllText(filePath);
text = text.Replace("$(OriginalProjectRoot)", originalProjectRoot);
File.WriteAllText(filePath, text);
}
}
private ProjectDirectory(string directoryPath)
{
DirectoryPath = directoryPath;
}
public string DirectoryPath { get; }
public void Dispose()
{
CleanupDirectory(DirectoryPath);
}
private static void CleanupDirectory(string filePath)
{
var tries = 5;
var sleep = TimeSpan.FromSeconds(3);
for (var i = 0; i < tries; i++)
{
try
{
Directory.Delete(filePath, recursive: true);
return;
}
catch when (i < tries - 1)
{
Console.WriteLine($"Failed to delete directory {filePath}, trying again.");
Thread.Sleep(sleep);
}
}
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<PreserveCompilationContext>true</PreserveCompilationContext>
<DefaultItemExcludes>$(DefaultItemExcludes);TestFiles\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Testing" Version="$(MicrosoftAspNetCoreTestingPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualStudioPackageVersion)" />
<PackageReference Include="xunit" Version="$(XunitPackageVersion)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,4 @@
{
"methodDisplay": "method",
"shadowCopy": false
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
<TargetFrameworks>$(StandardTestTfms);netstandard2.0</TargetFrameworks>
<PreserveCompilationContext>true</PreserveCompilationContext>
</PropertyGroup>

View File

@ -0,0 +1,11 @@
using System;
namespace SimpleMvc.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

View File

@ -0,0 +1,10 @@

namespace SimpleMvc
{
public class Program
{
public static void Main(string[] args)
{
}
}
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(OriginalProjectRoot)\..\..\Microsoft.AspNetCore.Razor.Test.MvcShim\Microsoft.AspNetCore.Razor.Test.MvcShim.csproj"/>
<ProjectReference Include="$(OriginalProjectRoot)\..\..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj"/>
</ItemGroup>
</Project>

View File

@ -0,0 +1,7 @@
@{
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>

View File

@ -0,0 +1,17 @@
@{
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<address>
One Microsoft Way<br />
Redmond, WA 98052-6399<br />
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br />
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

View File

@ -0,0 +1,108 @@
@{
ViewData["Title"] = "Home Page";
}
<div id="myCarousel" class="carousel slide" data-ride="carousel" data-interval="6000">
<ol class="carousel-indicators">
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
<li data-target="#myCarousel" data-slide-to="1"></li>
<li data-target="#myCarousel" data-slide-to="2"></li>
<li data-target="#myCarousel" data-slide-to="3"></li>
</ol>
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="~/images/banner1.svg" alt="ASP.NET" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Learn how to build ASP.NET apps that can run anywhere.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525028&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner2.svg" alt="Visual Studio" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
There are powerful new features in Visual Studio for building modern web apps.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525030&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner3.svg" alt="Package Management" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Bring in libraries from NuGet, Bower, and npm, and automate tasks using Grunt or Gulp.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525029&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner4.svg" alt="Microsoft Azure" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525027&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div class="row">
<div class="col-md-3">
<h2>Application uses</h2>
<ul>
<li>Sample pages using ASP.NET Core MVC</li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=518004">Bower</a> for managing client-side libraries</li>
<li>Theming using <a href="https://go.microsoft.com/fwlink/?LinkID=398939">Bootstrap</a></li>
</ul>
</div>
<div class="col-md-3">
<h2>How to</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=398600">Add a Controller and View</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699315">Manage User Secrets using Secret Manager.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699316">Use logging to log a message.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699317">Add packages using NuGet.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699318">Add client packages using Bower.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699319">Target development, staging or production environment.</a></li>
</ul>
</div>
<div class="col-md-3">
<h2>Overview</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=518008">Conceptual overview of what is ASP.NET Core</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699320">Fundamentals of ASP.NET Core such as Startup and middleware.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398602">Working with Data</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398603">Security</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699321">Client side development</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699322">Develop on different platforms</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699323">Read more on the documentation site</a></li>
</ul>
</div>
<div class="col-md-3">
<h2>Run &amp; Deploy</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Apps</a></li>
</ul>
</div>
</div>

View File

@ -0,0 +1,22 @@
@model ErrorViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - SimpleMvc</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-area="" asp-controller="Home" asp-action="Index" class="navbar-brand">SimpleMvc</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="About">About</a></li>
<li><a asp-area="" asp-controller="Home" asp-action="Contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr />
<footer>
<p>&copy; 2017 - SimpleMvc</p>
</footer>
</div>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,18 @@
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH">
</script>
</environment>

View File

@ -0,0 +1,3 @@
@using SimpleMvc
@using SimpleMvc.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}