Scott Hanselman

How do I find which directory my .NET Core console application was started in or is running from?

July 28, 2020 Comment on this post [6] Posted in DotNetCore
Sponsored By

stressedI got a great question emailed to me today. And while I could find the answer and email them back, I also have a limited number of keystrokes. Plus, every question that moves knowledge forward is a gift, and I don't want to ignore a gift. So instead, I've email my new friend a link to this blog!

Here is their question:

The tl;dr question: how do I find which directory the app was started in?

I have a CLI app in .NET Core 3 that is supposed to sit in your apps folder you have in your path, do something when you run it, then exit.

Unfortunately, it needs a single line of configuration (an API key), which store in a text file. I want to keep the app portable (ie. avoid going into other directories), so I want to store the config file right next to the executable.

And since I want it to be small and easily distributed, I set up .NET to merge and prune the EXE on build. (I think I got the idea from your blog btw, thanks! :) It is a simple app that does a single task, so I figure it should be one EXE, not 50 megabytes in 80 files.

And there the problem lies: If the config file is right next to the exe, I need to know where the exe is located. BUT, it seems that when I have the entire app built into a single EXE like this, .NET actually extracts the embedded DLL to some temporary location in my filesystem, then runs it from there. Every method for finding the startup assembly's location I have found, either by googling or exploring with reflection while it runs, only gives me the location in the temp directory, not the one where the app was actually launched from. The app then attempts to load the config file from this temp directory, which obviously fails.

Let's break this problem down:

  • .NET 3.1 LTS (long term support) has a cool feature where you can ship a single EXE with no dependencies. You ship "myapp.exe" and it just works. One file, no install.
    • When you run the EXE in .NET 3.1, the system "unzips" the app and its dependencies into a temp folder.
  • If you have a config file like myapp.exe.config, you'd like the user to keep that file NEXT TO THE EXE. That way you have two files next to each other and it's simple.
    • But how do you find this config file if you got started from a random temp folder?

Here's how things work in .NET Core 3.1:

using System;
using System.Diagnostics;
using System.IO;

namespace testdir
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Launched from {Environment.CurrentDirectory}");
Console.WriteLine($"Physical location {AppDomain.CurrentDomain.BaseDirectory}");
Console.WriteLine($"AppContext.BaseDir {AppContext.BaseDirectory}");
Console.WriteLine($"Runtime Call {Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)}");
}
}
}

And here's the output of this app when started from elsewhere. Look carefully at the paths:

~\Desktop> .\testdir\bin\Debug\netcoreapp3.1\win-x64\publish\testdir.exe
Launched from C:\Users\scott\Desktop
Physical location C:\Users\scott\AppData\Local\Temp\.net\testdir\30gxvy1b.yq3\
AppContext.BaseDir C:\Users\scott\AppData\Local\Temp\.net\testdir\30gxvy1b.yq3\
Runtime Call C:\Users\scott\Desktop\testdir\bin\Debug\netcoreapp3.1\win-x64\publish

You'll note that when on .NET 3.1 if you want to get your "original birth location" you have to do a little runtime dance. Starting with your current process, then digging into the MainModules's filename, then getting that file's Directory. You'll want to catch that at startup as it's not a super cheap call you want to make all the time.

How does it work in .NET Core 5 (> preview 7)? Well, because the assemblies are embedded and loaded from memory in 5, so like any other in-memory assembly, they don't have a location. Any use of Assembly.Location is going to be suspect.

NOTE: There is some work happening on an analyzer that would flag weird usage when you're using Single File publish, so you won't have to remember any of this. Additionally, if you are on .NET 5 and you want to have the .NET 3.1 temp file behavior, you can use a compatibility property called IncludeAllContentInSingleFile.

Here's the same app, running in .NET 5 as a single EXE and unfolding directly into memory (no temp files):

Launched from C:\Users\scott\Desktop
Physical location C:\Users\scott\Desktop\testdir\bin\Debug\net5.0\win-x64\publish\
AppContext.BaseDir C:\Users\scott\Desktop\testdir\bin\Debug\net5.0\win-x64\publish\
Runtime Call C:\Users\scott\Desktop\testdir\bin\Debug\net5.0\win-x64\publish

