What’s Old is New Again – Web Forms meets Blazor

BLAZOR!

I live for these types of conversations: “Hey, is there a way to upgrade my application to the new framework?” The answer is almost ALWAYS no, because the person asking me has already searched the web and is looking for some secret upgrade technique from me. This time, this conversation, well it was a bit more interesting.

“Hey Jeff, Blazor Server-Side and ASP.NET Web Forms are pretty similar in concepts. They both render code on the server and both have a component-based model. Is there a way we could somehow re-use markup between the two frameworks?” That question not only got my interest, but also started some interest with my colleague Dan Roth. We were planning to go on stage at Microsoft Ignite 2019 and talk about Blazor for Web Form developers… when Dan had the idea: “What if we had a shim, a component library that LOOKED and rendered HTML like the original ASP.NET controls? That could help with migration.”

The Concept

The idea is simple: ASP.NET Web Forms is not available to develop with .NET Core, but Blazor is. What if we could make it as simple as possible to copy your ASPX markup into a Blazor project and get the same markup delivered to the browser? We don’t want to enable developers to continue to develop with the same model, but to get their applications using the same UI, the same presentation that they’ve been using for years and a path forward. That is the goal.

This would require some components, some shims and maybe some trickery to make the default components from the Web Forms framework work. With that, we set out to try to make something simple as a proof of concept.

The Proof of Concept

Dan and I talk about the Blazor framework and what it means for Web Forms developers in this segment from Microsoft Ignite Live 2019

There are MILLIONS of ASP.NET Web Forms applications out there, and in our sample we showed an application with a few controls in use, but targeted this implementation (original source available):

<asp:ListView ID="productList" runat="server" 
    ItemPlaceholderID="itemPlaceHolder" ItemType="eShopLib.CatalogItem">
    <EmptyDataTemplate>
        <table>
            <tr>
                <td>No data was returned.</td>
            </tr>
        </table>
    </EmptyDataTemplate>
    <LayoutTemplate>
        <table class="table">
            <thead>
                <tr class="esh-table-header">
                    <th></th>
                    <th>Name</th>
                    <!-- SNIP -->
                </tr>
            </thead>
            <tbody>
                <asp:PlaceHolder runat="server" ID="itemPlaceHolder"></asp:PlaceHolder>
            </tbody>
        </table>
    </LayoutTemplate>
    <ItemTemplate>
        <tr>
            <td>
                <image class="esh-thumbnail" src='/Pics/<%#:Item.PictureFileName%>' />
            </td>
            <td><%#:Item.Name%></td>
            <!-- SNIP -->
        </tr>
    </ItemTemplate>
</asp:ListView>

This is a ListView being used to present a table of products with Edit, Details, and Delete links on the side. (For this article, I’ve trimmed the width of the table for readability.) Dan replicated the capabilities of this control using a custom Blazor component, also called ListView and we used this code to present the same UI but with Blazor (original source):

<ListView Items="Model.Data" TItem="CatalogItem">
    <EmptyDataTemplate>
        <tr>
            <td>No data was returned.</td>
        </tr>
    </EmptyDataTemplate>
    <TableHeader>
        <tr class="esh-table-header">
            <th></th>
            <th>Name</th>
            <!-- SNIP -->
        </tr>
    </TableHeader>
    <RowTemplate Context="catalogItem">
        <tr>
            <td>
                <img class="esh-thumbnail" src="@($"/Pics/{catalogItem.PictureFileName}")">
            </td>
            <td>
                <p>@catalogItem.Name</p>
            </td>
            <!-- SNIP -->
        </tr>
    </RowTemplate>
</ListView>

The tags are similar, the inner templates behave the same but have different names, and the formatting of the content inside the templates is ALMOST identical save for the change from <% %> notation to @( )

What If…

What if we could make this conversion even closer? I took some next steps and improved the capabilities of the ListView component so that we could now have this code output the same content as above:

<table class="table">
    <thead>
        <tr class="esh-table-header">
            <th></th>
            <th>Name</th>
            <!-- SNIP -->
        </tr>
    </thead>
    <tbody>
      <ListView @ref="productList" runat="server" Context="Item"
          ItemPlaceholderID="itemPlaceHolder" ItemType="eShopLib.CatalogItem">
          <EmptyDataTemplate>
                  <tr>
                      <td>No data was returned.</td>
                  </tr>
          </EmptyDataTemplate>
          <ItemTemplate>
              <tr>
                  <td>
                      <image class="esh-thumbnail" src='/Pics/@Item.PictureFileName' />
                  </td>
                  <td>@Item.Name</td>
                  <!-- SNIP -->
              </tr>
          </ItemTemplate>
      </ListView>
    </tbody>
</table>

Now we’re talking! By moving the LayoutTemplate content outside the ListView component, the rest of the ItemTemplate looks very similar. Almost all of the attributes on the ListView component are the SAME, with the only exception is the id attribute being renamed to @ref. The only addition is the Context attribute so that the Item object could be used in our templates.

What Next?

We think there’s an opportunity to make open source components to help facilitate this type of copy and paste conversion from simple Web Forms and controls. We’re just at the beginning of this process, but have opened a project on GitHub to analyze and work through these controls. This is NOT a Microsoft supported project, but rather a concept that I and my community of friends are working through to see what benefit it could have.

I have been diligently working on these components live on my Twitch stream, and have taken time off camera to work on documentation, samples, and some general clean up work.

The LiveView, Repeater, and DataList are all under construction as of the writing of this article. Simple ModelBinding to read and present data, data binding by specifying a DataSource all works with these controls. We’ve even added obsolete warnings if you copy over code that contains ViewState or runat="server" references.

The components are packaged using Azure DevOps and deployed NuGet as the Fritz.BlazorWebFormsComponents package If this is something that you are interested in seeing grow and give these older applications new life, please open some issues or send a pull request.