Skip to content

Instantly share code, notes, and snippets.

@booherbg
Last active March 9, 2022 21:14
Show Gist options
  • Save booherbg/bb43ed8e8021b72218e23fd6296f4af8 to your computer and use it in GitHub Desktop.
Save booherbg/bb43ed8e8021b72218e23fd6296f4af8 to your computer and use it in GitHub Desktop.
Up and running with dotnet

Notes from early experiments with dotnet on Mac OSX

Installation: dotnet and VS Code

Installation was amazingly easy. < 2 minutes and dotnet is available from the command line.

Don't forget to install the c# plugin for VS Code (powered by omnisharp).

Omnisharp

Omnisharp ships with the C# plugin, and there is configuration to use a globally installed mono. I did have mono installed but Omnisharp barfed with it. I uninstalled mono and changed the VS Code settings (search for mono) to never use globally installed Mono. Works great now! You can check the OmniSharp Log in the Output tab for more info.

If you don't have mono installed, you shouldn't have to worry about OmniSharp. It'll just work.

Misc. Errors

Ran into an error that looks like Method 'Create' does not have an implementation. Had to ensure that the right versions of psql and EF were installed (dont mix 5.x with 3.x). Ensure that .csproj file has the right versions of files and confirm at command line. Restart VS Code and Terminals if needed. Make sure 5.0.0 is not in your .csproj file (may be upgraded by accident).

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="3.1.5" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.5">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
    <PackageReference Include="NpgSql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.1" />
  </ItemGroup>
$ dotnet --list-runtimes
Microsoft.AspNetCore.App 3.1.4 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.5 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

$ dotnet list package
Project 'dotnet-bakery' has the following package references
   [netcoreapp3.1]:
   Top-level Package                                   Requested   Resolved
   > Microsoft.AspNetCore.SpaServices.Extensions       3.1.5       3.1.5
   > Microsoft.EntityFrameworkCore.Design              3.1.5       3.1.5
   > Npgsql.EntityFrameworkCore.PostgreSQL             3.1.4       3.1.4
   > NpgSql.EntityFrameworkCore.PostgreSQL.Design      1.1.1       1.1.1
   

In case of reinstalling EF 3.1.5:

$ dotnet tool uninstall --global dotnet-ef
$ dotnet tool update --global dotnet-ef --version 3.1.5

Restart VS Code and close out of all terminals, try again.

When running migrations, we got this error:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.TypeLoadException: Could not load type 'Microsoft.EntityFrameworkCore.Internal.SemanticVersionComparer' from assembly 'Microsoft.EntityFrameworkCore, Version=5.0.7.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

This was because EF was on 3.x but we've moved to .NET 5 (under .NET 6 runtime...)

First, upgraded the .csproj file to 5.0:

  <ItemGroup>
   <PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.0" />
   <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.0">
     <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     <PrivateAssets>all</PrivateAssets>
   </PackageReference>
   <PackageReference Include="newtonsoft.json" Version="13.0.1" />
   <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.7" />
   <PackageReference Include="NpgSql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.1" />
 </ItemGroup>

Then do a dotnet restore.

This will now cause Newtonsoft namespace to be missing. Do this:

dotnet add package newtonsoft.json

And now dotnet build should work.

Learning

C# Tutorials

ASP.NET Core

dotnet scaffolding

Some general notes from dotnet scaffolding:

  • --no-https will remove https support, which can help keep things simple (https complicates things for local development, and was giving me some trouble with delays and false starts. Can always use an https reverse proxy).
  • -o folder-name will put the project into a new folder instead of the current one

Example scaffolding recipes:

  • dotnet new console will generate a blank console project (great for learning c#)
  • dotnet new webapi will generate a WebAPI project with a WeatherForecast API endpoint, great for learning the API
  • dotnet new react will generate a WebAPI project with the same API as above, with react wired up and ready to go (no redux, jsx/ecmascript6).
  • dotnet new reactredux will generate the same as above, but with redux included AND in TypeScript instead of ECMAScript6.

General CLI notes

  • dotnet run will build and run the project, which will also launch npm start in the background on the client app
  • dotnet watch run will do the same, but restart the whole thing when any file is changed
  • dotnet build will just build the DLL files in the build/ folder
  • dotnet publish -r osx-x64 publish an executable build targeted at osx-64
  • dotnet publish -r linux-x64 --stand-alone true publish a stand-alone executable (targeted for linux-64)

Running React Webpack Separately

By default, dotnet will run webpack and npm start etc in the background, which can take awhile and remove some flexibility and control. Simple update Startup.cs to 1) remove the call to npm and 2) forward any client requests to port 3000 (which is exactly the opposite of the react/express dev cycle, which forwards port 3000 to 5000). The result is basically the same, just know that you don't want to visit http://localhost:3000 as that will just be the react app itself, and all API calls wont work (unless of course you set up a second proxy in package.json lol).

According to The Docs, in Startup.cs replace spa.UseReactDevelopmentServer with spa.UseProxyToSpaDevelopmentServer like so:

if (env.IsDevelopment())
{
    // spa.UseReactDevelopmentServer(npmScript: "start");
    spa.UseProxyToSpaDevelopmentServer("http://localhost:3000");
}

Azure CLI

For some reason I'm having a heck of a time with Azure CLI. I run az login which says I am logged in, but then immediately say "There are no subscriptions" and all future az commands require logging in. Maybe I have to do something else on the Azure web interface before it will work?

