无法在没有FrameworkAssemblies的情况下解决程序集引用问题

56

我正在尝试验证Protocol Buffers是否能够与ASP.NET团队的新便携式运行环境以及其他大多数现代环境一起使用。3.0.0-alpha4版本是一段时间以前使用profile259创建的,因此在某些情况下需要进行一些更改,但是我想尝试一下。我知道Oren Novotny关于针对.NET Core的帖子,并且预计需要对Google.Protobuf nuspec文件进行一些更改,但我遇到的错误让我感到困惑。

DNX版本:1.0.0-rc1-update1

我当前正在尝试测试的场景是面向dnx451的控制台应用程序。我有一个非常简单的示例应用程序:

using Google.Protobuf.WellKnownTypes;
using System;

public class Program
{
    public static void Main(string[] args)
    {
        Duration duration = new Duration { Seconds = 100, Nanos = 5555 };
        Console.WriteLine(duration);
    }
}

...还有一个小小的project.json文件:

{
  "compilationOptions": { "emitEntryPoint": true },
  "dependencies": { "Google.Protobuf": "3.0.0-alpha4" },

  "frameworks": {
    "dnx451": { }
  }
}
请注意,这里甚至没有使用dnxcore* - 具有讽刺意味的是,我可以顺利地使那个工作而没有问题。 dnu restore可以正常工作;dnx run失败并显示以下错误:Error: c:\Users\Jon\Test\Projects\protobuf-coreclr\src\ProtobufTest\Program.cs(9,9): DNX, Version=v4.5.1 error CS0012: 类型“Object”在未引用的程序集中定义。必须向程序集“System.Runtime,Version=4.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a”添加引用。进行以下更改会导致相同的错误:
- 在框架的dependencies部分明确添加对"System.Runtime": "4.0.0"的依赖
- 在框架的dependencies部分明确添加对"System.Runtime": "4.0.0-beta-23109"4.0.10-beta-*4.0.20-beta-*4.0.21-beta*的依赖。
- 在NuGet包中(本地)添加对System.Runtime的依赖项并针对其重新构建 - 更新了project.lock.json以包括System.Runtime v4.0.0,但仍会出现相同的错误。
- 同上,还包括lib\dotnet目录和依赖项
以下步骤可以正常工作(独立进行且没有dependencies条目),但让我感到困惑:
- 将Console.WriteLine调用更改为Console.WriteLine("foo")(除此之外没有进行任何更改)
- 将duration变量的类型更改为object而不是Duration
- 完全删除与协议缓冲区有关的所有提示,并改用TimeSpan或类似内容
- 在dnx451部分的project.json中添加以下内容:
"frameworkAssemblies": {
  "System.Runtime": ""
}

最终,我不希望用户为Protocol Buffers而这样做 - 至少不是为了这个。我假设这与我们如何构建协议缓冲区有关,但由于我没有彻底理解原因,很难解决。

