dotnet / templating

This repo contains the Template Engine which is used by dotnet new
https://docs.microsoft.com/dotnet/
MIT License
1.61k stars 368 forks source link

VS 2017 csproj Project Doesn't Seem to Work #442

Closed RehanSaeed closed 7 years ago

RehanSaeed commented 7 years ago

I just upgraded (Link to my project template) my xproj template to csproj. My xproj did not have any replacements but my new csproj has many. When I create a new template, all replacements work except the ones in the csproj file. Is csproj replacements supported in the current live version? If so, how can I debug the issue?

mlorbetske commented 7 years ago

All operations are supported on the csproj file, the syntax for conditions changes though to take advantage of the MSBUILD "condition" attribute. We use this in several templates, mvc is the closest in terms of complexity to the one you've posted. Essentially, the engine figures out which conditions belong solely to it and processes those - if a condition references any symbols that do not belong to the engine, they're treated as literals and emitted to the output stream. I'll take a closer look at your project file in a few minutes and reply with the contents for it that should work as expected.

mlorbetske commented 7 years ago

As promised:

Updated csproj (converted conditions to MSBuild conditions)

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

  <PropertyGroup Label="Build">
    <TargetFrameworks Condition="'$(NETCore)' == 'true' AND '$(NETFramework)' == 'true'">netcoreapp1.1;net461</TargetFrameworks>
    <TargetFramework Condition="'$(NETCore)' == 'true' AND '$(NETFramework)' != 'true'">netcoreapp1.1</TargetFramework>
    <TargetFramework Condition="'$(NETCore)' != 'true' AND '$(NETFramework)' == 'true'">net461</TargetFramework>
    <!-- CS1591 - Suppress warnings requiring commenting of all code. -->
    <NoWarn Condition="$(Swagger)">$(NoWarn);CS1591</NoWarn>
    <!-- Enable the generation of a comment XML file for this assembly used by Swagger. -->
    <GenerateDocumentationFile Condition="'$(Swagger)' == 'true'">true</GenerateDocumentationFile>
    <PreserveCompilationContext>true</PreserveCompilationContext>
    <!-- Used to store connection strings and other sensitive settings, so you don't have to check them into your source
         control provider. Only use this in Development, it is not intended for Production use.
         See http://docs.asp.net/en/latest/security/app-secrets.html -->
    <UserSecretsId>ApiTemplate-113f2d04-69f0-40c3-8797-ba3f356dd812</UserSecretsId>
  </PropertyGroup>

  <PropertyGroup Condition="'$(NETCore)' == 'true'">
      <PackageTargetFallback Condition ="'$(TargetFramework)' == 'netcoreapp1.1'">$(PackageTargetFallback);portable-net45+win8+wp8+wpa81</PackageTargetFallback>
  </PropertyGroup>

  <PropertyGroup Condition="$(NETFramework)">
    <RuntimeIdentifier Condition ="'$(TargetFramework)' == 'net461'">win7-x86</RuntimeIdentifier>
  </PropertyGroup>

  <PropertyGroup Label="Package">
    <Version>1.0.0</Version>
    <Authors>PROJECT-AUTHOR</Authors>
    <Company>PROJECT-AUTHOR</Company>
    <Product>PROJECT-TITLE</Product>
    <Description>PROJECT-DESCRIPTION</Description>
    <Copyright>Copyright © PROJECT-AUTHOR. All rights Reserved</Copyright>
  </PropertyGroup>

  <ItemGroup Label="Package References">
    <PackageReference Include="Boilerplate.AspNetCore" Version="2.2.2" />
    <PackageReference Include="Boilerplate.AspNetCore.Swagger" Version="2.1.0" Condition="'$(Swagger)' == 'true'" />
    <PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.0.0" Condition="'$(ApplicationInsights)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.AzureAppServicesIntegration" Version="1.0.1" Condition="'$(Azure)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.CookiePolicy" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.ApiExplorer" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Cors" Version="1.1.2" Condition="'$(CORS)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Json" Version="1.1.2" />
    <PackageReference Include="Microsoft.AspNetCore.Mvc.Formatters.Xml" Version="1.1.2" Condition="'$(DataContractSerializer)' == 'true' OR '$(XmlSerializer)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.ResponseCaching" Version="1.1.1" />
    <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="1.0.1" />
    <PackageReference Include="Microsoft.AspNetCore.Rewrite" Version="1.0.1" Condition="'$(HttpsEverywhere)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.1.1" Condition="'$(IIS)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.1" Condition="'$(Kestrel)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Https" Version="1.1.1" Condition="'$(Kestrel)' == 'true' AND '$(HttpsEverywhere)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.Server.WebListener" Version="1.1.1" Condition="'$(WebListener)' == 'true'" />
    <PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="1.1.1" />
    <PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="1.1.1" />
    <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.VisualStudio.Web.BrowserLink.Loader" Version="14.1.0" />
    <PackageReference Include="NWebsec.AspNetCore.Middleware" Version="1.0.0" />
    <PackageReference Include="NWebsec.AspNetCore.Mvc" Version="1.0.0" />
    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
  </ItemGroup>

  <!-- Tools - Command line tools which can be run using 'dotnet [Tool Name]'. -->
  <ItemGroup Label="Tools">
    <!-- dotnet watch - which allows you to edit code and refresh the browser to see your changes while the application is running. -->
    <DotNetCliToolReference Include="Microsoft.DotNet.Watcher.Tools" Version="1.0.0" />
    <!-- dotnet user-secrets - Lets you store sensitive configuration securely during development like connection strings. -->
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.0" />
    <!-- dotnet aspnet-codegenerator - Code Generation tool for ASP.NET Core used for generating controllers and views. -->
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" />
  </ItemGroup>

  <!-- Modify these constants to enable/disable a feature to debug the template. Note that this only affects the C#. -->
  <PropertyGroup Condition="'$(AuthoringMode)' != 'false'">
    <DefineConstants>$(DefineConstants);NETCore;NETFramework;Kestrel;IIS;NGINX;Azure;ApplicationInsights;StatusController;HttpsEverywhere;CORS;HumansTxt;RobotsTxt;Swagger;RequestId;UserAgent</DefineConstants>
  </PropertyGroup>
  <!-- Show the .template.config folder in the project. -->
  <ItemGroup Condition="$(AuthoringMode)">
    <None Include=".template.config\**\*" />
  </ItemGroup>
