Recommended UI Approaches for Azure AI Services Output

When displaying output from Azure AI services (like Cognitive Services, OpenAI, etc.), the UI should be tailored to the specific service and use case. Here are recommended approaches:

1. Text-Based AI Services (Language, Translation, etc.)

Recommended UI Components:

MudBlazor (for Blazor apps):

<MudPaper Elevation="3" Class="pa-4 my-4">
    <MudText Typo="Typo.h6">AI Analysis</MudText>
    <MudText>@_aiResponse</MudText>
    @if (!string.IsNullOrEmpty(_sentiment))
    {
        <MudChip Color="@(_sentiment == "Positive" ? Color.Success : 
                       _sentiment == "Negative" ? Color.Error : Color.Warning)"
                Class="mt-2">
            @_sentiment Sentiment
        </MudChip>
    }
</MudPaper>

For key phrases extraction:

<MudChipSet>
    @foreach (var phrase in _keyPhrases)
    {
        <MudChip>@phrase</MudChip>
    }
</MudChipSet>

2. Computer Vision/Image Analysis

Recommended UI:

<div style="position: relative;">
    <img src="@_imageUrl" style="max-width: 100%;" />
    @foreach (var obj in _detectedObjects)
    {
        <div style="position: absolute; 
                   left: @(obj.BoundingBox.Left * 100)%; 
                   top: @(obj.BoundingBox.Top * 100)%;
                   width: @(obj.BoundingBox.Width * 100)%;
                   height: @(obj.BoundingBox.Height * 100)%;
                   border: 2px solid red;">
            <span style="background: white; padding: 2px;">@obj.ObjectProperty</span>
        </div>
    }
</div>

3. Chat/Conversational AI (Azure OpenAI)

Recommended UI:

<MudContainer MaxWidth="MaxWidth.Medium">
    <MudPaper Elevation="3" Class="pa-4" Style="height: 60vh; overflow-y: auto;">
        @foreach (var message in _chatHistory)
        {
            <MudCard Class="my-2" Elevation="1">
                <MudCardHeader>
                    <MudAvatar>@(message.Role == "user" ? "U" : "AI")</MudAvatar>
                    <MudText Typo="Typo.subtitle2">@message.Role</MudText>
                </MudCardHeader>
                <MudCardContent>
                    <MarkdownString Value="@message.Content" />
                </MudCardContent>
            </MudCard>
        }
    </MudPaper>
    
    <MudTextField @bind-Value="_userMessage" 
                 Label="Type your message" 
                 Variant="Variant.Outlined"
                 FullWidth
                 Class="mt-4">
        <Adornment>
            <MudButton OnClick="SendMessage" 
                      Icon="@Icons.Material.Filled.Send"
                      Disabled="@_isProcessing" />
        </Adornment>
    </MudTextField>
</MudContainer>

4. Form Recognizer/Data Extraction

Recommended UI:

<MudTable Items="@_extractedData" Hover="true">
    <HeaderContent>
        <MudTh>Field</MudTh>
        <MudTh>Value</MudTh>
        <MudTh>Confidence</MudTh>
    </HeaderContent>
    <RowTemplate>
        <MudTd>@context.FieldName</MudTd>
        <MudTd>@context.Value</MudTd>
        <MudTd>
            <MudProgressLinear Value="@(context.Confidence * 100)" 
                              Color="@(context.Confidence > 0.9 ? Color.Success : 
                                     context.Confidence > 0.7 ? Color.Warning : Color.Error)"/>
        </MudTd>
    </RowTemplate>
</MudTable>

5. Custom Decision/Recommendation Services

Recommended UI:

<MudGrid>
    @foreach (var recommendation in _recommendations)
    {
        <MudItem xs="12" sm="6" md="4">
            <MudCard Elevation="5" Class="h-100">
                <MudCardHeader>
                    <MudAvatar Color="Color.Primary">@recommendation.Score.ToString("P0")</MudAvatar>
                    <MudText Typo="Typo.h6">@recommendation.Title</MudText>
                </MudCardHeader>
                <MudCardContent>
                    @recommendation.Description
                </MudCardContent>
                <MudCardActions>
                    <MudButton Variant="Variant.Text" Color="Color.Primary">View Details</MudButton>
                </MudCardActions>
            </MudCard>
        </MudItem>
    }
</MudGrid>

Best Practices for Azure AI UI

Visual Feedback:

Show loading states during API calls

