It has been a long time since I discovered this nice Reddit thread about Metric vs Imperial system. There was an amazing comment listing all kinds of ounces, barrels, gallons and other crazy stuff, but recently it got deleted.

Thanks to Removeddit, a site that keeps deleted Reddit comments, I succeeded in recovering it. I believe there was a reason for deleting the comment by its author, and I hope that the author of this brilliant text wouldn’t mind - I just have to post it here so I can read it and laugh again and again:

There are four different ounces in use:

  • A Troy ounce is about 31.1 grams.

  • An Avoirdupois ounce is about 28.3 grams.

  • An Imperial fluid ounce is about 28.4 ml.

  • A US fluid ounce is about 29.57 ml.

This is related to the fact that a US fluid ounce is 1/16 of a US pint, while an Imperial fluid ounce is 1/20 of an Imperial pint, and an Imperial pint and a US pint are different. There are in fact three pints:

  • An imperial pint about is 568 ml, or 20 Imperial fluid ounces.

  • A US pint is about 473 ml, or 16 US fluid ounces.

  • A dry pint is about 551 ml, or XXX dry fluid ounces... no, wait a "dry fluid ounce" doesn't exist, I wonder why.

This is in turn related to the fact that there are three gallons:

  • The Imperial gallon is defined in metric terms as exactly 4.54609 litres. It contains 8 Imperial pints.

  • The US gallon is defined as 231 cubic inches. It contains 8 US pints.

  • The dry gallon is defined as 1/8 of a US bushel. It contains 8 dry pints.

However, there are only two types of bushels:

  • The imperial bushel, equal to 8 imperial gallons.

  • The US dry bushel, equal to 8 US dry gallons.

There is no such thing as a US (non-dry) bushel, so if you want to convert a US gallon into the next higher US unit of volume, you have to use the beautiful correspondence:

  • 1 US bushel = 9.30918 US gallons

The next higher up unit for measuring units is the barrel (thanks /u/AML86 for the reminder). There are at least ten different units called a barrel. Among these we will mention:

  • A dry barrel is 7056 cubic inches, which converts to a convenient ~3.28 dry bushels.

  • A barrel for cranberries (yes, really) is 5826 cubic inches , more or less ~2.71 bushels.

  • An Oil barrel is 42 US gallons.

I have been speaking at various conferences and events for more than 10 years. Over the years, I tried several ways of recording my sessions. Most of the ways were just OK, but they were not 100% reliable, and it always bothered me when I did a conference with amazing sessions and some of them fail to record.

Recently, I have built my custom device that can do the entire recording or live-streaming job done without interfering with anything I want to present, and can be used on conferences where devices are changed frequently and each has a completely different setup.

But first, let me briefly list the ways I have been using, and point out their pros and cons.

Camtasia

Camtasia is probably the best software for screen recording. It comes with a simple editor where you can do basic processing of the video, and export it to common formats.

I was using Camtasia for a couple of years succesfully, however there are some things you’ll want to change in Camtasia settings, otherwise your recording can be easily ruined:

  1. The default keyboard shortcuts in Camtasia collide with some shortcuts in Visual Studio. For example, when you debug something during your presentation and press F10, Camtasia will stop the recording. If you don’t notice that, you have just lost the rest of your session.
  2. Camtasia stores the recordings to you C drive by default. If you have multiple drives (C for system and D for data), you may want to put it to some other location so you won’t run out of disk space. It is a good idea to place Camtasia temp folder to a USB stick (USB 3.0) so you’ll get full performance from your drive for your Visual Studio builds, virtualization or any other stuff you use in your presentation.
  3. Double-test your microphone settings so you don’t lose your audio. Recording the sound on a separate backup device is always a good idea. On some PCs, I have not been successful in recording the sound from Camtasia at all – sometimes I have been hitting various driver issues, or there was a problem when two audio devices on the PC used the same name.

The main issue with Camtasia is that it drains the performance of your machine while you are presenting. It is not a problem if you only have PowerPoint slides, but if you need to use Visual Studio, Docker and other stuff during the session, your machine will be much slower than without recording. Two or three times, my laptop got overheated and just turned off in the middle of my presentation. The recording was of course lost completely as the temp file was corrupt.