You'll note here that you get the behavior you expect with each call.

Here's the answer then:

    • You should use AppContext.BaseDirectory on .NET 5 to get the truth
    • You can use the runtime calls (and cache them) with MainModule (the last line in the example) for .NET 3.1.

Hope this helps!


Sponsor: Never miss a bug — send alerts to Slack or email, and find problems before your customers do, using Seq 2020.1.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Official Support for Remote Debugging a .NET Core Linux app in WSL2 from Visual Studio on Windows

July 23, 2020 Comment on this post [7] Posted in DotNetCore | Linux | VS2019
Sponsored By

I've blogged before about Developing on Docker with the new and improved Visual Studio Container Tools (and WSL2) and also Remote Debugging a .NET Core Linux app in WSL2 from Visual Studio on Windows.

It's the second one that I'm talking about today. You can now run .NET Core console and web apps in WSL2 and debug them directly from Visual Studio 2019!

What do you need?

Here's the experience in Visual Studio 2019 when the extension is installed. It "just works" and it makes it super easy to switch between running on Windows (under IIS or the Kestrel web server or under Kestrel under your default Linux distribution.

WSL 2 in the Visual Studio Debugging Menu

Check this out, you can see that .NET Core, from the Linux/WSL 2 perspective, is loaded out of /usr/share/dotnet/shared but my source remains on my /mnt/d drive (my Windows D:) and debugging Just Works.

image

You'll also notice that we are running on https://localhost:5001 and that localhost and ports from the Windows point of view maps to localhost and points (via a local tunnel that's transparent) to WSL 2.

How does the SSL cert work if WSL 2's Linux Kestrel web server is serving it?

You can see that there's a symbolic link between my WSL ~/.aspnet folder and my local profile in Windows so that this app shares SSL certs and that the same cert is served with Kestrel on Windows and Kestrel on Linux.

scott@IRONHEART:~$ cd .aspnet
scott@IRONHEART:~/.aspnet$ ls
DataProtection-Keys https
scott@IRONHEART:~/.aspnet$ cd https/
scott@IRONHEART:~/.aspnet/https$ ls
hanselminutes.core.pfx
scott@IRONHEART:~/.aspnet/https$ ls -alogF
total 12
drwxr-xr-x 2 4096 Jun 23 17:02 ./
drwxr-xr-x 4 4096 Jun 23 17:02 ../
lrwxrwxrwx 1 71 Jun 23 17:02 hanselminutes.core.pfx
->
/mnt/c/Users/scott/AppData/Roaming/ASP.NET/Https/hanselminutes.core.pfx*
scott@IRONHEART:~/.aspnet/https$

I broke that line up with the symbolic link -> along 3 lines so it wouldn't wrap on this blog.

Now you can run and debug .NET Core apps on Windows and Linux using both VS Code and Visual Studio 2019! I'm using Visual Studio 2019's free Community Edition and it works great. This helps me save money as I've moved my Podcast site to Linux in Azure and it makes my local development better match my cloud reality. Give it a try!


Sponsor: Centralize and search structured application logs to confidently diagnose problems - even faster and easier with Seq 2020.1!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Finding Joy in Making Happy Little Computer Videos on YouTube

July 21, 2020 Comment on this post [5] Posted in Learning .NET | Musings | Open Source
Sponsored By

Happy little coding videosWe're all remote and it's sad, but I've found some new joy of late in rebooting my little low-traffic newsletter AND making YouTube videos when the kids are asleep. You can go subscribe to my YouTube now and I encourage you to explore the Playlists.

I'm enjoying doing videos on topics like:

Here's some very recent videos I'm proud of!

Learning Git 101

But the Videos that people really seem to have enjoyed (which was surprising to me) was in this Playlist on "Computer Stuff They Didn't Teach You."

They are simple, calm, and quiet. Virtually no editing - it's all done in one shot - and I explain various topics around computers. Only one person complained that my voice made them fall asleep, so that's good!