<MudProgressCircular Indeterminate="true" Color="Color.Primary" 
                    Visible="@_isLoading" Class="my-4" />

Error Handling:

@if (!string.IsNullOrEmpty(_errorMessage))
{
    <MudAlert Severity="Severity.Error" Variant="Variant.Filled">
        @_errorMessage
    </MudAlert>
}

Confidence Indicators:

Visualize confidence scores for uncertain predictions

<MudTooltip Text="@($"Confidence: {_confidence:P2}")">
    <MudIcon Icon="@(_confidence > 0.9 ? Icons.Material.Filled.CheckCircle : 
                    _confidence > 0.7 ? Icons.Material.Filled.Warning : 
                    Icons.Material.Filled.Error)"
            Color="@(_confidence > 0.9 ? Color.Success : 
                   _confidence > 0.7 ? Color.Warning : Color.Error)" />
</MudTooltip>

Interactive Exploration:

Allow users to refine/correct AI outputs

<MudTextField @bind-Value="_correctedText" 
             Label="Correct the AI output"
             Visible="@_showCorrectionField" />

Responsive Design:

Ensure UI works across devices

<MudGrid>
    <MudItem xs="12" md="6">
        <!-- Input controls -->
    </MudItem>
    <MudItem xs="12" md="6">
        <!-- AI output -->
    </MudItem>
</MudGrid>

For enterprise applications, consider adding:

  • Export capabilities (PDF, CSV)
  • Audit trails of AI interactions
  • User feedback mechanisms (“Was this helpful?”)
  • Explanation components for AI decisions

Sharing a Single Model Across Multiple Child Components in Blazor WebAssembly

There are several effective ways to share a single model (data object) between multiple child components in Blazor WebAssembly. Here are the best approaches:

1. Cascading Parameters (Best for hierarchical components)
<!-- ParentComponent.razor -->
@page "/parent"

<CascadingValue Value="@SharedModel">
    <ChildComponent1 />
    <ChildComponent2 />
</CascadingValue>

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

2. Component Parameters (Best for direct parent-child relationships)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" />
<ChildComponent2 Model="@SharedModel" />

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

3. State Management Service (Best for app-wide sharing)

// SharedModelService.cs
public class SharedModelService
{
    private MyModel _model = new();
    
    public MyModel Model 
    {
        get => _model;
        set
        {
            _model = value;
            NotifyStateChanged();
        }
    }
    
    public event Action OnChange;
    
    private void NotifyStateChanged() => OnChange?.Invoke();
}

Register the service in Program.cs:

builder.Services.AddSingleton<SharedModelService>();

Use in components:

@inject SharedModelService ModelService

@code {
    protected override void OnInitialized()
    {
        ModelService.OnChange += StateHasChanged;
    }
    
    private void UpdateModel()
    {
        ModelService.Model.Property = "New Value";
    }
}

4. EventCallback Pattern (For parent-child communication)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" ModelChanged="@HandleModelChanged" />
<ChildComponent2 Model="@SharedModel" ModelChanged="@HandleModelChanged" />

@code {
    private MyModel SharedModel { get; set; } = new();
    
    private void HandleModelChanged(MyModel updatedModel)
    {
        SharedModel = updatedModel;
        StateHasChanged(); // Refresh all components
    }
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
    
    [Parameter]
    public EventCallback<MyModel> ModelChanged { get; set; }
    
    private async Task UpdateModel()
    {
        Model.Property = "New Value";
        await ModelChanged.InvokeAsync(Model);
    }
}

5. Fluxor/Redux Pattern (For complex state management)

// Install package
dotnet add package Fluxor.Blazor.Web

// Define state
public record MyModelState
{
    public MyModel Model { get; init; } = new();
}

// Define actions
public record UpdateModelAction(MyModel Model);

// Create reducer
public static class Reducers
{
    [ReducerMethod]
    public static MyModelState ReduceUpdateModelAction(MyModelState state, UpdateModelAction action)
        => state with { Model = action.Model };
}

Use in components:

@inject IState<MyModelState> ModelState
@inject IDispatcher Dispatcher

<p>@ModelState.Value.Model.Property</p>

<button @onclick="UpdateModel">Update</button>

@code {
    private void UpdateModel()
    {
        var updatedModel = ModelState.Value.Model with { Property = "New Value" };
        Dispatcher.Dispatch(new UpdateModelAction(updatedModel));
    }
}

  1. For simple parent-child relationships: Use Component Parameters
  2. For deep component trees: Use Cascading Parameters
  3. For app-wide state: Use State Management Service or Fluxor
  4. For complex applications: Consider Fluxor/Redux pattern
  5. Immutable models: When sharing models, consider making them immutable or implementing proper change notifications

