Scott Hanselman

Trying out new .NET Core Alpine Docker Images

November 22, '17 Comments [6] Posted in Docker | DotNetCore | Open Source
Sponsored By

Docker ContainersI blogged recently about optimizing .NET and ASP.NET Docker files sizes. .NET Core 2.0 has previously been built on a Debian image but today there is preview image with .NET Core 2.1 nightlies using Alpine. You can read about the announcement here about this new Alpine preview image. There's also a good rollup post on .NET and Docker.

They have added two new images:

  • 2.1-runtime-alpine
  • 2.1-runtime-deps-alpine

Alpine support is part of the .NET Core 2.1 release. .NET Core 2.1 images are currently provided at the microsoft/dotnet-nightly repo, including the new Alpine images. .NET Core 2.1 images will be promoted to the microsoft/dotnet repo when released in 2018.

NOTE: The -runtime-deps- image contains the dependancies needed for a .NET Core application, but NOT the .NET Core runtime itself. This is the image you'd use if your app was a self-contained application that included a copy of the .NET Core runtime. This is apps published with -r [runtimeid]. Most folks will use the -runtime- image that included the full .NET Core runtime. To be clear:

- The runtime image contains the .NET Core runtime and is intended to run Framework-Dependent Deployed applications - see sample

- The runtime-deps image contains just the native dependencies needed by .NET Core and is intended to run Self-Contained Deployed applications - see sample

It's best with .NET Core to use multi-stage build files, so you have one container that builds your app and one that contains the results of that build. That way you don't end up shipping an image with a bunch of SDKs and compilers you don't need.

NOTE: Read this to learn more about image versions in Dockerfiles so you can pick the right tag and digest for your needs. Ideally you'll pick a docker file that rolls forward to include the latest servicing patches.

Given this docker file, we build with the SDK image, then publish, and the result is about 219megs.

FROM microsoft/dotnet:2.0-sdk as builder  

RUN mkdir -p /root/src/app/dockertest
WORKDIR /root/src/app/dockertest

COPY dockertest.csproj .
RUN dotnet restore ./dockertest.csproj

COPY . .
RUN dotnet publish -c release -o published

FROM microsoft/dotnet:2.0.0-runtime

WORKDIR /root/
COPY --from=builder /root/src/app/dockertest/published .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000/tcp
CMD ["dotnet", "./dockertest.dll"]

Then I'll save this as Dockerfile.debian and build like this:

> docker build . -t shanselman/dockertestdeb:0.1 -f dockerfile.debian

With a standard ASP.NET app this image ends up being 219 megs.

Now I'll just change one line, and use the 2.1 alpine runtime

FROM microsoft/dotnet-nightly:2.1-runtime-alpine

And build like this:

> docker build . -t shanselman/dockertestalp:0.1 -f dockerfile.alpine

and compare the two:

> docker images | find /i "dockertest"
shanselman/dockertestalp 0.1 3f2595a6833d 16 minutes ago 82.8MB
shanselman/dockertestdeb 0.1 0d62455c4944 30 minutes ago 219MB

Nice. About 83 megs now rather than 219 megs for a Hello World web app. Now the idea of a microservice is more feasible!

Please do head over to the GitHub issue here https://github.com/dotnet/dotnet-docker-nightly/issues/500 and offer your thoughts and results as you test these Alpine images. Also, are you interested in a "-debian-slim?" It would be halfway to Alpine but not as heavy as just -debian.

Lots of great stuff happening around .NET and Docker. Be sure to also check out Jeff Fritz's post on creating a minimal ASP.NET Core Windows Container to see how you can squish .(full) Framework applications running on Windows containers as well. For example, the Windows Nano Server images are just 93 megs compressed.


Sponsor: Get the latest JetBrains Rider preview for .NET Core 2.0 support, Value Tracking and Call Tracking, MSTest runner, new code inspections and refactorings, and the Parallel Stacks view in debugger.

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Lightweight bundling, minifying, and compression, for CSS and JavaScript with ASP.NET Core and Smidge

November 8, '17 Comments [16] Posted in ASP.NET | DotNetCore | Open Source
Sponsored By

Yesterday I blogged about WebOptimizer, a minifier that Mads Kristensen wrote for ASP.NET Core. A few people mentioned that Shannon Deminick also had a great minifier for ASP.NET Core. Shannon has a number of great libraries on his GitHub https://github.com/Shazwazza including not just "Smidge" but also Examine, an indexing system, ClientDependency for managing all your client side assets, and Articulate, a blog engine built on Umbraco.

