XML Documentation file is not included in publish folder - asp.net-core

I've selected "Xml documentation file" in my ASP.NET Core MVC application and it displays "bin\Debug\net452\MyProject.xml" as output folder. The problem is that this file doesn't exist in publish folder. Do I need to someting additional to include it? Using .NET Core 1.0-RC4 and VS.NET 2017 RC4 (new csproject format).

If you're using project.json then you can control the files and folders that are both included and excluded by the publish process:
"publishOptions": {
"include": [
"wwwroot",
"appsettings.json",
"appsettings.*.json",
"web.config"
],
"exclude": [
"wwwroot/less"
]
}
For .csproj based projects here is a good resource for replicating old project.json settings in XML, for example:
<ItemGroup>
<Compile Include="..\Shared\*.cs" Exclude="..\Shared\Not\*.cs" />
<EmbeddedResource Include="..\Shared\*.resx" />
<Content Include="Views\**\*" PackagePath="%(Identity)" />
<None Include="some/path/in/project.txt" Pack="true" PackagePath="in/package.txt" />
<None Include="notes.txt" CopyToOutputDirectory="Always" />
<!-- CopyToOutputDirectory = { Always, PreserveNewest, Never } -->
<Content Include="files\**\*" CopyToPublishDirectory="PreserveNewest" />
<None Include="publishnotes.txt" CopyToPublishDirectory="Always" />
<!-- CopyToPublishDirectory = { Always, PreserveNewest, Never } -->
</ItemGroup>

Apparently this is not implemented in with dotnet publish but will be in the near future: https://github.com/dotnet/sdk/issues/795

The project.json file should have the "xmlDoc": true under "buildOptions":
{
"buildOptions": {
"xmlDoc": true
}
}
If you have documentation in your code like this:
/// <summary>
/// Documentation for FooClass
/// </summary>
public static class FooClass
{
// ...
}
Then you should have an xml file in your output that looks like this:
<doc>
<assembly>
<name>Foo.Bar</name>
</assembly>
<members>
<member name="T:Foo.Bar.FooClass">
<summary>
Documentation for FooClass
</summary>
</member>
</members>
</doc>

Tested on 1.1.2
ResolvedFileToPublish is the Item that publish uses to know which files to put in the publish folder. The Include is the file's source, and the RelativePath is where inside the publish folder the file should be placed.
ComputeFilesToPublish is exactly as its name implies - it is the Target that gathers all the files to be published.
<Target Name="CopyDocumentationFile"
AfterTargets="ComputeFilesToPublish">
<ItemGroup>
<ResolvedFileToPublish Include="#(FinalDocFile)"
RelativePath="#(FinalDocFile->'%(Filename)%(Extension)')" />
</ItemGroup>
</Target>
Just add this target to your .csproj and make sure that
GenerateDocumentationFile is set to true
<GenerateDocumentationFile>true</GenerateDocumentationFile>
https://github.com/dotnet/sdk/issues/795#issuecomment-289782712

In Asp.net Core, if you are having trouble setting DocumentationFile to work, we can do that by setting GenerateDocumentationFile property in .csproj:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Related

Problem Reading Embedded Resource With ASP.NET Core 3.1 in Console App

I'm failing at being able to read embedded resources in ASP.NET Core 3.1. Specifically, I'm following the example in the docs here:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/file-providers?view=aspnetcore-3.1
I've updated my csproj file to the following adding the <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="Data\sessions.json" />
<EmbeddedResource Include="Data\speakers.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EFLib\EFLib.csproj" />
<ProjectReference Include="..\RepositoryLib\RepositoryLib.csproj" />
<ProjectReference Include="..\SeedDataLib\SeedDataLib.csproj" />
</ItemGroup>
</Project>
I have console app as follows and I get the error below when I run it.
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Hello World!");
var manifestEmbeddedProvider =
new ManifestEmbeddedFileProvider(typeof(Program).Assembly); // ERROR HERE
{"Could not load the embedded file manifest 'Microsoft.Extensions.FileProviders.Embedded.Manifest.xml' for assembly 'TestConsoleApp'."}
I'm basically trying to do what I use to do in ASP.NET Core 2 which was this and it's not working.
var assembly = Assembly.GetEntryAssembly();
string[] resources = assembly.GetManifestResourceNames(); // debugging purposes only to get list of embedded resources
I faced the same issue you described. Make sure that you added the following package reference to the .csproj where embedded resources are declared. Once I added it to my project and rebuilt the solution, it started working.
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="3.1.0" />
</ItemGroup>

Replacement of AssemblyBuilder.DefineDynamicAssembly in .NET Core

How do I port the following code to .NET Core?
AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(
Guid.NewGuid().ToString()),
AssemblyBuilderAccess.RunAndSave);
Is it possible?
Add this to your project.json:
"dependencies": {
"System.Reflection.Emit": "4.0.1"
},
and use:
AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()),
AssemblyBuilderAccess.Run);
AssemblyBuilderAccess.RunAndSave is not supported at the moment - link to source.
For new .csproj projects, use:
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
</ItemGroup>

How to re-run property evaluation in MSBuild target?

I have a custom MSBuild target, partial snippet as follows ..
<Target Name="PublishHtm">
<PropertyGroup>
<PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
<PublishHtm>$(PublishHtmTemplateContents)</PublishHtm>
</PropertyGroup>
<WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
This is a rework attempt for this solution in that I'm trying to isolate this template to an external file. The template contains MSBuild property references such as $(ApplicationName). When doing this exactly as described in the linked solution, it works fine, but when loading the template in as a string, none of these property expressions are evaluated by the time it gets to the file.
<SPAN CLASS="BannerTextApplication">$(ApplicationName)</SPAN>
Is there an MSBuild expression/function I can use to get the string to be reevaluated given the context that the Target is being invoked?
BTW I'd rather not work around the problem using find/replace or regex replace, and stick with the MSBuild expression engine.
Using Visual Studio 2012 & .NET Framework 4.5.
Sorry for not getting back to this question for awhile.
Initially I thought that to solve this problem we'll need to bend MSBuild in very unusual way (plan for today was to write complex inline task which will do regex-replace in external file using Msbuild properties as tokens ). But I think this can be solved easier, using CDATA section, which is valid inside property definition:
Here is main script:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<MyOtherProperty>$([System.DateTime]::Now)</MyOtherProperty>
<Version>1.0.1b</Version>
<ProjectName>MSBuild Rox</ProjectName>
<Author>Alexey Shcherbak</Author>
</PropertyGroup>
<Target Name="Build">
<ItemGroup>
<PropsToPass Include="MyOtherProperty=$(MyOtherProperty)" />
<PropsToPass Include="Version=$(Version)" />
<PropsToPass Include="ProjectName=$(ProjectName)" />
<PropsToPass Include="Author=$(Author)" />
</ItemGroup>
<MSBuild Projects="TransformHTML.Template.proj" Properties="#(PropsToPass)" />
</Target>
</Project>
And here is your template. It's not pure html, it's still msbuild file, but at least without ugly encoding for html tags in xml. It's just a block in CDATA
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Transform">
<PropertyGroup>
<HtmlProperty><![CDATA[
<body>
<div>$(MyOtherProperty)</div>
<div>$(Version)</div>
<div>$(ProjectName)</div>
<div>$(Author)</div>
</body>
]]></HtmlProperty>
</PropertyGroup>
<Target Name="Transform">
<Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
</Target>
</Project>
Maybe it's not very elegant ( I personally don't like the section with #PropsToPass) but it'll do the job. You can put everything inline into single file and then you don't need to pass properties to MSBuild task. I don't like massive html-encoding from proposed "this solution" but I'd rather prefer to keep HTML template in the same script where it'll be transformed, just in nice html format, without encoding.
Single file example:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="Build">
<PropertyGroup>
<MyOtherProperty>$([System.DateTime]::Now)</MyOtherProperty>
<Version>1.0.1b</Version>
<ProjectName>MSBuild Rox</ProjectName>
<Author>Alexey Shcherbak</Author>
</PropertyGroup>
<Target Name="Build">
<PropertyGroup>
<HtmlProperty><![CDATA[
<body>
<div>$(MyOtherProperty)</div>
<div>$(Version)</div>
<div>$(ProjectName)</div>
<div>$(Author)</div>
</body>
]]></HtmlProperty>
</PropertyGroup>
<Message Text="HtmlProperty: $(HtmlProperty)" Importance="High" />
</Target>
</Project>
You can also download these files here
You can do it using Eval task
<Target Name="PublishHtm">
<PropertyGroup>
<PublishHtmTemplateContents>$([System.IO.File]::ReadAllText($(PublishHtmTemplatePath)))</PublishHtmTemplateContents>
<Eval Values="$(PublishHtmTemplateContents)">
<Output TaskParameter="Result" ItemName="EvalItemTemp"/>
</Eval>
<PublishHtm>%(EvalItemTemp.Identity)</PublishHtm>
</PropertyGroup>
<WriteLinesToFile Lines="$(PublishHtm)" File="$(PublishDir)\publish.htm" Overwrite="true"/>
</Target>
Actually the task does nothing except returning exactly the same value it received, however when you pass the returned value %(EvalItemTemp.Identity) to anywhere, msbuild does evaluation!
Eval task source:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Choose>
<When Condition="'$(MSBuildToolsVersion)' == 'Current' OR $(MSBuildToolsVersion.Split('.')[0]) >= 14">
<PropertyGroup>
<TasksAssemblyName>Microsoft.Build.Tasks.Core.dll</TasksAssemblyName>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TasksAssemblyName>Microsoft.Build.Tasks.v$(MSBuildToolsVersion).dll</TasksAssemblyName>
</PropertyGroup>
</Otherwise>
</Choose>
<UsingTask TaskName="Eval" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\$(TasksAssemblyName)">
<ParameterGroup>
<Values ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="True" Output="False" />
<Result ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="False" Output="True" />
</ParameterGroup>
<Task>
<Code Type="Class" Language="cs" Source="$(MSBuildThisFileDirectory)TaskSource\EvalTask.cs"/>
</Task>
</UsingTask>
</Project>
Where TaskSource\EvalTask.cs is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.Threading;
namespace Varonis.MSBuild.Tasks
{
public class Eval : Task
{
[Required]
public ITaskItem[] Values { get; set; }
[Output]
public ITaskItem[] Result { get; set; }
public override bool Execute()
{
Result = new TaskItem[Values.Length];
for (int i = 0; i < Values.Length; i++)
{
Result[i] = new TaskItem(Values[i].ItemSpec);
}
return true;
}
}
}

