Content Developer II at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
115492 stories

What's New in PWA - November 2023

1 Share

Cover photo displaying the text 'What's New in PWA'

Welcome to What’s New in PWA! This post will cover what is new in the world of Progressive Web Apps and PWABuilder over the last year or so. We’ll take a look at some of the latest browser capabilities, go over some numbers on PWA adoption, and spotlight some Progressive Web Apps we’ve recently encountered through the PWABuilder service.

Let’s jump into the latest browser capabilities.

New Browser Capabilities

The last year has seen browser capabilities continue to expand to support more flexible Progressive Web Apps. Let’s highlight a few that have made Progressive Web Apps more flexible.

Window Controls Overlay

Visual of space opened up by using Window Controls Overlay.

Window Controls Overlay is a newer feature that allows your Progressive Web App to look more like a native application by allowing usage of the space normally reserved for the browser’s window controls. This feature is available in both Edge and Chrome.

Learn more about Window Controls Overlay.

Windows 11 Widgets

Showcase of where widgets live on Windows 11.

Progressive Web Apps running on Edge can now define widgets for the Widgets Board on Windows 11. This is another great way for your app to integrate more fully on a native operating system.

Learn more about creating Widgets for Windows 11.

Edge Side Panel

PWA running in the Edge Side Panel.

Progressive Web Apps running on Edge can now make use of the Edge Side Panel, which allows your app to run side-by-side with other content. This is great for productivity apps and apps that may be used in conjunction with other content.

Learn more about the Edge Side Panel.

PWA By The Numbers

All of the following data is sourced from the Web Almanac, a crowd sourced project for tracking web development trends. We’ll be taking a look at some of the data associated with progressive web apps, such as service worker and app capabilities usage.

Service Worker Usage

First, let’s take a look at service workers. Right now, just under 2 percent of all websites employ the use of a service worker. However, this number increases when looking at only the most popular websites. For the top 10,000 sites, service worker usage is around 8 percent. This makes sense, as more popular sites are more likely to to have the support required to update to the latest web technologies.

Let’s also take a look at what service worker features are being utilized:

  • Around 50 percent of apps with service workers are using the fetch event.
  • Around 56 percent are using notification based events, such as notificationclick and push.
  • Syncing events aren’t as widely used, with only around 5 percent of apps using the sync event, and only .01 percent using the periodicsync event.

To see more servicer worker or PWA data, head over to the Web Almanac.

App Capabilities Usage

Though many app capabilities aren’t exclusive to Progressive Web Apps, their usage is still a good indicator of how developers are making use of the modern web. Let’s take a look at some of the most popular app capabilities in order:

  1. Async Clipboard API - 10 percent
  2. Web Share API - 9 percent
  3. Media Session API - 7.5 percent
  4. Device Memory API - 6 percent

To see more app capabilities data, head over to the Web Almanac.

App Spotlight

Tons of apps make their way through the PWABuilder service every day. Let’s spotlight a few of the most recent apps that have been submitted.


Melody Rex practice page

MelodyRex is a productivity tool that helps musicians track practice times, set goals, and more. It includes a metronome and social capabilities for interacting with your practice partners.

Some things I like about this app:

  • Proper use of offline support with a service worker
  • Effective use of notifications
  • Simple but effective UI and nice user experience features, such as a guided tutorial with tooltips.

Check out MelodyRex in the Microsoft Store.


Jam Hearts game open in the browser.

JAM HEARTS is a game Progressive Web App that’s just a great example of how great a simple game can be on the web. It’s fun and easy to play on any platform.

Some things I like about this app:

  • UI is visually pleasing and simple, and looks nice on any platform.
  • Design choices resemble a native application
  • Offline support

Check out JAM HEARTS in the Microsoft Store.

Read the whole story
3 hours ago
West Grove, PA
Share this story

Microsoft Paint’s OpenAI-powered ‘Cocreator’ image generator is here

1 Share
A picture of a pixelated cat generated by Paint Cocreator based on a user’s description.
Why draw the pixel cat in MS Paint when Cocreator can just make it for you? | Image: Microsoft