If you organize a conference and want to use Camtasia, you will need to convince all speakers to install it, and I totally understand the speakers who won’t do it. It is always risky to install anything before the presentation, especially when you don’t know the software. It can interfere with the things you want to show, and finally, there is never enough time to do it before the presentation and test it properly – everyone wants to focus on the session and not spend time by installing something. 

AverMedia Frame Grabbers

Over the years, I have also tried several frame grabbers from AverMedia, namely AverMedia ExtremeCap 910 and AverMedia Game Capture HD II.

The first one can record VGA or HDMI on a SD card, the second one records HDMI on its internal hard drive. Be careful about the SD card you want to plug in – not all SD cards worked for me.

The advantage is that your device is not affected by the recording at all, and it is simple to use – just plug the frame grabber between your laptop and projector.

On the other hand, there are several problems with this approach:

  1. You don’t know if the recording succeeded unless the presentation ends, and you check what has been recorded. Sometimes you’ll find an empty file, a hour-long video of entirely black screen, or just a part of the session.
  2. When the speaker changes the screen resolution during the presentation, or switches from Duplicate mode to Extend, the recording won’t probably survive this and ends or produce a corrupted file.
  3. The quality of audio recorded by the frame grabbers is terrible. You need a separate audio recording device, or a good external microphone that will be connected to the grabber.
  4. You’ll need to do post processing as the grabbers often don’t produce a video in a format which is suitable for direct upload.

All in all, this method failed me many times. I hardly remember a conference when 100% of recordings succeeded using this method.

On the other hand, my colleagues from Windows User Group are using this way for years and have successfully recorded hundreds of sessions.


My Custom Device

Few years ago, one of my colleagues was doing some live streams with OBS, and that inspired me to build my own device for recording or streaming.

Of course, I could install OBS directly on my laptop, but that wouldn’t work on conferences which I organize (convincing the speakers to install and configure OBS on their machines).

Instead, I built my device from the following parts:

  • Intel Nuc that runs Windows 10 and OBS. I got the version with Core i5 processor and put there 256GB SSD drive so I have enough space for the recordings.
  • Elgato Game Capture HD60 that can grab HDMI signal and behaves like a USB 3.0 webcam.
  • Zoom H1N for recording the sound. It is a good-quality dictaphone that can record audion on a micro-SD card, or behave like a USB microphone (that’s what I need).
  • Logitech C922 Pro Stream USB webcam to record the speaker.
  • Asus VT168H 15.6’’ LCD touch screen.

I have mounted the Intel Nuc on the back of the LCD display. Thanks to the fact that it is a touch screen, I don’t need mouse and keyboard connected.

I can then just connect the webcam, the grabber and the Zoom recorder in the USB ports (there is enough of them) and use OBS to record or stream.

I have tested the Elgato grabber that it survives when the screen resolution changes or Duplicate mode switches to Extend and vice versa. It survives even disconnecting and re-connection of any device, and I can always check what's being recorded or streamed in real-time thanks to the display.

The entire setup costs about $900, but it is the most reliable and flexible solution I have found so far.

Elgato grabber

Logitech webcam

Zoom recorder

Intel Nuc mounted on the LCD

Since the webcam can be placed few meters away from the speaker’s post, I also recommend connecting it using an active USB 3.0 extension cable.

Here is a photo of my studio I have built in our offices. It is ready for the speaker to just sit down, plug the HDMI cable in their laptop, and start presenting.

file2

file-9

The software

As I already mentioned, I am using OBS. It is an awesome open source project, and it proved to be very reliable.

You can define as many scenes as you want, and add any kinds of video or audio sources in every scene.

I have three scenes – just the screen, just the speaker or a combined view (I am using it most often). Also, for the live streams, I have a static scene for intro, intermission and outro.

OBS with Camera Only scene

OBS with Combo scene