Often when there's more than one way to do things, but one of the ways is made by a Microsoft employee like Mads - even if it's in his spare time - it can feel like inside baseball or an unfair advantage. The same would apply if I made a node.js library but a node.js core committer also made a similar one. Many things can affect whether an open source library "pops," and it's not always merit. Sometimes it's locale/location, niceness of docs, marketing, word of mouth, website. Both Mads and Shannon and a dozen other people are all making great libraries and useful stuff. Sometimes people are aware of other projects and sometimes they aren't. At some point a community wants to "pick a winner" but even as I write this blog post, someone else we haven't met yet is likely making the next great bundler/minifier. And that's OK!

I'm going to take a look at Shannon Deminck's "Smidge" in this post. Smidge has been around as a runtime bundler since the beginning of ASP.NET Core even back when DNX was a thing, if you remember that. Shannon's been updating the library as ASP.NET Core has evolved, and it's under active development.

Smidge supports minification, combination, compression for JS/CSS files and features a fluent syntax for creating and configuring bundles

I'll start from "dotnet new mvc" and then:

C:\Users\scott\Desktop\smidgenweb>dotnet add package smidge
Writing C:\Users\scott\AppData\Local\Temp\tmp325B.tmp
info : Adding PackageReference for package 'smidge' into project 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.
log : Restoring packages for C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj...
...SNIP...
log : Installing Smidge 3.0.0.
info : Package 'smidge' is compatible with all the specified frameworks in project 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.
info : PackageReference for package 'smidge' version '3.0.0' added to file 'C:\Users\scott\Desktop\smidgenweb\smidgenweb.csproj'.

Then I'll update appSettings.json (where logging lives) and add Smidge's config:

{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
},
"smidge": {
"dataFolder" : "App_Data/Smidge",
"version" : "1"
}
}

Let me squish my CSS, so I'll make a bundle:

app.UseSmidge(bundles =>
{
bundles.CreateCss("my-css", "~/css/site.css");
});

I refer to the bundle by name and the Smidge tag helper turns this:

<link rel="stylesheet" href="my-css" /> 

into this

<link href="/sb/my-css.css.v1" rel="stylesheet" />

Notice the generated filename with version embedded. That bundle could be one or more files, a whole folder, whatever you need.

Her eyou can see Kestral handling the request. Smidge jumps in there and does its thing, then the bundle is cached for the next request!

info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method Smidge.Controllers.SmidgeController.Bundle (Smidge) with arguments (Smidge.Models.BundleRequestModel) - ModelState is Valid
dbug: Smidge.Controllers.SmidgeController[0]
Processing bundle 'my-css', debug? False ...
dbug: Smidge.FileProcessors.PreProcessManager[0]
Processing file '/css/site.css', type: Css, cacheFile: C:\Users\scott\Desktop\smidgenweb\App_Data\Smidge\Cache\SONOFHEXPOWER\1\bb8368ef.css, watching? False ...
dbug: Smidge.FileProcessors.PreProcessManager[0]
Processed file '/css/site.css' in 19ms
dbug: Smidge.Controllers.SmidgeController[0]
Processed bundle 'my-css' in 73ms
info: Microsoft.AspNetCore.Mvc.Internal.VirtualFileResultExecutor[1]
Executing FileResult, sending file

The minified results are cached wherever you want (remember I said App_Data):

Compressed JS and CSS

This is a SUPER simple example. You can use Smidge's fluent interface to affect how an individual bundle is created and behaves:

bundles.CreateJs("test-bundle-3", "~/Js/Bundle3")
.WithEnvironmentOptions(BundleEnvironmentOptions.Create()
.ForDebug(builder => builder
.EnableCompositeProcessing()
.EnableFileWatcher()
.SetCacheBusterType<AppDomainLifetimeCacheBuster>()
.CacheControlOptions(enableEtag: false, cacheControlMaxAge: 0))
.Build()
);

Smidge is unique in its Custom Pre-Processing Pipeline. Similar to ASP.NET Core itself, if there's anything you don't like or any behavior you want to change, you can.

I'm sure Shannon would appreciate help in Documentation and Open Issues, so go check out Smidge at https://github.com/Shazwazza/Smidge!


Sponsor: Check out JetBrains Rider: a new cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

WebOptimizer - a Bundler and Minifier for ASP.NET Core

November 8, '17 Comments [20] Posted in ASP.NET | DotNetCore | Open Source
Sponsored By

ASP.NET Core didn't have a runtime bundler like previous versions of ASP.NET. This was a bummer as I was a fan. Fortunately Mads Kristensen created one and put it on GitHub, called WebOptimizer.

WebOptimizer - ASP.NET Core middleware for bundling and minification of CSS and JavaScript files at runtime. With full server-side and client-side caching to ensure high performance.

I'll try it out on a default ASP.NET Core 2.0 app.

First, assuming I've installed http://dot.net I'll run

C:\Users\scott\Desktop> cd squishyweb

C:\Users\scott\Desktop\squishyweb> dotnet new mvc
The template "ASP.NET Core Web App (Model-View-Controller)" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.

SNIP

Restore succeeded.

Then I'll add a reference to the WebOptimizer package. Be sure to check the versioning and pick the one you want, or use the latest.

C:\Users\scott\Desktop\squishyweb> dotnet add package LigerShark.WebOptimizer.Core --version 1.0.178-beta 

Add the service in ConfigureServices and add it (I'll do it conditionally, only when in Production) in Configure. Notice I had to put it before UseStaticFiles() because I want it to get the first chance at those requests.

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddWebOptimizer();
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseWebOptimizer();
app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();

app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}

After running "dotnet run" I'll request site.css as an example and see it's automatically minimized:

CSS minification automatically

You can control the pipeline with globbing like this:

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddWebOptimizer(pipeline =>
{
pipeline.MinifyJsFiles("js/a.js", "js/b.js", "js/c.js");
});
}

If I wanted to combine some files into an output "file" that'll be held/cached only in memory, I can do that also. To be clear, it'll never touch the disk, it's just a URL. Then I can just refer to it with a <link> within my Razor Page or main Layout.

services.AddWebOptimizer(pipeline =>
{
pipeline.AddCssBundle("/css/mybundle.css", "css/*.css");
});

WebOptimizer also supports automatic "cache busting" with a ?v= query string created by a TagHelper. It can even compile Scss (Sass) files into CSS. There's plugins for TypeScript, Less, and Markdown too!

WebOptimizer is open source and lives at https://github.com/ligershark/WebOptimizer. Go check it out, kick the tires, and see if it meets your needs! Maybe get involved and make a fix or help with docs! There are already some open issues you could start helping with.


Sponsor: Check out JetBrains Rider: a new cross-platform .NET IDE. Edit, refactor, test and debug ASP.NET, .NET Framework, .NET Core, Xamarin or Unity applications. Learn more and download a 30-day trial!

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Optimizing ASP.NET Core Docker Image sizes

October 31, '17 Comments [11] Posted in Docker | DotNetCore | Open Source
Sponsored By

ASP.NET Core on KubernetesThere is a great post from Steve Laster in 2016 about optimizing ASP.NET Docker Image sizes. Since then Docker has added multi-stage build files so you can do more in one Dockerfile...which feels like one step even though it's not. Containers are about easy and reliable deployment, and they're also about density. You want to use as little memory as possible, sure, but it also is nice to make them as small as possible so you're not spending time moving them around the network. The size of the image file can also affect startup time for the container. Plus it's just tidy.

I've been building a little 6 node Raspberry Pi (ARM) Kubenetes Cluster on my desk - like you do - this week, and I noticed that my image sizes were a little larger than I'd like. This is a bigger issue because it's a relatively low-powered system, but again, why carry around x unnecessary megabytes if you don't have to?

Alex Ellis has a great blog on building .NET Core apps for Raspberry Pi along with a YouTube video. In his video and blog he builds a "Console.WriteLine()" console app, which is great for OpenFaas (open source serverless platform) but I wanted to also have ASP.NET Core apps on my Raspberry Pi k8s cluster. He included this as a "challenge" in his blog, so challenge accepted! Thanks for all your help and support, Alex!

ASP.NET Core on Docker (on ARM)

First I make a basic ASP.NET Core app. I could do a Web API, but this time I'll do an MVC one with Razor Pages. To be clear, they are the same thing just with different starting points. I can always add pages or add JSON to either, later.

I start with "dotnet new mvc" (or dotnet new razor, etc). I'm going to be running this in Docker, managed by Kuberenetes, and while I can always change the WebHost in Program.cs to change how the Kestrel web server starts up like this:

