| Comments

At Build the Azure team launched a new service called Azure Static Web Apps in preview. This service is tailored for scenarios that really work well when you have a static web site front-end and using things like serverless APIs for your communication to services/data/etc. You should read more about it here: Azure Static Web Apps .

Awesome, so Blazor WebAssembly (Wasm) is a static site right? Can you use this new service to host your Blazor Wasm app? Let’s find out!

NOTE: This is just an experiment for me.  This isn’t any official stance of what may come with the service, but only what we can do now with Blazor apps.  As you see with Azure Static Web Apps there is a big end-to-end there with functions, debug experience, etc.  I just wanted to see if Blazor Wasm (as a static web app) could be pushed to the service.

As of this post the service is tailored toward JavaScript app development and works seamlessly in that setup. However, with a few tweaks (for now) we can get our Blazor app working. First we’ll need to get things setup!

Setting up your repo

The fundamental aspects of the service are deployment from your source in GitHub using GitHub Actions. So first you’ll need to make sure you have a repository on GitHub.com for your repository. I’m going to continue to use my Blazor Wasm Hosting Sample repo (which has different options as well to host Wasm apps) for this example. My app is the basic Blazor Wasm template, nothing fancy at all. Okay, we’ve got the repo set up, now let’s get the service setup.

Create the Azure Static Web App resource

You’ll need an Azure account of course and if you don’t have one, you can create an Azure account for free. Go ahead and do that and then come back here to make it easier to follow along. Once you have the account you’ll log in to the Azure portal and create a new resource using the Static Web App (Preview) resource type. You’ll see a simple form to fill out a few things like your resource group and a name for your app and the region.

Screenshot of Azure Portal configuration

The last thing there is where you’ll connect to your GitHub repo and make selections for what repo to use. It will launch you to authorize Azure Static Web Apps to make changes to your repo (for workflow and adding secrets):

Picture of GitHub permission prompt

Once authorized then more options show for the resource creation and just choose your org/repo/branch:

Picture of GitHub repo choices

Once you complete these selections, click Review+Create and the resource will create! The process will take a few minutes, but when complete you’ll have a resource with a few key bits of information:

Picture of finished Azure resource config

The URL of your app is auto-generated with probably a name that will make you chuckle a bit. Hey, it’s random, don’t try to make sense of it, just let the names like “icy cliff” inspire you. Additionally you’ll see the “Workflow file” YAML file and link. If you click it (go ahead and do that) it will take us over to your repo and the GitHub Actions workflow file that was created. We’ll take a look at the details next, but for now if you navigate to the Actions tab of your repo, you’ll see a fail. This is expected for us right now in our steps…more on that later.

Picture of workflows in Actions

In addition to the Actions workflow navigate to the settings tab of your repo and choose Secrets. You’ll see a new secret (with that random name) was added to your repo.

Picture of GitHub secrets

This is the API token needed to communicate with the service.

Why can’t you see the token itself and give the secret a different name? Great question. For now just know that you can’t. Maybe this will change, but this is the secret name you’ll have to use. It’s cool though, the only place it is used is in your workflow file. Speaking of that file, let’s take a look more in detail now!

Understanding and modifying the Action

So the initial workflow file was created and added to your workflow has all the defaults. Namely we’re going to focus on the “jobs” node of the workflow, which should start about line 12. The previous portions in the workflow define the triggers which you can modify if you’d like but they are intended to be a part of your overall CI/CD flow with the static site (automatic PR closure, etc.). Let’s look at the jobs as-is:

    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    - uses: actions/[email protected]
    - name: Build And Deploy
      id: builddeploy
      uses: Azure/[email protected]
        azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ICY_CLIFF_XXXXXXXXX }}
        repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
        action: 'upload'
        ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
        app_location: '/' # App source code path
        api_location: 'api' # Api source code path - optional
        app_artifact_location: '' # Built app content directory - optional
        ###### End of Repository/Build Configurations ######

Before we make changes, let’s just look. Oh see that parameter for api token? It’s using that secret that was added to your repo. GitHub Actions has built in a ‘secrets’ object that can reference those secrets and this is where that gets used. That is required for proper deployment. So there, that is where you can see the relationship to it being used!

This is great, but also was failing for our Blazor Wasm app. Why? Well because it’s trying to build it and doesn’t quite know how yet. That’s fine, we can help nudge it along! I’m going to make some changes here. First, change the checkout version to @v2 on Line 18. This is faster.