You can switch between the scenes using the touch screen. One of my friends is building a simple device with several buttons to switch the scenes and start/stop recording – it will be more accurate than the touch screen.

For streaming, you just need to enter the stream key in the Settings of OBS. OBS supports many streaming services. I stream on Twitch and then export the videos to YouTube (I often edit them a little bit).

When I just record, the videos are stored on the internal SSD drive I put in the Intel Nuc.

Post-processing

The videos recorded in OBS comes in FLV format. It can be changed, the reason for FLV is a support for missed frames which can happen, especially in streaming scenarios.

I use Adobe Premiere to edit my recordings, which doesn’t support FLV, but it is simple to convert FLV into MP4 using ffmpeg.

ffmpeg -i myvideo.flv -codec copy myvideo.mp4

The conversion is very quick and it is loseless – the stream itself is not affected, only the container is changed from FLV to MP4.

Conclusion

We have recently started coding live with my friend Michal Altair Valasek, and had a great fun. This device helped us to do the stream right away without spending much time to prepare it, and I have used the device to record the sessions on our latest conference. We had some issues and one of the sessions failed to record because of a human error, but next time we’ll be more careful.

I believe that I have finally found a reliable and still affordable way to record or stream sessions from conferences I organize.

Yesterday, we got a question from one of DotVVM customers. He was using the Business Pack GridView with the inline editing feature and asked us how to allow the user to save changes in the row by pressing Enter.

Default button in forms

DotVVM itself doesn’t include any specific functionality to handle the keyboard actions – we rely on default behavior in HTML.

The situation is quite easy to solve when you create a simple form – to make the button to respond to the Enter key, you need to make it a “submit” button, and it needs to be in a <form> element.

<form>
    <div>
        <label>User Name</label>
        <dot:TextBox Text="{value: UserName}" />
    </div>
    <div>
        <label>Password</label>
        <dot:TextBox Text="{value: Password}" Type="Password" />
    </div>

    <div>
        <dot:Button Text="Sign In" Click="{command: SignIn()}"
                    IsSubmitButton="true" />
    </div>
</form>

The only thing you need to do is to set IsSubmitButton to true so the button will add type=”submit”. And of course, the form fields and the button must be inside the <form> element.

Modal dialogs and GridView inline editing

A little bit interesting situation occurs in modal dialogs and GridView control where you want to allow the users to edit a single row and save the changes on Enter.

The ModalDialog control has the ContentTemplate and FooterTemplate child elements, so you will have the form fields in one template and the save button in the other. You would need to put the entire modal dialog in a <form> element to make it work, and since forms in HTML cannot be nested, it might be an issue if you have more complicated scenarios.

Using the default button while editing data in GridView is completely impossible to do because the table row is <tr> and you cannot place <form> inside. You would have to put the entire table in the <form> element which is not nice and there might be multiple submit buttons if you want to allow the user to edit any row.

Moreover, there is no standard way to react to the Escape key if the user wants to cancel the edit. 

Extending DotVVM

It might be easy enough to google for a piece of jQuery code which will find the <input> elements in the form, catch the Enter press and click the correct button.

However, it is not difficult to write a generic solution for this problem and make it reusable. Basically, we need to define a container in which the Enter and Escape keys will be redirected to a particular “default” or “cancel” button. Something like the <form> element does, but even if it’s not the <form>.

In DotVVM, you can declare attached properties that can be added to any HTML element or DotVVM control. It is the same concept as attached properties in WPF or other XAML-based frameworks.

The attached property in DotVVM can render additional HTML attributes or Knockout data-bindings to the element or control on which it is applied.

What I want to achieve is something like this:

<tr data-bind="dotvvm-formhelpers-defaultbuttoncontainer: true">
    ...
    <td>
        <dot:Button Text="Save" ...
                    data-dotvvm-formhelpers-defaultbutton="true" />
        <dot:Button Text="Cancel" ... 
                    data-dotvvm-formhelpers-cancelbutton="true" />
    </td>
</tr>