I'll be updating videos more often while it remains enjoyable and folks appreciate it. I will try to do some more Git videos soon on squashing, cherry picking, rebasing, and maintaining your branch while main keeps moving forward.

I hope you enjoy them!


Sponsor: Centralize and search structured application logs to confidently diagnose problems - even faster and easier with Seq 2020.1!

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Exploring the .NET open source hybrid ORM library RepoDB

July 16, 2020 Comment on this post [9] Posted in Open Source
Sponsored By

ShutterstockIt's nice to explore alternatives, especially in open source software. Just because there's a way, or an "official" way doesn't mean it's the best way.

Today I'm looking at RepoDb. It says it's "a hybrid ORM library for .NET. It is your best alternative ORM to both Dapper and Entity Framework." Cool, let's take a look.

Michael Pendon, the author puts his micro-ORM in the same category as Dapper and EF. He says "RepoDb is a new hybrid micro-ORM for .NET designed to cater the missing pieces of both micro-ORMs and macro-ORMs (aka full-ORMs). Both are fast, efficient and easy-to-use. They are also addressing different use-cases."

Dapper is a great and venerable library that is great if you love SQL. Repo is a hybrid ORM and offers more than one way to query, and support a bunch of popular databases:

Here's some example code:

/* Dapper */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.Query<Customer>("SELECT Id, Name, DateOfBirth, CreatedDateUtc FROM [dbo].[Customer];");
}

/* RepoDb - Raw */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.ExecuteQuery<Customer>("SELECT Id, Name, DateOfBirth, CreatedDateUtc FROM [dbo].[Customer];");
}

/* RepoDb - Fluent */
using (var connection = new SqlConnection(ConnectionString))
{
var customers = connection.QueryAll<Customer>();
}

I like RepoDB's strongly typed Fluent insertion syntax:

/* RepoDb - Fluent */
using (var connection = new SqlConnection(connectionString))
{
var id = connection.Insert<Customer, int>(new Customer
{
Name = "John Doe",
DateOfBirth = DateTime.Parse("1970/01/01"),
CreatedDateUtc = DateTime.UtcNow
});
}

Speaking of inserts, it's BulkInsert (my least favorite thing to do) is super clean:

using (var connection = new SqlConnection(ConnectionString))
{
var customers = GenerateCustomers(1000);
var insertedRows = connection.BulkInsert(customers);
}

The most interesting part of RepoDB is that it formally acknowledges 2nd layer caches and has a whole section on caching in the excellent RepoDB official documentation. I have a whole LazyCache subsystem behind my podcast site that is super fast but added some complexity to the code with more Func<T> that I would have preferred.

This is super clean, just passing in an ICache when you start the connection and then mention the key when querying.

var cache = CacheFactory.GetMemoryCache();
using (var connection = new SqlConnection(connectionString).EnsureOpen())
{
var products = connection.QueryAll<Product>(cacheKey: "products", cache: cache);
}

using (var repository = new DbRepository<Product, SqlConnection>(connectionString))
{
var products = repository.QueryAll(cacheKey: "products");
}

It also shows how to do generated cache keys...also clean:

// An example of the second cache key convention:
var cache = CacheFactory.GetMemoryCache();
using (var connection = new SqlConnection(connectionString).EnsureOpen())
{
var productId = 5;
Query<Product>(product => product.Id == productId,
cacheKey: $"product-id-{productId}",
cache: cache);
}

And of course, if you like to drop into SQL directly for whatever reason, you can .ExecuteQuery() and call sprocs or use inline SQL as you like. So far I'm enjoying RepoDB very much. It's thoughtfully designed and well documented and fast. Give it a try and see if you like it to?

Why don't you head over to https://github.com/mikependon/RepoDb now and GIVE THEM A STAR. Encourage open source. Try it on your own project and go tweet the author and share your thoughts!


Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

How to make separate Work and Personal Profiles with the New Microsoft Edge on Beyonce's Internet

July 14, 2020 Comment on this post [16] Posted in Win10
Sponsored By

Edge for WerkI'm a long time Chrome user but have been slowly finding myself using the new Edge (Edgium?) and am now basically living in it full time.

