dotnet / standard

This repo is building the .NET Standard
3.06k stars 427 forks source link

Can't use System.Configuration.Configuration manager in a .NET Standard2.0 library on .NET FX4.6 #506

Closed kiran-bobade closed 5 years ago

kiran-bobade commented 7 years ago

I have an assembly created in NetStandard2.0. It reads AppSettings using System.Configuration.ConfigurationManager. I have installed nuget package of System.Configuration.ConfigurationManager with version 4.4.X which is suitable for NetStandard2.0.

When I refer this assembly in console app (.Net Core) it is reading AppSettings properly, but when I refer this assembly in old .NetFramework(4.6.X) console app it is not working and throwing an exception.

Please see the code below.

Assembly 1: NetStandard 2.0

Nuget: System.Configuration.ConfigurationManager 4.4.0

 using System.Configuration;

 namespace Bootstrapper.Lib
 {
     public class Bootstrapper
     {           
         public void LoadAppSettings()
         {
               string serachPattern = ConfigurationManager.AppSettings["AssemblySearchPattern"];
         }
    }
}

Console App: NetFx 4.6.X

using System;
using Bootstrapper.Lib;
namespace Bootstrapper.Console
{
  class Program
  {
    static void Main(string[] args)
    {
        new Bootstrapper().LoadAppSettings();
    }
  }
}

Exception After Run:

'Could not load file or assembly 'System.Configuration.ConfigurationManager, 
 Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one 
 of its dependencies. The system cannot find the file specified.'

It will work with Console App developed using .NetCore.

Here is the other link I followed. #425

Please help!!!

urashid99 commented 7 years ago

@kiran-bobade, I see similar behavior when referencing a .Net Standard 2.0 project (with the ConfigurationManager package) from a Xamarin project (see below).

Could not load file or assembly 'System.Configuration.ConfigurationManager' or one of its dependencies

However, if I add the ConfigurationManager package in Xamarin project, and move the code into the Xamarin project, it works fine. Ideally I'd like to keep the ConfigurationManager code out of the Xamarin project.

edumartins7 commented 7 years ago

Same here. In my case, I created an ASP.NET Core 2 (.NET Core2) app and added an reference to an .netstandard2.0.2 class library, which then references EF6 through nuget. Then in startup.cs I tried to instantiate an DbContext by passing an IConfiguration as argument to its constructor. When the runtime callsbase(config.GetConnectionString("connsString")) this exception is throw.

joperezr commented 7 years ago

mm that is weird, ConfigurationManager in inbox on pretty much all platforms, so this doesn't make that much sense. What version of VS are you using to build? I ask because if you don't have VS15.3 or above then errors like this are expected since netstandard on netfx support was added then. If you do have a newer version of VS, can you try setting this property on your netfx .csproj:

<PropertyGroup>
  <_HasReferenceToSystemRuntime>true</_HasReferenceToSystemRuntime>
</PropertyGroup>
eric-swann-q2 commented 7 years ago

I'm experiencing this exact issue with System.Configuration. Any news for participants on how this was resolved?

joperezr commented 7 years ago

Which version of VS are you using? I would expect that if you are using a recent one (VS 15.3 or higher) this scenario should just work.

eric-swann-q2 commented 7 years ago

@joperezr : VS 2017 (15.4.4). It was the build coming off of our build server that was problematic, which doesn't use VS but has the latest SDK (2.0.3). It appeared to resolve when I explicitly added a reference to the System.Configuration.ConfigurationManager package to the top level (Net462) project. When it was indirectly referenced from the dependent NetStandard2.0 package, it was not resolving properly.

joperezr commented 7 years ago

I see, I would have expected that you wouldn't need to add a reference to that package directly from your net462 project but I'm glad things worked out. Just as FYI, we are currently working on building a metapackage that will add a lot of compatibility between .NETStandard and .NET Framework, and this one includes assemblies like System.Configuration.ConfigurationManager.

eric-swann-q2 commented 7 years ago