我期望如果我能找到一种让 dependencies 条目正常工作的方法,那么我就可以将该依赖项添加到 Protocol Buffers 本身中,这是没问题的 - 但似乎在 project.lock 文件中具有 System.Runtime v4.0.0 的依赖关系并不能帮助我,所以我肯定错过了什么 :(


1
我在一个简单的类库中遇到了非常相似的错误。找到了两种修改project.json文件以解决问题的方法 - http://nodogmablog.bryanhogan.net/2016/01/the-type-is-defined-in-an-assembly-that-is-not-referenced-system-runtime/ - Bryan
3个回答

23
所以如果你眯着眼看项目.json文件,它基本上就是一个nuspec加一点点描述编译选项和源代码的东西来构建项目。现在的nuspec有两个部分,frameworkAssemblies用于“内置”内容,dependencies用于其他NuGet依赖项。这里的意义与之相同。当你使用“框架”中的东西时,需要在frameworkAssemblies中指定,而不是作为NuGet包依赖项。
现在进入具体细节:
当您在.NET Framework上使用PCL或.NET Core基础库时,引用是对引用程序集(有时称为契约程序集)的引用。其中一些示例是像System.RuntimeSystem.Threading等。当使用MSBUILD基础的项目时,会运行一个任务,该任务基本上自动将所有System.*引用添加到C#编译器中,以避免这种混乱。这些程序集在.NET Framework上被称为facades。不幸的是,即使它们没有被使用,它也会添加所有这些程序集。在运行基于.NET Framework的csproj文件时,对System.Runtime的依赖关系会触发此行为。
之所以添加对同一包的引用不起作用,是因为这些契约程序集(如System.Runtime)的.NET Framework文件夹(net4*)中没有任何dll文件。如果你查看那些文件夹,你会看到一个空的_._文件。这样做的原因是因为当你声明一个NuGet包时,带有对System.RuntimeframeworkAssembly引用时,msbuild项目系统无法安装它(这是一个非常复杂的错误和设计问题)。
那可能会让事情变得更加模糊...

@JonSkeet 你可以像这样添加它 "frameworkAssemblies": { "System.Runtime": { "type": "build", "version": "" } },这样它就不会出现在最终的 nuspec 文件中,这应该是更加清晰的解决方案。 - Axel Heer
@AxelHeer: 我现在只使用"type": "build"来包含源代码...那对于框架程序集有什么意义呢?您是建议将其放在Protobuf nuspec文件中还是我的控制台应用程序的project.json文件中? - Jon Skeet
我们将着手解决这个问题。这些软件包应该足以在任何情况下工作。 - davidfowl
我已经添加了一个答案,展示了我认为处理这个问题的正确方式 - 你能验证一下它是否合适吗?我不想传播一种使事情变得更糟的解决方法。 - Jon Skeet
对于“其他NuGet依赖项的依赖关系”感到困惑。在我的Web应用程序的project.json文件中,我看到了以下内容 - "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.CSharp": "4.0.0", - Bryan
显示剩余3条评论

9
我已经接受了David Fowler的答案作为这一切发生的原因。现在就该我应该采取什么措施来解决它,看起来我只需要在nuspec文件中添加一个frameworkAssemblies元素,用于Google.Protobuf
<package>
  <metadata>
    ...
    <frameworkAssemblies>
      <frameworkAssembly assemblyName="System.Runtime" targetFramework="net45" />
    </frameworkAssemblies>
  </metadata>
  ...
</package>

那么,frameworkAssembly 的引用最终会出现在客户端项目的 project.lock.json 中,一切都很好。

然而,根据 David 的另一个评论(“我们将考虑修复此问题”),也许我根本不需要做任何事情...


这些信息对于NuGet包的开发人员似乎很有趣。为了使信息更好地索引和被找到,最好添加更多标签(如nuget、nuspec等)到你的问题中。 - Oleg
@Oleg:看起来很合理。完成了。 - Jon Skeet

5
我认为你的问题只存在于你选择了控制台应用程序,而不是“ASP.NET Web应用程序”/“ASP.NET 5模板”/“Empty”。我使用了简单的Empty模板进行了简单的测试,从NuGet添加了“Google.Protobuf”: “3.0.0-alpha4”,最后只需修改Startup.cs,使其使用Google.Protobuf.WellKnownTypes:
- 添加using Google.Protobuf.WellKnownTypes; - 在Configure中添加var duration = new Duration { Seconds = 100, Nanos = 5555 }; - 修改await context.Response.WriteAsync("Hallo World!");为await context.Response.WriteAsync(duration.ToString());
Startup.cs的最终代码如下:
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Extensions.DependencyInjection;
using Google.Protobuf.WellKnownTypes;

namespace ProtobufTest
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app)
        {
            app.UseIISPlatformHandler();

            var duration = new Duration { Seconds = 100, Nanos = 5555 };

            app.Run(async context =>
            {
                await context.Response.WriteAsync(duration.ToString());
            });
        }

        // Entry point for the application.
        public static void Main(string[] args) => WebApplication.Run<Startup>(args);
    }
}

ASP.NET 5应用程序成功在Web浏览器中显示了100.5555s。您可以从这里下载演示项目。
更新:我使用纯控制台DNX应用程序分析了问题,并使用代码找到了duration.ToString()方法的问题所在,该方法在ASP.NET环境中有效,但在纯控制台应用程序中无效。问题的原因很有趣,我正在尝试调查,但我想与其他人分享我的当前结果。
以下代码可正常工作:
using Google.Protobuf.WellKnownTypes;
using System;

namespace ConsoleApp3
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var duration = new Duration { Seconds = 100, Nanos = 5555 };
            Console.WriteLine("{0}.{1:D4}s", duration.Seconds, duration.Nanos);
        }
    }
}

您可以从这里下载可用的项目。

我额外添加了一行注释。

//[assembly: Guid("b31eb124-49f7-40bd-b39f-38db8f45def3")]

