ServiceStack v6.8

Celebrating 150M Total Downloads

Before we dive into the exciting new features in this release, we're happy to report that ServiceStack has eclipsed 150M total downloads!

Which comes just after a year from 100M downloads in v6 Release when we announced ServiceStack is now FREE for Individuals & OSS!

We're celebrating this new milestone with a brand new website, rewritten from Ruby's Jekyll with an exciting new Razor SSG project template we're also announcing in this release which takes advantage of the advances we've made in the last few releases with our npm dependency-free approach to Simple, Modern JavaScript, the built-in support for Prerendering Razor Pages and the rich Tailwind Vue Component Library to create an enjoyable experience for creating Fast, FREE, beautiful, CDN-hostable static generated websites & blogs!

Checkout the new Vue, Tailwind & Razor SSG powered servicestack.net website!

Migrating away from Jekyll

We have a lot to thank Jekyll for as the pioneer of statically generated websites and igniting the Jamstack movement showing the benefits of markdown powered, statically-generated websites. However managing Ruby from Windows/WSL has always been a source of friction which was awkward to manage from a different filesystem which ultimately prompted seeking an alternative solution after Jekyll upgrades broke RubyMine support forcing the use of text editors to maintain our website code-base and content.

Search for a better SSG Solution

Our experience with maintaining extensive documentation in VitePress and static website content in Jekyll has effectively taught us that any heavy content website that can be maintained in Markdown and statically generated, should be. Not only is it the fastest way to deliver static content from CDN edge caches, inexpensive & portable to host, but it's vastly more reliable than an App Server that's dependent on uptime of VMs, infrastructure dependencies, load balancer and firewall misconfigurations, account or billing issues, etc.

SSG C# Razor Pages with Vue & Tailwind

Deterred by the growing complexity of current SSG solutions, we decided to create a new solution using C# & Razor Pages with a clean implementation that allowed full control with an npm dependency-free solution letting us adopt our preferred approach to Simple, Modern JavaScript without any build-tooling or SPA complexity.

We're happy with the results of https://servicestack.net new Razor SSG website - a clean, crisp code-base utilizing simple JS Module Vue 3 components, the source code of which is publicly maintained at:

Which serves as a good example at how well Razor SSG scales for larger websites.

Benefits from Jekyll

Whilst still only at v1 release, we found it already had a number of advantages over the existing Jekyll static website:

  • Faster live reloads
  • C#/Razor more type-save & productive than Ruby/Liquid
  • Greater flexibility in implementing new features
  • Better IDE support (from Rider)
  • Ability to reuse our .NET libraries
  • Better development experience from Windows

Create FREE Razor SSG Websites from your Browser!

We're excited to finally be able to share the benefits of razor-ssg project template FREE to all .NET Developers for creating Fast, Beautiful Static Websites & Blogs that's not only Free to create, it's Free to host on GitHub Pages CDN and also designed to support running free from any local .NET Installations or IDEs where it can be maintained in GitHub Codespaces that you can do entirely from an iPad!

In this video, we walk through the entire workflow of creating, updating and adding features to a custom Razor SSG website from just a browser using Codespaces that auto publishes to your GitHub Repo's gh-pages branch where it's hosted for free on GitHub Pages CDN:

We're hope you're just as excited about being able to create new fast, static websites with Razor Pages & Markdown from a Browser and hope to see many fast beautiful static websites & blogs created with it.

Introducing Razor SSG

C# & Razor Pages alternative to Jekyll for creating beautiful websites & blogs

Razor SSG is a Razor Pages powered Markdown alternative to Ruby's Jekyll & Next.js that's ideal for generating static websites & blogs using C#, Razor Pages & Markdown.

GitHub Codespaces friendly

In addition to having a pure Razor + .NET solution to create fast, CDN-hostable static websites, it also aims to provide a great experience from GitHub Codespaces, where you can create, modify, preview & check-in changes before the included GitHub Actions auto deploy changes to its GitHub Pages CDN - all from your iPad!

Enhance with simple, modern JavaScript

For enhanced interactivity, static markdown content can be progressively enhanced with Vue 3 components, as done in this example that embed's the GettingStarted.mjs Vue Component to create new Razor SSG App's below with:

<div data-component="GettingStarted" data-props="{ template:'razor-ssg' }"></div>

Although with full control over the websites _Layout.cshtml, you're free to use any preferred JS Module or Web Component you prefer.

Razor Pages

Your website can be built using either Markdown .md or Razor .cshtml pages, although it's generally recommended to use Markdown to capture the static content for your website for improved productivity and ease of maintenance.

Content in Markdown, Functionality in Razor Pages

The basic premise behind most built-in features is to capture static content in markdown using a combination of folder structure & file name conventions in addition to each markdown page's frontmatter & content. This information is then used to power each feature using Razor pages for precise layout and functionality.

The template includes the source code for each website feature, enabling full customization that also serves as good examples for how to implement your own custom markdown-powered website features.

Markdown Feature Structure

All markdown features are effectively implemented in the same way, starting with a _folder for maintaining its static markdown content, a .cs class to load the markdown and a .cshtml Razor Page to render it:

Location Description
/_{Feature} Maintains the static markdown for the feature
Markdown.{Feature}.cs Functionality to read the feature's markdown into logical collections
{Feature}.cshtml Functionality to Render the feature
Configure.Ssg.cs Initializes and registers the feature with ASP .NET's IOC

Lets see what this looks like in practice by walking through the "Pages" feature:

Pages Feature

The pages feature simply makes all pages in the _pages folder, available from /{filename}.

Where the included pages:

/_pages

Are made available from:

Loading Pages Markdown

The code that loads the Pages feature markdown content is in Markdown.Pages.cs:

public class MarkdownPages : MarkdownPagesBase<MarkdownFileInfo>
{
    public MarkdownPages(ILogger<MarkdownPages> log, IWebHostEnvironment env) 
        : base(log,env) {}
    
    List<MarkdownFileInfo> Pages { get; set; } = new();
    public List<MarkdownFileInfo> VisiblePages => Pages.Where(IsVisible).ToList();

    public MarkdownFileInfo? GetBySlug(string slug) => 
        Fresh(VisiblePages.FirstOrDefault(x => x.Slug == slug));

    public void LoadFrom(string fromDirectory)
    {
        Pages.Clear();
        var fs = AssertVirtualFiles();
        var files = fs.GetDirectory(fromDirectory).GetAllFiles().ToList();
        var log = LogManager.GetLogger(GetType());
        log.InfoFormat("Found {0} pages", files.Count);

        var pipeline = CreatePipeline();

        foreach (var file in files)
        {
            var doc = Load(file.VirtualPath, pipeline);
            if (doc == null)
                continue;

            Pages.Add(doc);
        }
    }
}

Which ultimately just loads Markdown files using the configured Markdig pipeline in its Pages collection which is made available via its VisiblePages property which returns all documents in development whilst hiding Draft and content published at a Future Date from production builds.

Rendering Markdown Pages

The pages are then rendered in Page.cshtml Razor Page that's available from /{slug}

@page "/{slug}"
@model MyApp.Page
@inject MarkdownPages Markdown

@implements IRenderStatic<MyApp.Page>
@functions {
    public List<Page> GetStaticProps(RenderContext ctx)
    {
        var markdown = ctx.Resolve<MarkdownPages>();
        return markdown.VisiblePages.Map(page => new Page { Slug = page.Slug! });
    }
}

@{
    var doc = Markdown.GetBySlug(Model.Slug);
    if (doc.Layout != null) 
        Layout = doc.Layout == "none"
            ? null
            : doc.Layout;
    ViewData["Title"] = doc.Title;
}

<link rel="stylesheet" href="css/typography.css">
<section class="flex-col md:flex-row flex justify-center mt-16 mb-16 md:mb-12">
    <h1 class="text-4xl tracking-tight font-extrabold text-gray-900">
        @doc.Title
    </h1>
</section>    
<div class="mx-auto">
    <div class="mx-auto prose lg:prose-xl mb-24">
        @Html.Raw(doc.Preview)
    </div>
</div>

@await Html.PartialAsync("HighlightIncludes")
<script>hljs.highlightAll()</script>

Which uses a custom layout if one is defined in its frontmatter which speaking.md utilizes in its layout frontmatter:

---
title: Speaking
layout: _LayoutContent
---

To render the page using _LayoutContent.cshtml visible by the background backdrop in its /speaking page.

What's New Feature

The /whatsnew page is an example of creating a custom Markdown feature to implement a portfolio or a product releases page where a new folder is created per release, containing both release date and release or project name, with all features in that release maintained markdown content sorted in alphabetical order:

/_whatsnew

What's New follows the same structure as Pages feature which is loaded in:

and rendered in:

Blog Feature

The blog maintains its markdown posts in a flat folder which each Markdown post containing its publish date and URL slug it should be published under:

/_posts

  • 2023-01-21_start.md
  • 2023-03-21_javascript.md
  • 2023-03-28_razor-ssg.md

As the Blog has more features it requires a larger Markdown.Blog.cs to load its Markdown posts that is rendered in several different Razor Pages for each of its Views:

Page Description Example
Blog.cshtml Main Blog layout /blog
Posts/Index.cshtml Navigable Archive grid of Posts /posts
Posts/Post.cshtml Individual Blog Post (like this!) /posts/razor-ssg
Author.cshtml Display Posts by Author /posts/author/lucy-bates
Tagged.cshtml Display Posts by Tag /posts/tagged/markdown
Year.cshtml Display Posts by Year /posts/year/2023

General Features

Most unique markdown features are captured in their Markdown's frontmatter metadata, but in general these features are broadly available for all features:

  • Live Reload - Latest Markdown content is displayed during Development
  • Custom Layouts - Render post in custom Razor Layout with layout: _LayoutAlt
  • Drafts - Prevent posts being worked on from being published with draft: true
  • Future Dates - Posts with a future date wont be published until that date

Initializing and Loading Markdown Features

All markdown features are initialized in the same way in Configure.Ssg.cs where they're registered in ASP.NET Core's IOC and initialized after the App's plugins are loaded by injecting with the App's Virtual Files provider before using it to read from the directory where the markdown content for each feature is maintained:

public class ConfigureSsg : IHostingStartup
{
    public void Configure(IWebHostBuilder builder) => builder
        .ConfigureServices(services =>
        {
            services.AddSingleton<RazorPagesEngine>();
            services.AddSingleton<MarkdownPages>();
            services.AddSingleton<MarkdownWhatsNew>();
            services.AddSingleton<MarkdownBlog>();
        })
        .ConfigureAppHost(afterPluginsLoaded: appHost => {
            var pages = appHost.Resolve<MarkdownPages>();
            var whatsNew = appHost.Resolve<MarkdownWhatsNew>();
            var blogPosts = appHost.Resolve<MarkdownBlog>();
            
            var features = new IMarkdownPages[] { pages, whatsNew, blogPosts }; 
            features.Each(x => x.VirtualFiles = appHost.VirtualFiles);

            // Custom initialization
            blogPosts.Authors = Authors;

            // Load feature markdown content
            pages.LoadFrom("_pages");
            whatsNew.LoadFrom("_whatsnew");
            blogPosts.LoadFrom("_posts");
        });
    });
    //...
}

These dependencies are then injected in the feature's Razor Pages to query and render the loaded markdown content.

Custom Frontmatter

You can extend the MarkdownFileInfo type used to maintain the markdown content and metadata of each loaded Markdown file by adding any additional metadata you want included as C# properties on:

// Add additional frontmatter info to include
public class MarkdownFileInfo : MarkdownFileBase
{
}

Any additional properties are automatically populated using ServiceStack's built-in Automapping which includes rich support for converting string frontmatter values into native .NET types.

Updating to latest versions

You can easily update all the JavaScript dependencies used in postinstall.js by running:

node postinstall.js

This will also update the Markdown features *.cs implementations which is delivered as source files instead of an external NuGet package to enable full customization, easier debugging whilst supporting easy upgrades.

If you do customize any of the .cs files, you'll want to exclude them from being updated by removing them from:

const hostFiles = [
    'Markdown.Blog.cs',
    'Markdown.Pages.cs',
    'Markdown.WhatsNew.cs',
    'MarkdownPagesBase.cs',
]

Markdown Tag Helper

The included MarkdownTagHelper.cs can be used in hybrid Razor Pages like About.cshtml to render the /about page which requires the flexibility of Razor Pages with a static content component which you prefer to maintain inline with Markdown.

The <markdown /> tag helper renders plain HTML, which you can apply Tailwind's @typography styles by including typography.css and annotating it with your preferred prose variant, e.g:

<link rel="stylesheet" href="css/typography.css">
<markdown class="prose">
  Markdown content...
</markdown>

Static Static Generation (SSG)

All features up till now describes how this template implements a Markdown powered Razor Pages .NET application, where this template differs in its published output, where instead of a .NET App deployed to a VM or App server it generates static *.html files that's bundled together with /wwwroot static assets in the /dist folder that can be previewed by launching a HTTP Server from that folder with the built-in npm script:

npm run serve

To run npx http-server on http://localhost:8080 that you can open in a browser to preview the published version of your site as it would be when hosted on a CDN.

Static Razor Pages

The static generation functionality works by scanning all your Razor Pages and prerendering the pages with prerendering instructions.

Pages with Static Routes

Pages with static routes can be marked to be prerendered by annotating it with the [RenderStatic] attribute as done in About.cshtml:

@page "/about"
@attribute [RenderStatic]

Which saves the pre-rendered page using the pages route with a .html suffix, e.g: /{@page route}.html whilst pages with static routes with a trailing / are saved to /{@page route}/index.html as done for Posts/Index.cshtml:

@page "/posts/"
@attribute [RenderStatic]

Explicit generated paths

To keep the generated pages in-sync with using the same routes as your Razor Pages in development it's recommended to use the implied rendered paths, but if preferred you can specify which path the page should be rendered to instead with:

@page "/posts/"
@attribute [RenderStatic("/posts/index.html")]

Pages with Dynamic Routes

Prerendering dynamic pages follows Next.js getStaticProps convention which you can implement using IRenderStatic<PageModel> by returning a Page Model for each page that should be generated as done in Posts/Post.cshtml and Page.cshtml:

@page "/{slug}"
@model MyApp.Page

@implements IRenderStatic<MyApp.Page>
@functions {
    public List<Page> GetStaticProps(RenderContext ctx)
    {
        var markdown = ctx.Resolve<MarkdownPages>();
        return markdown.VisiblePages.Map(page => new Page { Slug = page.Slug! });
    }
}
...

In this case it returns a Page Model for every Visible markdown page in /_pages that ends up rendering the following pages in /dist:

  • /privacy.html
  • /speaking.html
  • /uses.html

Limitations

The primary limitations for developing statically generated Apps is that a snapshot of entire App is generated at deployment, which prohibits being able to render different content per request, e.g. for Authenticated users which would require executing custom JavaScript after the page loads to dynamically alter the page's initial content.

Otherwise in practice you'll be able develop your Razor Pages utilizing Razor's full feature-set, the primary concessions stem from Pages being executed in a static context which prohibits pages from returning dynamic content per request, instead any "different views" should be maintained in separate pages.

No QueryString Params

As the generated pages should adopt the same routes as your Razor Pages you'll need to avoid relying on ?QueryString params and instead capture all required parameters for a page in its @page route as done for:

Posts/Author.cshtml

@page "/posts/author/{slug}"
@model AuthorModel
@inject MarkdownBlog Blog

@implements IRenderStatic<AuthorModel>
@functions {
    public List<AuthorModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
        .AuthorSlugMap.Keys.Map(x => new AuthorModel { Slug = x });
}
...

Which lists all posts by an Author, e.g: /posts/author/lucy-bates, likewise required for:

Posts/Tagged.cshtml

@page "/posts/tagged/{slug}"
@model TaggedModel
@inject MarkdownBlog Blog

@implements IRenderStatic<TaggedModel>
@functions {
    public List<TaggedModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
        .TagSlugMap.Keys.Map(x => new TaggedModel { Slug = x });
}
...

Which lists all related posts with a specific tag, e.g: /posts/tagged/markdown, and for:

Posts/Year.cshtml

@page "/posts/year/{year}"
@model YearModel
@inject MarkdownBlog Blog

@implements IRenderStatic<YearModel>
@functions {
    public List<YearModel> GetStaticProps(RenderContext ctx) => ctx.Resolve<MarkdownBlog>()
        .VisiblePosts.Select(x => x.Date.GetValueOrDefault().Year)
            .Distinct().Map(x => new YearModel { Year = x });
}

...

Which lists all posts published in a specific year, e.g: /posts/year/2023.

Conceivably these "different views" could've been implemented by the same page with different ?author, ?tag and ?year QueryString params, but are instead extracted into different pages to support its statically generated *.html outputs.

Prerendering Task

The prerender AppTask that pre-renders the entire website is also registered in Configure.Ssg.cs:

  .ConfigureAppHost(afterAppHostInit: appHost =>
  {
      // prerender with: `$ npm run prerender` 
      AppTasks.Register("prerender", args =>
      {
          var distDir = appHost.ContentRootDirectory.RealPath.CombineWith("dist");
          if (Directory.Exists(distDir))
              FileSystemVirtualFiles.DeleteDirectory(distDir);
          FileSystemVirtualFiles.CopyAll(
              new DirectoryInfo(appHost.ContentRootDirectory.RealPath.CombineWith("wwwroot")),
              new DirectoryInfo(distDir));
          var razorFiles = appHost.VirtualFiles.GetAllMatchingFiles("*.cshtml");
          RazorSsg.PrerenderAsync(appHost, razorFiles, distDir).GetAwaiter().GetResult();
      });
  });
  //...

Which we can see:

  1. Deletes /dist folder
  2. Copies /wwwroot contents into /dist
  3. Passes all App's Razor *.cshtml files to RazorSsg to do the pre-rendering

Where it processes all pages with [RenderStatic] and IRenderStatic<PageModel> prerendering instructions to the /dist folder.

Previewing prerendered site

To preview your SSG website, run the prerendered task with:

npm run prerender

Which renders your site to /_dist which you can run a HTTP Server from with:

npm run serve

That you can preview with your browser at http://localhost:8080

Publishing

The included build.yml GitHub Action takes care of running the prerendered task and deploying it to your Repo's GitHub Pages where it will be available at:

https://$org_name.github.io/$repo/

Alternatively you can use a Custom domain for GitHub Pages by registering a CNAME DNS entry for your preferred Custom Domain, e.g:

Record Type Value TTL
mydomain.org CNAME org_name.github.io 3600

That you can either configure in your Repo settings or if you prefer to maintain it with your code-base, save the domain name to /wwwroot/CNAME, e.g:

www.mydomain.org

Feedback & Feature Requests Welcome

In future we'll look at expanding this template with generic Markdown features suitable for websites, blogs & portfolios, or maintain a shared community collection if there ends up being community contributions of Razor SSG & Markdown features.

In the meantime, we welcome any feedback or new feature requests at: https://servicestack.net/ideas

Flutter

The other primary focus in this release was improving the end-to-end development experience of creating Flutter Mobile Apps that integrate with your ServiceStack APIs. To demonstrate the seamless development experience, we walk through creating a new Blazor Server project that we run x mix flutter on to create a new Flutter App that we can use to quickly build a Mobile App to call its existing Todo .NET APIs using the built-in Typed Dart DTOs integration:

Create a Flutter Todo App with .NET APIs

Add Flutter Mobile Apps to Existing ServiceStack Projects with mix flutter

Now that we can see how these tools work together to be able to create a simple App, lets try our hand at a more advanced example and create a full CRUD Bookings App that calls new AutoQuery Bookings APIs.

Flutter and AutoQuery

In this video, we walk through creating a new Flutter Android Bookings App from scratch and add it to an existing .NET project using:

x mix flutter

After adding the Flutter App to the .NET project, we dive into the development process, where we demonstrate how to call typed .NET APIs using typed Dart DTOs for end-to-end typed integrations that can be updated from within Android Studio, making it easy to maintain your App in-sync over time.

Use Flutter to build a Bookings CRUD App, Fast

Rapidly develop full-featured CRUD Apps with Flutter & AutoQuery

Simple Walkthrough of creating Flutter Apps with VS Code

The above examples use Android Studio, however Flutter also provides a great development experience from VS Code, which is great to start with to gain a better understanding how the different integrations work together without being hidden behind UI tooling.

VS Code

Create a simple Flutter & .NET API App with VS Code

In this example we'll walk through creating a new Flutter Mobile App with a .NET API backend and demonstrate the development workflow of calling new .NET APIs with Dart using VS Code.

Pre-requisites

Ensure you have the Flutter SDK and the x dotnet tool installed:

dotnet tool install --global x

Both Flutter and .NET have great CLI tooling where you can get a great development experience using a text editor like VS Code with its great multi-terminal support.

1. Create your .NET Project

Start with your preferred project, or create an empty Project with your preferred project name:

x new web MyApp

That can be run immediately in the Host project with dotnet watch for a live reload dev UX:

dotnet watch

2. Add a new Flutter App to your project

Then from a new Terminal in your Solution folder (e.g. MyApp), add a new Flutter App with:

x mix flutter

This will run flutter create to add a new Flutter App to your solution that should look like:

It also adds the servicestack client library package that's pre-configured to call the default Hello API in new ServiceStack projects which we can test by running fluter from myapp_flutter:

flutter run

We recommend choosing [1] to run the Flutter App as a Windows Desktop App for the best dev UX on Windows, which after a few moments should open the Hello App in a Desktop App:

3. Create your .NET API

Lets create a new API to preview the typical dev workflow of calling new .NET APIs from Flutter. First we'll want to define our API's Service Contract with the Request and Response DTOs the API will accept and return in:

MyApp.ServiceModel/SearchFiles.cs

using ServiceStack;
using System.Collections.Generic;

namespace MyApp.ServiceModel;

public class SearchFiles : IReturn<SearchFilesResponse>
{
   public string Pattern { get; set; }
}

public class SearchFilesResponse
{
   public List<string> Results { get; set; }
}

Then add its Implementation that scans the .NET App's folder for files and returns the results:

MyApp.ServiceInterface/SearchFilesService.cs

using ServiceStack;
using MyApp.ServiceModel;

namespace MyApp.ServiceInterface;

public class SearchFilesServices : Service
{
   public object Any(SearchFiles request)
   {
       var files = VirtualFiles.GetAllMatchingFiles(request.Pattern ?? "*");
       return new SearchFilesResponse {
           Results = files.Map(x => x.VirtualPath)
       };
   }
}

TIP

dotnet watch should automatically detect the changes and reload the App to take effect

3. Update Flutter App

Then to update our clients Dart DTOs we just need to run x dart from the Flutter project:

x dart

Which can now be used in our App, by replacing the callService() and build() methods with:

void callService() async {
 var response = await client.get(SearchFiles(pattern: myController.text));
 setState(() {
   result = response.results!.join('\n');
 });
}
@@override
Widget build(BuildContext context) {
 return Scaffold(
   appBar: AppBar(title: Text(widget.title),),
   body: Center(
     child: Column(
       mainAxisAlignment: MainAxisAlignment.center,
       children: <Widget>[
         Container(
           padding: const EdgeInsets.only(top: 20),
           margin: const EdgeInsets.only(left: 100.0, right: 100.0),
           child: Column(
             children: [
               const Text(
                 'Search Files',
                 style:
                     TextStyle(fontWeight: FontWeight.bold, fontSize: 24.0),
               ),
               TextField(
                 decoration: const InputDecoration(
                   border: OutlineInputBorder(),
                   hintText: '*',
                 ),
                 controller: myController,
               )
             ],
           ),
         ),
         Padding(
           padding: const EdgeInsets.all(8.0),
           child: Text(
             result,
             style: const TextStyle(fontSize: 18),
           ),
         ),
       ],
     ),
   ),
 );
}

Which after a restart should now call our new SearchFiles API returning .NET App files that match the specified pattern:

We hope this was a good waltkthrough to showcase the simplified end-to-end typed dev model for working with ServiceStack's APIs, be sure to check out the Flutter videos for more in-depth explanations or head over to our Support Channels if you have any issues or questions.

ServiceStack Dart Client Improvements

To coincide with this release we've also released a new version of the servicestack Dart client library, you can update with:

dependencies:
  servicestack: ^2.0.3

Create Service Clients configured to use /api JSON Route

Which includes new ClientFactory.api() constructors that can be used to create multi-platform Native and Web clients that are configured to use the JSON /api pre-defined route, e.g:

import 'package:servicestack/web_client.dart' if (dart.library.io) 'package:servicestack/client.dart';

var client = ClientFactory.api(baseUrl);

Or if you're only building Flutter Apps for a single platform you can use the .api() constructor on the Service Client:

var client = JsonServiceClient.api(baseUrl);
var client = JsonWebClient.api(baseUrl);

New ApiResult APIs

Our C# Clients and JS/TypeScript clients have greatly benefited from the new Api* methods which encapsulates Successful and Failed API Responses in a single ApiResult<TResponse> value which can be passed down and bound to reactive components without any inhibitive try/catch statements.

These same benefits are now available in the Dart Service Clients with the new api() and apiVoid() methods, e.g:

var api = await client.api(Hello(name:"World"));

if (api.succeeded) {
    Inspect.dump(api.response);
} else {
    print(api.error!.message);
}

By default they'll call APIs using the APIs preferred default method which you're encouraged to explicitly annotate on your Request DTOs when they're not able to be implicitly inferred to ensure APIs are invoked with your preferred HTTP Method.

Reserved Words and built-in Types use PascalCase

To avoid conflicts C# properties named using Dart keywords or built-in types are rewritten in PascalCase. These keywords can be managed in the DartGenerator.ReservedWords set.

Flutter gRPC

Whilst using the native Dart DTOs and generic Dart Service Clients provides the most productive development experience with multi-platform support, developers who prefer or have existing investments in gRPC will be able to benefit from the improved development experience with:

mix flutter-grpc

Which works like mix flutter but instead adds a new Flutter gRPC App to an existing ServiceStack grpc project, with pre-configured gRPC integrations. To demonstrate this productive workflow we've created a new video which walks through the process of creating a new Flutter App from scratch, adding a Flutter gRPC App to an existing grpc .NET project, and using mix flutter-grpc to configure the Flutter App to work with ServiceStack's gRPC Services.

Fastest way to a working gRPC Server and Flutter solution

Easily configure a Flutter Apps with gRPC .NET services using mix flutter-grpc

The video also shows how to update the Dart DTOs and regenerate the gRPC clients automatically, making it easy to keep the Flutter App in sync with the backend .NET APIs.

Npgsql v7.0.2

Now that Npgsql's has achieved a period of stability without introducing major breaking changes in each release, we've gone ahead and added support for the latest stable version of Npgsql v7.0.2 in all .NET 6 / .NET Standard 2.0 and .NET Framework v4.7.2 builds in ServiceStack.OrmLite.PostgreSQL:

<PackageReference Include="Npgsql" Version="7.0.2" />

<PackageReference Include="ServiceStack.OrmLite.PostgreSQL" Version="6.*" />

Vue TypeScript JAMStack templates Upgraded

The vue-vite and vue-ssg templates have been upgraded to use the latest dependencies including Vite 4 and updated to integrate with the rich @servicestack/vue Tailwind component library, utilizing its shared client dependency and integrated auth, including an AutoQueryGrid version of their Bookings CRUD UI, enabling the creation of highly productive AutoQuery CRUD UIs:

Their use of TypeScript, Vite 4 and Vue SFC contains the ultimate development experience for Vue Apps with rich static analysis & optimal syntax for creating Vue 3 components. Checkout their Live Demos for instructions on how to quickly get started with either template:

OrmLite WhereExists APIs

The new WhereExists and WhereNotExists APIs allows creating typed SQL Expressions that use EXISTS or NOT EXISTS with a SQL Sub Select. For example we can construct an SQL Query like this:

SELECT a.*
FROM Table1 a
WHERE NOT EXISTS (SELECT NULL FROM Table2 WHERE a.Id = Table2.Table1Id)

By combining 2 Typed SQL Expressions together using the new WhereNotExists(sqlExpression) API, e.g:

var subExpr = db.From<Table2>()
    .Where<Table1,Table2>((a,b) => 
            b.Table1Id == Sql.TableAlias(a.Id, "a"))
    .Select(Sql.Custom("NULL"));

var q = db.From<Table1>(db.TableAlias("a"))
    .WhereNotExists(subExpr);

var results = db.Select(q);