Showing posts with label .NET Framework. Show all posts
Showing posts with label .NET Framework. Show all posts

Monday, January 13, 2020

Type.GetType Functionality Has Not Changed in .NET Core

In the last article, I talked about how the behavior of Type.GetType() has changed between .NET Framework and .NET Core. This is incorrect.
The behavior of Type.GetType() is the same; it is the runtime behavior that is different.
As I mentioned in that article, "the fastest way to get a right answer is to post a wrong one". And that worked here. An anonymous commenter posted a good description of what is happening (unfortunately they posted anonymously so I'm unable to give them credit where it is due).

Assembly Loading
The confusion comes from the my interpretation of the word "loading" in the documentation.

Here is part of the "Remarks" section that was quoted in the last article (link to doc: https://docs.microsoft.com/en-us/dotnet/api/system.type.gettype?view=netcore-3.1#System_Type_GetType_System_String_):


"You can use the GetType method to obtain a Type object for a type in another assembly if you know it's assembly-qualified name, which can be obtained from AssemblyQualifiedName. GetType causes loading of the assembly specified in typeName." (emphasis mine)
In this context, "loading" refers to loading the assembly into the context, not "loading" the assembly from the file system.

The difference really has to do with how assembly resolution works. I am not an expert in these details, so I won't go into them. The bottom line is that assembly resolution works differently in .NET Core compared to .NET Framework.

In either runtime, Type.GetType() causes the assembly to be loaded into the context. The difference in behavior that we see in the example from the previous article is due to assembly resolution and how assemblies are found on the file system.

I will be looking into the details to see how to make sense out of them.

The Recommended Solution
The "recommended" solution is to create a custom assembly load context so that you can handle this resolution yourself in any way you choose.

This approach is shown in the tutorial mentioned in the previous article: Create a .NET Core application with plugins.

This solution is a bit of overkill for my scenario. I don't really need a "plugin" architecture for this application. I need a dynamic loading solution. There will only be one data reader used by the application; which one is used is determined at runtime.

One reason to use a custom assembly context is to isolate the data reader dependencies from the rest of the application. If the data reader and main application need different versions of the same DLL, then loading everything into the same context will not work.

I will put together another version of this project using the plugin architecture, and we can look at the differences and benefits.

Update Jan 2020: Here is the article that describes using a custom assembly load context: Dynamically Loading Types in .NET Core with a Custom Assembly Load Context.

Frustration
My frustration regarding changes in C# and .NET Core are increasing rather than decreasing.

I am a big fan of .NET Core. I like the simpler approach, the more understandable templates, the cross-platform functionality, the runtime speed, and many other things.

For the most part, when converting applications between .NET Framework and .NET Core, things just work. But when things don't work, it is extremely frustrating to find what went wrong.

Previously I wrote about a change to UseShellExecute that cause an unexpected delay in converting an application. I spent a bit of time simply locating the thing that changed.

In this case, I knew that the assembly loading had changed in .NET Core. I knew that it was possible to load assemblies in a way that makes it easy to unload them. I also knew that it was possible to load multiple versions of the same assembly using these new features. What I did not know was that the default behavior had changed.

In the code-space, Type.GetType() appeared to be the problem. But the real problem was deeper than that. GetType is *technically* working the same. But the overall behavior is different.

I Am Not an Expert
I started this blog in February 2009 with the statement "I am not an expert." That has not changed. I have a bit of programming experience. And I've written lots of applications that make users happy. I also like to think that I'm always looking for ways to improve and expand my knowledge and experience.

My blog, speaking, and videos are all there to share what I've learned -- passing on a technique that helped me understand a subject. Occasionally someone leaves a comment like the following:
"Speaking now in 2020, I can really see how video likes generate more likes. This (and the video before it) are by FAR the best resource for Lambda & LINQ that I have managed [to] find." [Source]
This is from a video on lambda expressions and LINQ that was published in 2015, and it is encouraging that people still find the information useful. That's why I continue.
Unfortunately, over the past couple years, I feel that Microsoft and .NET are leaving the non-experts behind. More and more features and changes require the .NET programmer to become an expert whether they like it or not.
This is discouraging to me since I am on a mission to help the non-expert programmer. That mission is not to create experts; it is to create programmers who can quickly and efficiently make their users happy. You should not need to be an expert to do that.

It's easy to say "just don't use the new features", but that is not always possible. Even if you do not use the new features, you need to understand them. That's because the sample code and tutorials that you find online use these features.

A big reason I've spent so much time trying to make lambda expressions understandable is not because I want people to use lambda expressions (although they are certainly free to). The reason I teach people about lambda expressions is because so many online samples use them. So even if you do not use them in your own code, you need to understand them so that these samples make sense.

With the changes to the frameworks and the languages (in particular Interfaces in C# 8), there is a lot to keep up with. Of the over 50 changes made to the C# language over the last few years, many developers are familiar with 2 or 3 of the more popular ones.

I have spoken with quite a few developers over the past year who do not know what .NET Standard is or why it is important. Some people would say these are bad developers, but I disagree. Many are developers who are focused on getting solutions to users. Some have said "We're not doing cross-platform so we don't care about .NET Core [and .NET Standard]". It's an unfortunately common misconception, and it results from not having enough information to understand the real impact of these things as we move forward to .NET 5.

I'm not advocating that the development environment should be frozen; just that we should take time to understand the impact of the changes that we're making. Like every technology, there are pros and cons. We need to make sure that the benefits are worth it and that we are not leaving a big part of the developer landscape behind.

If we are spending our time retooling and converting applications, when will we have time to actually deliver solutions to our users?

Bringing It Back Around
That was a bit of rant there. It is something that has been weighing on me for a while. And I don't like to point out problems without having a solution.

In this case, I'm going to keep learning, keep moving forward. And when I come across roadblocks, I will find ways around them. And you will be the first to hear about those solutions, even if it is an acknowledged hack or workaround. And when someone points out a better solution, I will pass that along as well.

I'm not ready to give up at this point; hopefully you aren't either.

Monday, January 21, 2019

Weirdness with EF Core 2 (SQLite), .NET Standard, and .NET Framework

I've run into some issues referencing .NET Standard projects from a .NET Framework project -- specifically in regard to Entity Framework Core 2 and SQLite. I encountered this when moving some of my demo code to newer projects.

Here's the error that I get:


This is a missing DLL. I've managed to come up with 2 workarounds -- neither of which I like -- but they get the projects working.

Let's take a look at the project.

This article is technically *not* part of the More DI series of articles. However, the issue is present in the code described in that series. The code is available on GitHub: https://github.com/jeremybytes/di-decorators.

The Projects
The solution consists of 12 projects which are a mix of project types. Here are the full-framework projects:


The PeopleViewer project is a WPF application using .NET Framework 4.7.2.

The test projects are also .NET Framework due to current requirements of NUnit 3.x.

All of the "library" projects are .NET Standard 2.0:


The .NET Standard projects include the Presentation (which includes the view model), the Readers (including the CSV data reader, the SQL data reader, the Service data reader, and all of the decorators), and the Shared projects.

There is one .NET Core project:


This is the WebAPI service that provides data for the Service data reader.

Project References
Project references can get a bit weird when using .NET Standard with .NET Framework. When I was first having issues, I came across Scott Hanselman's article: Referencing .NET Standard Assemblies from both .NET Core and .NET Framework, and this helped. Let's see how.

Here are the dependencies from the "PersonReader.SQL" project (listed in the PersonReader.SQL.csproj file):


We can see the references to Entity Framework Core and SQLite.

The "PeopleViewer" project does not reference either of these NuGet packages directly:


Based on the advice provided in Scott Hanselman's article, I manually edited the "PeopleViewer.csproj" file to add a "RestoreProjectStyle" value of "PackageReference":


This took care of some of my issues: the ServiceReader works. But the SQLReader is still broken.

The Service Data Reader Works
The Service data reader project has a reference to NewtonSoft.Json (which is probably not a surprise):


By using the "PackageReference" setting, the correct version of the Newtonsoft.Json DLLs made it to the output folder of the "PeopleViewer" project.


You can look at the article "Adding Retry with the Decorator Pattern" for more information on running this code.

Be sure to read Scott Hanselman's article for the details on this setting and why it's needed.

The SQL Data Reader is Broken
But even with this setting, the SQL Data Reader is still broken. Let's take a closer look.

First, we'll update the "ComposeObjects" method to use the SQLReader object. This is in the "PeopleViewer" project, App.xaml.cs file -- although the code in the GitHub project is more complex than what we have here since it uses all of the decorators.


This builds a MainWindow that uses a PeopleReaderViewModel that uses a SQLReader.

When we run the application and click the "Refresh People" button, we get the error:


This indicates that we're missing a DLL that's needed for SQLite functionality.

If we check the output folder, we see that we have many Entity Framework DLLs, including one that references SQLite:


But "e_sqlite3.dll" is nowhere to be found.

As a side note, I tried this with the most recent version of EF Core/SQLite (version 2.2.1 at the time of writing) and had the same results.

I managed to work around this in 2 different ways. If there's a better solution, *PLEASE* let me know about it in the comments. I do not like either of these options.

Workaround #1: Include the NuGet Package in the .NET Framework Project
The first workaround I found was to include the Entity Framework Core / SQLite NuGet packages in the "PeopleViewer" project (the WPF application).

This works because the application now has all of the DLLs that it needs:


The "e_sqlite3.dll" is in the "x64" folder (and the "x86" folder as well).

Why I Don't Like This
I don't like this solution because it creates a dependency on a NuGet package that the .NET Framework project (the WPF application) does not explicitly need. The .NET Standard library (the SQL data reader) is the project that needs the package in order to work. This solution seems to mess up the dependency graph.

It can get a bit worse. If the version of the NuGet package referenced by the .NET Framework project is different from the version referenced in the .NET Standard project, there will be runtime errors. (I've run into this with Newtonsoft.Json references.)

Workaround #2: Copying the Needed Files to the Output Folder
The second workaround I came up with is to copy the missing files into the output folder. Since the bulk of the DLLs are making it to the output folder, I figured that just including the "e_sqlite3.dll" that's missing might be the best way to go.

To do this, I grabbed the "x64" and "x86" folders above, and put them into an "AdditionalFiles" folder at the root of the solution.


This folder already exists: it has the text file used by the CSV data reader (People.txt) as well as the SQLite database used by the SQL data reader (People.db). So adding a couple more files doesn't seem like too much of a problem.

The files are copied to the output folder using a post-build event on the "PeopleViewer" project:


This copies the contents of the "AdditionalFiles" folder to the output folder. For more information on Visual Studio build events, refer to "Using Build Events in Visual Studio to Make Life Easier".

The other thing I had to do was explicitly add these files to the Git repository. The .gitignore file doesn't usually include DLLs, so I had to add these two files manually. You can see them in the GitHub repository: https://github.com/jeremybytes/di-decorators/tree/master/AdditionalFiles/x64.

This has the same effect as Workaround #1: the files are added to the output folder:


Why I Don't Like This
The reason I don't like this workaround is the same as above: There's a good chance that the versions will get out of sync in the future. In addition, I don't like the idea of adding executable files to the Git repository.

Frustration
There is a bug somewhere. I'm not sure if it's a bug in the Entity Framework Core packages (specifically the SQLite package). I'm not sure if it's a conflict between the package management schemes in the .NET Standard project vs. the .NET Framework project. And I have to admit that this is a pretty big frustration for me.

The frustration is 2-fold -- first for myself. I've been coding in the .NET framework for close to 15 years. I really would not expect to have these types of difficulties, even with things that are relatively new. But as a developer, I deal with this type of issue all the time.

The bigger frustration is when I think about other developers. Many developers in the .NET space do not have the type of experience that I have. If I am having difficulty, then that means that there are a large number of other developers who will also have difficulty.

This is even a bigger concern since the bulk of my effort is spent helping developers learn more about C# and .NET -- in particular, I work with the "dark matter developers" described by Scott Hanselman . I have a fear that if we make things too complex in the environment, we will have difficulty getting new developers on board.

This is just one example of what is causing me concerns. The other items are a topic for another day.

Wrap Up
If you have any advice on handling this issue, please let me know. I would file a bug report, but as noted, I'm not exactly sure where the bug is.

If you have run into this same issue, understand that I do not like either of these workarounds. They add concerns about versioning. But they do get the code working, and that's always the first step.

Happy Coding!