Scott Hanselman

Implicit Usings in .NET 6

September 16, 2021 Comment on this post [2] Posted in DotNetCore
Sponsored By

Magic".NET 6 introduces implicit namespace support for C# projects. To reduce the amount of using directives boilerplate in .NET C# project templates, namespaces are implicitly included by utilizing the global using feature introduced in C# 10."

NOTE: Did you know that Visual Basic has had this very feature forever?

Remember that C# as a language is itself versioned and in .NET 6 we'll have support for C# 10 features like global usings, which are super cool.

Since we don't want to break existing stuff, there's some things to consider. First, for new projects this is on by default but for existing projects this will be off by default. This offers the best of both worlds.

When you create a new .NET 6 project it will enable this new property:

<ImplicitUsings>enable</ImplicitUsings>

Read more about this breaking change here. This build property builds upon (utilizes) the C# global using feature feature which means any .cs in your project can have a line like:

global using global::SomeNamespace;

The SDK uses a target to autogenerate a .cs file called ImplicitNamespaceImports.cs that will be in your obj folder, but you can - if you desire - have full control and add or remove namespaces to taste.

This gives advanced users who understand target file a huge amount control while still allowing newbies to reap the benefits. Other way to think about it is - if you care, you can control it all. If you don't, it'll just make things easier and cleaner.

Let's look at some code to point out that it's pretty cool. Oleg gives a great example doing some basic threading where there's three lines of code (cool) and three more lines of usings to bring in the namespace support for the actual work (less cool).

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

Console.WriteLine("Hello World");
await Task.Delay(1000);
List<int> _ = new ();

With implicating usings (implicitly bringing in default namespaces) .NET apps with C# 10 can do more out of the box. It's faster to get started because the 90% of the stuff you do all the time is already available and ready to be used!

Maybe this example is too simple? What If you were using a simple Web Worker app? Check out Wade's example.

System
System.Collections.Generic
System.IO
System.Linq
System.Net.Http
System.Threading
System.Threading.Tasks
System.Net.Http.Json
Microsoft.AspNetCore.Builder
Microsoft.AspNetCore.Hosting
Microsoft.AspNetCore.Http
Microsoft.AspNetCore.Routing
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Hosting
Microsoft.Extensions.Logging

This is a lot of boilerplate if you just want a web app. If I'm using the Microsoft.Net.Sdk.Worker SDK in my project file, or just Microsoft.NET.Sdk.Web, I don't have think about or include any of these - they are there implicitly!

You may initially love implicit usings, as I do, or you may find it to be too "magical." I would remind you that most innovations feel magical, especially if they aren't in your face. The Garbage Collector is taken for granted by the majority of .NET developers, while I found it magical when I had spent the previous 10 years managing my own memory down to the byte.

Hope you enjoy this new feature as we get closer to .NET 6's release.


Sponsor:  The No. 1 reason developers choose Couchbase? You can use your existing SQL++ skills to easily query and access JSON. That’s more power and flexibility with less training. Learn more.

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 twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service

How to detect if the User's OS prefers dark mode and change your site with CSS and JS

September 14, 2021 Comment on this post [15] Posted in ASP.NET
Sponsored By

Dark Mode SwitchI got a tweet from Stevö John who said he found my existing light theme for my blog to be jarring as he lives in Dark Mode. I had never really thought about it before, but once he said it, it was obvious. Not only should I support dark mode, but I should detect the user's preference and switch seamlessly. I should also support changing modes if the browser or OS changes as well. Stevö was kind enough to send some sample CSS and a few links so I started to explore the topic.

There's a few things here to consider when using prefers-color-scheme and detecting dark mode:

  • Using the existing theme as much as possible.
    • I don't want to have a style.css and a style-dark.css if I can avoid it. Otherwise it'd be a maintenance nightmare.
  • Make it work on all my sites
  • Consider 3rd party widgets
    • I use a syntax highlighter (very very old) for my blog, and I use a podcast HTML5 player from Simplecast for my podcast. I'd hate to dark mode it all and then have a big old LIGHT MODE podcast player scaring people away. As such, I need the context to flow all the way through.
  • Consider the initial state of the page as well as the stage changing.
    • Sure, I could just have the page look good when you load it and if you change modes (dark to light and back) in the middle of viewing my page, it should also change, right? And also consider all the requirements above.