</Project>

Updates template.json (Renamed TargetFramework to Framework)

// https://github.com/dotnet/templating/wiki/%22Runnable-Project%22-Templates#configuration
{
  "author": "Muhammad Rehan Saeed (RehanSaeed.com)",
  "classifications": [ "Web", "WebAPI", "Boilerplate", "MVC", "API" ],
  "name": "ASP.NET Core 1.1.1 MVC 6 Boilerplate API",
  "groupIdentity": "Boilerplate",
  "identity": "Boilerplate.AspNetCore.Api.CSharp",
  "shortName": "bapi",
  "tags": {
    "language": "C#"
  },
  "sourceName": "ApiTemplate",
  "preferNameDirectory": true,
  "guids": [
    "837bc53e-0271-4e9c-b5b5-c60ea7a7c7b5",
    "113f2d04-69f0-40c3-8797-ba3f356dd812"
  ],
  "sources": [
    {
      "modifiers": [
        // Swagger
        {
          "condition": "(!Swagger)",
          "exclude": [
            "Constants/HomeControllerRoute.cs",
            "Controllers/HomeController.cs",
            "ViewModelSchemaFilters/**/*"
          ]
        },
        // Framework
        {
          "condition": "(!NETFramework)",
          "exclude": [
            "app.config"
          ]
        },
        // ReverseProxyWebServer
        {
          "condition": "(!IIS)",
          "exclude": [
            "web.config"
          ]
        },
        {
          "condition": "(!NGINX)",
          "exclude": [
            "mime.types",
            "nginx.conf"
          ]
        },
        // HttpsEverywhere
        {
          "condition": "(!HttpsEverywhere)",
          "exclude": [
            "DevelopmentCertificate.pfx"
          ]
        },
        // StatusController
        {
          "condition": "(!StatusController)",
          "exclude": [
            "Constants/StatusControllerRoute.cs",
            "Controllers/StatusController.cs"
          ]
        },
        // CORS
        {
          "condition": "(!CORS)",
          "exclude": [
            "Constants/CorsPolicyName.cs",
            "Controllers/StatusController.cs"
          ]
        },
        // HumansTxt
        {
          "condition": "(!HumansTxt)",
          "exclude": [
            "wwwroot/humans.txt"
          ]
        },
        // RobotsTxt
        {
          "condition": "(!RobotsTxt)",
          "exclude": [
            "wwwroot/robots.txt"
          ]
        }
      ]
    }
  ],
  "symbols": {
    // Title
    "Title": {
      "type": "parameter",
      "datatype": "string",
      "defaultValue": "Project Title",
      "replaces": "PROJECT-TITLE",
      "description": "The name of the project which determines the assembly product name. If the Swagger feature is enabled, shows the title on the Swagger UI."
    },
    // Description
    "Description": {
      "type": "parameter",
      "datatype": "string",
      "defaultValue": "Project Description",
      "replaces": "PROJECT-DESCRIPTION",
      "description": "A description of the project which determines the assembly description. If the Swagger feature is enabled, shows the description on the Swagger UI."
    },
    // Author
    "Author": {
      "type": "parameter",
      "datatype": "string",
      "defaultValue": "Project Author",
      "replaces": "PROJECT-AUTHOR",
      "description": "The name of the author of the project which determines the assembly author, company and copyright information."
    },

    // Swagger
    "Swagger": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Swagger is a format for describing the endpoints in your API. Swashbuckle is used to generate a Swagger document and to generate beautiful API documentation, including a UI to explore and test operations, directly from your routes, controllers and models."
    },
    // Framework
    "Framework": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": ".NET Core",
          "description": "Run cross platform (on Windows, Mac and Linux). The framework is made up of NuGet packages which can be shipped with the application so it is fully stand-alone."
        },
        {
          "choice": ".NET Framework",
          "description": "Gives you access to the full breadth of libraries available in .NET instead of the subset available in .NET Core but requires it to be pre-installed."
        },
        {
          "choice": "Both",
          "description": "Target both .NET Core and .NET Framework."
        }
      ],
      "defaultValue": "Both",
      "description": "Decide which version of the .NET Framework to target."
    },
    "NETCore": {
      "type": "computed",
      "value": "(Framework == \".NET Core\" || Framework == \"Both\")"
    },
    "NETFramework": {
      "type": "computed",
      "value": "(Framework == \".NET Framework\" || Framework == \"Both\")"
    },
    // PrimaryWebServer
    "PrimaryWebServer": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "Kestrel",
          "description": "A web server for ASP.NET Core that is not intended to be internet facing as it has not been security tested. IIS or NGINX should be placed in front as reverse proxy web servers."
        },
        {
          "choice": "WebListener",
          "description": "A Windows only web server. It gives you the option to take advantage of Windows specific features, like Windows authentication, port sharing, HTTPS with SNI, HTTP/2 over TLS (Windows 10), direct file transmission, and response caching WebSockets (Windows 8)."
        }
      ],
      "defaultValue": "Kestrel",
      "description": "The primary web server you want to use to host the site."
    },
    "Kestrel": {
      "type": "computed",
      "value": "(PrimaryWebServer == \"Kestrel\")"
    },
    "WebListener": {
      "type": "computed",
      "value": "(PrimaryWebServer == \"WebListener\")"
    },
    // ReverseProxyWebServer
    "ReverseProxyWebServer": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "Internet Information Services (IIS)",
          "description": "A flexible, secure and manageable Web server for hosting anything on the Web using Windows Server. Select this option if you are deploying your site to Azure web apps. IIS is preconfigured to set request limits for security."
        },
        {
          "choice": "NGINX",
          "description": "A free, open-source, cross-platform high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. It does have a Windows version but it's not very fast and IIS is better on that platform. If the HTTPS Everywhere feature is enabled, NGINX is pre-configured to enable the most secure TLS protocols and ciphers for security and to enable HTTP 2.0 and SSL stapling for performance."
        },
        {
          "choice": "Both",
          "description": "Support both reverse proxy web servers."
        }
      ],
      "defaultValue": "Both",
      "description": "The internet facing reverse proxy web server you want to use in front of the primary web server to host the site."
    },
    "IIS": {
      "type": "computed",
      "value": "(ReverseProxyWebServer == \"Internet Information Services (IIS)\" || ReverseProxyWebServer == \"Both\")"
    },
    "NGINX": {
      "type": "computed",
      "value": "(ReverseProxyWebServer == \"NGINX\" || ReverseProxyWebServer == \"Both\")"
    },
    // CloudProvider
    "CloudProvider": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "Azure",
          "description": "The Microsoft Azure cloud. Adds logging features that let you see logs in the Azure portal."
        },
        {
          "choice": "None",
          "description": "No cloud provider is being used."
        }
      ],
      "defaultValue": "None",
      "description": "Select which cloud provider you are using if any, to add cloud specific features."
    },
    "Azure": {
      "type": "computed",
      "value": "(CloudProvider == \"Azure\")"
    },
    // Analytics
    "Analytics": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "Application Insights",
          "description": "Monitor internal information about how your application is running, as well as external user information using the Microsoft Azure cloud."
        },
        {
          "choice": "None",
          "description": "Not using any analytics."
        }
      ],
      "defaultValue": "None",
      "description": "Monitor internal information about how your application is running, as well as external user information."
    },
    "ApplicationInsights": {
      "type": "computed",
      "value": "(Analytics == \"Application Insights\")"
    },
    // ApplicationInsightsInstrumentationKey
    "ApplicationInsightsInstrumentationKey": {
      "type": "parameter",
      "datatype": "string",
      "replaces": "APPLICATION-INSIGHTS-INSTRUMENTATION-KEY",
      "description": "Your Application Insights instrumentation key e.g. 11111111-2222-3333-4444-555555555555."
    },
    // HttpsEverywhere
    "HttpsEverywhere": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Use the HTTPS scheme and TLS security across the entire site, redirects HTTP to HTTPS and adds a Strict Transport Security (HSTS) HTTP header with preloading enabled. Configures the primary and reverse proxy web servers for best security and adds a development certificate file for use in your development environment."
    },
    // PublicKeyPinning
    "PublicKeyPinning": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Adds the Public-Key-Pins (HPKP) HTTP header to responses. It stops man-in-the-middle attacks by telling browsers exactly which TLS certificate you expect. You must have two TLS certificates for this to work, if you get this wrong you will have performed a denial of service attack on yourself."
    },
    // CORS
    "CORS": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Browser security prevents a web page from making AJAX requests to another domain. This restriction is called the same-origin policy, and prevents a malicious site from reading sensitive data from another site. CORS is a W3C standard that allows a server to relax the same-origin policy. Using CORS, a server can explicitly allow some cross-origin requests while rejecting others."
    },
    // XmlFormatter
    "XmlFormatter": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "DataContractSerializer",
          "description": "The default XML serializer you should use. Requires the use of [DataContract] and [DataMember] attributes."
        },
        {
          "choice": "XmlSerializer",
          "description": "The alternative XML serializer which is slower but gives more control. Uses the [XmlRoot], [XmlElement] and [XmlAttribute] attributes."
        },
        {
          "choice": "None",
          "description": "No XML formatter."
        }
      ],
      "defaultValue": "None",
      "description": "Choose whether to use the XML input/output formatter and which serializer to use."
    },
    "DataContractSerializer": {
      "type": "computed",
      "value": "(XmlFormatter == \"DataContractSerializer\")"
    },
    "XmlSerializer": {
      "type": "computed",
      "value": "(XmlFormatter == \"XmlSerializer\")"
    },
    // StatusController
    "StatusController": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "An endpoint that returns the status of this API and it's dependencies, giving an indication of it's health. This endpoint can be called by site monitoring tools which ping the site or by load balancers which can remove an instance of this API if it is not functioning correctly."
    },
    // RequestId
    "RequestId": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Require that all requests send the X-Request-ID HTTP header containing a GUID. This is useful where you have access to the client and server logs and want to correlate a request and response between the two."
    },
    // UserAgent
    "UserAgent": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "false",
      "description": "Require that all requests send the User-Agent HTTP header containing the application name and version of the caller."
    },
    // RobotsTxt
    "RobotsTxt": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Adds a robots.txt file to tell search engines not to index this site."
    },
    // HumansTxt
    "HumansTxt": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "Adds a humans.txt file where you can tell the world who wrote the application. This file is a good place to thank your developers."
    },
    // AuthoringMode
    "AuthoringMode": {
      "type": "generated",
      "generator": "constant",
      "parameters": {
        "value": false
      }
    }
  }
}

