How to Separate Code From UI In Blazor.Net
There's also another solution here which is similar to Louis Hendrick's point that:
You can use an inherited code behind and an injected ViewModel on the same Razor View if you have a need to or if you want to keep page lifecycle code separate from your data bindings.
Consider 'state' as an alternative to view-models
In recent years there has been much talk of managing the current status of an application using the concept of 'State'. This is something that's been particularly popular in the React (and now other JS frameworks) world since the rise of the Flux pattern (and in particular the Redux implementation).
What's the difference between state and a view-model?
A view-model typically represents the state of a particular page and will often comprise of properties related to how that page is rendered (e.g. the data for a select list, an extra property to say if a section of the page should be visible etc.) and also a property that holds the object with the data to be bound on that page (e.g. a SalesOrder
class say).
The State based approach does much the same thing, but instead of grouping state by the page that applies to (as a view-model does) the state based approach often groups code by behaviour (e.g. all the state to do with ordering a Pizza, so what the current Pizza comprises of and also what UI elements should be shown if an order is in process) and recognises that state may be displayed by multiple components - so the State objects won't necessarily map directly to a single razor file in the way that a ViewModel typically would.
Why take the state approach?
The state based approach has two main benefits:
- Because the state class has no dependency on the UI class or framework (so no reference to Blazor, Razor etc.) it can be tested like any other C# class. That means you can e.g. check if a button will be disabled when a property on the data class is set to a certain value by just testing that
MyState.SaveButtonEnabled' property is
true`. That's much simpler than trying to test behaviour via UI automation or the like. - The state based approach takes account of the fact that the state of an area of functionality in an app often crosses several components or pages. For smaller Single Page Apps (SPAs) it's often sufficient to have a single state object to represent the whole app. Obviously that approach only really works for a SPA where the whole app lives for the duration of the user's session.
An excellent example and tutorial, courtesy of the .NET team
This is easier with an example, and thankfully the Microsoft Blazor team's Blazing Pizza's blazor-workshop provides a superb one.
As a quick example from that tutorial - this is the OrderState
class that holds the current state relating to the in-progress order:
public class OrderState { public event EventHandler StateChanged; public bool ShowingConfigureDialog { get; private set; } public Pizza ConfiguringPizza { get; private set; } public Order Order { get; private set; } = new Order(); public void ShowConfigurePizzaDialog(PizzaSpecial special) { ConfiguringPizza = new Pizza() { Special = special, SpecialId = special.Id, Size = Pizza.DefaultSize, Toppings = new List<PizzaTopping>(), }; ShowingConfigureDialog = true; } public void CancelConfigurePizzaDialog() { ConfiguringPizza = null; ShowingConfigureDialog = false; StateHasChanged(); } public void ConfirmConfigurePizzaDialog() { Order.Pizzas.Add(ConfiguringPizza); ConfiguringPizza = null; ShowingConfigureDialog = false; StateHasChanged(); } public void RemoveConfiguredPizza(Pizza pizza) { Order.Pizzas.Remove(pizza); StateHasChanged(); } public void ResetOrder() { Order = new Order(); } private void StateHasChanged() { StateChanged?.Invoke(this, EventArgs.Empty); } } ```
Note that this state class has no concept of the UI that's bound to it, but it does have properties that control the UI's behaviour.
The razor classes still have the @functions blocks too in that example, but they are considerably simplified by introduce properties in the State class that have explicit roles in controlling the UI behaviour (e.g. ShowingConfigureDialog
). For example, from index.razor:
<ul class="pizza-cards"> @if (specials != null) { @foreach (var special in specials) { <li onclick="@(() => OrderState.ShowConfigurePizzaDialog(special))" style="background-image: url('@special.ImageUrl')"> <div class="pizza-info"> <span class="title">@special.Name</span> @special.Description <span class="price">@special.GetFormattedBasePrice()</span> </div> </li> } } </ul> </div> ```
That whole tutorial is excellent, I'd strongly suggest working through it.
But I don't want C# code in my razor files...
You can still put the code from the @code
block in the base-class's file and also use the state approach.
The reason people tend not to do that is that if your state file is driving the UI behaviour then the @code
wiring code normally ends up as just a few lines, so often it doesn't seem worth putting in a separate file.
You just need to inherit from ComponentBase
in your ItemComponent
class like this.
public class ItemComponent : ComponentBase
{
public async Task<ItemModel[]> GetItems()
{
ItemModel[] ItemList;
HttpClient Http = new HttpClient();
ItemList = await Http.GetJsonAsync<ItemModel[]>("api/Item/GetItems");
return ItemList;
}
}
The article is a little out of date as BlazorComponent
was renamed a while ago.
Just make sure to move all of the code you have in the functions
block of your view into the base class as mixing the two approaches can have odd side effects.
You have two options. The first was already mentioned by Chris Sainty. Create a class that inherits from ComponentBase and inherit it in your Razor view.
Your class would be defined as:
public class MyBaseClass : ComponentBase
And in your Razor view you use:
@inherits MyBaseClass
This makes MyBaseClass become a code behind page for your Razor view and it is able to override all of the lifecycle events for the view.
The second option is to create a ViewModel. You create a standard C# class and inject it into your Razor view using property injection.
You define your class normally:
public class MyViewModel
And inject it into your Razor view:
@inject MyViewModel
This ViewModel class is not aware of the page lifecycle events and has no dependencies on anything Blazor related. If you just want to bind your Razor view to an object and need something that can be reused (or want to put it in a shared project) this can be a good choice.
You can use an inherited code behind and an injected ViewModel on the same Razor View if you have a need to or if you want to keep page lifecycle code separate from your data bindings.
I have read the article about the parent class approach by creating a class that inherits from ComponentBase and simply inheriting from that base class in your component. I'm not a fan because it forces me to expose class structure that should be maintained internally/privately to the class, and keeping track of protected for inheritance I suppose is the right answer there.
However, I may be missing something here so please don't slaughter me for recommending this, but why can't you just use the partial directive, create a 'sidecar' (my terminology) file of ComponentName.razor.cs and simply declare the class as a partial class. I tried this and it worked fine...
using the current as of this writing template project, in the Counter component, I simply stripped all of the code out to result in the following:
@page "/counter"
<h1>Counter</h1>
<p>Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
Then I proceeded to create the sidecar file Counter.razor.cs and populated with:
using Microsoft.AspNetCore.Components;
namespace FirstBlazorWasm.Pages //my test namespace
{
public partial class Counter //<--- note the partial class definition
{
private int currentCount;
private void IncrementCount()
{
currentCount++;
}
}
}
Call me Mr. year 2003, but it works. :)