You can set your Chrome/Edge browser to use System Settings, Light, or Dark. Search for Theme in Settings.

Choose your Theme, Dark or Light

All this, and I can only do it on my lunch hour because this blog isn't my actual day job. Let's go!

The prefers-color-scheme CSS Media Query

I love CSS @media queries and have used them for many years to support mobile and tablet devices. Today they are a staple of responsive design. Turns out you can just use a @media query to see if the user prefers dark mode.

@media (prefers-color-scheme: dark) {

Sweet. Anything inside here (the C in CSS stands for Cascading, remember) will override what comes before. Here's a few starter rules I changed. I was just changing stuff in the F12 tools inspector, and then collecting them back into my main CSS page. You can also use variables if you are an organized CSS person with a design system.

These are just a few, but you get the idea. Note the .line-tan example also where I say 'just put it back to it's initial value.' That's often a lot easier than coming up with "the opposite" value, which in this case would have meant generating some PNGs.

@media (prefers-color-scheme: dark) {
body {
color: #b0b0b0;
background-color: #101010;
}

.containerOuter {
background-color: #000;
color: #b0b0b0;
}

.blogBodyContainer {
background-color: #101010;
}

.line-tan {
background: initial;
}

#mainContent {
background-color: #000;
}
...snip...
}

Sweet. This change to my main css works for the http://hanselman.com main site. Let's do the blog now, which includes the 3rd party syntax highlighter. I use the same basic rules from my main site but then also had to (sorry CSS folks) be aggressive and overly !important with this very old syntax highlighter, like this:

@media (prefers-color-scheme: dark) {
.syntaxhighlighter {
background-color: #000 !important
}

.syntaxhighlighter .line.alt1 {
background-color: #000 !important
}

.syntaxhighlighter .line.alt2 {
background-color: #000 !important
}

.syntaxhighlighter .line {
background-color: #000 !important
}
...snip...
}

Your mileage may vary but it all depends on the tools. I wasn't able to get this working without the !important which I'm told is frowned upon. My apologies.

Detecting Dark Mode preferences with JavaScript

The third party control I use for my podcast is a like a lot of controls, it's an iFrame. As such, it takes some parameters as URL querystring parameters.

I generate the iFrame like this:

<iframe id='simpleCastPlayeriFrame' 
title='Hanselminutes Podcast Player'
frameborder='0' height='200px' scrolling='no'
seamless src='https://player.simplecast.com/{sharingId}'
width='100%'></iframe>

If I add "dark=true" to the querystring, I'll get a different player skin. This is just one example, but it's common that 3rd party integrations will either want a queryString or a variable or custom CSS. You'll want to work with your vendors to make sure they not only care about dark mode (thanks Simplecast!) and that they have a way to easily enable it like this.

Dark Mode and Light Mode Simplecast Podcast player

But this introduce some interesting issues. I need to detect the preference with JavaScript and make sure the right player gets loaded.

I'd also like to notice if the theme changes (light to dark or back) and dynamically change my CSS (that part happens automatically by the browser) and this player (that's gotta be done manually, because dark mode was invoked via a URL querystring segment.)

Here's my code. Again, not a JavaScript expert but this felt natural to me. If it's not super idiomatic or it just sucks, email me and I'll do an update. I do check for window.matchMedia to at least not freak out if an older browser shows up.

if (window.matchMedia) {
var match = window.matchMedia('(prefers-color-scheme: dark)')
toggleDarkMode(match.matches);

match.addEventListener('change', e => {
toggleDarkMode(match.matches);
})

function toggleDarkMode(state) {
let simpleCastPlayer = new URL(document.querySelector("#simpleCastPlayeriFrame").src);
simpleCastPlayer.searchParams.set("dark", state);
document.querySelector("#simpleCastPlayeriFrame").src = simpleCastPlayer.href;
}
}