AssemblyInfo.cs 文件中,不要保留不必要的引用,例如 "Microsoft.CSharp",因为这个引用可能包含许多其他的引用。在演示项目中,project.json 文件包含以下内容:

{
  ...

  "dependencies": {
    "Google.Protobuf": "3.0.0-alpha4"
  },

  "frameworks": {
    "dnx451": { },
    "dnxcore50": {
      "dependencies": {
        "System.Console": "4.0.0-beta-23516"
      }
    }
  }
}

顺便提一下,在“frameworks”的“dnxcore50”部分中包括“System.Console”:“4.0.0-beta-23516”是必需的,因为在“DNX 4.5.1”的“mscorlib”中存在“Console”命名空间(用于“Console.WriteLine”)。如果尝试在“共同”依赖项级别上添加“System.Console”:“4.0.0-beta-23516”,则会出现以下错误:
错误CS0433 类型“Console”存在于“System.Console,Version = 4.0.0.0,Culture = neutral,PublicKeyToken = b03f5f7f11d50a3a”和“mscorlib,Version = 4.0.0.0,Culture = neutral,PublicKeyToken = b77a5c561934e089”中 ConsoleApp3.DNX 4.5.1
更新2:可以替换该行
Console.WriteLine("{0}.{1:D4}s", duration.Seconds, duration.Nanos);

为了

Console.WriteLine((object)duration);

使其正常工作。仅使用Console.WriteLine(duration);var str = duration.ToString();的用法会产生您所描述的错误。
更新3: 我验证了代码duration.ToString()调用这些行,它使用这些行进行格式化。看起来duration.ToString()确实与WellKnownTypes类型(如Duration)的((object)duration).ToString()相同。
我认为最重要的是最新的备注。所述问题仅存在于dnx451(或dnx452或dnx46)。如果一个人删除这些行,问题就不存在了。
"dnx451": { },

如果项目中project.json"frameworks"部分设置为"dnxcore50",那么程序将只编译为DNX Core 5.0。这样可以轻松验证是否存在任何问题。

更新4:最终我找到了一个非常简单的解决方法:只需将"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"依赖项添加到项目中即可。

{
  "dependencies": {
    "Google.Protobuf": "3.0.0-alpha4",
    "Microsoft.AspNet.Hosting": "1.0.0-rc1-final"
  }
}

它会加载许多不必要的dll,但现在依赖项将会被正确解析。 最终项目 可以在dnx451和dnxcore50上编译没有任何问题。我的理解是:“Google.Protobuf”可以与dnx451和dnxcore50一起使用,但RC1的自动依赖项解析仍然有缺陷,它无法正确解析“Google.Protobuf”的一些必需依赖项。

当然,直接添加不必要的"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"引用只能看作是一种解决方法。我认为,在ASP.NET 5和DNX中使用的依赖项解析仍然存在缺陷。我之前发布了一个问题(链接),该问题仍然未解决。这个问题提供了一个例子,说明直接包含的依赖关系可能提供由dnu restore解析出的另一种结果。这就是我开始比较工作代码的依赖关系(最初发布给您的),与不工作的项目所依赖的依赖关系之间的原因。在进行一些测试后,我找到了解决方法,并将其减少到仅一个依赖项:"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"


你可以从 project.json"frameworks" 部分中删除 "dnx451": { }。应用程序将仅使用 dnxcore50 运行。您还可以验证程序是否正常工作。因此,您的代码 var duration = new Duration { Seconds = 100, Nanos = 5555 };duration.ToString() 可以在 DNX Core 5.0 中成功运行。这是您想要验证的吗? - Oleg
我已经在问题中说过它可以在dnxcore上运行。我想让它在dnx451上,以控制台应用程序的形式运行。关键是我可能有用户希望在那个环境中使用它,所以我需要让它能够工作,并且我希望更详细地了解依赖项解析。我已将此添加到问题中 - 更改为使用dnxcore或Web应用程序无法帮助我解决此特定情况。 - Jon Skeet
@JonSkeet:可能是因为你提到了ASP.NET 5,所以才会有误解。 - Oleg
1
@Oleg ASP.NET 5也可以运行控制台应用程序 :) - khellang
这就是为什么我澄清了问题。但是,“ASP.NET 5”通常用于表示整个环境...这就是为什么当您执行“文件/新建项目”时,可以在“Web”下得到“控制台应用程序(Package)”,以及为什么http://docs.asp.net/en/latest/dnx/console.html是ASP.NET文档中的控制台应用程序文档... - Jon Skeet
显示剩余16条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接