In my last live coding session, I have a web application built with DotVVM which doesn’t connect to a database directly. It consumes a REST API instead, which is quite common approach in microservices applications.

Application architecture

 

 

The validation logic should be definitely implemented in the backend service. The REST API can be invoked from other microservices and we certainly don’t want to duplicate the logic on multiple places. In addition to that, the validation rules might require some data which would not be available in the admin portal (uniqueness of the user’s e-mail address for example).

Basically, the backend service needs to report the validation errors to the admin portal and the admin portal needs to report the error in the browser so the text field can be highlighted.

 

The solution is quite easy as both DotVVM and ASP.NET MVC Core use the same model validation mechanisms.

 

Implementing the Validation Logic

In my case, the API controller accepts and returns the following object (some properties were stripped out). I have used the standard validation attributes (Required) for the simple validation rules and implemented the IValidatableObject interface to provide enhanced validation logic for dependencies between individual fields.

public class EventDTO : IValidatableObject
{
    public string Id { get; set; }

    [Required]
    public string Title { get; set; }
    public string Description { get; set; }
    public List<EventDateDTO> Dates { get; set; } = new List<EventDateDTO>();
    ... 
    public DateTime RegistrationBeginDate { get; set; }
    public DateTime RegistrationEndDate { get; set; }

    [Range(1, int.MaxValue)]
    public int MaxAttendeeCount { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (!Dates.Any())
        {
            yield return new ValidationResult("The event must specify at least one date!");
        }

        if (RegistrationBeginDate >= RegistrationEndDate)
        {
            yield return new ValidationResult("The date must be greater than registration begin date!", new [] { nameof(RegistrationEndDate) });
        }

        for (var i = 0; i < Dates.Count; i++)
        {
            if (Dates[i].BeginDate < RegistrationEndDate)
            {
                yield return new ValidationResult("The date must be greater than registration end date!", new[] { nameof(Dates) + "[" + i + "]." + nameof(EventDateDTO.BeginDate) });
            }
            if (Dates[i].BeginDate >= Dates[i].EndDate)
            {
                yield return new ValidationResult("The date must be greater than begin date!", new[] { nameof(Dates) + "[" + i + "]." + nameof(EventDateDTO.EndDate) });
            }
        }
        ...
    }
}

Notice that when I validate the child objects, the property path is composed like this: Dates[0].BeginDate.

 

Reporting the validation errors on the API side

When the client posts an invalid object, I want the API to respond with HTTP 400 Bad Request and provide the list of errors. In ASP.NET MVC Core, there is the ModelState object which can be returned from the API. It is serialized as a map of property paths and lists of errors for the particular property.

I have implemented a simple action filter which verifies that the model is valid. If not, it produces the HTTP 400 response.

public class ModelValidationFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
        else
        {
            base.OnActionExecuting(context);
        }
    }
}

You can apply this attribute on a specific action, or as a global filter. If something is wrong, I am getting something like this from the API:

{
    "Title": [
        "The event title is required!"
    ],
    "RegistrationEndDate": [
        "The date must be greater than registration begin date!"
    ],
    "Dates[0].BeginDate": [
        "The date must be greater than registration end date!"
    ]
}

 

Consuming the API from the DotVVM app

I am using Swashbuckle.AspNetCore library to expose the Swagger metadata of my REST API In my DotVVM application. I am also using NSwag to generate the client proxy classes.

This tooling can save quite a lot of time and is very helpful in larger projects. If you make any change in your API and regenerate the Swagger client, you will get a compile errors on all places that needs to be adjusted, which is great.

On the other hand, I am still missing a lot of features in Swagger pipeline, for example support for generic types. Also, even that NSwag provides a lot of extensibility points, sometimes I had to use nasty hacks or regex matching to make it generate the things I really needed.

Swagger generates the ApiClient class with async methods corresponding to the individual controller actions. I can call these methods from my DotVVM viewmodels.

public async Task Save()
{
    await client.ApiEventsPostAsync(Item);
    Context.RedirectToRoute("EventList");
}

Now, whenever I get HTTP 400 from the API, I need to read the list of validation errors, fill the DotVVM ModelState with them and report them to the client.

 

Reporting the errors from DotVVM

I have created a class called ValidationHelper with a Call method. It accepts a delegate which is called. If it returns a SwaggerException with the status code of 400, we will handle the exception. Actually, there are two overloads - one for void methods, one for methods returning some result.

public static class ValidationHelper
{

