I'm trying to validate that Protocol Buffers is going to work with the new portable runtimes from the ASP.NET team and ideally most other modern environments. The 3.0.0-alpha4 build was created a while ago using profile259, so I would expect some changes to be required in some cases, but I thought I'd give it a try. I'm aware of Oren Novotny's post about targeting .NET Core, and expected to have to make some changes to the Google.Protobuf nuspec file, but the error I'm running into has me stumped.
DNX version: 1.0.0-rc1-update1
The scenario I'm currently trying to test is a console app targeting dnx451. I have a very simple sample app:
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);
}
}
... and a tiny project.json
:
{
"compilationOptions": { "emitEntryPoint": true },
"dependencies": { "Google.Protobuf": "3.0.0-alpha4" },
"frameworks": {
"dnx451": { }
}
}
Note that I'm not even using dnxcore*
here - ironically, I got that to work without issues.
dnu restore
works fine; dnx run
fails with:
Error: c:\Users\Jon\Test\Projects\protobuf-coreclr\src\ProtobufTest\Program.cs(9,9): DNX,Version=v4.5.1 error CS0012: The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
The following changes result in the same error:
- Explicitly adding a dependency to
"System.Runtime": "4.0.0"
in the dependencies
section for the framework
- Explicitly adding a dependency to
"System.Runtime": "4.0.0-beta-23109"
in the dependencies
section for the framework, and likewise for 4.0.10-beta-*
, 4.0.20-beta-*
and 4.0.21-beta*
.
- Adding dependencies to
System.Runtime
within the NuGet package (locally) and rebuilding against that - project.lock.json
was updated to include System.Runtime v4.0.0, but the same error occurred
- Ditto including a
lib\dotnet
directory in the package, as well as the dependencies
Steps that did work (independently, and with no dependencies
entries), but confuse me:
Ultimately, I don't want users to have to do this - at least, not for the sake of Protocol Buffers. I'm assuming this is something to do with how we're building Protocol Buffers, but as I don't understand the cause properly, it's hard to fix.
I expect that if I could work out a way of making a dependencies
entry work, I could then add that dependency into Protocol Buffers itself, which would be fine - but as having a dependency on System.Runtime v4.0.0 in the project.lock file doesn't seem to help, I must be missing something :(
So if you squint and look at the project.json, it's basically a nuspec with a little bit of goop to describe what compilation options and sources are needed to build to project. Nuspecs today have 2 sections, frameworkAssemblies
for "built in" stuff and dependencies
for other nuget dependencies. It has the same meaning here. When you use something from "the framework", it needs to be specified in frameworkAssemblies
vs being a nuget package dependency.
Now onto specifics:
When you use a PCL or .NET Core based library on .NET Framework, the references are to reference assemblies (sometimes called contract assemblies). Some examples of these are things like System.Runtime
, System.Threading
etc. When using MSBUILD based projects, there is a task that runs which basically automatically adds all of the System.*
references to the C# compiler to avoid this mess. These assemblies are called facades on .NET Framework. The unfortunate part is that it adds ALL of them even if they aren't used. A dependency on System.Runtime
is the trigger for this behavior (when running on .NET Framework based csproj files).
The reason adding a reference to the same package doesn't work is because the .NET Framework folder (net4*) for those contract assemblies (like System.Runtime), don't have any dlls in them. If you look at those folders, you'll see an empty _._
file. The reasoning for this is because when you declare a nuget package with a frameworkAssembly
reference to System.Runtime
, the msbuild project systems fails to install it (very complicated bug and design problem).
That probably made things fuzzier...
I've accepted David Fowler's answer as the reason why all of this happened. Now in terms of what I should do about it, it looks like I just need to add a frameworkAssemblies
element in the nuspec file for Google.Protobuf
:
<package>
<metadata>
...
<frameworkAssemblies>
<frameworkAssembly assemblyName="System.Runtime" targetFramework="net45" />
</frameworkAssemblies>
</metadata>
...
</package>
That frameworkAssembly
reference then ends up in the project.lock.json
in the client project, and all is well.
However, judging by David's other comment ("We're going to look at fixing this") I may not need to do anything anyway...
It seems to me that your problem exist only because you've chosen Console application instead of "ASP.NET Web Application" / "ASP.NET 5 Templates" / "Empty". I made simple test usage Empty template, have added "Google.Protobuf": "3.0.0-alpha4"
from NuGet and finally just modified Startup.cs
so that it uses Google.Protobuf.WellKnownTypes
:
- added
using Google.Protobuf.WellKnownTypes;
- added
var duration = new Duration { Seconds = 100, Nanos = 5555 };
inside of Configure
- modified
await context.Response.WriteAsync("Hallo World!");
to await context.Response.WriteAsync(duration.ToString());
The final code of 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);
}
}
The resulting ASP.NET 5 application displayed successfully 100.5555s
in the web browser.
You can download the demo project from here.
UPDATED: I analysed the problem with pure console DNX application, which uses the code and could found the the reason of the problem in duration.ToString()
method, which works in ASP.NET environment, but not in pure Console application. The reason of the problem is interesting and I'm trying to investigate, but I wanted to share my current results with other
I could make working the following code:
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);
}
}
}
One can download working project from here.
I commented additionally the line
//[assembly: Guid("b31eb124-49f7-40bd-b39f-38db8f45def3")]
in AssemblyInfo.cs
to have no unneeded reference to "Microsoft.CSharp"
, which have a lot of other references. The project.json
contains in the demo project:
{
...
"dependencies": {
"Google.Protobuf": "3.0.0-alpha4"
},
"frameworks": {
"dnx451": { },
"dnxcore50": {
"dependencies": {
"System.Console": "4.0.0-beta-23516"
}
}
}
}
By the way including "System.Console": "4.0.0-beta-23516"
in "dnxcore50"
part of "frameworks"
is required because Console
namespace (for Console.WriteLine
) exist in mscorlib
of DNX 4.5.1
. If one would try to add "System.Console": "4.0.0-beta-23516"
on the level of common dependencies one get the error with starts with the text
Error CS0433 The type 'Console' exists in both 'System.Console,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' and
'mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089' ConsoleApp3.DNX 4.5.1
UPDATED 2: One can replace the line
Console.WriteLine("{0}.{1:D4}s", duration.Seconds, duration.Nanos);
to
Console.WriteLine((object)duration);
to make it working. Just the usage of Console.WriteLine(duration);
or var str = duration.ToString();
produces the error, which you described.
UPDATED 3: I verified that the code duration.ToString()
calls the lines which use the lines for formatting. It seems that the code duration.ToString()
does really the same as ((object)duration).ToString()
for WellKnownTypes
types (like Duration
).
The latest remark which I find important. The described problem exist only for dnx451 (or dnx452 or dnx46). If one would remove the lines
"dnx451": { },
from "frameworks"
part of project.json
then the program will be compiles for DNX Core 5.0 only ("dnxcore50"
). One can easy verify that one will don't have any problem more.
UPDATED 4: Finally I've found very simple workaround to your problem: one needs just add "Microsoft.AspNet.Hosting": "1.0.0-rc1-final"
dependency to the project:
{
"dependencies": {
"Google.Protobuf": "3.0.0-alpha4",
"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"
}
}
It follows to loading many unneeded dlls, but now the dependencies will be do correctly resolved.
The final project can be compiled without any problems for both dnx451 and dnxcore50. I interpret the results as following: "Google.Protobuf" do work with both dnx451 and dnxcore50, but the automatic dependency resolving of RC1 is still buggy and it can't correctly resolves some required dependencies of "Google.Protobuf".
Of cause adding directly unneeded "Microsoft.AspNet.Hosting": "1.0.0-rc1-final"
reference can be seen as a workaround only. I think that dependency resolution used in ASP.NET 5 and DNX is still buggy. I posted some time before the issue, which is still opened. The issue provides an example when the resolving of direct included dependency can provide another results as dependencies resolved by dnu restore
. It was the reason why I started to compare the dependency of working code, which I posted you initially with the dependencies of not-working project. After some tests I found the workaround and reduced it to the only dependency: "Microsoft.AspNet.Hosting": "1.0.0-rc1-final"
.