Updates to dotnetcli.host.json (made Framework still show up as TargetFramework)

{
  "symbolInfo": {
    "Framework": {
      "longName": "TargetFramework"
    }
  }
}
RehanSaeed commented 7 years ago

@mlorbetske That was very cool of you do convert it for me. I'll take a detailed look tomorrow morning. With that nugget of knowledge, I think I've now got enough material to write another post about symbols.

RehanSaeed commented 7 years ago

I've given this a look and there are a few problems with this approach.

  1. You have to move elements into their own PropertyGroup if they already have a Condition which means you cannot control the order and appearance of the XML elements. I just wrote a blog post this weekend where I show how to use the label attribute to group your XML elements to make it easier to hand edit your csproj file.
<PropertyGroup Label="Build">
</PropertyGroup>
  1. I can no longer conditionally include comments. Unless I move the comment and element I'm trying to commout out into it's own PropertyGroup with a Condition, However, this causes an exception:
 <PropertyGroup Condition="'$(Swagger)' == 'true'">
    <!-- CS1591 - Suppress warnings requiring commenting of all code. -->
    <NoWarn>$(NoWarn);CS1591</NoWarn>
    <!-- Enable the generation of a comment XML file for this assembly used by Swagger. -->
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
PS C:\Temp> dotnet new bapi --name Foo
Error while processing file /Api-CSharp.csproj.
Check InnerException for details
Error running handler Microsoft.TemplateEngine.Core.Operations.InlineMarkupConditional+Impl at position 0 in Unicode (UT
F-8) bytes of >.