WebHost.CreateDefaultBuilder(args)
.UseUrls(http://*:5000;http://localhost:5001;https://hostname:5002)

For Docker use cases it's easier to change the listening URL with an Environment Variable. Sure, it could be 80, but I like 5000. I'll set the ASPNETCORE_URLS environment variable to http://+:5000 when I make the Dockerfile.

Optimized MultiStage Dockerfile for ASP.NET

There's a number of "right" ways to do this, so you'll want to think about your scenarios. You'll see below that I'm using ARM (because Raspberry Pi) so if you see errors running your container like "qemu: Unsupported syscall: 345" then you're trying to run an ARM image on x86/x64. I'm going to be building an ARM container from Windows but I can't run it here. I have to push it to a container registry and then tell my Raspberry Pi cluster to pull it down and THEN it'll run, over there.

Here's what I have so far. NOTE there are some things commented out, so be conscious. This is/was a learning exercise for me. Don't you copy/paste unless you know what's up! And if there's a mistake, here's a GitHub Gist of my Dockerfile for you to change and improve.

It's important to understand that .NET Core has an SDK with build tools and development kits and compilers and stuff, and then it has a runtime. The runtime doesn't have the "make an app" stuff, it only has the "run an app stuff." There is not currently an SDK for ARM so that's a limitation that we are (somewhat elegantly) working around with the multistage build file. But, even if there WAS an SDK for ARM, we'd still want to use a Dockerfile like this because it's more efficient with space and makes a smaller image.

Let's break this down. There are two stages. The first FROM is the SDK image that builds the code. We're doing the build inside Docker - which is lovely, and  great reliable way to do builds.

PRO TIP: Docker is smart about making intermediate images and doing the least work, but it's useful if we (the authors) do the right thing as well to help it out.

For example, see where we COPY the .csproj over and then do a "dotnet restore"? Often you'll see folks do a "COPY . ." and then do a restore. That doesn't allow Docker to detect what's changed and you'll end up paying for the restore on EVERY BUILD.

By making this two steps - copy the project, restore, copy the code, this means your "dotnet restore" intermediate step will be cached by Docker and things will be WAY faster.

After you build, you'll do a publish. If you know the destination like I do (linux-arm) you can do a RID (runtime id) publish that is self-contained with -r linux-arm (or debian, or whatever) and you'll get a complete self-contained version of your app.

Otherwise, you can just publish your app's code and use a .NET Core runtime image to run it. Since I'm using a complete self-contained build for this image, it would be overkill to ALSO include the .NET runtime. If you look at the Docker hub for Microsoft/dotnet You'll see images called "deps" for "dependencies." Those are images that sit on top of debian that include the things .NET needs to run - but not .NET itself.

The stack of images looks generally like this (for example)

  • FROM debian:stretch
  • FROM microsoft/dotnet:2.0-runtime-deps
  • FROM microsoft/dotnet:2.0-runtime

So you have your base image, your dependencies, and your .NET runtime. The SDK image would include even more stuff since it needs to build code. Again, that's why we use that for the "as builder" image and then copy out the results of the compile and put them in another runtime image. You get the best of all worlds.

FROM microsoft/dotnet:2.0-sdk as builder  

RUN mkdir -p /root/src/app/aspnetcoreapp
WORKDIR /root/src/app/aspnetcoreapp

#copy just the project file over
# this prevents additional extraneous restores
# and allows us to re-use the intermediate layer
# This only happens again if we change the csproj.
# This means WAY faster builds!
COPY aspnetcoreapp.csproj .
#Because we have a custom nuget.config, copy it in
COPY nuget.config .
RUN dotnet restore ./aspnetcoreapp.csproj

COPY . .
RUN dotnet publish -c release -o published -r linux-arm

#Smaller - Best for apps with self-contained .NETs, as it doesn't include the runtime
# It has the *dependencies* to run .NET Apps. The .NET runtime image sits on this
FROM microsoft/dotnet:2.0.0-runtime-deps-stretch-arm32v7

#Bigger - Best for apps .NETs that aren't self-contained.
#FROM microsoft/dotnet:2.0.0-runtime-stretch-arm32v7

# These are the non-ARM images.
#FROM microsoft/dotnet:2.0.0-runtime-deps
#FROM microsoft/dotnet:2.0.0-runtime

WORKDIR /root/
COPY --from=builder /root/src/app/aspnetcoreapp/published .
ENV ASPNETCORE_URLS=http://+:5000
EXPOSE 5000/tcp
# This runs your app with the dotnet exe included with the runtime or SDK
#CMD ["dotnet", "./aspnetcoreapp.dll"]
# This runs your self-contained .NET Core app. You built with -r to get this
CMD ["./aspnetcoreapp"]

Notice also that I have a custom nuget.config, so if you do also you'll need to make sure that's available at build time for dotnet restore to pick up all packages.

I've included by commented out a bunch of the FROMs in the second stage. I'm using just the ARM one, but I wanted you to see the others.

Once we have the code we build copied into our runtime image, we set our environment variable so our all listens on port 5000 internally (remember that from above?) Then we run our app. Notice that you can run it with "dotnet foo.dll" if you have the runtime, but if you are like me and using a self-contained build, then you'll just run "foo."

To sum up:

  • Build with FROM microsoft/dotnet:2.0-sdk as builder
  • Copy the results out to a runtime
  • Use the right runtime FROM for you
    • Right CPU architecture?
    • Using the .NET Runtime (typical) or using a self-contained build (less so)
  • Listening on the right port (if a web app)?
  • Running your app successfully and correctly?
  • Do you have a .dockerignore? Super important for .NET Builds, as you don't' want to copy over /obj, /bin, etc, but you do want /published.
    obj/
    bin/
    !published/

Optimizing a little more

There are a few pre-release "Tree Trimming" tools that can look at your app and remove code and binaries that you are not calling. I included Microsoft.Packaging.Tools.Trimming as well to try it out and get even more unused code out of my final image by just adding a package to my project.

Step 8/14 : RUN dotnet publish -c release -o published -r linux-arm /p:LinkDuringPublish=true
---> Running in 39404479945f
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB
aspnetcoreapp -> /root/src/app/aspnetcoreapp/bin/release/netcoreapp2.0/linux-arm/aspnetcoreapp.dll
Trimmed 152 out of 347 files for a savings of 20.54 MB
Final app size is 33.56 MB

If you run docker history on your final image you can see exactly where the size comes from. If/when Microsoft switches from a Debian base image to an Alpine one, this should get even smaller.

C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker history c60
IMAGE CREATED CREATED BY SIZE COMMENT
c6094ca46c3b 3 minutes ago /bin/sh -c #(nop) CMD ["dotnet" "./aspnet... 0B
b7dfcf137587 3 minutes ago /bin/sh -c #(nop) EXPOSE 5000/tcp 0B
a5ba51b91d9d 3 minutes ago /bin/sh -c #(nop) ENV ASPNETCORE_URLS=htt... 0B
8742269735bc 3 minutes ago /bin/sh -c #(nop) COPY dir:cc64bd3b9bacaeb... 56.5MB
28c008e38973 3 minutes ago /bin/sh -c #(nop) WORKDIR /root/ 0B
4bafd6e2811a 4 hours ago /bin/sh -c apt-get update && apt-get i... 45.4MB
<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:8b7cf813a113aa2... 85.7MB

Here is the evolution of my Dockerfile as I made changes and the final result got smaller and smaller. Looks like 45 megs trimmed with a little work or about 20% smaller.

C:\Users\scott\Desktop\k8s for pi\aspnetcoreapp>docker images | find /i "aspnetcoreapp"
shanselman/aspnetcoreapp 0.5 c6094ca46c3b About a minute ago 188MB
shanselman/aspnetcoreapp 0.4 083bfbdc4e01 12 minutes ago 196MB
shanselman/aspnetcoreapp 0.3 fa053b4ee2b4 About an hour ago 199MB
shanselman/aspnetcoreapp 0.2 ba73f14e29aa 4 hours ago 207MB
shanselman/aspnetcoreapp 0.1 cac2f0e3826c 3 hours ago 233MB

Later I'll do a blog post where I put this standard ASP.NET Core web app into Kubernetes using this YAML description and scale it out on the Raspberry Pi. I'm learning a lot! Thanks to Alex Ellis and Glenn Condron and Jessie Frazelle for their time!


Sponsor: Create powerful Web applications to manage each step of a document’s life cycle with DocuVieware HTML5 Viewer and Document Management Kit. Check our demos to acquire, scan, edit, annotate 100+ formats, and customize your UI!

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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Learning about the F# SAFE stack - Suave.io, Azure, Fable, Elmish

October 8, '17 Comments [16] Posted in DotNetCore | Open Source
Sponsored By

Last month I looked at a Functional Web with ASP.NET Core and F#'s Giraffe. Giraffe is F# middleware that takes ASP.NET Core's pipeline in a new direction with a functional perspective. However, Giraffe isn't the only F# web stack to choose from! There's Freya, WebSharper, and there's also a very interesting and quite complete story with The SAFE Stack.

The SAFE Stack

The SAFE Stack is an open source stack, like LAMP, or WAMP, or other acronym stacks, except this one is all open source .NET with a functional perspective. From the announcement blog:

  • Suave model for server-side web programming
  • Azure for cloud-based systems
  • Fable for Javascript-enabled applications.
  • Elmish for an easy-to-understand UI programming mode

To be clear, while this is a prescriptive stack, it's not a stack that forces you to do anything. You can swap bits out as you please.

Fable is particularly interesting as it's an F# to JavaScript transpiler. Try Fable online here http://fable.io/repl and turn F# into JavaScript LIVE! Their Sudoku sample is particularly impressive.

Here's two small - but interesting - examples where F# code ends up as JavaScript which ends up creating ReactJS components:

let words size message =
    R.span [ Style [ !!("fontSize", size |> sprintf "%dpx") ] ] [ R.str message ]

let buttonLink cssClass onClick elements = 
    R.a [ ClassName cssClass
          OnClick (fun _ -> onClick())
          OnTouchStart (fun _ -> onClick())
          Style [ !!("cursor", "pointer") ] ] elements

Then later in a Menu.fs that also turns into JavaScript eventually, you can see where they conditionally render the Button for logout, or links for for other Views for the Home Page or Wishlist. You can read lots more about Fable over at the Compositional IT blog.

let view (model:Model) dispatch =
    div [ centerStyle "row" ] [ 
          yield viewLink Home "Home"
          if model.User <> None then 
              yield viewLink Page.WishList "Wishlist"
          if model.User = None then 
              yield viewLink Login "Login" 
          else 
              yield buttonLink "logout" (fun _ -> dispatch Logout) [ str "Logout" ]
        ]

Elmish for F# takes the Model View Update (MVU) architecture and brings it to F# and the browser. There's a good breakdown of the value Elmish provides also at the Compositional IT blog.

Suave is its own cross platform Web Server. Here's Hello World in Suave.

open Suave

startWebServer defaultConfig (Successful.OK "Hello World!")

Suave has been around for while and gets better all the time. I blogged about it in 2015 and got it running on Azure. My blog post is not a best practice any more - it was a spike attempt by me - and fortunately they've moved on and improved things.

Here's Suave getting set up within the sample app. Check out how they route HTTP Verbs and URL paths.

    let serverConfig =
        { defaultConfig with
            logger = Targets.create LogLevel.Debug [|"ServerCode"; "Server" |]
            homeFolder = Some clientPath
            bindings = [ HttpBinding.create HTTP (IPAddress.Parse "0.0.0.0") port] }

    let app =
        choose [
            GET >=> choose [
                path "/" >=> Files.browseFileHome "index.html"
                pathRegex @"/(public|js|css|Images)/(.*)\.(css|png|gif|jpg|js|map)" >=> Files.browseHome

                path "/api/wishlist/" >=> WishList.getWishList loadFromDb ]

            POST >=> choose [
                path "/api/users/login" >=> Auth.login

                path "/api/wishlist/" >=> WishList.postWishList saveToDb
            ]

            NOT_FOUND "Page not found."

        ] >=> logWithLevelStructured Logging.Info logger logFormatStructured

    startWebServer serverConfig app

Very interesting stuff! There are so many options in .NET open source. I'll be doing podcasts on this stack soon.

Trying out the SAFE Stack

If you're using Visual Studio Community 2017, make sure you've got F# support included. I double-checked under Individual components. You can run the VS2017 installer multiple time and add and remove stuff without breaking things, so don't worry. If you are using other versions of VS, check here http://fsharp.org/use/windows/ to get the right download for your machine. If you're using Visual Studio Code, you'll want the Ionide F# plugin for Code.

Adding F# to Visual Studio Community 2017

Once I had node and yarn installed, it was easy to try out the sample app and get it running locally with "build run." It uses DotNetWatcher, so you can just keep it running in the background and work on your code and it'll recompile and restart as you go.

The SAFE stack running

The complete "SAFE" stack demo website is LIVE here http://fable-suave.azurewebsites.net (login test/test/) and all the source code for the app is here: https://github.com/SAFE-Stack/SAFE-BookStore.

UPDATE: One important point from F# Community Member Steffan Forkmann: "One thing I'd like to add is that SAFE is also an initiative to get multiple companies to give commercial support for this stack. This is IMHO very important for commercial use of an open source stack."


Sponsor: Do you know how many errors, crashes and performance issues your users are experiencing? Raygun installs in minutes. Discover problems you didn't even know about and replicate bugs with greater speed and accuracy. 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
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb
Page 1 of 8 in the DotNetCore category Next Page

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