@joperezr Agreed, not sure why that's the behavior I'm observing, but I can live with the extra package reference. Thanks for the additional info.

aspnerd commented 6 years ago

I'm having this same issue in vs 15.5.4 .net framework using a .net standard 2.0 both trying to use system.configuration.configurationmanager.

loop-evgeny commented 6 years ago

Also running into this with many (but, weirdly, not all!) .NET Framework 4.6.2 assemblies referencing a .NET Standard 2.0 class library that uses System.Configuration.ConfigurationManager 4.4.1. The workaround of adding a reference to the top-level assembly (EXE or NUnit test class library) seems to work.

Craigfis commented 6 years ago

I'm hitting this issue using VS 15.6.7.

joperezr commented 6 years ago

The package that I mentioned above is Microsoft.Windows.Compatibility in case anybody hitting an issue wants to try it out. It is still in preview but might be a good workaround for folks hitting issues.

nzandy commented 6 years ago

@joperezr Can you please confirm what the correct fix is for this issue?

I don't want to add a reference to System.Configuration.Configuration manager from my .NET 4.7.2 project to prevent runtime error. My main concern with this is that someone in the future may see this reference as "unused" and remove it, the project will still build but will lead to runtime errors.

Can you explain how the above package (Compatability) can be used as a proper solution?

Also in this situation - I am converting a class library to .NET standard for an enterprise solution - we may have 50+ projects that consume this - so potentially I need to add this reference in 50 places?

joperezr commented 6 years ago

@nzandy

Can you explain how the above package (Compatability) can be used as a proper solution?

.NET Frameworks run on different platforms (for example .NET Framewrok 4.7.2 or .NET Core 2.1) and each platform defines types in certain assemblies. Some of the types will live on one assembly on one platform, but on a different one on another platform. One example is ConfigurationManager class, which lives in System.Configuration.ConfigurationManager on .NET Core, and in System.Configuration on .NET Framework. In order for types to unify when running a .NET Core app that consumes .NET Framework libraries you need some re-mappers that will make sure that all of these types unify to the same assembly. That is what the Compatibility package provides. It ensures that all of the types that live on different assemblies for different frameworks will unify to the same assembly at runtime. On this Compatibility package, System.Configuration.ConfigurationManager will also be includded.

I am converting a class library to .NET standard for an enterprise solution - we may have 50+ projects that consume this - so potentially I need to add this reference in 50 places?

If you are using the PackageReference model in your project, MSBuild should automatically add these indirect dependencies for you. For example: if your library A.dll depends on the compatibility package, and your project B has a Project To Project reference to project A, then B will automatically get a reference to the compatibility package.

I hope this answers your questions but do let me know if you need any help.

AlexDenton commented 6 years ago

@joperezr So I have a Dotnet Standard 2.0 project that depends on a Dotnet Framework 4.6.2 project. I have a dependency on System.Configuration.ConfigurationManager and I'm trying to use the ConnectionStringSettings type but I get this error:

error CS0012: The type 'ConnectionStringSettings' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.

I tried adding the Microsoft.Windows.Compatibility library to the Dotnet Standard project but I still get the same error.

Is this a supported use case?

It seems if I convert the Dotnet Framework 4.6.2 project to Dotnet Standard the error is fixed but that isn't feasible for me at the moment.

joperezr commented 6 years ago

@AlexDenton who depends on the package System.Configuration.ConfigurationManager? your .NetStandard lib or your .NET Framewrok one? From what I can see, it looks like the problem should be happening on your .NET Framework one, and seems like what you need is to add a reference to that project like:

<ItemGroup>
  <Reference Include="System.Configuraiton" />
</ItemGroup>

Since that is the assembly where ConnectionStringSettings lives for .NET Framework.

Is this a supported use case?

Technically no. What we support officially is for a .NET Framework project to depend on a .NET Standard one, but not the other way around. The reason is that .NET Standard should run on different platforms, while .NET Framework is only supported on Windows. So what you are doing is writing a .NET Standard lib (which should run everywhere where .NET Standard is supported) depending on a .NET Framework lib (which will only run where .NET Framework runs). The reason why we 'allow' these type of dependencies is because a lot of what lives in .NET Framework 4.6.2 is already part of .NET Standard, so we assume that you will only call those APIs and that your app should just work. On the other hand, .NET Standard runs everywhere that .NET Framework runs which is why having if you had your dependency go the other way it would just work.

AlexDenton commented 6 years ago

@joperezr

who depends on the package System.Configuration.ConfigurationManager?

They both do. I have created a sample repo to demonstrate the issue: https://github.com/AlexDenton/DependencyProblem

Technically no. What we support officially is for a .NET Framework project to depend on a .NET Standard one, but not the other way around. The reason is that .NET Standard should run on different platforms, while .NET Framework is only supported on Windows. So what you are doing is writing a .NET Standard lib (which should run everywhere where .NET Standard is supported) depending on a .NET Framework lib (which will only run where .NET Framework runs). The reason why we 'allow' these type of dependencies is because a lot of what lives in .NET Framework 4.6.2 is already part of .NET Standard, so we assume that you will only call those APIs and that your app should just work. On the other hand, .NET Standard runs everywhere that .NET Framework runs which is why having if you had your dependency go the other way it would just work.

That all makes sense. I definitely understood I was getting into murky waters by going down this path but I'm trying to do a gradual migration and this seemed like the easier path for me right now. That being said, the specific library I'm trying to reference should work for both ways (System.Configuration).