Start: >

Check InnerException for details.
Non-negative number required.
Parameter name: length
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 leng
th, Boolean reliable)
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 leng
th)
   at Microsoft.TemplateEngine.Core.Util.ProcessorState.AdvanceBuffer(Int32 bufferPosition)
   at Microsoft.TemplateEngine.Core.Operations.InlineMarkupConditional.Impl.FindEnd(IProcessorState processorState, Int3
2& bufferLength, Int32& currentBufferPosition)
   at Microsoft.TemplateEngine.Core.Operations.InlineMarkupConditional.Impl.HandleMatch(IProcessorState processor, Int32
 bufferLength, Int32& currentBufferPosition, Int32 token, Stream target)
   at Microsoft.TemplateEngine.Core.Util.ProcessorState.Run()
  1. It's mixing concerns which is forcing the use of dotnetcli.host.json.
mlorbetske commented 7 years ago

I see. I've added support for taking either of these approaches (or combining them as needed) with #444.

<!--#if (Condition) -->
<!-- stuff -->
<!--#endif -->

and

<stuff Condition="'$(Condition)' == ''" />

will both work after this change, to disable the evaluation of the condition on the element, you'd do

<!--/-:msbuild-conditional:noEmit -->
...
<stuff Condition="'$(Condition)' == ''" />

which would turn off processing of those conditions for everything below it (or until a corresponding <!--/+:msbuild-conditional:noEmit --> is found.

RehanSaeed commented 7 years ago

Can you also please fix the exception that occurs when you have a comment in the conditional PropertyGroup. As soon as I remove comments, it starts working.

The advantage of the Condition approach is that it makes debugging and authoring easier because you can change settings easily using DefineConstants. Perhaps you could join PropertyGroups with the same Label property?

Before

<PropertyGroup Label="Build">
    ...
</PropertyGroup>
<PropertyGroup Label="Build" Condition="'$(NETCore)' == 'true'">
    ...
</PropertyGroup>

After

<PropertyGroup Label="Build">
    ...
    ...
</PropertyGroup>
RehanSaeed commented 7 years ago

I also just discovered that adding Condition to TargetFrameworks stops dotnet restore from working entirely. So using the XML comment syntax is the way to go for these elements. For a template author you would comment out the individual ones and focus on the one with multiple frameworks specified anyway to make sure it all works.

<TargetFrameworks Condition="'$(NETCore)' == 'true' AND '$(NETFramework)' == 'true'">netcoreapp1.1;net461</TargetFrameworks>
<TargetFramework Condition="'$(NETCore)' == 'true' AND '$(NETFramework)' != 'true'">netcoreapp1.1</TargetFramework>
<TargetFramework Condition="'$(NETCore)' != 'true' AND '$(NETFramework)' == 'true'">net461</TargetFramework>
mlorbetske commented 7 years ago

The changes for this just got merged into the CLI for the next release.

mlorbetske commented 7 years ago

Closing this since the relevant changes have made it in to the CLI. Feel free to re-open if there's more to be done here