UPDATE: I finally can login to Azure. The issue was that previously I forgot my password, but resetting didnt work because MS wouldn't accept my email as my username (doh). So I was stuck. Well apparently I did that enough times that it locked my account, which sent me to another form that I was able to use to unlock my account. Through this process I was able to change my password (and save it!) and then log into MS Services.

Once logged in I went to the Azure portal, and verified my account (but did NOT put in credit card info). I don't know if the first step was required or not.

But not I can get into az which is great. It seems that the previous problem was that I did have a Microsoft account, but did not yet have an Azure account.

blaine$ az login
You have logged in. Now let us find all the subscriptions to which you have access...
The following tenants don't contain accessible subscriptions. Use 'az login --allow-no-subscriptions' to have tenant level access.
039d8805-2014-4214-880c-9c70fa38925e
No subscriptions found.

blaine$ az login --allow-no-subscriptions
You have logged in. Now let us find all the subscriptions to which you have access...
The following tenants don't contain accessible subscriptions. Use 'az login --allow-no-subscriptions' to have tenant level access.
039d8805-2014-4214-880c-9c70fa38925e
[
  {
    "cloudName": "AzureCloud",
    "id": "039d8805-2014-4214-880c-9c70fa38925e",
    "isDefault": true,
    "name": "N/A(tenant level account)",
    "state": "Enabled",
    "tenantId": "039d8805-2014-4214-880c-9c70fa38925e",
    "user": {
      "name": "blaineb@emergingprairie.com",
      "type": "user"
    }
  }
]

I can't find the command line instructions that I had before, but I did find some great documentation on simple deployment from VS Code. I installed the Azure App Service extension and followed the directions to login to Azure. I now have a Deploy Web App... option when I right click on a folder (for example, one generated with dotnet publish -c Release -o ./publish

When clicking Deploy App... I am presented with a notification that I do not have any subscriptions (pretty much the same error as before), but with a link to go create one. I put in my CC info (to finish account verification), and Azure assures me I won't be charged as long as I don't specifically upgrade to a paid tier.

As soon as I verified, I get a TOO_MANY_REDIRECTS error from Azure, lol.

I do see I have a default subscription now, but VS Code doesn't believe me. I guess I'll finish this another day.

UPDATE: Once you create a free 'web app' subscription on Azure, you can deploy through VS Code directly (select subscription, create a new app). Works pretty well.

Deploying to Heroku

Deploying to heroku worked surprisingly well. I did have one hiccup where npm is used to build the client app, but Heroku can't install the node buildpack since we don't have a package.json in the top level directory of the project. I added a package.json and it works without a hitch (npm init --yes will produce a basic one).

Make sure you have a git repo set up so that heroku remote will work properly.

Create the heroku app, install the dotnet buildpack, and set it to the project (not sure if necessary). It also helps to set your environment variable to production so that you dont end up building the development version.

heroku create --buildpack https://github.com/jincod/dotnetcore-buildpack.git
heroku buildpacks:set jincod/dotnetcore
heroku config:set ASPNETCORE_ENVIRONMENT=Production

# For NodeJS Support (building locally)
heroku buildpacks:add --index 1 heroku/nodejs

# Then to deploy, just commit and push:
git push heroku master

Surprisingly, it works great! The client gets built on Heroku and served out by the Kestrel engine. See the GitHub page for the buildpack on how to set up migrations, if necessary. I also disabled service workers because it made it difficult to figure out if the app was getting updated properly on deploy.

Heroku + Postgres

First, go through the standard heroku magic:

heroku addons:create heroku-postgresql:hobby-dev
heroku pg:push your_database DATABASE_URL

Now the database is set up, but we have to wire our app up to use it. To get postgres wired up, we have to create the proper connection string. The one that comes in from DATABASE_URL isnt in the right format. For now just manually create it and check for it in Startup.cs

heroku config:add DATABASE_URL_STR="Host=ec2-blah.compute-1.amazonaws.com;Port=5432;Username=...;Password=...;Database=...;"

Update Startup.cs appropriately:

string DATABASE_URL = Environment.GetEnvironmentVariable("DATABASE_URL_STR");
string connectionString = (DATABASE_URL == null ? Configuration.GetConnectionString("DefaultConnection") : DATABASE_URL);
Console.WriteLine($"Using connection string: {connectionString}");

services.AddDbContext<ApplicationContext>(options =>
   options.UseNpgsql(connectionString)
);

Its hard to believe it, but it works! heroku logs will confirm that we can see the new connection string and everything works as expected when you visit the site (heroku open). Wow!

Postgres Setup

Gotta install PostgreSQL and the EntityFarmeworkCore.Design library (so we can do code-first and migrations).

dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package NpgSql.EntityFrameworkCore.PostgreSQL.Design
dotnet add package Microsoft.EntityFrameworkCore.Design

dotnet tool install --global dotnet-ef

Migrations

Make a class, and add the class to the DB context. Add the DB Context to your setup in Startup.cs. Make sure each class at least has an id defined: public int id { get; set; } or Entity will (righly) complain about a missing primary key.

dotnet ef migrations add Initial
dotnet ef database update

To remove / reset the migrations:

dotnet ef migrations remove

To reset the database:

dotnet ef database update 0

General Notes & Resources

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment