Why does modifying project output directories cause: IOException was unhandled "Cannot locate resource 'app.xaml'."

9

In an attempt to consolidate project settings into property sheets for both C++ and C# projects, the following property sheet was constructed:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!--
      Trying to support both C++ and C# projects by introducing derived 
      properties and setting the appropriate output properties.
  -->
  <PropertyGroup Label="UserMacros">
    <ProjectOrAssemblyName Condition="'$(AssemblyName)'==''">$(ProjectName)</ProjectOrAssemblyName>
    <ProjectOrAssemblyName Condition="'$(ProjectName)'==''">$(AssemblyName)</ProjectOrAssemblyName>
    <ShortPlatform Condition="'$(Platform)'=='Win32'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='x86'">x86</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='x64'">x64</ShortPlatform>
    <ShortPlatform Condition="'$(Platform)'=='AnyCPU'">AnyCPU</ShortPlatform>
  </PropertyGroup>
  <PropertyGroup>
    <OutputPath>$(OutputRelativePath)/$(ProjectOrAssemblyName)_$(ShortPlatform)_$(Configuration)/</OutputPath>        
    <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(ProjectOrAssemblyName)_$(ShortPlatform)</BaseIntermediateOutputPath>
    <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
    <IntDir>$(IntermediateOutputPath)</IntDir>
    <OutDir>$(OutputPath)</OutDir>
  </PropertyGroup>
</Project>

This property sheet will move all build output to a separate location OutputRelativePath (defined in separate property sheet or directly in project file) outside directories that contain source code for easier cleanup etc. However, after setting this up and build works fine and all unit tests work fine, it was clear that a WPF executable project was not fine, since running the application with above property sheet results in the infamous:

IOException was unhandled "Cannot locate resource 'app.xaml'."

Why does changing the output paths result in this error? And how can it be determined that the cause is project build output paths? Can this be seen in generated code? I could not find it? And isn't this a bug?

NOTE: Using the following property sheet works, but only if IntermediateOutputPath contains BaseIntermediateOutputPath.

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
    <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
    <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
  </PropertyGroup>
</Project>

So it appears, that somehow it is expected that output paths contain the AssemblyName properties or similar.

UPDATE FOR XAML STYLES IN ANOTHER ASSEMBLY: The same applies to xaml ResourceDictionary if these - e.g. Brushes.xaml - are located in another assembly and this assembly has changed the OutputPath also, this also throws an exception:

XamlParseException was unhandled for set property Source 
with InnerException "Cannot locate resource 'Brushes.xaml'" 

So all in all it appears output location changes the xaml resource names so these cannot be discovered at runtime, somehow. The odd thing is it is not a problem at design time...


UPDATE: Minimal steps to reproduce the exception:

Open Visual Studio 2013

Create new C# project WPF Application e.g. XamlIntermediateOutputPathBug

Unload Project

Edit Project File

After first PropertyGroup insert new PropertyGroup as:

<PropertyGroup>
  <OutputRelativePath>$(ProjectDir)..\Build</OutputRelativePath>
  <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)/</OutputPath>
  <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
  <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)/</IntermediateOutputPath>
  <IntDir>$(IntermediateOutputPath)</IntDir>
  <OutDir>$(OutputPath)</OutDir>
</PropertyGroup>  

Delete OutputPath properties in remaining PropertyGroups e.g.

<OutputPath>bin\Debug\</OutputPath>  

and:

<OutputPath>bin\Release\</OutputPath>  

This should then throw an IOException on start for mainwindow.xaml. This is due to the $(AssemblyName).g.resources embedded resource is given the following name:

.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.g.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.g.resources
{
  // Offset: 0x00000000 Length: 0x000003BC
}
.mresource public 'Build/Obj_Exe/XamlIntermediateOutputPathBug_AnyCPU_Debug/XamlIntermediateOutputPathBug.Properties.Resources.resources' as Build_Obj_Exe_XamlIntermediateOutputPathBug_AnyCPU_Debug_XamlIntermediateOutputPathBug.Properties.Resources.resources
{
  // Offset: 0x000003C0 Length: 0x000000B4
}