    public static async Task Call(IDotvvmRequestContext context, Func<Task> apiCall)
    {
        try
        {
            await apiCall();
        }
        catch (SwaggerException ex) when (ex.StatusCode == "400")
        {
            HandleValidation(context, ex);
        }
    }

    public static async Task<T> Call<T>(IDotvvmRequestContext context, Func<Task<T>> apiCall)
    {
        try
        {
            return await apiCall();
        }
        catch (SwaggerException ex) when (ex.StatusCode == "400")
        {
            HandleValidation(context, ex);
            return default(T);
        }
    }

    ...
}

The most interesting is of course the HandleValidation method. It parses the response and puts the errors in the DotVVM model state:

private static void HandleValidation(IDotvvmRequestContext context, SwaggerException ex)
{
    var invalidProperties = JsonConvert.DeserializeObject<Dictionary<string, string[]>>(ex.Response);

    foreach (var property in invalidProperties)
    {
        foreach (var error in property.Value)
        {
            context.ModelState.Errors.Add(new ViewModelValidationError() { PropertyPath = ConvertPropertyName(property.Key), ErrorMessage = error });
        }
    }

    context.FailOnInvalidModelState();
}

And finally, there is one little caveat - DotVVM has a different format of property paths than ASP.NET MVC uses, because the viewmodel on the client side is wrapped in Knockout observables. Instead of using Dates[0].BeginDate, we need to use Dates()[0]().BeginDate, because all getters in Knockout are just function calls.

This hack won’t be necessary in the future versions of DotVVM as we plan to implement a mechanism that will detect and fix this automatically.

private static string ConvertPropertyName(string property)
{
    var sb = new StringBuilder();
    for (int i = 0; i < property.Length; i++)
    {
        if (property[i] == '.')
        {
            sb.Append("().");
        }
        else if (property[i] == '[')
        {
            sb.Append("()[");
        }
        else
        {
            sb.Append(property[i]);
        }
    }
    return sb.ToString();
}

 

Using the validation helper

There are two ways how to use the API we have just created. Either you can wrap your API call in the ValidationHelper.Call method. Alternatively, you can implement a DotVVM exception filter that will call the HandleException method directly.

public async Task Save()
{
    await ValidationHelper.Call(Context, async () =>
    {
        await client.ApiEventsPostAsync(Item);
    });

    Context.RedirectToRoute("EventList");
}

 

In the user interface, the validation looks the same as always - you mark an element with Validator.Value="{value: property}" and configure the behavior of validation on the element itself, or on any of its ancestors, for example Validation.InvalidCssClass="has-error" to decorate the elements with the has-error class when the property is not valid.

The important thing is that you need to set the save button's Validation.Target to the object you are passing to the API, so the property paths would lead to corresponding properties on the validation target.

 

You can look at the ErrorDetail page and its viewmodel in the OpenEvents project repository to see how the things work.

 

Next Steps

Currently, this solution validates only on the backend part, not in the DotVVM application and of course not in the browser. To make the client-side validation work, the DotVVM application would have to see the validation attributes on the EventDetailDTO object. Right now, the object is generated using Swagger, so it doesn’t have these attributes.

The NSwag generator could be configured not to generate the DTOs, so we would be able to have the DTOs in a class library project that would be referenced by both admin portal and backend app. This would allow to do the same validation in the admin portal and because DotVVM would see the validation attributes, it could enforce some validation rules in the browser.

 

My third live coding session is scheduled to Tuesday, January 2nd on 6:30 PM (Central Europe Time).

Last time, I have built a simple user interface for editing the events in my registration portal. This time, I will do a little bit of CSSing, implement the validation and show how Bootstrap for DotVVM can make your life easier.

 

Watch the live stream on Twitch.

The recording from the second live stream is here:


Few days ago, I have done a live coding session - in the first part, I was building the core of event registration system.

I really enjoyed it, so here is the date for the next session: Tuesday, December 19 at 6:30 PM (Central Europe Time). 

This time, I will be building the core of the admin portal in DotVVM that will allow to manage individual events and reservations.

The live coding session starts on Tuesday, 12 December at 6 PM
(Central Europe Time) on
my new Twitch channel.

 

Recently, I have seen Jeff Fritz doing live coding sessions and it looks like a great idea. I was thinking about something similar months ago, because I’d love to create as much content about DotVVM as possible, and so I decided to try it too. 

 

In November, we are preparing a conference called UPDATE. It will happen in Prague, Czech Republic, and there will be more than 20 foreign speakers - Microsoft employees, RDs and MVPs talking about .NET, cloud and security related topics.