The <tr> element specifies my custom Knockout binding handler which catches all Enter and Escape key presses from its children. If the Enter is pressed inside the <tr> element, this handler will find the control marked with data-dotvvm-formhelpers-defaultbutton attribute and clicks on it. A similar thing will be done for the Escape key, only the data attribute is different.

I didn’t want to use the button IDs of as there will be multiple rows in the grid and I would need to generate unique IDs for the buttons. Marking the control with the data attribute looks nicer to me.

I am setting all attributes and binding handlers to true. Actually, their values are not important at all because they are not used, but I needed something to be there. 

The binding handler should also stop the propagation of the event because the grid may be in a modal dialog which might want to use this concept too and we don’t want to submit two things with one press of Enter.

So first, let’s declare the attached properties so we can use them in DotVVM markup:

[ContainsDotvvmProperties]
public class FormHelpers
{
    [AttachedProperty(typeof(bool))]
    [MarkupOptions(AllowBinding = false)]
    public static readonly DotvvmProperty DefaultButtonContainerProperty
        = DelegateActionProperty<bool>.Register<FormHelpers>("DefaultButtonContainer", AddDefaultButtonContainer);

    [AttachedProperty(typeof(bool))]
    [MarkupOptions(AllowBinding = false)]
    public static readonly DotvvmProperty IsDefaultButtonProperty
        = DelegateActionProperty<bool>.Register<FormHelpers>("IsDefaultButton", AddIsDefaultButton);

    [AttachedProperty(typeof(bool))]
    [MarkupOptions(AllowBinding = false)]
    public static readonly DotvvmProperty IsCancelButtonProperty
        = DelegateActionProperty<bool>.Register<FormHelpers>("IsCancelButton", AddIsCancelButton);


    private static void AddDefaultButtonContainer(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmProperty property, DotvvmControl control)
    {
        writer.AddKnockoutDataBind("dotvvm-formhelpers-defaultbuttoncontainer", "true");
    }

    private static void AddIsDefaultButton(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmProperty property, DotvvmControl control)
    {
        writer.AddAttribute("data-dotvvm-formhelpers-defaultbutton", "true");
    }

    private static void AddIsCancelButton(IHtmlWriter writer, IDotvvmRequestContext context, DotvvmProperty property, DotvvmControl control)
    {
        writer.AddAttribute("data-dotvvm-formhelpers-cancelbutton", "true");
    }
}

As you can see, I have added the FormHelpers class in the project. It contains three attached properties:

  • DefaultButtonContainer is used to mark the element in which the keys should be handled.
  • IsDefaultButton is used to mark the default button inside the container – it will respond to the Enter key.
  • IsCancelButton is used to mark the cancel button inside the container – it will respond to the Escape key.

The DelegateActionProperty.Register allows to create a DotVVM property that calls a method before the element is rendered. This is the right place for us to render the Knockout data-bind expression for the first property, and the data attributes for the other properties.

Notice that the class is marked with the ContainsDotvvmProperties attribute. This is necessary for DotVVM to be able to discover these properties when the application starts.

The binding handler

All the magic happens inside the Knockout binding handler. It is a very powerful tool for extensibility and if you learn how to create your own binding handlers, you’ll get to the next level of interactivity. And thanks to DotVVM and its concept of resources and controls, it is very easy to bundle these binding handlers in a DLL and reuse them in multiple projects.

ko.bindingHandlers["dotvvm-formhelpers-defaultbuttoncontainer"] = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        $(element).keyup(function (e) {
            var buttons = [];
            if (e.which === 13) {
                buttons = $(element).find("*[data-dotvvm-formhelpers-defaultbutton=true]");
            } else if (e.which === 27) {
                buttons = $(element).find("*[data-dotvvm-formhelpers-cancelbutton=true]");
            }

            if (buttons.length > 0) {
                $(e.target).blur();
                $(buttons[0]).click();
                e.stopPropagation();
            }
        });
    },
    update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
    }
};

The binding handler is just an object with init and update functions. The init is called whenever an element with data-bind=”dotvvm-formhelpers-defaultbuttoncontainer: …” appears in the page. It doesn’t matter if the element is there from the beginning or if it appears later (for example when a new row is added to the Grid). It is called in all these cases.

