The power of REST API bindings
Published: 4/30/2018 1:47:20 PMThe release of DotVVM 2.0 is very close and we have spent last few months by testing DotVVM 2.0 in real projects to make sure the migration process is smooth.
According ot the survey we have published recently, we found our that the most wanted feature were REST API bindings. To demonstrate their power, we have built a sample application built only with REST API bindings. It is called Weekly Planner and it is available on GitHub.
Weekly Planner Sample
The app allows adding tasks to any of the days ina week, marking them as completed, editing and deleting them, and dragging them between the boxes. All these operations are handled by a REST API which is hosted in the same application; we are using ASP.NET MVC Core for that.
Thanks to the REST API bindings, the viewmodel is very small – it contains only the state of the modal dialogs, and a date which is used to determine the current week.
As you can see, the only method that is called using a classic DotVVM approach is the SignOut method. Because it makes a redirect, it cannot be ported to the REST API currently.
[Authorize]
public class DefaultViewModel : DotvvmViewModelBase
{
public DateTime CurrentDate { get; set; } = DateTime.Today;
public string UserName => Context.HttpContext.User.Identity.Name;
public TaskDetailDialogViewModel AddDialog { get; set; } = new TaskDetailDialogViewModel();
public TaskDetailDialogViewModel EditDialog { get; set; } = new TaskDetailDialogViewModel();
public async Task SignOut()
{
await Context.GetAuthentication().SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
await Context.GetAuthentication().SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme);
Context.RedirectToRoute(Context.Route.RouteName);
}
}
The list of days and their tasks is not part of the viewmodel – the collections are used only by the controls in the page. The Repeater control which renders the boxes for the week days looks like this:
<dot:Repeater DataSource="{value: Api.RefreshOnChange(_api.ApiTasksGet(CurrentDate), _root.CurrentDate).Days}"
class="week-layout" WrapperTagName="section">
The _api.ApiTasksGet method is generated using DotVVM Command Line and it issues HTTP GET to /api/tasks.
Notice the Api.RefreshOnChange which tells DotVVM to reevaluate the method _api.ApiTasksGet whenever _root.CurrentDate changes. Currently, we cannot do this automatically.
The great feature of REST API bindings in DotVVM is that the _api.ApiTasksGet method is also reevaluated whenever any POST, PUT or DELETE request is made to the same URL, so we can just add, edit or delete tasks and the Repeater is refreshed automatically – there is no need to play with Api.RefreshOnChange or Api.RefreshOnEvent methods.
Modal Dialogs
To keep the demo as simple as possible, we haven’t use any commercial controls for the dialogs. Instead, they are just <div> elements shown or hidden by a Visible binding. A couple of CSS then makes them look like modal dialogs.
Every modal dialog has a small object in the viewmodel. This object represents its state – whether the dialog is open, what’s in the text field and so on.
The dialogs are shown and hidden using Static Command bindings. Because the bindings are only manipulating with the viewmodel itself and don’t call any function on the server, the operations with a modal dialog do not require any communication with the server. The binding expression is just translated to JavaScript and evaluated on the client side.
<dot:LinkButton Click="{staticCommand: _root.AddDialog.IsDisplayed = true; _root.AddDialog.Task.DueDate = Date; _root.AddDialog.Task.Text = ""; _root.AddDialog.Focus = true}">
<i class="far fa-plus-square fa-lg"></i> Add Task
</dot:LinkButton>
We are aware that the chain of assignments is not helping the cleanliness of the code and we will be definitely addressing this in next releases of DotVVM. Right now, one of the guys from DotVVM team works on a translation of C# methods to JavaScript, and we hope that this feature be added it in the framework soon. It would allow to declare this “ShowDialog” method in the viewmodel and have invoked by a LinkButton, still with no communication between the client and the server.
Drag & Drop
DotVVM didn’t offer any options for making a drag&drop functionality. In this sample application, you can find a custom control called DraggableList. It is not finished and there is definitely a lot work on it, but it allows to implement a basic drag&drop functionality in MVVM environment.
Basically, the DraggableList behaves like a Repeater – you can give it a collection of objects and it will render a list of items. It allows to reorder the items in the list (the change is immediately applied to the underlying collection), or move items from one DraggableList to another (provided that they have their GroupName property set to the same value). Again, this moves the object from one collection to another.
In the sample app, only moving from one list to another is enabled – reordering is disabled. When the user drops the item in a different list, the app will call _api.ApiTasksMoveByIdPost – a REST API method that can change the due date of a specified task. The event is called after the item is added to the target collection, so you can access its _parent context.
<cc:DraggableList DataSource="{value: Tasks}"
ItemDropped="{staticCommand: _api.ApiTasksMoveByIdPost(Id, _parent.Date)}"
AllowedOperations="MoveToAnotherList"
class="task-list">
...
</cc:DraggableList>
Manipulation with Date on client
There were another two things we needed to avoid invoking classic commands - adding or subtracting weeks, and getting the current week number.
As you can see, the button for navigating one week back looks like this:
<dot:LinkButton Click="{staticCommand: CurrentDate = CurrentDate.AddDays(-7)}">
<i class="fas fa-arrow-left fa-lg"></i>
</dot:LinkButton>
By default, DotVVM cannot translate DateTime.AddDays method. However, the binding translation mechanism is extensible, so it is possible to tell DotVVM the JavaScript equivalent of any C# method. It is not easy as the APIs are very different, but basically we need to generate the following JS code:
var d = CurrentDate(); CurrentDate(new Date(d.getFullYear(), d.getMonth(), d.getDate() – 7, d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
There is also one tiny detail – DateTime properties are converted to string before they are stored in the viewmodel – so we must call dotvvm.globalize.parseDotvvmDate. This is a workaround to avoid unwanted conversions between UTC and local times.
DotVVM offers an API for building JavaScript syntax trees, which is much more reliable than building this code as a string (including handling all the naming conflicts etc.). The registration of a custom translator looks like this:
public class DateTimeAddDaysTranslator : IJavascriptMethodTranslator
{
public JsExpression TryTranslateCall(LazyTranslatedExpression context, LazyTranslatedExpression[] arguments, MethodInfo method)
{
var tmp = new JsTemporaryVariableParameter();
return new JsBinaryExpression(
new JsAssignmentExpression(new JsSymbolicParameter(tmp), new JsInvocationExpression(new JsIdentifierExpression("dotvvm").Member("globalize").Member("parseDotvvmDate"), context.JsExpression())),
BinaryOperatorType.Sequence,
new JsNewExpression("Date",
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getFullYear")),
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getMonth")),
new JsBinaryExpression(new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getDate")), BinaryOperatorType.Plus, arguments[0].JsExpression()),
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getHours")),
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getMinutes")),
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getSeconds")),
new JsInvocationExpression(new JsMemberAccessExpression(new JsSymbolicParameter(tmp), "getMilliseconds"))
)
);
}
}
This translator needs to be registered in DotvvmStartup.cs file:
config.Markup.JavascriptTranslator.MethodCollection.AddMethodTranslator(typeof(DateTime), nameof(DateTime.AddDays), new DateTimeAddDaysTranslator());
We expect that we will provide translations for this kind of operations for basic .NET types in the future releases of DotVVM. Or we might use WebAssembly for that, uh?
Conclusion
We expect that REST API bindings will be used mainly for loading data in ComboBox or GridView controls, but as you can see, they are pretty universal and can be used for various scenarios. There are also a couple of nice integration scenarios we’d like to play with - Azure Functions for example.
We’ll love to hear your feedback. Feel free to clone the repository and experiment with the sample app.
I am the CEO of RIGANTI, a small software development company located in Prague, Czech Republic.
I am Microsoft Most Valuable Professional and the founder of DotVVM project.