Building Contextual Experiences w/ Blazor
Hi there! My name is Hassan Habib, I’m a Sr. Engineering Manager @ Microsoft. This is my very first blog post on the ASP.NET team blog. You may know me from my OData posts. Few weeks ago I reached out to Daniel Roth wondering if it would be a good idea to share how Microsoft engineers use Microsoft products to build our own systems. It’s a little something we call “Run Microsoft on Microsoft” – Daniel was very supportive and we worked together to make it possible, and for that I’m very grateful.
As I continue to work with internal teams inside Microsoft to develop end-to-end enterprise solutions, my experiences with Microsoft technologies continue to evolve into so many different directions. These experiences I thought they might be useful for all the engineers out there to see what it’s like to bring Microsoft technologies into perspective when it comes to building real-life end to end products. In my blog posts I will try to bring you that perspective, how Microsoft engineers leverage Microsoft technologies to solve some of the most complex problems and drive some of the best experiences in the world.
In addition to internal Microsoft projects, I’m a heavy contributor to the Open Source Community. For most of the examples and concepts I try to introduce in my blog posts, I will make sure that a sample code is included or a reference of an open source project is added so you can see the whole picture, from raw storage data all the way up to mobile, web and desktop applications. So, without any further ado, here goes my first post! I hope you enjoy it!
A couple of months ago, our ASP.NET team announced several of Blazor’s hottest features including Hot Reload, WebAssembly ahead-of-time compilation and many other features that supercharges the engineering and web development experience tremendously.
But one of the most important features introduced was the DynamicComponent
capability. A .NET 6 new feature that allows Blazor components to render dynamically and fluently by passing in the class type of the component and letting the new built-in Blazor capability handle rendering that component.
This new capability enables web developers to easily adapt to the contextual design pattern. A pattern that makes web applications context-aware in terms of personalized user experiences that fit exactly what a particular user need and more likely to be different from another user utilizing the same web application.
The idea of contextual experiences goes hand in hand with data-driven design where your backend services providing your frontend with data are agnostically the ones in control of what kind of capabilities your user interface is going to have based on the data provided. This pattern has proven itself to be quite effective when building dynamic applications that adjust themselves based on a particular user profile or preferences.
For instance, users with accessibility needs may require a different set of components to perform a certain task that may differ from other users. Similarly, users with particular preferences may choose to adjust their user experience to fit their needs by adding or removing particular components from their dashboards. But more commonly, dynamic data-driven UI components can become very handy with enterprise applications that require adjustment in the type of data it tries to collect from it’s users.
A good example for that is when providing a form to request information about a certain topic. Depending on user’s selection for an answer, a new set of questions may appear. The contextual experience in this case let’s each user choose their own adventure. This pattern can be seen in most of questionnaires, request forms and all other types of applications that adjust themselves based on the current user choices.
In the next few sections, I’m going to walk you through the entire process of architecting, designing and developing contextual user experiences using Blazor’s DynamicComponent
capability.
High Level Design
In order to implement context-aware Blazor applications, we need to understand how to architect our API/UI components to go hand-in-hand to provide the best possible experience. Here’s some rules for a context-aware system design:
Pure Data (API Side)
When designing your models for an API system – your models on the API side should not have anything related to the UI experience from a terminology or styling standpoints. For instance, your data living in your backend systems should not have a preference called TextBox
or DropDownList
. That oversteps into the boundaries of the rendering not the supplying of data. Your API data may, however, learn about the raw data type of the information its providing. For instance, the primitive data types could play a role in describing the type of data needs to be rendered. But that’s not always the case, since multi-line text and single-line text may render differently based on the component provided. In that case a custom, raw and generic type needs to be put in place for the UI client to know how to map it and eventually render the data dynamically.
Mapping (UI Side)
On the UI side, your Blazor application may contain mapping components or services that knows how to map raw data types into their designated UI components. For instance, your Blazor application will be responsible for receiving raw data of type Text
and map that type to a UI component TextBoxComponent
. This way, you have created a level of abstraction by leveraging the generic terminology of your data types to match whichever UI component the current UI client you are building would like to render that to.
This pattern would also allow rendering the same raw data in different ways based on the type of the UI applications. For instance, UI components built for mobile apps may choose to render Text
to Entry
as is the case in Xamarin apps for instance. And maybe your desktop Windows Forms Application may choose to render Text
data type to TextBox
and so on.
The Implementation
Let’s turn the aforementioned theory into a reality. Let’s assume you have an API that provides the following body of data:
{
"options":
[
"Choice",
"Choices",
"Text"
]
}
In your Blazor client, your API broker hands over the above model to a view service that does the following:
public IEnumerable<OptionView> RetrieveAllOptionViews()
{
List<string> options = this.optionService.RetrieveAllOptions();
return options.Select(option => new OptionView
{
Text = option,
Value = $"{option}Base"
});
}
The above function will call it’s foundation dependency to retrieve all options in their raw API format. Then map each and every result into a text value as a representative for an option in a drop down list and the value as the name of the UI component.
For instance, if one of the values in that list is Choice
the dropdown option text would be Choice
and the value would be the stringified value of the UI component that corresponds to the text value, which would be ChoiceBase
.
Now, let’s take this further to the Page level where everything will be tied up together as follows:
In an Index
page we are going to define our dependency as the aforementioned view service, along with a couple of functions:
public partial class Index : ComponentBase
{
[Inject]
public IOptionViewService OptionViewService { get; set; }
public IEnumerable<OptionView> OptionViews { get; set; }
public Type SelectedComponent { get; set; }
protected override void OnInitialized()
{
SelectedComponent = typeof(ChoiceBase);
this.OptionViews = this.OptionViewService.RetrieveAllOptionViews();
}
public void SetComponent(ChangeEventArgs changeEventArgs)
{
string fullComponentName = changeEventArgs.Value;
SelectedComponent = Type.GetType(typeName: fullComponentName);
}
}
The properties of the backend side of the Index
Page provides a list of all the OptionViews
that come from the OptionViewService
ready to be consumed as is. Additionally, a property for the default selection which is SelectedComponent
– this particular property is important to be initialized with some value in order for the DynamicComponent
to be able to render it.
Now, based on the selection, the SetComponent
function will trigger to reset the value of the SelectedComponent
and re-render through the DynamicComponent
as follows:
<select @>
<Iterations Items="OptionViews" T="OptionView" Context="optionView">
<option value="@optionView.Value">@optionView.Text</option>
</Iterations>
</select>
<DynamicComponent Type="SelectedComponent" />
For every OptionView
an option will be made, and when the selection changes the SelectedComponent
will also change accordingly triggering a new value that corresponds to the dropdown list option.
Here’s a sequence diagram to visualize the event flow of the events in the above code:
Now, let’s take a look at how DynamicComponent
handles rendering components based on the user’s choice:
Now, the above examples is just a small manifestation of the original pattern. Providing truly contextual experiences is a two-way street. Offering dynamic components alone can only be fruitful in read-only mode. But what happens when you need to send your APIs the selection back as a stored value? How do we render these stored values back onto a dynamic component as a pre-set parameter? Can DynamicComponent
support nested components?
All of these questions, and more will be addressed in the next part of this article, so stay tuned!
Here’s some final notes:
- The source code of above demo can be found here.
- Here’s a full end-to-end demo video for the same pattern.
3 comments
recently I did this on .net 5.0. devexpress gridview does not work correctly with the generic “object” argument and IQueryable data source, it requires the exact type. However, generic argument type is generated in runtime and can be changed. It was easier with DynamicComponent because I wouldn’t have to use RenderFragment directly 🙂
👍😉
Great article!! Looking forward for the next post.