NOTE: I suspect this will change to be the default soon, but you can change it now to use v2

Now we need to get .NET SDK set up to build our Blazor app. So after the checkout step, let’s add another to first set up the .NET SDK we want to use. It will look like this, using the setup-dotnet action:

    - uses: actions/[email protected]
    - name: Setup .NET SDK
      uses: actions/[email protected]
        dotnet-version: 3.1.201

Now that we are setup, we need to build the Blazor app. So let’s add another step that explicitly builds the app and publish to a specific output location for easy reference in a later step!

    - uses: actions/[email protected]
    - name: Setup .NET SDK
      uses: actions/[email protected]
        dotnet-version: 3.1.201

    - name: Build App
      run: dotnet publish -c Release -o published

There, now we’ve got it building!

NOTE: I’m taking a bit of a shortcut in this tutorial and I’d recommend the actual best practice of Restore, Build, Test, Publish as separate steps. This allows you to more precisely see what is going on in your CI and clearly see what steps may fail, etc.

Our Blazor app is now build and prepared for static deployment in the location ‘published’ referenced in our ‘-o’ parameter during build. All the files we need start now at the root of that folder. A typical Blazor Wasm app published will have a web.config and a wwwroot at the published location.

Picture of Windows explorer folders

Let’s get back to the action defaults. Head back to the YAML file and look for the ‘app_location’ parameter in the action. We now want to change that to our published folder location, but specifically the wwwroot location as the root (as for now the web.config won’t be helpful). So you’d change it to look like this (a snippet of the YAML file)

    - uses: actions/[email protected]
    - name: Setup .NET SDK
      uses: actions/[email protected]
        dotnet-version: 3.1.201

    - name: Build App
      run: dotnet publish -c Release -o published

    - name: Build And Deploy
      id: builddeploy
      uses: Azure/[email protected]
        azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_ICY_CLIFF_XXXXXXXXX }}
        repo_token: ${{ secrets.GITHUB_TOKEN }} # Used for Github integrations (i.e. PR comments)
        action: 'upload'
        ###### Repository/Build Configurations - These values can be configured to match you app requirements. ######
        app_location: 'published/wwwroot' # App source code path
        api_location: '' # Api source code path - optional
        app_artifact_location: 'published/wwwroot' # Built app content directory - optional
        ###### End of Repository/Build Configurations ######

This tells the Static Web App deployment steps to push our files from here. Go ahead and commit the workflow file back to your repository and the Action will trigger and you will see it complete:

Screenshot of completed workflow steps

We have now successfully deployed our Blazor Wasm app to the Static Web App Preview service! Now you’ll note that there is a lot of output in the Deploy step, including warnings about build warnings. For now this is okay as we are not relying on the service to build our app (yet). You’ll also see the note about Functions not being found (reminder we changed our parameter to not have that value). Let’s talk about that.

What about the Functions?

For now the service will automatically build a JavaScript app including serverless functions built using JavaScript in this one step. If you are a .NET developer you’ll most likely be building your functions in C# along with your Blazor front-end. Right now the service doesn’t automatically allow you to specify an API location in your project for C# function classes and automatically build them. Hopefully in the future we will see that be enabled. Until then you’ll have to deploy your functions app separately. You can do it in the same workflow though if it is a part of your same repo. You’ll just leverage the other Azure Functions GitHub Action to accomplish that. Maybe I should update my sample repo to also include that?

But wait, it is broken!