Microsoft is officially launching its Cocreator image-generating AI feature within the Paint app for Windows 11. The new integrated text-to-image generator, powered by OpenAI’s DALL-E 3 model, was previously available only to Windows Insiders. As Windows Central points out, the new Cocreator button in Microsoft Paint has now been widely released, giving all users the ability to enter a description of something they’re visualizing and get three generated images to choose between.

While the image generator is a new addition to Microsoft Paint, the company has already sprinkled the DALL-E 3 text-to-image-making capability into its other services. Microsoft’s Bing search chatbot was where users initially punched in image requests, but that’s...

Continue reading…

Read the whole story
3 hours ago
West Grove, PA
Share this story

Entity Framework in .NET Aspire

1 Share

A path through the infrastructure

.NET Aspire is an opinionated, cloud ready stack for building observable, production ready, distributed applications.

.NET Aspire is currently in preview and focuses on simplifying the developer experience with orchestration and automatic service discovery features. There's a huge potential for .NET Aspire beyond this initial valuable feature set.

Being in preview, .NET Aspire may not yet support all the scenarios or workloads you may be comfortable with. It's an opinionated framework, which means differences of opinion are natural and expected. Currently, one of those opinions seems to be a focus on containers. The sample solutions that the new dotnet templates provide are a great example of the benefits of containerization. The .NET Aspire starter solution that dotnet new --use-redis-cache --output AspireStarter generates, out of the box, is something that, when debugged, will download, run, and utilize a Docker Redis image. (I've worked with teams where getting each member productive in a development environment has ended up being days of work.) The AppHost component of a .NET Aspire solution codifies abstract aspects of the architectural decisions that automates the generation and deployment of a development environment<(!--and configuration provides the details from future decisions about other environments-->.)

A container focus is empowered by .NET Aspire's orchestration features. An independent orchestration responsibility enables better separation of release and deploy concerns from build and test concerns; shifting right those decisions that release and deploy depend on. (i.e., the ability to develop, execute, and evaluate solutions are discernibly left of release and operation.) Containers are an established method of componentizing a distributed system with independent servers (sometimes called "tiers.") This provides flexibility to deploy and execute in a development environment even before architectural decisions about a production topology have been considered. For example, debugging the .NET Aspire starter app automatically spins up a Redis container in Docker, but it's extremely unlikely that's how it will be deployed in production. In production, will there be one only Redis instance? If you have many instances, what sort of gateway or reverse proxy to that pool of instances will be utilized? Will it be on-prem or cloud? Will it be Azure, AWS, or Google Cloud? The beauty of Aspire's orchestration feature is that it doesn't matter yet; you can configure orchestration to figure it out at run-time, one environment at a time!

