56

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:

  • Changing the Console.WriteLine call to just Console.WriteLine("foo") (but no other changes)
  • Changing the type of the duration variable to object instead of Duration
  • Removing all hint of Protocol Buffers entirely, and instead using TimeSpan or similar
  • Adding the following to project.json in the dnx451 section:

    "frameworkAssemblies": {
      "System.Runtime": ""
    }
    

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 :(

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I hit a very similar error for a simple class library. Found two way of altering the project.json file to fix the issue - http://nodogmablog.bryanhogan.net/2016/01/the-type-is-defined-in-an-assembly-that-is-not-referenced-system-runtime/ – Bryan Jan 20 '16 at 15:16

3 Answers3

23

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...

Alex Wiese
  • 8,142
  • 6
  • 42
  • 71
davidfowl
  • 37,120
  • 7
  • 93
  • 103
  • So just to clarify, is the solution to add a `frameworkAssembly` reference in the Google.Protobuf package, tied to a specific target platform? – Jon Skeet Dec 07 '15 at 17:29
  • @JonSkeet you can add it like `"frameworkAssemblies": { "System.Runtime": { "type": "build", "version": "" } }` so it doesn't get into the final nuspec file, which should be the cleaner solution. – Axel Heer Dec 07 '15 at 19:01
  • @AxelHeer: I've only used `"type": "build"` for including source before now... what does it mean for framework assemblies? And are you suggesting putting that in the Protobuf nuspec file, or in my console app's project.json? – Jon Skeet Dec 07 '15 at 19:20
  • @JonSkeet adding facades like `System.Runtime` as framework assemblies in the project.json leads to entries in the nuspec file, if `dnu pack` is used to generate a nuget package. Using `"type": "build"` leads to a successful build, since it gets included for compilation, but the nuget package nuspec doesn't reference it -- the facade only contains type forwards anyway. Without any plans for packaging you can ignore my comment... – Axel Heer Dec 07 '15 at 19:31
  • We're going to look at fixing this. The packages should be enough to work in either scenario. – davidfowl Dec 09 '15 at 09:25
  • I've added an answer showing what I think is the right way to handle this - could you validate that it's appropriate? I'd hate to propagate a workaround that makes things worse somehow. – Jon Skeet Dec 09 '15 at 10:22
  • (Aargh, hadn't seen your previous comment, for some reason.) – Jon Skeet Dec 09 '15 at 11:37
  • Confused about this "dependencies for other nuget dependencies." In my project.json for a web app I see - "dependencies": { "Microsoft.AspNet.Mvc": "6.0.0-rc1-final", "Microsoft.CSharp": "4.0.0", – Bryan Jan 20 '16 at 15:23
9

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...

Community
  • 1
  • 1
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • The information seems to be interesting for developers of NuGet packages. It would be probably good to add more tags (nuget, nuspec etc) to your question to make the information better indexed and be found. – Oleg Dec 09 '15 at 11:30
  • @Oleg: Seems reasonable. Done. – Jon Skeet Dec 09 '15 at 11:36
5

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".

Oleg
  • 220,925
  • 34
  • 403
  • 798
  • But I want the library to work from within console apps as well as web apps. The point is that it's meant to be portable... when I said "ASP.NET v5" I meant that in all the ways... ASP.NET seems to mean more than web apps these days... will clarify the question. – Jon Skeet Dec 07 '15 at 15:25
  • You wrote about "ASP.NET v5". Do you want to test it with DNX Core 5.0? If you do need "ASP.NET v5" application and you don't want to run in inside of console that you should change the start of application. Project.json have `"Microsoft.AspNet.Server.Kestrel"` as dependency and `web` as `command`. It allows to start the application as self hosting in console application. One have to use still the same ASP.NET application as before. One will see the results not on the console, but in wed browser instead (on the client side). – Oleg Dec 07 '15 at 15:31
  • I want to test it with various runtimes, using the new runtime environments built by the ASP.NET v5 team. dnxcore50 already works, which is why it isn't in my question. I'm not interested in a web app at the moment - I'm trying to get a console app working with a dnx451 target, as per project.json. – Jon Skeet Dec 07 '15 at 15:35
  • You can remove `"dnx451": { },` from `"frameworks"` part of the `project.json`. The application will then work using `dnxcore50` only. You can verify that the program works too. Thus your code `var duration = new Duration { Seconds = 100, Nanos = 5555 };` and `duration.ToString()` do work successfully with DNX Core 5.0. Is it what you wanted to validate? – Oleg Dec 07 '15 at 15:40
  • I've already said (in the question) that it works with dnxcore. I want to get it to work with dnx451, with a console application. The point is that I may well have users who wish to use it in that environment, so I need to get that to work, and I wish to understand the dependency resolution in more detail. I've added that to the question - changing to use dnxcore or a web app don't help me solve this specific scenario. – Jon Skeet Dec 07 '15 at 15:41
  • @JonSkeet: Probably there are misunderstanding because you wrote about ASP.NET 5. – Oleg Dec 07 '15 at 15:45
  • 1
    @Oleg ASP.NET 5 runs console applications as well :) – khellang Dec 07 '15 at 15:45
  • That's why I've clarified the question. But yes, "ASP.NET 5" is often used to mean the whole environment... which is why when you do File/New Project you can end up with "Console Application (Package)" under "Web", and why http://docs.asp.net/en/latest/dnx/console.html is console application documentation within the ASP.NET documentation... – Jon Skeet Dec 07 '15 at 15:51
  • @JonSkeet: Sorry, I have to go away for about an hour. If your problem will be not solve till the time I will try to help you. I still not full understand your goals. I thought that your main goal is testing `Google.Protobuf` under ASP.NET 5 and different versions of dnx. What I suggested you can be really use to verify it and for testing too. The form of the test application (the usage of console application) is interesting too, but it goes away for the main goal (validation whether `Google.Protobuf` is correctly works under ASP.NET 5). Sorry, but I have to go now. Till later... – Oleg Dec 07 '15 at 15:59
  • The point is that "under ASP.NET 5" doesn't necessarily mean "in a web app" given that ASP.NET 5 includes console apps. But that's fine. I'd suggest you might want to delete this answer for now, and maybe edit and undelete later. – Jon Skeet Dec 07 '15 at 16:08
  • @JonSkeet: I still think that ASP.NET 5 is only web application and you mean DNX. In any way I'm back now and I can reproduce the problem with console application. I'll analyse it and post you more information. I have to remark that I'm new in ASP.NET 5 and it's not what I do the whole time. Nevertheless I could not see any other answer on you question and tried to help you. I'll post you new information soon... – Oleg Dec 07 '15 at 17:15
  • @Oleg: You may want to hold off on that - I believe David Fowler is investigating and/or writing up an answer write now. – Jon Skeet Dec 07 '15 at 17:19
  • @JonSkeet: I have some progress in making it working in console application. The problem seems in `duration.ToString()`. I can display `Console.WriteLine(duration.Seconds);` for example. If you want I can post more details. – Oleg Dec 07 '15 at 17:36
  • @JonSkeet. I posted **UPDATED** part to my answer with additional details. – Oleg Dec 07 '15 at 17:57
  • @JonSkeet: I found very simple workaround: one need just add `"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"` to dependencies of the project additionally to `"Google.Protobuf": "3.0.0-alpha4"` (even for console application). See **UPDATED 4** part of my answer and [the demo project](http://www.ok-soft-gmbh.com/ForStackOverflow/ConsoleApp3.zip) which is very simple and can be compiled successfully. – Oleg Dec 07 '15 at 21:43
  • @Oleg: Thanks, although I suspect that's actually just pulling in System.Runtime from elsewhere... And I wouldn't want to impose that dependency on clients unless there's really no other option. – Jon Skeet Dec 07 '15 at 21:44
  • @JonSkeet: You are welcome! I wrote in the last part of my answer, that adding of `"Microsoft.AspNet.Hosting": "1.0.0-rc1-final"` is **only workaround**, which shows for me that the reason of the problem, which you described, is **not in the code of `Google.Protobuf`, but in the DNX module, which resolves the dependency automatically**. Automatically resolving of dependencies of `Google.Protobuf` works incorrectly. You should post new issue to [dnx on GitHub](https://github.com/aspnet/dnx/issues). – Oleg Dec 07 '15 at 21:49
  • @Oleg: Right (sorry, was only picking that up on mobile). Will investigate what the project.json looks like. It sounds like the problem is reasonably well understood, just not easily fixed, based on David's answer. WIll have to explore a few other avenues. – Jon Skeet Dec 07 '15 at 22:54
  • @JonSkeet: Please inform me about the final solution. In any way DNX can't resolve automatically all dependencies of `Google.Protobuf`, but explicit including of some dependencies solves the problem. You know better `Google.Protobuf` and knows it's dependencies. In any way I repeat that if adding of "Microsoft.AspNet.Hosting": "1.0.0-rc1-final" as direct dependency solves the issue then either you have to add some additional metadata to `Google.Protobuf` or DNX should fix the bug. **The binaries of `Google.Protobuf` works correctly**, one need just inject required dependent dlls. – Oleg Dec 07 '15 at 23:06
  • Just tried your workaround, and looking at project.lock.json, it basically works because it adds `"frameworkAssemblies": "System.Runtime"` into the file. So I probably just need to work out how to change the nuspec file for protobuf to do the same... – Jon Skeet Dec 08 '15 at 09:55
  • @JonSkeet: You are right! Changing `"frameworks"` part of `project.json` to `"frameworks": { "dnx451": { "frameworkAssemblies": { "System.Runtime": "4.0.10.0" } }, "dnxcore50": { "dependencies": { "System.Console": "4.0.0-beta-23516" } } }` is better workaround. It would be fine if one could place the information to the nuspec file. – Oleg Dec 08 '15 at 11:05