toggleDarkMode is a method so I can use it for the initial state and the 'change' state. It uses the URL object because parsing strings is so 2000-and-late. I set the searchParams rather than .append because I know it's always set. I set it.

As I write this I supposed I could have stored the document.querySelector() like I did the matchMedia, but I just saw it now. Darn. Still, it works! So I #shipit.

I am sure I missed a page or two or a element or three so if you find a white page or a mistake, file it here https://github.com/shanselman/hanselman.com-bugs/issues and I'll take a look when I can.

All in all, a fun lunch hour. Thanks Stevö for the nudge!

Now YOU, Dear Reader can go update YOUR sites for both Light Mode and Dark Mode.


Sponsor:  The No. 1 reason developers choose Couchbase? You can use your existing SQL++ skills to easily query and access JSON. That’s more power and flexibility with less training. Learn more.

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 twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service

Minimal APIs in .NET 6 but where are the Unit Tests?

September 09, 2021 Comment on this post [0] Posted in DotNetCore | Open Source | Web Services
Sponsored By

imageMinimal APIs in .NET 6 is great. But where are the Unit Tests?! Often testing is missed or forgotten because it's perceived as difficult or complex.

But it's super fun and very easy! Once tests are easy to write, WRITE A LOT OF THEM.

Here's a simple Unit Test of a Web API:

[Fact]
public async Task GetTodos()
{
await using var application = new TodoApplication();

var client = application.CreateClient();
var todos = await client.GetFromJsonAsync<List<Todo>>("/todos");

Assert.Empty(todos);
}

Look how nice that is. Client and Server (Application) are right there, and the HTTP GET is just a function call (as this is a Unit Test, not an integration test that covers end-to-end full stack).

Here's the TodoApplication application factory that creates a Host with a mocked out in memory version of a SQLite database.

class TodoApplication : WebApplicationFactory<Todo>
{
protected override IHost CreateHost(IHostBuilder builder)
{
var root = new InMemoryDatabaseRoot();

builder.ConfigureServices(services =>
{
services.AddScoped(sp =>
{
// Replace SQLite with the in memory provider for tests
return new DbContextOptionsBuilder<TodoDbContext>()
.UseInMemoryDatabase("Tests", root)
.UseApplicationServiceProvider(sp)
.Options;
});
});

return base.CreateHost(builder);
}
}

Nice and clean. You're talking directly to the API, testing just the Unit of Work. No need for HTTP, you're just calling a clean method on the existing API, directly.

That's a simple example, just getting Todos. How would we test making one (POSTing to our Todo application as a Minimal .NET 6 API?)

[Fact]
public async Task PostTodos()
{
await using var application = new TodoApplication();

var client = application.CreateClient();
var response = await client.PostAsJsonAsync("/todos", new Todo { Title = "I want to do this thing tomorrow" });

Assert.Equal(HttpStatusCode.Created, response.StatusCode);

var todos = await client.GetFromJsonAsync<List<Todo>>("/todos");

Assert.Single(todos);
Assert.Equal("I want to do this thing tomorrow", todos[0].Title);
Assert.False(todos[0].IsComplete);
}

You could abstract the setup away if you wanted to and start with an Server/App and Client ready to go, but it's just two lines.

Here we are asserting that it returned an HTTP 200 - even though the HTTP networking stack isn't involved we are still able to test intent. Then we confirm that we created a Todo and could successfully retrieve it from the (in-memory) database.

Pretty slick!


Sponsor: YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt.

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 twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service

Minimal APIs at a glance in .NET 6

September 07, 2021 Comment on this post [14] Posted in DotNetCore | Open Source | Web Services
Sponsored By

imageDavid Fowler doesn't have a blog. I think the psychic weight of having a blog would stress him out. Fortunately, David's 'blog' is actually hidden in his prolific GitHub commits and GitHub Gists.

David has been quietly creating  an amazing piece of documentation for Minimal APIs in .NET 6. At some point when it's released we'll work with David to get everything promoted to formal documentation, but as far as I'm concerned if he is slapping the keyboard anywhere and it shows up anywhere with a URL then I'm happy with the result!