Processing batch items in parallel

I have an ItemGroup, and want to process all its items in parallel (using a custom task or an .exe).
I could write my task/exe to accept the entire ItemGroup and process its items in parallel internally. However, I want this parallelism to work in conjunction with MSBuild's /maxCpuCount param, since otherwise I might end up over-parallelizing.
This thread says there's no way.
My testing shows that MSBuild's /maxCpuCount only works for building different projects, not items (see code below)
How can I process items from an ItemGroup in parallel?
Is there a way to author a custom task to work in parallel in conjunction with MSBuild's Parallel support?
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build" >
<!-- Runs only once - I guess MSBuild detects it's the same project -->
<!--<MSBuild Projects="$(MSBuildProjectFullPath);$(MSBuildProjectFullPath)" Targets="Wait3000" BuildInParallel="true" />-->
<!-- Runs in parallel!. Note that b.targets is a copy of the original a.targets -->
<MSBuild Projects="$(MSBuildProjectFullPath);b.targets" Targets="Wait3000" BuildInParallel="true" />
<!-- Runs sequentially -->
<ItemGroup>
<Waits Include="3000;2000"/>
</ItemGroup>
<Wait DurationMs="%(Waits.Identity)" />
</Target>
<Target Name="Wait3000">
<Wait DurationMs="3000" />
</Target>
<UsingTask TaskName="Wait" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >
<ParameterGroup>
<DurationMs ParameterType="System.Int32" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
Log.LogMessage(string.Format("{0:HH\\:mm\\:ss\\:fff} Start DurationMs={1}", DateTime.Now, DurationMs), MessageImportance.High);
System.Threading.Thread.Sleep(DurationMs);
Log.LogMessage(string.Format("{0:HH\\:mm\\:ss\\:fff} End DurationMs={1}", DateTime.Now, DurationMs), MessageImportance.High);
</Code>
</Task>
</UsingTask>
</Project>
I know this is old, but if you get a few minutes, revisit your attempt to use the MSBuild task. Using the Properties and/or AdditionalProperties reserved item metadata elements* will resolve the issue you described in your code sample ("Runs only once - I guess MSBuild detects it's the same project").
The MSBuild file below processes items from an ItemGroup in parallel via MSBuild's parallel support (including /maxCpuCount). It does not use BuildTargetsInParallel from the MSBuild Extension Pack, nor any other custom or inline task.
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build" >
<ItemGroup>
<Waits Include="3000;2000"/>
</ItemGroup>
<ItemGroup>
<ProjectItems Include="$(MSBuildProjectFullPath)">
<Properties>
WaitMs=%(Waits.Identity)
</Properties>
</ProjectItems>
</ItemGroup>
<MSBuild Projects="#(ProjectItems)" Targets="WaitSpecifiedMs" BuildInParallel="true" />
</Target>
<Target Name="WaitSpecifiedMs">
<Wait DurationMs="$(WaitMs)" />
</Target>
</Project>
* Well-hidden under "Properties Metadata" on the MSBuild Task reference page.
As you said yourself, you can't parallelize on target or task level, you can yield though.
My custom tasks parallelize heavily using TPL, i.e. my base task wrapper has a ForEach wrapper.
public bool ForEach<T>(IEnumerable<T> enumerable, Action<T> action, int max = -1)
{
return enumerable != null && Parallel.ForEach(enumerable, new ParallelOptions { MaxDegreeOfParallelism = max }, (e, s) =>
{
if (Canceled)
s.Stop();
if (s.ShouldExitCurrentIteration)
return;
action(e);
Interlocked.Increment(ref _total);
}).IsCompleted;
}
Typically limit is omitted and managed by .NET itself, with few exception like non-thread safe operations like MSDeploy, deploying SSRS reports that has a config DoS limit of 20 from single IP, or a zip task that degrades heavily if it's more than CPU count even by 1. It's probably not worth reading maxCpuCount and use Environment.ProcessorCount or %NUMBER_OF_PROCESSORS%, but you can try parsing the command line or reflecting on the host object, e.g. my base task class has this method to get all properties, targets, etc. for various extra special global flags.
private void Engine(object host)
{
var type = host.GetType();
if (type.FullName != "Microsoft.Build.BackEnd.TaskHost")
{
Log.Warn("[Host] {0}", type.AssemblyQualifiedName);
return;
}
var flags = BindingFlags.NonPublic | BindingFlags.Instance;
var taskLoggingContext = type.GetProperty("LoggingContext", flags).GetValue(host, null);
var targetLoggingContext = taskLoggingContext.GetType().GetProperty("TargetLoggingContext", flags).GetValue(taskLoggingContext, null);
ProjectTask = taskLoggingContext.GetType().GetProperty("Task", flags).GetValue(taskLoggingContext, null).To<ProjectTaskInstance>();
ProjectTarget = targetLoggingContext.GetType().GetProperty("Target", flags).GetValue(targetLoggingContext, null).To<ProjectTargetInstance>();
var entry = type.GetField("requestEntry", flags).GetValue(host);
var config = entry.GetType().GetProperty("RequestConfiguration").GetValue(entry, null);
Project = config.GetType().GetProperty("Project").GetValue(config, null).To<ProjectInstance>();
Properties = Project.Properties.ToDictionary(p => p.Name, p => p.EvaluatedValue);
Typical task would look something like this using ForEach:
public class Transform : Task
{
[Required]
public ITaskItem[] Configs { get; set; }
protected override void Exec()
{
//...
ForEach(Configs, i =>
{
//...
}, Environment.ProcessorCount);
//...
}

How can I get MSBUILD to evaluate and print the full path when given a relative path?

How can I get MSBuild to evaluate and print in a <Message /> task an absolute path given a relative path?
Property Group
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
Task
<Message Importance="low" Text="Copying '$(Source_Dir.FullPath)' to '$(Program_Dir)'" />
Output
Copying '' to 'c:\Program Files (x86)\Program\'
In MSBuild 4.0, the easiest way is the following:
$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)\your\path'))
This method works even if the script is <Import>ed into another script; the path is relative to the file containing the above code.
(consolidated from Aaron's answer as well as the last part of Sayed's answer)
In MSBuild 3.5, you can use the ConvertToAbsolutePath task:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="Test"
ToolsVersion="3.5">
<PropertyGroup>
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<Target Name="Test">
<ConvertToAbsolutePath Paths="$(Source_Dir)">
<Output TaskParameter="AbsolutePaths" PropertyName="Source_Dir_Abs"/>
</ConvertToAbsolutePath>
<Message Text='Copying "$(Source_Dir_Abs)" to "$(Program_Dir)".' />
</Target>
</Project>
Relevant output:
Project "P:\software\perforce1\main\XxxxxxXxxx\Xxxxx.proj" on node 0 (default targets).
Copying "P:\software\Public\Server\" to "c:\Program Files (x86)\Program\".
A little long-winded if you ask me, but it works. This will be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.
In MSBuild 2.0, there is an approach which doesn't resolve "..". It does however behave just like an absolute path:
<PropertyGroup>
<Source_Dir_Abs>$(MSBuildProjectDirectory)\$(Source_Dir)</Source_Dir_Abs>
</PropertyGroup>
The $(MSBuildProjectDirectory) reserved property is always the directory of the script that contains this reference.
This will also be relative to the "original" project file, so if placed inside a file that gets <Import>ed, this won't be relative to that file.
MSBuild 4.0 added Property Functions which allow you to call into static functions in some of the .net system dlls. A really nice thing about Property Functions is that they will evaluate out side of a target.
To evaluate a full path you can use System.IO.Path.GetFullPath when defining a property like so:
<PropertyGroup>
<Source_Dir>$([System.IO.Path]::GetFullPath('..\..\..\Public\Server\'))</Source_Dir>
</PropertyGroup>
The syntax is a little ugly but very powerful.
Wayne is correct that well-known metadata does not apply to properties - only to items. Using properties such as "MSBuildProjectDirectory" will work, but I'm not aware of a built in way to resolve the full path.
Another option is to write a simple, custom task that will take a relative path and spit out the fully-resolved path. It would look something like this:
public class ResolveRelativePath : Task
{
[Required]
public string RelativePath { get; set; }
[Output]
public string FullPath { get; private set; }
public override bool Execute()
{
try
{
DirectoryInfo dirInfo = new DirectoryInfo(RelativePath);
FullPath = dirInfo.FullName;
}
catch (Exception ex)
{
Log.LogErrorFromException(ex);
}
return !Log.HasLoggedErrors;
}
}
And your MSBuild lines would look something like:
<PropertyGroup>
<TaskAssembly>D:\BuildTasks\Build.Tasks.dll</TaskAssembly>
<Source_Dir>..\..\..\Public\Server\</Source_Dir>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<UsingTask AssemblyFile="$(TaskAssembly)" TaskName="ResolveRelativePath" />
<Target Name="Default">
<ResolveRelativePath RelativePath="$(Source_Dir)">
<Output TaskParameter="FullPath" PropertyName="_FullPath" />
</ResolveRelativePath>
<Message Importance="low" Text="Copying '$(_FullPath)' to '$(Program_Dir)'" />
</Target>
You are trying to access an item metadata property through a property, which isn't possible. What you want to do is something like this:
<PropertyGroup>
<Program_Dir>c:\Program Files (x86)\Program\</Program_Dir>
</PropertyGroup>
<ItemGroup>
<Source_Dir Include="..\Desktop"/>
</ItemGroup>
<Target Name="BuildAll">
<Message Text="Copying '%(Source_Dir.FullPath)' to '$(Program_Dir)'" />
</Target>
Which will generate output as:
Copying 'C:\Users\sdorman\Desktop' to 'c:\Program Files (x86)\Program\'
(The script was run from my Documents folder, so ..\Desktop is the correct relative path to get to my desktop.)
In your case, replace the "..\Desktop" with "......\Public\Server" in the Source_Dir item and you should be all set.
If you need to convert Properties to Items you have two options. With msbuild 2, you can use the CreateItem task
<Target Name='Build'>
<CreateItem Include='$(Source_Dir)'>
<Output ItemName='SRCDIR' TaskParameter='Include' />
</CreateItem>
and with MSBuild 3.5 you can have ItemGroups inside of a Task
<Target Name='Build'>
<ItemGroup>
<SRCDIR2 Include='$(Source_Dir)' />
</ItemGroup>
<Message Text="%(SRCDIR2.FullPath)" />
<Message Text="%(SRCDIR.FullPath)" />
</Target>