Well maybe you find out that the routing of URLs may not work all the time.  You’re right!  You need to supply a routes.json file located in your app’s wwwroot directory to provide the global rewrite rule so that URLs will always work.  The routes.json file should look like

  "routes": [
      "route": "/*",
      "serve": "/index.html",
      "statusCode": 200

and put in your source project’s wwwroot folder.  This will be picked up by the service and interpreted so routes work!

Considerations and Summary

So you’ve now seen it’s possible, but you should also know the constraints. I’ve already noted that you’ll need to deploy your Functions app separately and you have to build your Blazor app in a pre-step (which I think is a good thing personally), so you may be wondering why might you use this service. I’ll leave that answer to you as I think there are scenarios will it will be helpful and I do believe this is just a point in time for the preview and more frameworks hopefully will be supported. I know those of us on the .NET team are working with the service to better support Blazor Wasm, for example.

Another thing that Blazor build does for you is produce pre-compressed files for Brotli and Gzip compression delivered from the server. When you host Blazor Wasm using ASP.NET Core, we deliver these files to the client automatically (via middleware). When you host using Windows App Service you can supply a web.config to have rewrite rules that will solve this for you as well (you can in Linux as well). For the preview of the Static Web App service and Blazor Wasm, you won’t automatically get this, so your app size will be the default uncompressed sizes of the assemblies and static assets.

I hope that you can give the service a try with your apps regardless of if they are Blazor or not. I just wanted to demonstrate how you would get started using the preview making it work with your Blazor Wasm app. I’ve added this specific workflow to my Blazor Wasm Deployment Samples repository where you can see other forms as well on Azure to deploy the client app.

I hope this helps see what’s possible in preview today!

| Comments

Everyone!  As a part of my responsibilities on the Visual Studio team for .NET tools I try to spend the time using our products in various different ways, learning what pitfalls customers may face and ways to solve them.  I work a lot with Azure services and how to deploy apps and I’m a fan of GitHub Actions so I thought I’d share some of my latest experiments.  This post will outline the various ways as of this writing you can host Blazor WebAssembly (Wasm) applications.  We actually have some great documentation on this topic in the Standalone Deployment section of our docs but I wanted to take it a bit further and demonstrate the GitHub Actions deployment of those options to Azure using the Azure GitHub Actions.

Let’s get started!

If you don’t know what Blazor Wasm is then you should read a bit about What is Blazor on our docs.  Blazor Wasm enables you to write your web application front-end using C# with .NET running in the browser.  This is different than previous models that enabled you to write C# in the browser like Silverlight where a separate plug-in was required to enable this.  With modern web standards and browser, WebAssembly has emerged as a standard to enable compilation of high-level languages for web deployment via browsers.  Blazor enables you to use C# and create your web app from front-end to back-end using a single language and .NET.  It’s great.  When you create a Blazor Wasm project and publish the output you are essentially creating a static site with assets that can be deployed to various places as there is no hard server requirement (other than to be able to serve the content and mime types).  Let’s explore these options…

ASP.NET Core-hosted

For sure the simplest way to host Blazor Wasm would be to also use ASP.NET Core web app to serve it.  ASP.NET Core is cross-platform and can run pretty much anywhere.  If you are likely using C# for all your development, this is likely the scenario you’d be using anyway and you can deploy your web app, which would container your Blazor Wasm assets as well to the same location (Linux, Windows, containers).  When creating a Blazor Wasm site you can choose this option in Visual Studio by selecting these options:

Blazor Wasm creation in Visual Studio

or using the dotnet CLI using this method:

dotnet new blazorwasm --hosted -o YourProjectName

Both of these create a solution with a Blazor Wasm client app, ASP.NET Core Server app, and a shared (optional) library project for sharing code between the two (like models or things like that).  This is an awesome option and your deployment method would follow the same method of deploying the ASP.NET Core app you’d already be using.  I won’t focus on that here as it isn’t particularly unique.  One advantage of using this method is ASP.NET Core already has middleware to properly serve the pre-compressed Brotli/gzip formats of your Blazor Wasm assets from the server, reducing the payload across the wire.  You’ll see more of this in below options, but using ASP.NET Core does this automatically for you.  You can deploy your app to Azure App Service or really anywhere else easily.


  • You’re deploying a ‘solution’ of your full app in one place, using the same tech to host the front/back end code
  • ASP.NET Core enables a set of middleware for you for Blazor routing and compression

Be Aware:

  • Basically billing.  Know that you would most likely host in an App Service or non-serverless (consumption) model.  It’s not a negative, just an awareness.

Azure Storage

If you just have the Blazor Wasm site and are calling in to a set of web APIs, serverless functions, or whatever and you just want to host the Wasm app only then using Storage is an option.  I actually already wrote about this previously in this blog post Deploy a Blazor Wasm Site to Azure Storage Using GitHub Actions so I won’t repeat it here…go over there and read that detail.

Example GitHub Action Deployment to Azure Storage: azure-deploy-storage.yml


  • Consumption-based billing for storage.  You aren’t paying for ‘on all the time’ compute
  • Blob-storage managed (many different tools to see the content)

Be Aware:

  • Routing: errors will need to be routed to index.html as well and even though they will be ‘successful’ routes, it will still be an HTTP 404 response code.  This could be mitigated by adding Azure CDN in front of your storage and using more granular rewrite rules (but this is also an additional service)
  • Pre-compressed assets won’t be served as there is no middleware/server to automatically detect and serve these files.  Your app will be larger than it could be if serving the compressed brotli/gzip assets.

Azure App Service (Windows)

You can directly publish your Blazor Wasm client app to Azure App Service for Windows.  When you publish a Blazor Wasm app, we provide a little web.config in the published output (unless you supply your own) and this contains some rewrite information for routing to index.html.  Since App Service for Windows uses IIS when you publish this output this web.config is used and will help your app routing.  You can also publish from Visual Studio using this method as well:

Visual Studio publish dialog

or using GitHub Actions easily using the Azure Actions.  Without the ASP.NET Core host you will want to provide IIS with better hinting on the pre-compressed files as well.  This is documented in our Brotli and Gzip documentation section and a sample web.config is also provided in this sample repo.  This web.config in the root of your project (not in the wwwroot) will be used during publish instead of the pre-configured one we would provide if there was none.

Example GitHub Action Deployment to Azure App Service for Windows: azure-app-svc-windows-deploy.yml


  • Easy deployment and default routing configuration provided in published output
  • Managed PaaS
  • Publish easily from Actions or Visual Studio

Be Aware:

  • Really just understanding your billing choices for the App Service

Azure App Service (Linux w/Containers)

If you like containers, you can put your Blazor Wasm app in a container and deploy that where supported, including Azure App Service Containers!  This enables you to encapsulate a little bit more in your own container image and also control the configuration of the server a bit more.  For Linux, you’d be able to specify a specific OS image you want to host your app and even supply the configuration of that server.  This is nice because we need to do a bit of that for some routing rules for the Wasm app.  Here is an example of a Docker file that can be used to host a Blazor Wasm app:

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env

COPY . ./
RUN dotnet publish -c Release

FROM nginx:1.18.0 AS build
RUN apt-get update && apt-get install -y git wget build-essential libssl-dev libpcre3-dev zlib1g-dev
RUN CONFARGS=$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p') \
    git clone https://github.com/google/ngx_brotli.git && \
    cd ngx_brotli && git submodule update --init && cd .. && \
    wget -nv http://nginx.org/download/nginx-1.18.0.tar.gz -O - | tar -xz && \
    cd nginx-1.18.0 && \ 
    ./configure --with-compat $CONFARGS --add-dynamic-module=../ngx_brotli

WORKDIR nginx-1.18.0
RUN    make modules

FROM nginx:1.18.0 as final

COPY --from=build /src/nginx-1.18.0/objs/ngx_http_brotli_filter_module.so /usr/lib/nginx/modules/
COPY --from=build /src/nginx-1.18.0/objs/ngx_http_brotli_static_module.so /usr/lib/nginx/modules/

WORKDIR /var/www/web
COPY --from=build-env /app/bin/Release/netstandard2.1/publish/wwwroot .
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80 443

In this configuration we’re using an image to first build/publish our Blazor Wasm app, then using the nginx:1.18.0 image as our base and building the nginx_brotli compression modules we want to use (lines 8-19,23-24).  We want to supply some configuration information to the nginx server and we supply an nginx.conf file that looks like this:

load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;
events { }
http {
    include mime.types;
    types {
        application/wasm wasm;
    server {
        listen 80;
        index index.html;

        location / {
            root /var/www/web;
            try_files $uri $uri/ /index.html =404;

        brotli_static on;
        brotli_types text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/x-icon image/vnd.microsoft.icon image/bmp image/svg+xml application/octet-stream application/wasm;
        gzip on;
        gzip_types      text/plain application/xml application/x-msdownload application/json application/wasm application/octet-stream;
        gzip_proxied    no-cache no-store private expired auth;
        gzip_min_length 1000;

Now when we deploy the Docker image is composed, provided to Azure Container Registry and then deployed to App Service for us.  In the above example, the first two lines are loading the modules we build in the Docker image previously.

Example GitHub Action Deployment to Azure App Service using Linux Container: azure-app-svc-linux-container.yml


  • Containers are highly customizable, allowing you some portability and flexibility
  • Easy deployment from Actions and Visual Studio (you can use the same publish mechanism in VS)

Be Aware

  • Additional service here of using Azure Container Registry (or another registry to pull from)
  • Understanding your billing plan for App Service
  • Might need more configuration awareness to take advantage of pre-compressed assets (by default nginx requires an additional module for brotli and you’d have to rebuild it into nginx)
    • NOTE: The example repo has a sample configuration which adds brotli compression support for nginx

Azure App Service (Linux)

Similarly to App Service for Windows you could also just use App Service for Linux to deploy your Wasm app.  However there is a big known workaround you have to achieve right now in order to enable this method.  Primarily this is because there is no default configuration or ability to use the web.config like you can for Windows.  Because of this if you use the Visual Studio publish mechanism it will appear as if the publish fails.  Once completed and you navigate to your app you’d get a screen that looks like the default “Welcome to App Service” page if no content is there.  This is a bit of a false positive :-).  Your content/app DOES get published using this mechanism, but since we pus the publish folder the App Service Linux configuration doesn’t have the right rewrite defaults to navigate to index.html.  Because of this I’d recommend if Linux is your desired host, that you use containers to achieve this.  However you CAN do this using GitHub Actions as you manipulate the content to push.

Example GitHub Action Deployment to Azure App Service Linux: azure-app-svc-linux-deploy.yml


  • Managed PaaS

Be Aware:

  • Cannot publish ideally from Visual Studio
  • No pre-compressed assets will be served
  • Understand your billing plan for App Service


Just like you have options with SPA frameworks or other static sites, for a Blazor Wasm client you have similar options as well.  The unique aspects of pre-compressed assets provide some additional config you should be aware of if you aren’t using ASP.NET Core hosted solutions, but with a small bit of effort you can get it working fine. 

All of the samples I have listed here are provided in this repository: timheuer/blazor-deploy-samples and would love to see any issues you may find.  I hope this helps summarize the documentation we have on configuring options in Azure to support Blazor Wasm.  What other tips might you have?

Stay tuned for more!

| Comments

I run my site on Subtext which has been around for 6+ years in some form (Subtext is a fork of .Text from way back).  As a part of the framework, there was initially built-in capabilities for tracking referral traffic.  On each view of the application, it would tick a referral note and you could see this in the statistics view of the admin pages.

As the standards (for lack of a better term) of tracking Page Views, Referrals, etc. moved to more proven/consistent reporting like Google Analytics (or other platforms) these type of platform tracking became worthless to me.  I never checked them because, frankly, I didn’t believe them anyway.  The problem is that Subtext is still tracking this information for me and taking up valuable little bytes in my database.

For Subtext, specifically, contributors have created scripts and maintenance pages to help manage some of these referrals that may not matter to folks and are just taking up space.  I am one of those people.  In my recent migration to SQL Azure I wanted to take advantage of 100MB pricing.  Surely my blog was not bigger than that.  To my surprise my blog was 650MB in size. 


I hadn’t run my database maintenance script in a while and decided to run that which purges the referral tracking.  It got down to 35MB.  Yeah baby.  In fact this topic has been discussed on a few times on the Subtext developer mailing list and even tracking as a issue for the project.  In the meantime I wanted to solve it myself for my blog.

In Subtext there is a stored proc that runs to get some of the entry tracking data called subtext_TrackEntry.  Within that proc is where it looks to see if it is a referral and adds that data.  I simply altered my proc on my end to be like this (keeping in the old function just so that I know what I did in case I needed to revert back):

   1: ALTER PROCEDURE [dbo].[subtext_TrackEntry]
   2: @EntryID INT, @BlogId INT, @Url NVARCHAR (255)=NULL, @IsWeb BIT
   4: AS
   5: -- Removing the referral tracking
   6: -- if(@Url is not NULL AND @IsWeb = 1)
   7: -- BEGIN
   8: --    EXEC [dbo].[subtext_InsertReferral] @EntryID, @BlogId, @Url
   9: -- END
  10: EXEC [dbo].[subtext_InsertEntryViewCount] @EntryID, @BlogId, @IsWeb

Now I’m no longer tracking referrals because my analytics package is doing that for me already.  My database is now representative of things that matter to me, rather than things I just want to clean up.  If you are a Subtext user and never knew that referral logging was wasting your database (and you are using an analytic package to track that anyway), then I hope this helps! 

| Comments

We’ve update the .NET RIA Services build for May 2009.  The updated May preview can be downloaded here: .NET RIA Services May 2009.  You may have to uninstall your previous version before installing this one. 

What is .NET RIA Services?
Microsoft .NET RIA Services simplifies the traditional n-tier application pattern by bringing together the ASP.NET and Silverlight platforms. The RIA Services provides a pattern to write application logic that runs on the mid-tier and controls access to data for queries, changes and custom operations. It also provides end-to-end support for common tasks such as data validation, authentication and roles by integrating with Silverlight components on the client and ASP.NET on the mid-tier.

This update incorporates a bunch of fixes that have been reported in the RIA Services forum.  This is a good place to report issues and communicate with the dev team.  Most fixes came from the forum.  In addition, DomainService base classes now infer some data access metadata based on the model (length, required for example).  There are a few other samples that were added to this as well to note:

Also upon installing you’ll see a new project type called the Business Application Template.  Right now it looks and feels exactly like the navigation template with the Silverlight tools, but has a few defaults added, namely authentication.  You’ll see in the ASP.NET project there with a Services folder and three new files: AuthenticationService, UserInformation, UserRegistrationService.  These are all services to expose authentication and membership integration.  Running the template by default will give you a new “login” link in the upper right and clicking on that implements a new ChildWindow for login:

Biz App Login Template

Notice the "Register now” link which then uses another ChildWindow control for registration:

Biz App Register Template

Registering a new user uses the UserRegistrationService to add a new user to the system.  If you look at the UserRegistrationService you’ll see that it uses the ASP.NET Membership APIs:

   1: namespace BusinessApplication2.Web
   2: {
   3:     [EnableClientAccess]
   4:     public class UserRegistrationService : DomainService
   5:     {
   6:         // NOTE: This is a sample code to get your application started. In the production code you would 
   7:         // want to provide a mitigation against a denial of service attack by providing CAPTCHA 
   8:         // control functionality or verifying user's email address.
  10:         public void AddUser(UserInformation user)
  11:         {
  12:             MembershipCreateStatus createStatus;
  14:             // NOTE: ASP.NET by default uses SQL Server Express to create the user database. 
  15:             // CreateUser will fail if you do not have SQL Server Express installed.
  17:             Membership.CreateUser(user.UserName, user.Password, user.Email, user.Question, user.Answer, true, null, out createStatus);
  18:             if (createStatus != MembershipCreateStatus.Success)
  19:             {
  20:                 throw new DomainServiceException(ErrorCodeToString(createStatus));
  21:             }
  22:         }
  24:         // other methods exist here
  25:         // ...
  26:     }
  27: }

Because the membership APIs are provider-based, you can leverage whatever you want for your membership model and still use those APIs.  The example uses the default mechanism in ASP.NET.

Download the updated RIA Services bits today and take a look at the new template for integrating with the various ASP.NET providers.  Thanks for the continued feedback and keep it coming!

| Comments

[previously named "Silverlight as the V in ASP.NET MVC" but changed per comments]

One thing that I’m excited about is learning new technologies.  Moving to the Silverlight team, I’ve moved away from a breadth of technology knowledge to something a bit more narrow.  Now I feel like all other developers trying to keep up with the technologies we are releasing.  As such, I’m a beginner for most.  One such technology is ASP.NET MVC, which was just released to release candidate stability.

I thought I’d play around with it in the context of Silverlight and use Silverlight as the “view” in the model-view-controller concept.  It’s easy to link the two.  In fact when you create a new Silverlight project, you now have the option of creating an ASP.NET MVC application as the host:

So right from the beginning, you can marry the two together.  Now from here, how can we leverage Silverlight as a view.  Well, here’s my take…learn with me (and comment where you’d do it differently/better and why).

First, I’m still going to create my MVC architecture.  I’m using the Northwind database for simplicity sake in this learning task.  I’ve created a LINQ to SQL data model to that database (which is a local SQLExpress instance).  I then wanted to take the simple task of showing the products by category and displaying them in a simple Silverlight application.

NOTE: Yes, this Silverlight view is basically just a layout of ListBoxes, but remember, this is just a learning experiment for us.  You may also ask yourself about Authentication/Authorization…again, this post is about an experiment and not a full-featured implementation, so there are bound to be missing pieces.

I decided to create a CategoryController and a ProductController which would handle the actions to retrieve list of categories and products and then drill into the detail of a product.  From there I still need some web View to be a container for my real view, the Silverlight application.  I created a View for Category, since essentially that’s the initial view my user would see.  All it is is an index page hosting my Silverlight application:

   1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="SilverMvc.Web.Views.Category.Index" %>
   2: <asp:Content ID="Content1" ContentPlaceHolderID="MainContent" runat="server">
   3:     <p>
   4:         <object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
   5:             <param name="source" value="ClientBin/SilverlightWithMvc.xap"/>
   6:             <param name="onerror" value="onSilverlightError" />
   7:             <param name="background" value="white" />
   8:             <param name="minRuntimeVersion" value="2.0.31005.0" />
   9:             <param name="autoUpgrade" value="true" />
  10:             <a href="http://go.microsoft.com/fwlink/?LinkID=124807" style="text-decoration: none;">
  11:                  <img src="http://go.microsoft.com/fwlink/?LinkId=108181" alt="Get Microsoft Silverlight" style="border-style: none"/>
  12:             </a>
  13:         </object>
  14:     </p>
  15: </asp:Content>

Now all I have to do is start building my real view that will be here, the SilverlightWithMvc.xap application.  But first I obviously need my controllers to respond so some requests.  I wanted the CategoryController to display a list of categories and a list of products associated with a category.  But remember, my View will not be the ASPNET View, but rather my Silverlight app.  How then could I get the ViewData to be sent to my Silverlight application.

In MVC, typically your code will return a View from the action, similar to my Index view in my CategoryController:

   1: public ActionResult Index()
   2:         {
   3:             return View();
   4:         }

See where the return is an ActionResult (common) and the return is a View, also common.  This return would expect that there would be a View named Index in the project hierarchy, which in our case there is and again, is the initial hosting page for our app (located in /Views/Category/Index.aspx).  But what about our other data.  I wanted to add a controller action called List and Products that would list all the categories (List) and then list all the products for a given category (Products).  But I just wanted the data.  It turns out that the MVC framework can give us just the data instead of the View.  There is a Json return type.  So using my LINQ queries I created the two actions:

   1: public ActionResult List()
   2:         {
   3:             NorthwindDataContext db = new NorthwindDataContext();
   4:             var cats = from cat in db.Categories
   5:                        select new
   6:                        {
   7:                            cat.CategoryID,
   8:                            cat.CategoryName
   9:                        };
  11:             return Json(cats);
  12:         }
  14:         public ActionResult Products(int id)
  15:         {
  16:             NorthwindDataContext db = new NorthwindDataContext();
  17:             var prods = from prod in db.Products.Where(cat => cat.CategoryID == id)
  18:                         select new
  19:                         {
  20:                             prod.ProductID,
  21:                             prod.ProductName,
  22:                             prod.UnitPrice,
  23:                         };
  24:             return Json(prods);
  25:         }

As you can see the return type is Json and passing in the model data of what I need…as opposed to saying return View([modelData]).  I now get Json formatted data as a return result.  Great, now how to consume them?

Within Silverlight, we know we can consume Json data and we can take advantage of that capability to bring MVC and Silverlight together.  First let’s look at my layout UI code for the Silverlight application…it’s basically going to be a cascading ListBox view:

   1: <UserControl x:Class="SilverMvc.Page"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   4:     <Grid x:Name="LayoutRoot" Background="White">
   5:         <StackPanel Orientation="Horizontal">
   6:             <StackPanel Orientation="Vertical" Margin="0,0,25,0" x:Name="CategoryListing">
   7:                 <TextBlock Text="Select a category..." FontWeight="Bold" />
   8:                 <ListBox Width="200" Height="150" x:Name="CategoryList" ItemsSource="{Binding}" SelectionChanged="CategoryList_SelectionChanged" />
   9:             </StackPanel>
  10:             <StackPanel Orientation="Vertical" Margin="0,0,25,0" x:Name="ProductListings" Visibility="Collapsed">
  11:                 <TextBlock Text="Select a product..." FontWeight="Bold" />
  12:                 <ListBox Width="200" Height="150" x:Name="ProductList" SelectionChanged="ProductList_SelectionChanged" />
  13:             </StackPanel>
  14:             <StackPanel Orientation="Vertical" x:Name="ProductDetail" Visibility="Collapsed">
  15:                 <TextBlock Text="Product Details:" FontWeight="Bold" />
  16:                 <Grid>
  17:                     <Grid.RowDefinitions>
  18:                         <RowDefinition Height="Auto" />
  19:                         <RowDefinition Height="Auto" />
  20:                     </Grid.RowDefinitions>
  21:                     <Grid.ColumnDefinitions>
  22:                         <ColumnDefinition Width="Auto" />
  23:                         <ColumnDefinition Width="Auto" />
  24:                     </Grid.ColumnDefinitions>
  25:                     <TextBlock Grid.Column="0" Grid.Row="0" Text="Product Name: " />
  26:                     <TextBox Width="200" Grid.Column="1" Grid.Row="0" Text="{Binding Path=ProductName, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" HorizontalAlignment="Left" VerticalAlignment="Top" />
  27:                     <TextBlock Grid.Column="0" Grid.Row="1" Text="Unit Price: " />
  28:                     <TextBox Width="200" Grid.Column="1" Grid.Row="1" Text="{Binding Path=UnitPrice, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}" HorizontalAlignment="Left" VerticalAlignment="Top" />
  29:                 </Grid>
  30:             </StackPanel>
  31:         </StackPanel>
  32:     </Grid>
  33: </UserControl>

Where one selection drives the next portion of the layout, etc.  You can see that I have several {Binding} statements in there as well as some selection changed handlers.  Let’s look at what happens on Loaded of the app:

   1: void Page_Loaded(object sender, RoutedEventArgs e)
   2:         {
   3:             WebClient mvc = new WebClient();
   4:             mvc.OpenReadCompleted += new OpenReadCompletedEventHandler(mvc_OpenReadCompleted);
   5:             mvc.OpenReadAsync(new Uri("http://localhost:33828/Category/List"));
   6:         }

You can see that I’m making a WebRequest call to a URI that happens to be our CategoryController with the List action command.  On the return event handler I’m getting that stream of data, which we know to be Json data, and setting the DataContext of my first ListBox:

   1: void mvc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
   2:         {
   3:             DataContractJsonSerializer json = new DataContractJsonSerializer(typeof(List<Category>));
   4:             List<Category> cats = (List<Category>)json.ReadObject(e.Result);
   5:             CategoryList.DisplayMemberPath = "CategoryName";
   6:             CategoryListing.DataContext = cats;
   7:         }

Now you may at this point be asking a few questions about some of my decisions here within Silverlight.  First, the use of DataContractJsonSerializer.  Where is it?  Add a reference to System.ServiceModel.Web and you’ll get it.  You can also see that I’m using the ReadObject method and casting it to a List<Category>. 

Why not use System.Json and LINQ to JSON?  You could absolutely.  In doing so you could use your LINQ skills and get the data out of the Json stream and put it into a new object.  You’ll still have to create a local class representation because Silverlight can’t bind to an anonymous type.  This is the first reason I like just using the serializer.  The second reason is size.  I don’t know why I’m so picky, but I am.  Using the DataContractSerializer method here, my app is about 7K.  Adding a reference to System.Json and using those methods, my app is 27K.  For me, there is no additional benefit in code for what I’m doing to add that extra size, so I choose not to.  But you can…absolutely.  Your application needs may be different and the size cost/benefit analysis may result in a different outcome than mine…but there, I’ve stated my reasons here.

Where is Category defined?  Great question!  Category is a class defined in my Silverlight object that maps the data to a strongly-typed class..here’s the class definition:

   1: public class Category
   2:     {
   3:         public int CategoryID { get; set; }
   4:         public string CategoryName { get; set; }
   5:     }

Now that that is done, my data is retrieved, typed and bound to my UI.  Then in the category ListBox when a category is selected, we trigger a similar event to retrieve the products within that category and populate the product list using the same technique.  The final stage is that when a product is selected we populate a simple UI to show the details.  We could also have called another controller action, but here since we had most of the data in the product listing, we simply push the data to the DataContext of the layout container for the details:

   1: private void ProductList_SelectionChanged(object sender, SelectionChangedEventArgs e)
   2:         {
   3:             if (ProductList.SelectedIndex > -1)
   4:             {
   5:                 Product p = ProductList.SelectedItem as Product;
   6:                 ProductDetail.DataContext = p;
   7:                 ProductDetail.Visibility = Visibility.Visible;
   8:             }
   9:         }

You can see we get the SelectedItem as a Product (another internal class like Category) and make it the DataContext of the ProductDetail StackPanel who’s children have binding instructions.  The end result is this “view” below (the headings above the list boxes are not in the UI, but rather just labels to map to the controller action used to populate:

As I completed this little experiment, several things did come to mind…

  • What about deep linking?  The URI in the app didn’t change.
  • Now that I’m looking at a detail view, does the URI stating the initial view matter?

I think mostly these are “simple” things, but important to an MVC developer audience.  The interaction of URI semantics with Silverlight are important and things we are addressing in Silverlight 3 to make even this simple experiment easier.

So tell me, ASP.NET MVC experts, does this make sense?  Good/bad/what would you do different?  Help me learn how you would make the interaction between ASP.NET MVC and Silverlight better?

You can download my sample code for this project here (requires SQL 2008 and VS2008 with Silverlight and ASP.NET MVC tools installed): SilverlightWithMvc.zip