Let's explore a bit here and I encourage you to head over to the main Gist here.

To start, we see how easy it is to make a .NET 6 (minimal) app to say Hello World over HTTP on localhost:5000/5001

var app = WebApplication.Create(args);

app.MapGet("/", () => "Hello World");

app.Run();

Lovely. It's basically nothing. Can I do more HTTP Verbs? Yes.

app.MapGet("/", () => "This is a GET");
app.MapPost("/", () => "This is a POST");
app.MapPut("/", () => "This is a PUT");
app.MapDelete("/", () => "This is a DELETE");

What about other verbs? More than one?

app.MapMethods("/options-or-head", new [] { "OPTIONS", "HEAD" }, () => "This is an options or head request ");

Lambda expressions, not objects, are our "atoms" that we build molecules with in this world. They are the building blocks.

app.MapGet("/", () => "This is an inline lambda");

var handler = () => "This is a lambda variable";

app.MapGet("/", handler)

But it's just a function, so you can organize things however you want!

var handler = new HelloHandler();

app.MapGet("/", handler.Hello);

class HelloHandler
{
public string Hello()
{
return "Hello World";
}
}

You can capture route parameters as part of the route pattern definition.

app.MapGet("/users/{userId}/books/{bookId}", (int userId, int bookId) => $"The user id is {userId} and book id is {bookId}");

Route constraints are influence the matching behavior of a route. See how this is in order of specificity:

app.MapGet("/todos/{id:int}", (int id) => db.Todos.Find(id));
app.MapGet("/todos/{text}", (string text) => db.Todos.Where(t => t.Text.Contains(text));
app.MapGet("/posts/{slug:regex(^[a-z0-9_-]+$)}", (string slug) => $"Post {slug}");

Attributes can be used to explicitly declare where parameters should be bound from! So you can pick and choose from all over!

using Microsoft.AspNetCore.Mvc;

app.MapGet("/{id}", ([FromRoute]int id,
[FromQuery(Name = "p")]int page,
[FromServices]Service service,
[FromHeader(Name = "Content-Type")]string contentType) => { });

I can customize the response:

app.MapGet("/todos/{id}", (int id, TodoDb db) => 
db.Todos.Find(id) is Todo todo
? Results.Ok(todo)
: Results.NotFound()
);

Here's a cool example of taking a file upload and writing it to a local file. Nice and clean.

app.MapGet("/upload", async (HttpRequest req) =>
{
if (!req.HasFormContentType)
{
return Results.BadRequest();
}

var form = await req.ReadFormAsync();
var file = form.Files["file"];

if (file is null)
{
return Results.BadRequest();
}

var uploads = Path.Combine(uploadsPath, file.FileName);
await using var fileStream = File.OpenWrite(uploads);
await using var uploadStream = file.OpenReadStream();
await uploadStream.CopyToAsync(fileStream);

return Results.NoContent();
})
.Accepts<IFormFile>("multipart/form-data");

Go check out this great (and growing) online resource to learn about .NET 6 minimal APIs.


Sponsor: YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt.

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 twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service

A .NET 6 Minimal API Todo example Playground

September 02, 2021 Comment on this post [3] Posted in DotNetCore | Open Source | Web Services
Sponsored By

Minimalism from UnsplashI really like minimal Web APIs. I've liked the idea for years. With .NET 6, it's starting to happen! Damian Edwards has an interesting minimal API Playground on his GitHub and Maria Naggaga did a great talk on Minimal APIs in .NET 6 that's up on YouTube!

Let's explore! I'm running the latest .NET 6 and you can run it on Windows, Mac, or Linux and I cloned it to a folder locally.

There's two versions of a complete Todo API in this sample, one using Entity Framework Core and one using Dapper for data access. Both are lightweight ORMs (object relational mappers). Let's explore the Dapper example that uses SQLite.

The opening of the code in this example doesn't require a Main() which removes a nice bit of historically unneeded syntactic sodium. The Main is implied.

using System.ComponentModel.DataAnnotations;
using Microsoft.Data.Sqlite;
using Dapper;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("TodoDb") ?? "Data Source=todos.db";
builder.Services.AddScoped(_ => new SqliteConnection(connectionString));
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

At this point we've got a SQLite connection string ready to go scoped in the Services Dependency Injection Container (fancy words for "it's in the pile of stuff we'll be using later") and we've told the system we want a nice UI for our Open API (Swagger) web services description. It's WSDL for JSON, kids!

Then a call to EnsureDb which, ahem, ensures there's a database!

await EnsureDb(app.Services, app.Logger);

What's it look like? Just a little make this table if it doesn't exist action:

async Task EnsureDb(IServiceProvider services, ILogger logger)
{
logger.LogInformation("Ensuring database exists at connection string '{connectionString}'", connectionString);

using var db = services.CreateScope().ServiceProvider.GetRequiredService<SqliteConnection>();
var sql = $@"CREATE TABLE IF NOT EXISTS Todos (
{nameof(Todo.Id)} INTEGER PRIMARY KEY AUTOINCREMENT,
{nameof(Todo.Title)} TEXT NOT NULL,
{nameof(Todo.IsComplete)} INTEGER DEFAULT 0 NOT NULL CHECK({nameof(Todo.IsComplete)} IN (0, 1))
);";
await db.ExecuteAsync(sql);
}