The update function is called whenever the value of the expression in the data-bind attribute changes. Since we always have true here, we don’t need anything in the update function.

As you can see, I just subscribed to the keyup event on the element which gets this binding handler. This event bubbles from the control which received the key press to the root of the document. If the key code is 13 (Enter) or 27 (Escape), I look for the button with the correct data attribute.

If there is such a button (or possibly more of them), I click on it and stop propagation of the event.

I need to call blur before clicking the button because when the user changes the value of a text field, it is written in the viewmodel when the control loses focus. I need to trigger this event manually before the click event is triggered on the button. Otherwise, the new value wouldn’t be stored in the viewmodel at the right time.

The last thing is to register this script file so DotVVM knows about it. Place this code in DotvvmStartup.cs:

config.Resources.Register("FormHelpers", new ScriptResource()
{
    Location = new UrlResourceLocation("~/FormHelpers.js"),
    Dependencies = new [] { "knockout", "jquery" }
});

We also must make sure that the script is present in the page where we use the attached properties. The easiest way is to add the following control in the page (or in the master page if you use this often).

<dot:RequiredResource Name="FormHelpers" />

Currently, we don’t have any mechanism to tell the property to request this resource automatically, so you need to include the resource manually.


Using the attached properties

Now the markup can look like this:

<tr FormHelpers.DefaultButtonContainer>
    ...
    <td>
        <dot:Button Text="Save" ...
                    FormHelpers.IsDefaultButton />
        <dot:Button Text="Cancel" ... 
                    FormHelpers.IsCancelButton />
    </td>
</tr>

The nice thing about the true values of these properties is that you don’t need to write =”true” in the markup, you can just specify the property name.

But wait, how do I apply the attached property to the GridView table row? The <tr> element is rendered by the control itself, it is not in my code.

Luckily, there is the RowDecorators property which allows to “decorate” the <tr> element rendered by the control. And there is also EditRowDecorators which is used for the rows which are in the inline editing mode.

<bp:GridView DataSource="{value: Countries}" InlineEditing="true">
    <Columns>
        <bp:GridViewTextColumn ValueBinding="{value: Id}" HeaderText="ID" IsEditable="false" />
        <bp:GridViewTextColumn ValueBinding="{value: Name}" HeaderText="Name" />
        <bp:GridViewTemplateColumn>
            <ContentTemplate>
                <dot:Button Text="Edit" Click="{command: _root.Edit(_this)}" />
            </ContentTemplate>
            <EditTemplate>
                <dot:Button Text="Save" Click="{command: _root.Save(_this)}" FormHelpers.IsDefaultButton />
                <dot:Button Text="Cancel" Click="{command: _root.CancelEdit(_this)}" FormHelpers.IsCancelButton />
            </EditTemplate>
        </bp:GridViewTemplateColumn>
    </Columns>
    <EditRowDecorators>
        <dot:Decorator FormHelpers.DefaultButtonContainer />
    </EditRowDecorators>
</bp:GridView>

As you can see, I have used <dot:Decorator> to apply the FormHelpers.DefaultButtonContainer property to the rows.

The buttons are rendered in the EditTemplate and I have just applied the properties to them.

GridView with inline edit mode

Now the user can change the value and use Enter and Escape keys to click Save or Cancel button.


Conclusion

Knockout binding handlers are very powerful and can help to improve the user experience. In fact, most of the DotVVM controls are just a cleverly written binding handlers.

Thanks to the attached properties and strong-typing nature of DotVVM, you also have IntelliSense in the editor, and you can bundle these pieces of infrastructure in your custom DLLs or NuGet packages and reuse them in multiple projects.

IntelliSense for attached properties

The FormHelpers class may be included as part of future releases of DotVVM Business Pack since this is quite common user requirement.

If you have any questions, feel free to ask on our Gitter chat.

Recently, I have run into an interesting issue with one of my websites – it runs in Azure App Service and I was using automated deployments from VSTS Azure DevOps.