We were looking for some event registration system recently, but because of many reasons (such as Czech legislation requirements), we have decided to build our own one. Since this is not the only conference we are doint, the system will allow to host multiple events, offer tickets with different prices and use various cancellation policies. We’d like to open source the core of the project, and it will be also a nice sample app for DotVVM.

 

The first live stream will start on Tuesday, 12 December at 6 PM (Central Europe Time). Follow my Twitter @hercegtomas - I will announce the URL of the stream there.

The plan is to build the database, then a REST API exposing the core functionality, and finally the DotVVM application which will allow the attendees to register and which will allows the organization team to see and manage the orders.

I don’t know how many things I will manage to do in 2 hours, so there may be other live streams where I will continue.

Parts of the series

.NET Framework has always been very popular and widely used in enterprise environment. There are tons of business-oriented applications written in .NET.

From its first version, .NET enabled the developers to build both desktop and web applications, and especially at the beginning, it was really easy to use. Amateur developers, students or even kids were able to create a simple Win Forms app and Microsoft tried to make web development as simple as Win Forms or VB6. That’s how ASP.NET Web Forms was born.

Despite the fact Microsoft has moved their attention to ASP.NET MVC years ago, ASP.NET Web Forms is still very popular and there are many web apps using it. They needs to be maintained and extended with new features.

Microsoft has been working on .NET Core lately, but there are not plans to bring Web Forms onto this new platform. Of course, Web Forms is supported as long as .NET Framework is supported, so there is no need to panic, but still…

Everyone knows that it’s time to move on. But where to?


Rewrite? No way!

Imagine you are a Web Forms developer taking care of a 15 years old web app. The app probably doesn’t work perfectly in modern browsers, imposes a possible security risk as it uses outdated libraries, and the users complain every other day about the UX because the app needs to reload the entire page on every click. And “Hey, there was no JavaScript in 2002!" is not a really good explanation.

You suggest that the app should be rewritten on every meeting during last five years, but nobody listens or the answer is always the same - there is no time or money for that. You know that there is a huge technology debt, however the application must survive next 5 years and there is a backlog full of new features and screens to be delivered – and they need them yesterday.


Modernize!

If the application is 10 years old, there is no reasonable chance it could be rewritten in less than half of that time. And of course, no company can stop evolving its business critical app for 5 years while it is being rewritten.

Starting to build a new version in parallel with maintaining the old one can be a way to go, but it is very expensive. The development team needs to be doubled, and it requires a massive communication between both teams as there is typically a huge amount of know-how to be transferred. A lot of time is spent with studying how (or even why) the old code works because its original author now works somewhere else.

That’s why many companies are trying to modernize their solutions. In the ASP.NET Web Forms, there are some ways how to slowly migrate to a more modern stack – screen by screen, module by module. The path is long and sometimes painful, but it allows to keep introducing new features and extend the lifetime of the application, at least for couple of years.


Steps

There are several things you can do to modernize your old ASP.NET Web Forms applications. I will try to address these topics and decisions in the next parts of this series, but here is a quick overview:

You can use modern front-end framework for new modules of the application. The choice of the UI framework depends on the type of the application and also on the skills of the team. The new parts of the application can use a completely different UI stack, but if they use the same CSS, the users won’t notice. In the meantime, the old screens may be rewritten to the new technology one by one.

There are many improvements for the business layer that can be done. Of course, monolithic applications cannot be converted to microservices easily, however some parts of the business layer can often be extracted and maybe containerized. The business layer class libraries can be converted to .NET Standard, which will allow them to be consumed from .NET Core.

Moreover, SQL might not be the right store for everything. Most of the ASP.NET web applications store all their data in a SQL database. Sometimes, it is a good idea and the relational approach is necessary, however using another type of storage in some parts of the application might remove a lot of complexity. In addition, there are new laws and regulations concerning data privacy (GDPR for example). You should review which personal data you have and who can access them.

And remember that modernization is an opportunity for refactoring, cleanup and introducing new concepts. If you are not using dependency injection or automated tests, you should seriously think about starting with them now, at least for the new parts of the application.

Also, you should think about the overall architecture of the application. Maybe some parts can be moved in the cloud, replaced by something else and so on. If there is something that doesn’t scale, it should be the first thing to think about. Create a list of priorities and try to address the ambitious plans for next years in the design.


Next part: Modernizing ASP.NET Web Forms Applications (Part 2)