Why?

  • My work use O365/M365 logins and it is great with a "Work profile" and "Personal profile" that keeps EVERYTHING separate.
  • It even has a "open link as Work/Personal" right click menu that I use WAY more often than I would have thought.
    Open Link as Work
  • It'll auto switch you to Work or Personal logins when you end up getting a OneDrive link or are logging into Azure.
  • Runs on my last Windows 7 machine through my Windows 10 machines, and <GASP> since it syncs everything AND has work/personal profiles I use it on my iPhone 11 Max.

But the main reason? I really like the way it deals with PWAs - Progressive Web Apps. Basically "make this website an app." I'll pretend if you pretend. You can use Outlook, Twitter, Gmail, Teams, TONS of websites as apps that are no-install. They all still run in Edge (with the Chromium heart) but they are pinned to your taskbar and/or start menu. A bunch of folks on my team legit don't install Office or Teams anymore. They use all of Office.com as a PWA. I was surprised but it works.

Here's some of my installed PWAs/apps:

Install this site as an app

If I visit Twitter.com for example and click the Circle with the Plus inside it in the URL bar, I'll see this.

image

Once it's "installed" I can pin it and run it and it looks like this in the taskbar. Four of these apps are PWAs with Edge. Gmail's icon is lame and old looking. They should fix that for their PWA.

Pinned apps with Edge

Lovely.

Making custom Pinned Edge icons with Profiles

I mentioned profiles before. Here's mine. I have no idea why I bothered to hide the emails.

Edge profile picker

The picker is nice, but I actually wanted TWO DIFFERENT EDGES pinned to my Taskbar. A Work and a Personal Edge. I find it easier to compartmentalize and easier than switching.

UPDATE: It seems as of a recent version of Edge you can just open Edge the usual way, switch to the Profile you want, then right click the running Edge in your Taskbar and "Pin to Taskbar" and you'll get your custom Edge with the Profile Directory switch correctly configured! Super convenient. That means the manual steps below are not needed unless you want to understand the internals, add a custom switch (which can also be done from Properties), or apply a custom icon.

Right click your Desktop and say New Shortcut from the right click menu.

File | New Shortcut

Put this in the location box above but change USERNAME to the right one for YOUR folder structure. And note --profile-directory. You can find your Profile folders in C:\Users\USERNAME\AppData\Local\Microsoft\Edge SxS\User Data. Mine are Profile 1 and 2, where 1 is my first Personal one and 2 is Work. Check yours and do the right thing. Note also the msGuidedSwitchAllowed switch that is optional. That tells you if you want Edge to suggest another profile if you visit a website that you really need your Work (or anotherr profile) logged in for.

"C:\Users\USERNAME\AppData\Local\Microsoft\Edge SxS\Application\msedge.exe" --profile-directory="Profile 2" --enable-features=msGuidedSwitchAllowed

Paste this in and hit Next, then Name the shortcut. If you like, use an Icon Editor and make a custom icon overlay for your work or personal Edge so you can tell the difference! I like Liquid Icon for simple editing or Greenfish Icon Editor.

I thought about using the Microsoft logo as an overlay for work but instead chose Beyoncé, so it's Edge for Werk.

image

Since it's a custom profile it can have a custom icon. Edge will make one for you you can use with YOUR face in your Profile X folder as mentioned above. Now my Desktop has two Edge icons, just like I like it.

I can also have the Canary (early builds), Dev version, or Stable Edge pinned. Lots of choices.

Edge for Werk

This makes it easy for me to run and keep track of what context I'm running in when I'm using a personal machine for Work.

Also note if you go to edge://settings/profiles/multiProfileSettings you can decide which profile a new URL external link will open with! You can also turn off Auto profile switching if you like.

Multiple profile preferences

All this, plus PWAs has made my browsing on Beyoncé's Internet quite nice lately.


Sponsor: Have you tried developing in Rider yet? This fast and feature-rich cross-platform IDE improves your code for .NET, ASP.NET, .NET Core, Xamarin, and Unity applications on Windows, Mac, and Linux.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook bluesky subscribe
About   Newsletter
Hosting By
Hosted on Linux using .NET in an Azure App Service

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.