After the website was deployed, it didn’t start – I was getting HTTP 502.


Diagnostics

When I deploy something into Azure App Service and the app doesn’t start, I go to the Kudu console first (https://nameofyoursite.scm.azurewebsites.net) and look in LogFiles/eventlog.xml.

If there is a problem with app startup (configuration error, missing DLLs or an exception thrown during the initialization of the application), there is a chance the error will be in this file.

If you are using Application Insights and the exception occurs on startup, it will probably not be recorded because the Application Insights DLLs may not be loaded and initialized.

You can also turn on filesystem logging in Azure portal to find more details.


Cannot create directory? What?

A quick look in eventlog.xml using Kudu console told me that I am getting FileNotFoundException (Could not find file 'D:\home\site\wwwroot\Temp'.) from Directory.CreateDirectory.

It was quite strange – the path was correct and the function should actually create that directory instead of complaining that the path doesn’t exist.

In general, it is not the best idea for a web app to write in the filesystem, but most web apps does this, at least they write some log files, store uplaoded files temporarily before they are processed and so on.


After a few minutes, I discovered another weird thing – when I was browsing the wwwroot folder in Kudu, the Temp folder was not there, and when I tried to create it using Kudu, I got the following error:

409 Conflict: Cannot delete directory. It is either not empty or access is not allowed.

What? I was creating a directory, not deleting anything.


I tried to use mkdir Temp in the command line, but got the following:

The system cannot find the file specified.


Desperate enough, I tried to connect using FTP and the folder was there! I tried to delete it, but I got the same results from the app and from the Kudu.

Then I noticed that FTP shows me old versions of some files. So the app must have been running from a different folder. I double checked the FTP and Kudu addresses, but they were the same.


What was even more strange – the previous version of the web app did the same writes in the filesystem and it worked. The startup code didn’t change at all and the app worked normally before the deployment.

What has changed? What have I done?


Azure App Service Deploy Task

The only difference was the deployment process. The previous version of the website was deployed few months ago directly from Visual Studio.

This time, I tried to deploy using Azure DevOps which has a very nice deployment task for that.  It worked for the test site so I just create a different environment for production and deployed there.

I have looked at the definition of the deployment task, but haven’t found anything unusual – it was quite straight-forward – take the build artifacts and push them in the Azure App Service.

image


What now? Because it was a production site with some traffic, I decided to just deploy from Visual Studio to fix the error quickly, and then dive into the cause of the issue. So I hit Publish and couldn’t believe the error message:

Invalid access to memory location.

I started googling and finally found the answer.


Run from Package

The 4.* version of the Azure App Service Deploy task is using Run from Package application mode by default, which means that it uploads a ZIP file with the app (they call it Zip Deploy) and sets WEBSITE_RUN_FROM_ZIP application setting to 1.

The application then runs from the ZIP package - there is a virtual file system which makes the application and Kudu console see the contents of the ZIP package in the wwwroot folder.

The virtual file system it is not used when you connect using FTP, so that’s why I was seing different files in the folder.

And because the application runs from the ZIP package, it cannot write to its filesystem. Sadly, the error messages produced by I/O functions are not helpful.

Since most web apps I have seen write in their filesystem, this is quite significant change of behavior, and making it a default option in Azure DevOps deployment task can lead to a lot of confusion.

I didn’t know about this feature at all, and what is more, the setting is hidden in VSTS task so I didn’t notice it. You need to expand the Additional Deployment Options section and click on the Select deployment method checkbox, which is unchecked by default. Only after these two clicks, you can see the dropdown with deployment methods – ZipDeploy is the default one.

I needed to change it to use WebDeploy so the application files will be stored as normal files and the application can write in the filesystem like it could before.

image


And don’t forget to remove the WEBSITE_RUN_FROM_ZIP application setting, otherwise the deployment will fail with Invalid access to memory location error.

In the previous blog post, I wrote about replacing the authentication with OWIN Security libraries. In this part, I would like to introduce DotVVM – an open source framework which allows building web applications using the MVVM pattern and is very easy to use. I will also show how you can modernize old Web Forms applications using DotVVM.

Parts of the series


I have started the DotVVM project when I noticed that Web Forms are not going to be supported on ASP.NET Core. I realized that there are thousands of companies still heavily relying on legacy Web Forms applications.

In many cases, it is just not possible to rewrite the applications from scratch. They have often been developed for more than 10 years and the idea of rewriting them in less than half of that time is ridiculous. Especially in the current world of over-complicated JavaScript frameworks which add tremendous amount of complexity and make almost impossible to be a full-stack web developer.


How does DotVVM work?

The idea behind DotVVM was quite simple – to build a successor of ASP.NET Web Forms, with support of .NET Core, and get rid of the biggest pain points Web Forms had.

No ViewState

Web Forms store state of all controls in the page in a cryptic hidden field which is called viewstate. Although it is possible to specify which controls can use it and which cannot, still, it is not possible to get rid of it completely, and some controls stop working without it.

In DotVVM, the controls in the page are stateless. The page is backed by a viewmodel, which is a C# class you need to write, and all the state is stored in the viewmodel instance. The purpose of viewmodel is to store page state and handle user actions (clicking a button for example).

You can make postbacks in DotVVM, same as in Web Forms. But you can control what will be transferred from the client to the server – instead of hundreds of kilobites of encrypted state, it is a plain JSON object which corresponds with the C# viewmodel. You can specify bind direction to particular properties, so they won’t be transferred in both ways.

And what’s best – for most actions, you don’t need to do a full postback. DotVVM 2.0 brought REST API bindings, which can significantly reduce the amount of data in the viewmodel, and Static Command Services, which can also avoid transferring the entire viewmodel to the server and back. And we are working on even more interesting things right now.

Clean HTML

Web Forms controls contain many properties to change how the controls look like – FontSize, BackColor and so on. These properties emit inline styles, which is considered as a bad practise, and the HTML rendered by some controls is difficult to style using CSS.

DotVVM relies on CSS. All controls produce as simple HTML as possible, and everything is easily styleable. You can specify which HTML elements should be produced by some controls (Repeater, for example), so you can use the control to render unordered lists, or just a bunch of divs. Also, we have documented what each control produces.

Testability & Better Architecture

The viewmodels in DotVVM don’t reference controls from the UI – actually, they even don’t know which control will use a particular property. Thanks to the data-binding, any change made to the properties will be reflected by the controls, and vice-versa. This makes the viewmodels testable – you can just create an instance of the viewmodel, set some properties, call a method and make sure it did what it should do.

Also, DotVVM natively supports dependency injection, action filters and many other concepts known from MVC or ASP.NET Core. It supports both old ASP.NET and new ASP.NET Core, and can be used together with any other framework in one application. This is super-interesting for the rest of this article.



How can DotVVM help with existing Web Form applications?

If you need to extend Web Forms applications with new pages or areas, you should definitely consider writing these new parts in DotVVM. The syntax is cleaner, you can access your business layer the same way as in Web Forms, and finally, many things are easier to do in DotVVM.

If you know that you will need to run this application next five years or more, you may use DotVVM to modernize it, and possibly upgrade to .NET Core. You can take Web Forms pages and rewrite them one by one while keeping the entire application still working. After a while, when you get rid of all Web Forms pages, you might be able to upgrade the project to .NET Core (DotVVM supports both .NET Framework and .NET Core). DotVVM is a live project and is constantly adding new features, so your platform will be in sync with the world of web development which changes every year.


How to start?

I have started building a NuGet package called DotVVM.Adapters.WebForms. It will contain several controls that allow closer integration of the two technologies.

  • There are dotvvm:RouteLink and webforms:RouteLink controls, which can render a link to DotVVM or ASP.NET Web Forms route, which is helpful when making a transition between the two parts.
  • Currently, I am working on a control which can host DotVVM user control and its viewmodel in a Web Forms page. This is more difficult to do as it requires some changes in the framework itself, but still, it would be a nice step forward.

The project is hosted on GitHub and I am looking for a feedback from community – it will help us to choose the next steps and prioritize the ideas we have.

I have also created a DotVVM cheat sheet for Web Forms developers so you can see the differences between these two technologies.


Installing DotVVM package

To integrate DotVVM with Web Forms, you need to perform the following steps:

  1. Install DotVVM.Owin and Microsoft.Owin.Host.SystemWeb packages in the project.

  2. Open the csproj file in a text editor and add change the ProjectTypeGuids element to this:
    <ProjectTypeGuids>{94EE71E2-EE2A-480B-8704-AF46D2E58D94};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>

  3. Add DotvvmStartup.cs file in the project. This file contains the configuration of DotVVM:
    public class DotvvmStartup : IDotvvmStartup, IDotvvmServiceConfigurator
    {
        public void Configure(DotvvmConfiguration config, string applicationPath)
        {
            config.AddWebFormsAdapterControls();
    
            RegisterRoutes(config, applicationPath);
            RegisterControls(config, applicationPath);
            RegisterResources(config, applicationPath);
        }
    
        private void RegisterRoutes(DotvvmConfiguration config, string applicationPath)
        {
            // ...
        }
    
        private void RegisterControls(DotvvmConfiguration config, string applicationPath)
        {
            
        }
    
        private void RegisterResources(DotvvmConfiguration config, string applicationPath)
        {
            
        }
    
        public void ConfigureServices(IDotvvmServiceCollection services)
        {
        }
    }

  4. Add Startup.cs in the project. This file contains the OWIN pipeline configuration. You may already have this file in the project, if you are using SignalR or another OWIN-based library.
    [assembly: OwinStartup(typeof(YourNamespace.Startup))]
    
    namespace YourNamespace
    {
        public class Startup
        {
            public void Configuration(IAppBuilder app)
            {
                app.UseDotVVM<DotvvmStartup>(HostingEnvironment.ApplicationPhysicalPath);
            }
        }
    }

  5. Create Views and ViewModels folders somewhere in the project.

  6. Create a DotVVM Master Page and copy all contents of ASP.NET Web Forms Master Page. The goal is to make DotVVM page look exactly the same as the Web Forms one. You need to have two – one for Web Forms, one for DotVVM. It is not possible to reuse it.

  7. Replace all links (e.g. in the menu) with <webforms:RouteLink> controls.

  8. Replace <asp:ContentPlaceHolder> with <dot:ContentPlaceHolder>.

  9. Look for runat=”server” in all controls and replace all Web Forms controls with their DotVVM equivalents:
    1. There are no HyperLink, Label, Image, Panel controls in DotVVM – use their HTML equivalents.
    2. DotVVM doesn’t have controls like Menu or Calendar – you need to use HTML and CSS to make any menu you may need. We have the Calendar control in DotVVM Business Pack.
    3. Form controls (TextBox, CheckBox…), Repeater, FileUpload, GridView and many other controls have the same names.
    4. Replace all appearance properties (FontSize etc.) with proper CSS. I strongly recommend using LESS or SCSS.
    5. Replace all properties which are set from the code-behind, with data-binding, and place the corresponding properties in the master page viewmodel. If there are any values which cannot change while the page is loaded, you can use resource binding to render them directly in HTML.

    The migration shouldn't bee difficult - most of the master pages I have seen, are quite simple and use only basic controls.

  10. Start creating new pages in DotVVM using the master page. You can always make a link to a Web Forms page using <webforms:RouteLink> control, and you can always link to a DotVVM page using <dotvvm:RouteLink runat=”server”> control in a Web Forms page.

  11. Make sure the single-sign on works as expected. DotVVM should see the user identity even if you are using Forms Authentication. I strongly recommend to move to OWIN Security, which was described in the previous part.


Of course, there are so many Web Forms specific stuff that I couldn’t cover everything. However, I have tried to apply all these steps in 10 various Web Forms applications I made years ago, and I was able to integrate DotVVM very quickly.

If you try this approach, definitely let us know on our Gitter – we are very interested in any issues you might have run into. And if you have any ideas, what controls you’d like to have ported, we are listening.