Having dug in a bit more on my own I think I kind of understand the issue. It seems the .NET Framework System.Configuration.ConfigurationManager library depends on System.Configuration 4.0.0.0 while the .NET Standard System.Configuration.ConfigurationManager library depends on System.Configuration 4.0.1.0. And then the interesting thing is according to this (https://apisof.net/catalog/System.Configuration) those two libraries have two totally different `PublicKeyToken's. I could be wrong (I'm diving deeper than I've had to before) but that seems like it's probably the issue.

I'm not clear on if this is something the Microsoft.Windows.Compatibility package is supposed to solve or if this is just an irreconcilable problem. Perhaps the repo I linked will explain better what I'm trying to do.

Oh, and thanks for your help! I'm eager to get migrated but I'm trying to do it one chunk at a time. Figuring this out would make that much easier.

Cheers!

AlexDenton commented 6 years ago

@joperezr So having slept on it I think I have a clearer picture of what's happening. When I install System.Configuration.ConfigurationManger on my 4.6.2 project it automatically references the System.Configuration 4.0.0 assembly that's installed on the machine. But when I install it on my Dotnet Standard 2.0 project it doesn't pull in that assembly and seems to reference this: https://github.com/dotnet/corefx/blob/master/src/System.Configuration.ConfigurationManager/ref/System.Configuration.cs.

Because those two assemblies have different PublicKeyTokens it's not compatible. However, I can't remove the System.Configuration reference in the 4.6.2 project because then I can't use ConnectionStringSettings. So I appear to be stuck.

Again, this may not be something you guys care to support but hopefully I at least articulated what I'm experiencing more clearly.

Thanks again!

joperezr commented 6 years ago

The assembly versions have nothing to do with the problem you are seeing. The real problem here is the following: Your framework project thinks that the type ConnectionStringSettings lives on System.Configuration.dll so it depends on that. For your StandardProject on the other hand, this type lives on System.Configuration.ConfigurationManager.dll. In order for both projects to agree that this type is the same, we need to pass a special System.Configuration.dll to our StandardProject that will redirect this type back to System.ConfigurationManager.dll. That way, when the compiler is resolving ConnectionStringsSettings for both the standard project and the framework project it uses this logic: FrameworkProject.dll -> System.Configuration.dll -> System.Configuration.ConfigurationManager.dll and StandardProject.dll -> System.Configuration.ConfigurationManager.dll so they agree on the same type and the compiler is happy. Given that this is really a scenario that we don't support (referencing a framework project from a standard project) then we don't really have that System.Configuration.dll shim that would redirect the type to S.C.ConfigurationManager.dll but we do have the shim if instead of targeting netstandard2.0 you target netcoreapp2.0. This shim (or remapper) can be obtained by referencing the Microsoft.Windows.Compatibility package.

I took the liberty of adding these two changes into a fork I made from your repo so you can take a look at those changes here: https://github.com/joperezr/DependencyProblem/commit/c2c3670ede12428312bd291cd80f4eacda6d21c8

I know that this might be a bunch of info, but I hope that it all makes sense. If it doesn't please don't hesitate to ask questions. FWIW, in order for you to continue your progress of moving to .NET Standard, I would start with the leaf projects ( the ones that don't have other project dependencies) that way you will always be in supported land (since as I said before, referencing a .NET Standard library form a .NET Framework library is totally supported) so going the other way would definitely cause you less troubles.

AlexDenton commented 6 years ago

Given that this is really a scenario that we don't support (referencing a framework project from a standard project) then we don't really have that System.Configuration.dll shim that would redirect the type to S.C.ConfigurationManager.dll but we do have the shim if instead of targeting netstandard2.0 you target netcoreapp2.0

I see! I hadn't considered trying the compatibility library with .NET Core. I assumed it worked the same for either but that's just my ignorance.

I know that this might be a bunch of info, but I hope that it all makes sense

I follow you at least at a high level. My curiosity has me wondering what Microsoft.Windows.Compatibility is actually doing. I looked briefly at https://github.com/dotnet/corefx/tree/master/pkg/Microsoft.Windows.Compatibility but I didn't spend enough time to digest it.

FWIW, in order for you to continue your progress of moving to .NET Standard, I would start with the leaf projects

Yeah, I've been trying to do as you suggest. I was hoping to deploy one of my microservices independently on .NET Core but eventually ran into a shared dependency that started this whole problem. Unfortunately that project gets used by all of my microservices so I'll have to find another path.

Anyway. That's probably oversharing but at least I have a potential option to help me move forward.

Once again, thanks for your help!

joperezr commented 6 years ago

I follow you at least at a high level. My curiosity has me wondering what Microsoft.Windows.Compatibility is actually doing. I looked briefly at https://github.com/dotnet/corefx/tree/master/pkg/Microsoft.Windows.Compatibility but I didn't spend enough time to digest it.

Glad to know it made some sense and that you know the path forward. As to for what the compatibility package does, among other things, it will restore package: Microsoft.Windows.Compatibility.Shims, which if you download from here and inspect its contents, you'll find that inside the folder of lib\netcoreapp2.0 there is a System.Configuration.dll that will now be referenced by your project which will take care of the remapping of ConnectionStringsSettings to System.Configuration.ConfigurationManager.dll. That is what makes the build with my changes to all start working and compiling now.

jzabroski commented 6 years ago

Glad to know it made some sense and that you know the path forward.

I'm sorry, but I don't understand why targeting .NET Core 2.0 is the "path forward". I thought the purpose of .NET Standard was to provide the standardization, and .NET Core is only for new hotness. Now it's really confusing to me that .NET Core 2.0 is the best way to target net461 dependencies. Can you please rephrase? ...because my brain has yet to accept/wrap my head around this mess.

makklawrence commented 6 years ago

I've been having all sorts of Dependency issue, this one System.Configuration was a big one. Honestly whoever suggested Microsoft.Windows.Compatability saved me a heap of time and time in the future. It's excellent. Thanks!

PhilipDaniels commented 6 years ago

I just ran into this. I have

When I ran WebSiteB I was getting the cannot load System.Configuration exception detailed above. I added Microsoft.Windows.Compatibility to WebsiteB and the problem went away (at the cost of a lot of new packages and dlls being added to the project). Is this the right thing to do? I am confused because the docs for that package say it is only for .Net Core apps: https://www.nuget.org/packages/Microsoft.Windows.Compatibility

Or should I be adding Microsoft.Windows.Compatibility to the LibraryA?

Would really like some guidance on what we are supposed to do here, because LibraryA is used in about 100 other projects, most of which are still on Net Framework 4.6.2. I'm trying to port it to Net Standard in order to move forward.

UPDATE: I think I answered my own question, it doesn't work if you just add it to the LibraryA.

Still find the docs confusing though.

johnybaryah commented 5 years ago

@PhilipDaniels I had the same exact scenario as you. .Net Framework 4.5 Class library that deals with databases - which is referenced into a .net Core project... I still don't understand why but I had to install https://www.nuget.org/packages/System.Configuration.ConfigurationManager/ in both projects to make it work.

ryno1234 commented 5 years ago

@joperezr, I'm still not seeing the solution here. Perhaps I've just missed it since there have been multiple conversation threads here with multiple different scenarios.

I have the following setup: Console App (.Net Framework 4.6.1) -> Library (.Net Standard) -> System.Configuration.ConfigurationManager.dll

In this case, the Console App is not getting my System.Configuration.ConfigurationManager.dll copied to the bin and I'm getting the same exception as in the original post:

Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.

Adding a reference to System.Configuration.ConfigurationManager.dll to the main console app is not a realistic solution for us and seems like a workaround at best.

nzandy commented 5 years ago

@ryno1234

Are you able to convert your .NET 4.6.1 Console app to use instead of packages.config?

Following the advice here https://docs.microsoft.com/en-us/nuget/reference/migrate-packages-config-to-package-reference

You can right click your packages.config and click "Migrate packages.config to PackageReference"

I think this would solve your issue, note that there are specific projects that won't be unable to be converted to this format (I can find some more info on this if you require, but that migration tool above should alert you if this is the case for you)

nzandy commented 5 years ago

For reference, here is the specific issue you are running into (with a very good explanation):

https://github.com/dotnet/standard/issues/481

y0l0Git commented 5 years ago

Had same issue.

Had a class library project built in dotnetstandard 2.0 and a Windows Service Application built in dotnet framework 4.6.1. Got same error:

Could not load file or assembly 'System.Configuration.ConfigurationManager, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' or one of its dependencies. The system cannot find the file specified.

Added library: Microsoft.Windows.Compatibility from nugget. Now I have access to DotNetFramework API's from DotNetStandard 2.0. Hope this helps someone.

ericstj commented 5 years ago

Just to be clear here: System.Configuration.ConfigurationManager is not part of .NETStandard2.0. It's a package you're referencing. That package has to provide different implementations since in some cases it's providing an implementation and in other cases it's forwarding to existing desktop types. It needs to be referenced in applications consuming it, especially if those applications have a different target framework than the library using packages (eg: net4x vs netstandard) so that it can provide the correct implementation for your application.

NuGet 2 / packages.config did not flow packages across ProjectReferences: one of the big reasons NuGet3 was created. That is the cause of the FileNotFoundException: you will see that for any package you use from a .NETStandard library project. The folks who designed the .NETStandard SDK (/cc @livarcocc @nguerrera @dsplaisted) were very careful about not copying the netstandard implementation DLLs to the output directory so that you get a FileNotFoundException here, rather than bad runtime behavior due to choosing the wrong implementation. (see https://github.com/dotnet/corefx/issues/21197 where folks disable that behavior)

The solution is to reference the package in one of many ways:

  1. Explicitly reference the package in the application
  2. Modify the project to use PackageReference instead of packages.config (and ensure your packagereferences are not marked with PrivateAssets)
  3. Use a package reference instead of a project reference to the netstandard library (since package references carry dependency information).

I'd really love it if PackageReference was the default. @rrelyea and @rido-min and others working on NuGet have reasons we can't do that. As a result we're in this place where we have this common pitfall.

jzabroski commented 5 years ago

@ericstj Thanks for the thoughtful reply. Would I be tongue-in-cheek if I suggested that in order for Rob and Ricardo to consider their work accomplished, they handle scenarios like the ones FluentMigrator deals with when exposing its library logic as an MSBuild task? :) A PR would be even nicer...

ericstj commented 5 years ago

Here are a couple issues. Also adding @jainaashish. https://github.com/NuGet/Home/issues/4942 https://github.com/NuGet/Home/issues/5877

I'm honestly not sure where NuGet stands on changing the default from packages.config to PackageReference. I suspect there will always be some old projects that don't migrate and folks want to work with new projects/packages. I wonder if we got NuGet, SDK, or MSBuild to identify a problematic case and raise an error if it would help. If you had an MSBuild error that could be suppressed that told you that you had a projectreference to some project using PackageReference but you were not, do you think that would help with this issue?

I think MSBuild tasks as an app model need some love as well, which is part of what we're discussing https://github.com/Microsoft/msbuild/issues/1309. Let's try to keep that discussion separte, I think it has some unique problems since it adds the host/plugin problem space.

timdoke commented 5 years ago

For what it's worth, I think you should put in a packages.config to PackageReference converter for web application projects (that have a global asax) before you default anything like that.

jzabroski commented 5 years ago

@timdoke That's not a bad suggestion, but it would probably belong as part of https://github.com/RSuter/DNT#solution-commands - I realize that isn't a Microsoft-sponsored project, and Visual Studio tooling is probably a must for most, but if you're a library author on .NET SDK projects, @RSuter 's DNT extensions are a MUST.

timdoke commented 5 years ago

@jzabroski Nice.. If that works to convert to PackageReference from packages.config for web application projects, that would be great. It does say it can perform that operation against all csproj files.. I might have to test it out.

RicoSuter commented 5 years ago

@jzabroski thanks for mentioning dnt! New commands are always welcome! 🙂

But maybe you should checkout this converter which migrates to new sdk-style projects - it looks promising: https://github.com/hvanbakel/CsprojToVs2017

If its only about packages.config to PackageReferences then it would be quite easy to do in dnt - probably everything needed is already there somewhere...

jzabroski commented 5 years ago

I agree, @RSuter 's switch-assemblies-to-projects command is not quite what you're looking for but it's close. I think I've seen one that converts CsprojToVs2017 besides that one, too, though, but can't remember where I saw it.

EDIT: Found it. It's the same one. @natemcmaster has it in dotnet-tools repo: dotnet-migrate-2017

AdamJachocki commented 5 years ago

@joperezr : VS 2017 (15.4.4). It was the build coming off of our build server that was problematic, which doesn't use VS but has the latest SDK (2.0.3). It appeared to resolve when I explicitly added a reference to the System.Configuration.ConfigurationManager package to the top level (Net462) project. When it was indirectly referenced from the dependent NetStandard2.0 package, it was not resolving properly.

I was struggling with this whole f*** day. You saved my life!

wtgodbe commented 5 years ago

Closing as the discussion seems to be resolved, and we have pre-existing tracking for the underlying issue (see https://github.com/dotnet/standard/issues/936#issuecomment-432548325)

SashaPshenychniy commented 5 years ago

Just one more idea if anyone isn't satisfied with Microsoft.Compatibility.Windows Nuget (it actually worked for me but I felt risky to commit so many new Nuget packages, especially taking into account package description didn't quite match my scenario, so I was not sure why it was working in fact).

I managed to make things work by making project, which uses ConfigurationManager, a multi-target framework project. So when my class library is used from .NET Framework project, it is compiled using native framework System.Configuration.dll, and when it is referenced by .NET Standard or Core-targeted project - it is compiled using Nuget System.Configuration.ConfigurationManager.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net471</TargetFrameworks>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
    <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.5.0" />
  </ItemGroup>
  <ItemGroup Condition=" '$(TargetFramework)' == 'net471'">
    <Reference Include="System.Configuration" />
  </ItemGroup>

</Project>
synek317 commented 3 years ago

Today I've found another workaround, that works for me, but I have no idea why. At the beginning of my application (in my case - in the [OneTimeSetUp] of NUnit tests), I do

System.Reflection.Assembly.Load("System.Configuration.ConfigurationManager");