as can be seen with ildasm.exe and opening the MANIFEST for the assembly. As can also be seen the normal resources also gets a wrong name with the output path prefixed. This can, however, be fixed by setting the LogicalName in the project file for this resource (see MissingManifestResourceException when running tests after building with MSBuild (.mresource has path in manifest)). This does not appear to be possible for xaml resources...

Having looked at the configuration I noticed I use / at the end of the OutputPath and IntermediateOutputPath, removing these it appears to work, see below:

<PropertyGroup>
  <OutputRelativePath>$(ProjectDir)..\Build</OutputRelativePath>
  <OutputPath>$(OutputRelativePath)/$(AssemblyName)_$(Platform)_$(Configuration)</OutputPath>
  <BaseIntermediateOutputPath>$(OutputRelativePath)/Obj_Exe/$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
  <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)</IntermediateOutputPath>
  <IntDir>$(IntermediateOutputPath)/</IntDir>
  <OutDir>$(OutputPath)/</OutDir>
</PropertyGroup>  

I find this rather curious... any insight into why this would be the case or if this is actually true is appreciated. Note that the C++ IntDir and OutDir instead must have a trailing backslash, otherwise you will get warnings about this.


c#
wpf
xaml
visual-studio-2012
msbuild
asked on Stack Overflow Jun 13, 2013 by nietras • edited May 23, 2017 by Community

2 Answers

4

Setting the MSBuild output verbosity to "Diagnostic" quickly revealed the source of the problem:

1>   (TaskId:21)
1>  Microsoft (R) Build Task 'ResourcesGenerator' Version '4.0.30319.33440 built by: FX45W81RTMREL'. (TaskId:21)
1>  Copyright (C) Microsoft Corporation 2005. All rights reserved. (TaskId:21)
1>  
1>   (TaskId:21)
1>  Generating .resources file: '..\Build/Obj_Exe/WpfApplication8_AnyCPU_Debug/WpfApplication8.g.resources'... (TaskId:21)
1>  Reading Resource file: 'C:\Users\hpass_000\Projects\Build\Obj_Exe\WpfApplication8_AnyCPU_Debug\MainWindow.baml'... (TaskId:21)
1>  Resource ID is 'mainwindow.baml'. (TaskId:21)
1>  Generated .resources file: '..\Build/Obj_Exe/WpfApplication8_AnyCPU_Debug/WpfApplication8.g.resources'. 

Note the mix of forward and backward slashes in the path names. Windows itself knows how to handle forward slashes in path names well. But that capability is often lacking in other software, it is lacking in the resource generator task. Which requires a true backslash as a path separator, a forward slash is valid in a resource name. Fix:

 <OutputPath>$(OutputRelativePath)\$(AssemblyName)_$(Platform)_$(Configuration)\</OutputPath>
 <BaseIntermediateOutputPath>$(OutputRelativePath)\Obj_Exe\$(AssemblyName)_$(Platform)</BaseIntermediateOutputPath>
 <IntermediateOutputPath>$(BaseIntermediateOutputPath)_$(Configuration)\</IntermediateOutputPath>

In other words, I simply replaced / with \. Which solved the problem.

answered on Stack Overflow Dec 21, 2013 by Hans Passant • edited Dec 21, 2013 by Hans Passant
1

WinFX and Xaml targets perform some behind the scenes hacks/magic when the Xaml references a type located within the current project. During this build task the wpf.csproj is copied to tempfilename.tmp_proj, several nodes related to assembly references are trimmed, and the file is compiled into the IntermediateOutputPath. This allows the Xaml compiler to reference types within the temp assembly.

answered on Stack Overflow Dec 16, 2013 by Nicodemeus

User contributions licensed under CC BY-SA 3.0