Performance Considerations

  • Avoid excessive re-rendering by implementing ShouldRender
  • Use [Parameter] public MyModel Model { get; set; } carefully as it can cause unnecessary renders
  • For large models, consider using view models or DTOs instead of full domain models

Show/Hide element in Blazor WebAssembly

Blazor WebAssembly doesn’t allow direct manipulation of DOM. Here is how to show/hide DOM element without using Javascript Interop;

The hidden html attribute also works to hide an element.

<p hidden>This paragraph should be hidden.</p>

To bind to Model:

 <p hidden="@HideLabel">I am Hidden When HideLabel == true</p>

 <p hidden="@(!HideLabel)">I am Hidden when Hidelabel == false</p>
    
 <button @onclick="@Toggle">Show/Hide</button>

 @code {
      private bool HideLabel { get; set; } = false;
      private void Toggle()
      {
         HideLabel =   !HideLabel;
      }      
 } 

Edit: You can also use a CSS class to hide/show an element:

<div class="font-italic @(HideLabel ? "d-none" : "d-show")">
   I am Hidden When HideLabel == true
</div>

Reference

https://stackoverflow.com/questions/63693734/how-to-show-hide-an-element-in-real-time-blazor

StateHasChanged() vs InvokeAsync(StateHasChanged) in Blazor WebAssembly

I have tried calling StateHasChanged() – instead of InvokeAsync(StateHasChanged) – in a Timer’s Elapsed event, and it works as expected

That must have been on WebAssembly. When you try that on Blazor Serverside I would expect an exception. StateHasChanged() checks if it runs on the right thread.

The core issue is that the rendering and calling StateHasChanged both have to happen on the main (UI) thread. Actually that is “on the SynchronizationContext” but for all intents and purposes you can think of it as being a single-thread, just as in WinForms, WPF and other GUIs). The virtual DOM is not thread-safe.

The main Blazor life-cycle events (OnInit, AfterRender, ButtonClick) are all executed on that special thread so in the rare case that you need StateHasChanged() there it can be called without InvokeAsync().

A Timer is different, it is an “external event” so you can’t be sure it will execute on the correct thread. InvokeAsync() delegates the work to Blazor’s SynchronizationContext that will ensure it does run on the main thread.

But Blazor WebAssembly only has 1 thread so for the time being external events always run on the main thread too. That means that when you get this Invoke pattern wrong you won’t notice anything. Until one day, when Blazor Wasm finally gets real threads, your code will fail. As is the case with your Timer experiment.

What is “Blazor’s synchronization context”?

In .net a synchronization context determines what happens with (after) await. Different platforms have different settings, the Blazor synccontext is a lot like that of WinForms and WPF. Mainly, the default is .ConfigureAwait(true): resume on the same thread/context.

I sometimes see .ConfigureAwait(false) in toplevel Blazor Wasm code. That too will blow up when we get real threads there. It is fine to use in services called from Blazor, but not for the toplevel methods.

And finally, await InvokeAsync(StateHasChanged) or await InvokeAsync(() => StateHasChanged()) is just about lambda’s in C#, nothing to do with Blazor. The first short form is a little more efficient.

I also sometimes see InvokeAsync() called without await

That will work. It probably is better than the other option: making the calling method (like the Timer’s OnTick) an async void. So do use this from a synchronous code path.

Reference

https://stackoverflow.com/questions/65230621/statehaschanged-vs-invokeasyncstatehaschanged-in-blazor

Blazor WebAssembly and Antiforgery token

EditForm comes with built-in anti-forgery token support. Blazor automatically secures the EditForm instances, saving you the hassle of explicitly handling CSRF protection.

Blazor WebAssembly apps run entirely in the browser and do not have a server-side processing pipeline where you would typically configure a middleware such as app.UseAntiforgery(). If your Blazor WebAssembly app interacts with server-side APIs, you should manage anti-forgery at the API level. However, if you already use token-based authentication to secure communication, anti-forgery tokens are generally not necessary. Token-based authentication, by its nature, mitigates the risks associated with CSRF, making additional anti-forgery tokens redundant.

Reference

https://learn.microsoft.com/en-us/xandr/digital-platform-api/token-based-api-authentication