Visual Studio 2017 csproj .NET Core build - views

2020-07-10 06:07发布

问题:

I have a project running in .NET Core and I am using VS2017 as IDE.

When I build my project through Visual Studio 2017 it does not automatically add the Views folder and the wwwroot folder to the output in [projectRoot]/bin/Debug/netcoreapp1.1/win10-x64(BuildDir). This means that if I try to run my website directly from the .exe file created in the bin folder, I get an error with the missing views and wwwroot. If I manually copy these folders to the BuildDir then the views load correctly.

This I can setup in my .csproj file with the following:

<Content Update="Views\**">
  <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

And then the Views work but now my layout file is not being compiled so I get the following:

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - FirstAgenda</title>

    <environment names="Development">
        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
        <link rel="stylesheet" href="~/css/site.css"/>
        <link rel="stylesheet" href="~/css/overwrite.css" asp-append-version="true" />
    </environment>
    <environment names="Staging,Production">
        <link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.6/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" />
        <link rel="stylesheet" href="~/css/overwrite.css" asp-append-version="true" />
    </environment>
</head>

This means that my root folder is not being targeted correctly. And also kinda tells me that just copying the files to the output directory is incorrect.

All of the above is working if I do a publish of my application (also without the addition to the .csproj file). I just have a project runner that I would like to be able to point to the Debug version of my websites executable file, because it is very easy to forget to do a publish compared to just building the project with VS2017.

I just don't know where to go from this and any help will be appreciated?

EDIT:

Added the stripped down version of csproj (which does not work):

<Project ToolsVersion="15.0" Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp1.1</TargetFramework>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Autofac.Extensions.DependencyInjection" Version="4.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="1.1.0" />
    <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.MicrosoftAccount" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="1.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer.Design" Version="1.1.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="1.1.0-preview4-final" />
    <PackageReference Include="IdentityServer4" Version="1.3.1" />
    <PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.0" />
    <PackageReference Include="IdentityServer4.EntityFramework" Version="1.0.0" />
    <PackageReference Include="OctoPack" Version="3.5.2" />
    <PackageReference Include="Serilog.Extensions.Logging.File" Version="1.0.0" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.ViewCompilation" Version="1.1.0" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet">
      <Version>1.1.0-preview4-final</Version>
    </DotNetCliToolReference>
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools">
      <Version>1.0.0-msbuild1-final</Version>
    </DotNetCliToolReference>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools">
      <Version>1.0.0-msbuild1-final</Version>
    </DotNetCliToolReference>
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\[OWNPROJECT]" />
    <ProjectReference Include="..\[OWNPROJECT1]" />
  </ItemGroup>
</Project>

Also tried creating a new project with dotnet new mvc

And it is also not working.

My dotnet core cli version is [1.0.1].

EDIT:

I followed the steps you outlined. I also tried to create a new project with dotnet new mvc and followed your steps. Both gave me the same errors. It was unable to find the appsettings.json. If I then add a propertygroup to the .csproj. That tells msbuild to copy appsettings.json and web.config to the output

<ItemGroup>
  <Content Update="appsettings.json;web.config">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

I can then start the program through the exe, but when I access the index, then when I try to access one of the views it gives me:

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[0]
    An unhandled exception has occurred while executing the request
    System.InvalidOperationException: The view 'Index' was not found. The following locations were searched:
  /Views/Home/Index.cshtml
  /Views/Shared/Index.cshtml

UPDATE:

Shaun Lutins answer seems to be working for me. Both with build and publish options. Only problem was that I was getting an error:

Duplicate 'Content' items were included. The .NET SDK includes 'Content' items from your project directory by default.

But it was resolved by changing the following:

<ItemGroup>                                                                            
   <Content Include="appsettings.json" CopyToOutputDirectory="Always" />
   <Content Include="Views\**\*" CopyToOutputDirectory="Always" />
   <Content Include="wwwroot\**\*" CopyToOutputDirectory="Always" />
</ItemGroup>      

to

<ItemGroup>                                                                            
   <Content Update="appsettings.json;web.config" CopyToOutputDirectory="PreserveNewest"/>
   <Content Update="Views\**\*" CopyToOutputDirectory="PreserveNewest" />
   <Content Update="wwwroot\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>      

回答1:

The challenge as I have interpreted it is to run dotnet build and be able to execute the compiled EXE from anywhere. The answer requires two steps:

  1. copy required content to the bin, and
  2. set the ContentRoot and WebRoot relative to the bin.

First, modify the csproj to copy required content to the bin. Out of the box, dotnet build does not copy Views, appsettings.json, or wwwroot to the bin. So, we need to specify CopyToOutputDirectory explicitly; and to do that, we also need to set EnableDefaultContentItems to false, otherwise our explicit settings will duplicate the default content item settings.

Second, modify Program.cs to specify the new ContentRoot and WebRoot. Out of the box, Program.cs sets UseContentRoot(Directory.GetCurrentDirectory()). Problematically for our situation, the current directory is the directory from which we run the executable. So, if we run the EXE from the command line at our desktop, then the current directory will be C:/Users/MyUser/Desktop. The app will not find the Views, appsettings.json, and other content.

Here are some working steps that I have used to meet the challenge as I have interpreted it.

Working Steps

  1. cd C:\temp
  2. dotnet new mvc
  3. Update the temp.csproj with the "Working csproj" XML.
  4. Update the Program.cs with the "Working Program.cs" code.
  5. dotnet restore
  6. dotnet build
  7. cd ..
  8. From anywhere, run C:\temp\bin\Debug\netcoreapp1.0\win10-x64\temp.exe

Working csproj

<Project Sdk="Microsoft.NET.Sdk.Web">                                                    

  <PropertyGroup>                                                                        
    <TargetFramework>netcoreapp1.0</TargetFramework>                                     
    <RuntimeIdentifier>win10-x64</RuntimeIdentifier>                                     
    <OutputType>exe</OutputType>                                                         
    <EnableDefaultContentItems>false</EnableDefaultContentItems>
  </PropertyGroup>                                                                       

  <ItemGroup>                                                                            
     <Content Include="appsettings.json" CopyToOutputDirectory="Always" />
     <Content Include="Views\**\*" CopyToOutputDirectory="Always" />
     <Content Include="wwwroot\**\*" CopyToOutputDirectory="Always" />
  </ItemGroup>                                                                           

  <ItemGroup>                                                                            

     <!-- Package references omitted for clarity -->

  </ItemGroup>                                                                           

</Project>  

Working Program.cs

public class Program
{
    public static void Main(string[] args)
    {
        // C:\temp\bin\Debug\netcoreapp1.0\win10-x64\temp.dll            
        var assemblyFilePath = 
            System.Reflection.Assembly.GetEntryAssembly().Location;

        // C:\temp\bin\Debug\netcoreapp1.0\win10-x64\
        var binDirectory = 
            System.IO.Path.GetDirectoryName(assemblyFilePath);

        Console.WriteLine(assemblyFilePath);
        Console.WriteLine(binDirectory);

        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(binDirectory) // <--
            .UseWebRoot("wwwroot") // <--
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}


回答2:

I've posted this as an issue to the ASP.NET Core repo on GitHub: https://github.com/aspnet/Home/issues/2897

While the accepted answer works, it feels a bit like a hack.

There are problems with it too. The build process is slow when there are a lot of files to copy, and the build process does not remove files that have been deleted.

The accepted answer is also a misuse of the EnableDefaultContentItems setting.

So while I'm glad there is a short-term solution, a long-term solution to the problem needs to be sought.

Hopefully, the issue I've raised at GitHub will work someway towards a more permenant solution.