Next we'll "map" some paths for /error as well as paths for our API's UI so when I hit /swagger with a web browser it looks nice:

if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/error");
}

app.MapGet("/error", () => Results.Problem("An error occurred.", statusCode: 500))
.ExcludeFromDescription();

app.MapSwagger();
app.UseSwaggerUI();

Then sprinkle in a little Hello World just to give folks a taste:

app.MapGet("/", () => "Hello World!")
.WithName("Hello");

app.MapGet("/hello", () => new { Hello = "World" })
.WithName("HelloObject");

You can see how /hello would return a JSON object of Hello: "World"

What's that WithName bit at the end? That names the API and corresponds to 'operationId" in the generated swagger/openAPI json file. It's a shorthand for .WithMetadata(new EndpointNameMetadata("get_product")); which was surely no fun at all.

Now let's get some Todos from this database, shall we? Here's all of them and just the complete ones:

app.MapGet("/todos", async (SqliteConnection db) =>
await db.QueryAsync<Todo>("SELECT * FROM Todos"))
.WithName("GetAllTodos");

app.MapGet("/todos/complete", async (SqliteConnection db) =>
await db.QueryAsync<Todo>("SELECT * FROM Todos WHERE IsComplete = true"))
.WithName("GetCompleteTodos");

Lovely. But what's this Todo object? We haven't seen that. It's just a object that's shaped right. Perhaps one day that could be a record rather than a class but neither Dapper or EFCore support that yet it seems. Still, it's minimal.

public class Todo
{
public int Id { get; set; }
[Required]
public string? Title { get; set; }
public bool IsComplete { get; set; }
}

Let's get a little fancier with an API that gets a Todo but it might not find the result! It may produce an HTTP 200 OK or an HTTP 404 NotFound.

app.MapGet("/todos/{id}", async (int id, SqliteConnection db) =>
await db.QuerySingleOrDefaultAsync<Todo>("SELECT * FROM Todos WHERE Id = @id", new { id })
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.WithName("GetTodoById")
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);

Don't be sad if you don't like SQL like this, it's just a choice amongst many. You can use whatever ORM you want, worry not.

A thought: The .Produces are used by the OpenAPI/Swagger system. In my mind, it'd be nice to avoid saying it twice as the Results.Ok and Results.NotFound is sitting right there, but you'd need a Source Generator or aspect-oriented post compilation weaver to tack in on after the fact. This is the only part that I don't like.

Go explore the code and check it out for yourself!


Check out our Sponsor! YugabyteDB is a distributed SQL database designed for resilience and scale. It is 100% open source, PostgreSQL-compatible, enterprise-grade, and runs across all clouds. Sign up and get a free t-shirt!

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 twitter subscribe
About   Newsletter
Hosting By
Hosted 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.