But, with every decision comes compromise. Technologies that depend on the physical resources that come from those decisions (that we're now effectively deferring) introduce some challenges with some existing software development idioms. A chicken-and-egg situation: if how to connect to physical resources may only be known at run-time, what happens to design-time technologies that depend on that connection information?

One popular technology in .NET, Entity Framework, suffers one of those challenges in .NET Aspire (Possibly only in code-first scenarios. Many Entity Framework examples detail adding Entity Framework support to an existing component (resource, like console app, ASP.NET Core web API, Razor app, etc.), creating a circular dependency between its project and the existence of an executing database (i.e., a valid database connection string.) In database-first, you have an existing application with existing physical databases and practices to utilize them in a development environment. With .NET Aspire, developers are shifted left from the decisions that provide the resources that things like migrations add <migration-name> and dotnet ef database update require to function properly.

To be clear, the way .NET Aspire works is that the orchestration (AppHost) executes, figures out the various connection strings, and overrides the appsettings by setting environment variables before running the other components. The premise behind this means that at run-time, whatever is in appsettings is ignored. dotnet ef command doesn't execute at run-time; it effectively runs at design-time and gets its configuration from appsettings, so it's out of sync with reality.

The basic guidance is to abstract those types of dependencies as .NET Aspire resources. Nothing new conceptually, but this might be an application of the principles of abstraction at a level less commonly applied. Refining that guidance to using Entity Framework: the database should be an independent resource. Independent resources are modeled in .NET as either separate projects or separate solutions. Luckily, an .NET Aspire sample addresses this. Let's look into the details.

The structure of the eShopLite sample overlaps with the .NET Aspire starter dotnet new template. It has a Blazor web frontend, a web API, an Aspire AppHost, and an Aspire service defaults project. Additionally, there is a shopping cart service (BasketService), and the catalog database (CatalogDb) project is an abstraction of the database resource.

The CatalogDb looks very similar to what you'd end up with following Tutorial: Create a web API with ASP.NET Core: an ASP.NET Core web API that leverages Entity Framework, and is effectively a gateway to a backend database. Although, that tutorial uses Entity Framework in-memory rather than via PostgreSQL. The way eShopLite supports Entity Framework is through the CatalogDb project. CatalogDb is like a stub project to the rest of the solution: Aspire doesn't execute it, but CatalogService depends upon it for the database model classes and DbContext (utilized more like a class library.) Nothing connects to the CatalogDb web API. The CatalogDb project contains all the Entity Framework design-time details and references, allowing you to utilize Entity Framework's features like migrations add <migration-name> and dotnet ef database update. The target of Entity Framework operations like migration add and database update would depend on the configuration in appsettings.json. Initialization/seeding of the data is handled in CatalogDbInitializer within CatalogDb, as well as migrations at run-time (startup). CatalogDb appsettings connection strings must be in sync with the run-time values for ef commands to work.

In summary, if you want to utilize Entity Framework in a basic .NET Aspire application, adding a project to contain the entity models, context, and Entity Framework references and supporting a database engine container is a recommended place to get started. I suspect this guidance may be refined as .NET Aspire evolves.

I'm still wrapping my head around how .NET Aspire can support other non-containerized workloads like Azure SQL. Still, a containerized design melds nicely with the idea of independent resources (or nodes) in .NET Aspire. .NET Aspire also helps to more clearly delineate concerns like design, build, test, release, and deploy. As with .NET Aspire, containerization is an easier starting point for someone interested in distributed applications.

I look forward to how .NET Aspire evolves.

Read the whole story
3 hours ago
West Grove, PA
Share this story

Building a Critter Stack Application: Marten as Event Store

1 Share

Hey, did you know that JasperFx Software is ready for formal support plans for Marten and Wolverine? Not only are we trying to make the “Critter Stack” tools be viable long term options for your shop, we’re also interested in hearing your opinions about the tools and how they should change.

Let’s build a small web service application using the whole “Critter Stack” and their friends, one small step at a time. For right now, the “finished” code is at CritterStackHelpDesk on GitHub.

The posts in this series are:

  1. Building a Critter Stack Application: Event Storming
  2. Building a Critter Stack Application: Marten as Event Store (this post)

Event Sourcing

Event Sourcing is a style of persistence where the single source of truth, system state is a read only, append only sequence of all the events that resulted in a change in the system state. Using our HelpDesk incident tracking application we first started describing in the previous post on Event Storming, that results in a sequence like this:

SequenceIncident IdEvent Type
An event log

As you could probably guess already from the table above, the events will be stored in one single log in the sequential order they were appended. You can also see that events will be categorized by their relationship to a single logical incident. This grouping is typically called a “stream” in event sourcing.

As a first quick foray into event sourcing, let’s look at using the Marten library to create an event store for our help desk application built on top of a PostgreSQL database.

In case you’re wondering, Marten is merely a fancy library that helps you access and treat the rock solid PostgreSQL database engine as both a document database and as an event store. Marten was purposely built on PostgreSQL specifically because of the unique JSON capabilities of PostgreSQL. It’s possible that the event store portion of Marten eventually gets ported to other databases in the future (Sql Server), but it’s highly unlikely that the document database feature set would ever follow.

Using Marten as an Event Store

This code is all taken from the CritterStackHelpDesk repository, and specifically the EventSourcingDemo console project. The repository’s README file has instructions on running that project.

First off, let’s build us some events that we can later store in our new event store:

public record IncidentLogged(
    Guid CustomerId,
    Contact Contact,
    string Description,
    Guid LoggedBy

public class IncidentCategorised
    public IncidentCategory Category { get; set; }
    public Guid UserId { get; set; }

public record IncidentPrioritised(IncidentPriority Priority, Guid UserId);

public record AgentAssignedToIncident(Guid AgentId);

public record AgentRespondedToIncident(        
    Guid AgentId,
    string Content,
    bool VisibleToCustomer);

public record CustomerRespondedToIncident(
    Guid UserId,
    string Content

public record IncidentResolved(
    ResolutionType Resolution,
    Guid ResolvedBy,
    DateTimeOffset ResolvedAt

You’ll notice there’s a (hopefully) consistent naming convention. The event types are named in the past tense and should refer clearly to a logical event in the system’s workflow. You might also notice that these events are all built with C# records. This isn’t a requirement, but it makes the code pretty terse and there’s no reason for these events to ever be mutable anyway.

Next, I’ve created a small console application and added a reference to the Marten library like so from the command line:

dotnet new console
dotnet add package Marten

Before we even think about using Marten itself, let’s get ourselves a new, blank PostgreSQL database spun up for our little application. Assuming that you have Docker Desktop or some functional alternative on your development machine, there’s a docker compose file in the root of the finished product that we can use to stand up a new database with:

docker compose up -d

Note, and this is an important point, there is absolutely nothing else you need to do to make this new database perfectly usable for the code we’re going to write next. No manual database setup, no SQL scripts for you to run, no other command line scripts. Just write code and go.

Next, we’re going to configure Marten in code, then:

  1. Start a new “Incident” stream with a couple events
  2. Append additional events to our new stream

The code to do nothing but what I described is shown below:

// This matches the docker compose file configuration
var connectionString = "Host=localhost;Port=5433;Database=postgres;Username=postgres;password=postgres";

// This is spinning up Marten with its default settings
await using var store = DocumentStore.For(connectionString);

// Create a Marten unit of work
await using var session = store.LightweightSession();

var contact = new Contact(ContactChannel.Email, "Han", "Solo");
var userId = Guid.NewGuid();

// I'm telling the Marten session about the new stream, and then recording
// the newly assigned Guid for this stream
var customerId = Guid.NewGuid();
var incidentId = session.Events.StartStream(
    new IncidentLogged(customerId, contact, "Software is crashing",userId),
    new IncidentCategorised
        Category = IncidentCategory.Database,
        UserId = userId

await session.SaveChangesAsync();

// And now let's append an additional event to the 
// new stream
session.Events.Append(incidentId, new IncidentPrioritised(IncidentPriority.High, userId));
await session.SaveChangesAsync();

Let’s talk about what I just did — and did not do — in the code above. The DocumentStore class in Marten establishes the storage configuration for a single, logical Marten-ized database. This is an expensive object to create, so there should only ever be one instance in your system.

The actual work is done with Marten’s IDocumentSession service that I created with the call to store.LightweightSession(). The IDocumentSession is Marten’s unit of work implementation and plays the same role as DbContext does inside of EF Core. When you use Marten, you queue up operations (start a new event stream, append events, etc.), then commit them in one single database transaction when you call that SaveChangesAsync() method.

For anybody old enough to have used NHibernate reading this, DocumentStore plays the same role as NHibernate’s ISessionFactory.

So now, let’s read back in the events we just persisted, and print out serialized JSON of the Marten data just to see what Marten is actually capturing:

var events = await session.Events.FetchStreamAsync(incidentId);
foreach (var e in events)
    // I elided a little bit of code that sets up prettier JSON
    // formatting
    Console.WriteLine(JsonConvert.SerializeObject(e, settings));

The raw JSON output is this:

  "Data": {
    "CustomerId": "314d8fa1-3cca-4984-89fc-04b24122cf84",
    "Contact": {
      "ContactChannel": "Email",
      "FirstName": "Han",
      "LastName": "Solo",
      "EmailAddress": null,
      "PhoneNumber": null
    "Description": "Software is crashing",
    "LoggedBy": "8a842212-3511-4858-a3f3-dd572a4f608f"
  "EventType": "Helpdesk.Api.IncidentLogged, Helpdesk.Api, Version=, Culture=neutral, PublicKeyToken=null",
  "EventTypeName": "incident_logged",
  "DotNetTypeName": "Helpdesk.Api.IncidentLogged, Helpdesk.Api",
  "IsArchived": false,
  "AggregateTypeName": null,
  "StreamId": "018c1c9b-5bd0-4273-947d-83d28c8e3210",
  "StreamKey": null,
  "Id": "018c1c9b-5f03-47f5-8c31-1d1ba70fd56a",
  "Version": 1,
  "Sequence": 1,
  "Timestamp": "2023-11-29T19:43:13.864064+00:00",
  "TenantId": "*DEFAULT*",
  "CausationId": null,
  "CorrelationId": null,
  "Headers": null
  "Data": {
    "Category": "Database",
    "UserId": "8a842212-3511-4858-a3f3-dd572a4f608f"
  "EventType": "Helpdesk.Api.IncidentCategorised, Helpdesk.Api, Version=, Culture=neutral, PublicKeyToken=null",
  "EventTypeName": "incident_categorised",
  "DotNetTypeName": "Helpdesk.Api.IncidentCategorised, Helpdesk.Api",
  "IsArchived": false,
  "AggregateTypeName": null,
  "StreamId": "018c1c9b-5bd0-4273-947d-83d28c8e3210",
  "StreamKey": null,
  "Id": "018c1c9b-5f03-4a19-82ef-9c12a84a4384",
  "Version": 2,
  "Sequence": 2,
  "Timestamp": "2023-11-29T19:43:13.864064+00:00",
  "TenantId": "*DEFAULT*",
  "CausationId": null,
  "CorrelationId": null,
  "Headers": null
  "Data": {
    "Priority": "High",
    "UserId": "8a842212-3511-4858-a3f3-dd572a4f608f"
  "EventType": "Helpdesk.Api.IncidentPrioritised, Helpdesk.Api, Version=, Culture=neutral, PublicKeyToken=null",
  "EventTypeName": "incident_prioritised",
  "DotNetTypeName": "Helpdesk.Api.IncidentPrioritised, Helpdesk.Api",
  "IsArchived": false,
  "AggregateTypeName": null,
  "StreamId": "018c1c9b-5bd0-4273-947d-83d28c8e3210",
  "StreamKey": null,
  "Id": "018c1c9b-5fef-4644-b213-56051088dc15",
  "Version": 3,
  "Sequence": 3,
  "Timestamp": "2023-11-29T19:43:13.909+00:00",
  "TenantId": "*DEFAULT*",
  "CausationId": null,
  "CorrelationId": null,
  "Headers": null

And that’s a lot of noise, so let me try to summarize the blob above:

  • Marten is storing each event as serialized JSON in one table, and that’s what you see as the Data leaf in each JSON document above
  • Marten is assigning a unique sequence number for each event
  • StreamId is the incident stream identity that groups the events
  • Each event is assigned a Version that reflects its position within its stream
  • Marten tracks the kind of metadata that you’d probably expect, like timestamps, optional header information, and optional causation/correlation information (we’ll use this much later in the series when I get around to discussing Open Telemetry)

Summary and What’s Next

In this post I introduced the core concepts of event sourcing, events, and event streams. I also introduced the bare bones usage of the Marten library as a way to create new event streams and append events to existing events. Lastly, we took a look at the important metadata that Marten tracks for you in addition to your raw event data. Along the way, we also previewed how the Critter Stack can reduce development time friction by very happily building out the necessary database schema objects for us as needed.

What you are probably thinking at this point is something to the effect of “So what?” After all, jamming little bits of JSON data into the database doesn’t necessarily help us build a user interface page showing a help desk technician what the current state of each open incident is. Heck, we don’t yet have any way to understand the actual current state of any incident!

Fear not though, because in the next post I’ll introduce Marten “Projections” capability that will help us create the “read side” view of the current system state out of the raw event data in whatever format happens to be most convenient for that data’s client or user.

Read the whole story
3 hours ago
West Grove, PA
Share this story

Pulumi Cloud Adds Multi-factor Authentication

1 Share

We are excited to announce that all users of Pulumi Cloud can now secure their account with multi-factor authentication (MFA). By requiring an additional verification step during the login process, MFA shields against unauthorized access, reducing the risk of breaches. This feature aligns with our commitment to providing robust security measures for our users. As an organization administrator, you can further protect your organization by having your members enable MFA.

How to set it up

Let’s walk through the steps to enable MFA on your account:

  1. Click on your account avatar in the top right corner
  2. Navigate to Account Settings
  3. Scroll to the MFA section
  4. Press Enroll Screenshot of enrolling in MFA
  5. Use your authenticator application of choice to scan the QR code or paste the code
  6. On your next login, you will be prompted for a passcode: Screenshot of login experience with MFA enabled

You are enrolled! When signing in you will now be asked for a one-time passcode.


Initially, MFA in Pulumi Cloud will support TOTP (time-based one-time passwords) and it will only be available for Pulumi Cloud-backed users. Users authenticating with a third party (such as GitHub or GitLab) will need to use MFA through those providers at this time. Some other future improvements we are considering and will prioritize based on feedback we hear from customers are extending support for WebAuthN/passkeys, Duo, SMS/Email OTP and admins enforcing MFA for their entire organization.

Wrapping it up

We’re committed to continually evolving our services to meet the needs of our diverse and growing user base. Stay tuned for more updates and features as we progress on this journey together.

Read the whole story
3 hours ago
West Grove, PA
Share this story

The Most Upvoted Visual Studio Code Feature

1 Share

Up until yesterday (technically, a few days ago), tabs were bound to the same vscode window, but today, things have changed.

What happens if I release a tab outside the window... here’s the magic!

Floating window

The thing is, if you’re not amazed by this new feature, well, you should know that this is the most upvoted issue ever on vscode.

You can check by yourself if you don't believe me, just sort the issues by upvotes (👍) and this is the result:


If you don't see it working for you, it's probably because you're using the stable version of vscode. As of today the feature is still in preview and only available in the Insiders edition.

I mean, not that this is a problem, you can get it from the official website


This version is basically a client that gets updated pretty much once a day including the latest features.

Does that mean that sometimes things are broken? Yeah, it could happen, but in the last couple of years I think it happend just twice to have bugs making it unusable, which anyway got fixed in just a few hours, so nothing really to worry about.

One that I remember was on the Explorer tab, clicking on folders did not open/collapse them, making it pretty much unusable. I was searching files by name from the quick pick menu. Anyway, in probably an hour or two it got fixed and in any case the stable version was still working. Not a big deal.

Playing with the new feature

On Mac I noticed a little issue when dragging the tab, with that unskippable animation (you can also see in the gif above) and I played a little bit with it, by spawning multiple windows, grouping them and closing one tab vs the full window.

I also noticed that global shortcuts (such as opening the terminal) even if launched on the floating windows are actually sent to the main window, which I think it makes sense.

But features like this are better seen than explained, so as usual I recorded a short demo for my YouTube channel, enjoy:

Keeping up to date

If you'd like to know more and how the feature is evolving, you can keep an eye on the open issues on the GitHub repo. All issues are tagged with the workbench-auxwindow label, so you can filter them out here:

Thanks for reading this article, I hope you found it interesting!

I recently launched my Discord server to talk about Open Source and Web Development, feel free to join:

Do you like my content? You might consider subscribing to my YouTube channel! It means a lot to me ❤️
You can find it here:

Feel free to follow me to get notified when new articles are out ;)

Read the whole story
3 hours ago
West Grove, PA
Share this story
Next Page of Stories