Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/instructions/blazor.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
description: 'Blazor component and application patterns'
applyTo: '**/*.razor, **/*.razor.cs, **/*.razor.css'
---

## Blazor Code Style and Structure

- Write idiomatic and efficient Blazor and C# code.
- Follow .NET and Blazor conventions.
- Use Razor Components appropriately for component-based UI development.
- Prefer inline functions for smaller components but separate complex logic into code-behind or service classes.
- Async/await should be used where applicable to ensure non-blocking UI operations.

## Naming Conventions

- Follow PascalCase for component names, method names, and public members.
- Use camelCase for private fields and local variables.
- Prefix interface names with "I" (e.g., IUserService).

## Blazor and .NET Specific Guidelines

- Utilize Blazor's built-in features for component lifecycle (e.g., OnInitializedAsync, OnParametersSetAsync).
- Use data binding effectively with @bind.
- Leverage Dependency Injection for services in Blazor.
- Structure Blazor components and services following Separation of Concerns.
- Always use the latest version C#, currently C# 13 features like record types, pattern matching, and global usings.

## Error Handling and Validation

- Implement proper error handling for Blazor pages and API calls.
- Use logging for error tracking in the backend and consider capturing UI-level errors in Blazor with tools like ErrorBoundary.
- Implement validation using FluentValidation or DataAnnotations in forms.

## Blazor API and Performance Optimization

- Utilize Blazor server-side or WebAssembly optimally based on the project requirements.
- Use asynchronous methods (async/await) for API calls or UI actions that could block the main thread.
- Optimize Razor components by reducing unnecessary renders and using StateHasChanged() efficiently.
- Minimize the component render tree by avoiding re-renders unless necessary, using ShouldRender() where appropriate.
- Use EventCallbacks for handling user interactions efficiently, passing only minimal data when triggering events.

## Caching Strategies

- Implement in-memory caching for frequently used data, especially for Blazor Server apps. Use IMemoryCache for lightweight caching solutions.
- For Blazor WebAssembly, utilize localStorage or sessionStorage to cache application state between user sessions.
- Consider Distributed Cache strategies (like Redis or SQL Server Cache) for larger applications that need shared state across multiple users or clients.
- Cache API calls by storing responses to avoid redundant calls when data is unlikely to change, thus improving the user experience.

## State Management Libraries

- Use Blazor's built-in Cascading Parameters and EventCallbacks for basic state sharing across components.
- Implement advanced state management solutions using libraries like Fluxor or BlazorState when the application grows in complexity.
- For client-side state persistence in Blazor WebAssembly, consider using Blazored.LocalStorage or Blazored.SessionStorage to maintain state between page reloads.
- For server-side Blazor, use Scoped Services and the StateContainer pattern to manage state within user sessions while minimizing re-renders.

## API Design and Integration

- Use HttpClient or other appropriate services to communicate with external APIs or your own backend.
- Implement error handling for API calls using try-catch and provide proper user feedback in the UI.

## Testing and Debugging in Visual Studio

- All unit testing and integration testing should be done in Visual Studio Enterprise.
- Test Blazor components and services using bUnit and xUnit.
- Debug Blazor UI issues using browser developer tools and Visual Studio's debugging tools for backend and server-side issues.
- For performance profiling and optimization, rely on Visual Studio's diagnostics tools.
- Do not use grep to search file contents. Use the Select-String PowerShell cmdlet or Visual Studio's built-in search functionality.

## Security and Authentication

- Implement Authentication and Authorization in the Blazor app where necessary using ASP.NET Identity or JWT tokens for API authentication.
- Use HTTPS for all web communication and ensure proper CORS policies are implemented.

## API Documentation and Swagger

- Use Swagger/OpenAPI for API documentation for your backend API services.
- Ensure XML documentation for models and API methods for enhancing Swagger documentation.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
TextInputType="TextInputType.Password"
@bind-Value="@MyValue"
Immediate="true"
Required="true"
MessageCondition="@(i => i.When(() => MyValue.Length < 8)
.Display("8+ characters", MessageState.Error)
.When(() => MyValue.Any(char.IsDigit) == false)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
<FluentTextInput Label="Error state"
MessageState="@MessageState.Error"
Message="This is an error message."
MessageCondition="@FluentFieldCondition.Always" />

<FluentTextInput Label="Warning state"
MessageState="@MessageState.Warning"
Message="This is a warning message."
MessageCondition="@FluentFieldCondition.Always" />

<FluentTextInput Label="Success state"
MessageState="@MessageState.Success"
Message="This is a success message."
MessageCondition="@FluentFieldCondition.Always" />

<FluentTextInput Label="Custom state"
Message="This is a custom message."
MessageIcon="@(new Icons.Regular.Size24.Accessibility())"
MessageCondition="@FluentFieldCondition.Always" />
<EditForm Model="@model" OnValidSubmit="@HandleValidSubmit" FormName="entry_form" novalidate="true">

<FluentTextInput Label="Error state"
@bind-Value="model.Text"
MessageState="@MessageState.Error"
Message="This is an error message."
MessageCondition="@FluentFieldCondition.Always"
Required="true" />


<FluentTextInput Label="Warning state"
MessageState="@MessageState.Warning"
Message="This is a warning message."
MessageCondition="@FluentFieldCondition.Always" />

<FluentTextInput Label="Success state"
MessageState="@MessageState.Success"
Message="This is a success message."
MessageCondition="@FluentFieldCondition.Always" />

<FluentTextInput Label="Custom state"
Message="This is a custom message."
MessageIcon="@(new Icons.Regular.Size24.Accessibility())"
MessageCondition="@FluentFieldCondition.Always" />

<div>
<FluentButton Type="ButtonType.Submit" Appearance="ButtonAppearance.Primary">Submit</FluentButton>
</div>
</EditForm>

@code {
[SupplyParameterFromForm]
private Model model { get; set; } = new();

private void HandleValidSubmit()
{
Console.WriteLine("HandleValidSubmit called");

// Process the valid form
}

private class Model
{
public string? Text { get; set; } = string.Empty;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,57 @@

<h1>Starfleet Starship Database</h1>
<p>
This form uses the Fluent UI input components. It uses a `DataAnnotationsValidator` and `FluentValidationSummary`.
This form uses the Fluent UI input components. It uses a `DataAnnotationsValidator`, a `FluentValidationSummary`
and `FluentValidationMessage`s.

On the `EditForm` component, the `novalidate="true"` attribute is set to disable the browser's native validation UI, allowing the Fluent UI components to handle validation feedback.
</p>

<h2>New Ship Entry Form</h2>

<EditForm Model="@starship" OnValidSubmit="@HandleValidSubmit" novalidate="true">
<DataAnnotationsValidator />
<FluentValidationSummary />


<FluentStack Orientation="Orientation.Vertical">
<div>
<FluentTextInput Name="identifier" @bind-Value="starship.Identifier" Label="Identifier" Required="true" />
</div>
<div>
<FluentTextArea Name="description" @bind-Value="starship.Description" Label="Description (min. 10 characters)" Required="true" />
</div>
<div>
<FluentSelect Name="class" Id="classification" @bind-Value="starship.Classification" TOption="string" TValue="string" Required="true" Label="Primary Classification">
<FluentOptionString Value="">Select classification ...</FluentOptionString>
<FluentOptionString Value="Exploration">Exploration</FluentOptionString>
<FluentOptionString Value="Diplomacy">Diplomacy</FluentOptionString>
<FluentOptionString Value="Defense">Defense</FluentOptionString>
</FluentSelect>
</div>
<div>
<FluentTextInput TextInputType="TextInputType.Number" Name="accommodation" @bind-Value="starship.MaximumAccommodation" Label="Maximum Accommodation" Required="true" />
</div>
<div>
<FluentCheckbox Name="approved" @bind-Value="starship.IsValidatedDesign" Required="true" Label="Engineering approval" />
</div>
<div>
<FluentDatePicker Name="production_date" Id="proddate" @bind-Value="starship.ProductionDate" Label="Production Date" Required="true" />
</div>
<div>
<FluentSwitch Name="teleporter" @bind-Value="starship.HasTeleporter" Label="Has a Teleporter" />
</div>
<FluentButton Type="ButtonType.Submit" Appearance="ButtonAppearance.Primary">Submit</FluentButton>
</FluentStack>
<DataAnnotationsValidator />
<FluentValidationSummary />


<FluentStack Orientation="Orientation.Vertical">
<div>
<FluentTextInput Name="identifier" @bind-Value="starship.Identifier" Label="Identifier" Required="true" />
@* <FluentValidationMessage For="@(() => starship.Identifier)" /> *@
</div>
<div>
<FluentTextArea Name="description" Width="75%" @bind-Value="starship.Description" Label="Description (min. 10 characters)" Required="true" />
@* <FluentValidationMessage For="@(() => starship.Description)" /> *@
</div>
<div>
<FluentTextInput Name="password" @bind-Value="starship.Password" Label="Password" Required="true" />
</div>
<div>
<FluentSelect Name="class" Id="classification" @bind-Value="starship.Classification" TOption="string" TValue="string" Required="true" Label="Primary Classification">
<FluentOptionString Value="">Select classification ...</FluentOptionString>
<FluentOptionString Value="Exploration">Exploration</FluentOptionString>
<FluentOptionString Value="Diplomacy">Diplomacy</FluentOptionString>
<FluentOptionString Value="Defense">Defense</FluentOptionString>
</FluentSelect>
@* <FluentValidationMessage For="@(() => starship.Classification)" /> *@
</div>
<div>
<FluentTextInput Name="accommodation" @bind-Value="starship.MaximumAccommodation" Label="Maximum Accommodation" Required="true" />
@* <FluentValidationMessage For="@(() => starship.MaximumAccommodation)" /> *@
</div>
<div>
<FluentCheckbox Name="approved" @bind-Value="starship.IsValidatedDesign" Required="true" Label="Engineering approval" />
@* <FluentValidationMessage For="@(() => starship.IsValidatedDesign)" /> *@
</div>
<div>
<FluentDatePicker Name="production_date" Id="proddate" @bind-Value="starship.ProductionDate" Label="Production Date" Required="true" />
@* <FluentValidationMessage For="@(() => starship.ProductionDate)" /> *@
</div>
<div>
<FluentSwitch Name="teleporter" @bind-Value="starship.HasTeleporter" Label="Has a Teleporter" />
</div>
<FluentButton Type="ButtonType.Submit" Appearance="ButtonAppearance.Primary">Submit</FluentButton>
</FluentStack>
</EditForm>

<div style="margin-top: 3rem;"><a href="http://www.startrek.com/">Star Trek</a>, ©1966-2023 CBS Studios, Inc. and <a href="https://www.paramount.com">Paramount Pictures</a></div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
form div:not(.fluent-stack-vertical) {
display: inline-flex;
margin-bottom: 1rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ icon: Form
The Fluent UI Razor components work with a validation summary in the same way the standard Blazor (input) components do. An extra component is provided to make it possible to show a validation summary that follows the Fluent Design guidelines:

- FluentValidationSummary
- FluentValidationMessage

See the [documentation](https://learn.microsoft.com/en-us/aspnet/core/blazor/forms/validation?view=aspnetcore-10.0#validation-summary-and-validation-message-components) on the Learn site for more information on the standard components. As the Fluent component is based on the standard component, the same documentation applies

Expand All @@ -25,3 +26,6 @@ Not all of the library's input components are used in this form. No data is actu

{{ API Type=FluentValidationSummary }}

## API FluentValidationMessage

{{ API Type=FluentValidationMessage }}
10 changes: 9 additions & 1 deletion examples/Tools/FluentUI.Demo.SampleData/Starship.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public class Starship
[StringLength(16, ErrorMessage = "Identifier too long (16 character limit)")]
public string? Identifier { get; set; }

/// <summary>
/// The unique identifier for the starship.
/// </summary>
[Required]
[MinLength(8, ErrorMessage = "Password is too short")]
[RegularExpression(@"[A-Z][a-z]{\d}+", ErrorMessage = "Password must contain at least one uppercase letter, one lowercase letter and one digit")]
public string? Password { get; set; }

/// <summary>
/// The description of the starship.
/// </summary>
Expand All @@ -46,7 +54,7 @@ public class Starship
/// Maximum accommodation capacity of the starship.
/// </summary>
[Required(ErrorMessage = "Maximum accommodation is required")]
[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000)")]
//[Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000)")]
public string? MaximumAccommodation { get; set; }

/// <summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Core/Components/Base/FluentInputBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// ------------------------------------------------------------------------

using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.FluentUI.AspNetCore.Components.Utilities;
Expand Down Expand Up @@ -84,6 +85,9 @@ protected FluentInputBase(LibraryConfiguration configuration)

#region IFluentField

/// <inheritdoc />
LambdaExpression? IFluentField.ValueExpression => ValueExpression;

/// <inheritdoc cref="IFluentField.FocusLost" />
public virtual bool FocusLost { get; protected set; }

Expand Down Expand Up @@ -241,6 +245,8 @@ public virtual async ValueTask DisposeAsync()
_cachedServices?.DisposeTooltipAsync(this);
_cachedServices?.Dispose();
await JSModule.DisposeAsync();

GC.SuppressFinalize(this);
}

/// <summary>
Expand Down
23 changes: 10 additions & 13 deletions src/Core/Components/Field/Condition/FluentFieldCondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,20 @@ public bool Build(FluentFieldConditionOptions? options = null)
{
if (item.Condition.Invoke())
{
var coloredMessage = FluentFieldParameterSelector.StateToMessageTemplate(item.State, item.Message);

messages.Add(builder =>
{
builder.OpenElement(0, "div");
builder.AddContent(1, FluentField.CreateIcon(item.Icon ?? FluentFieldParameterSelector.StateToIcon(item.State)));

if (item.State is null)
{
builder.AddContent(2, item.Message);
}
else
builder.OpenComponent<FluentText>(0);
builder.AddComponentParameter(1, "As", TextTag.Span);
builder.AddComponentParameter(2, "Color", item.State == MessageState.Error ? Color.Error : Color.Info);
//builder.AddAttribute(4, "slot", "message");
builder.AddAttribute(5, "style", "display: flex; align-items: center;");
builder.AddAttribute(6, "ChildContent", (RenderFragment)(contentBuilder =>
{
builder.AddContent(2, coloredMessage);
}
contentBuilder.AddContent(0, FluentField.CreateIcon(item.Icon ?? FluentFieldParameterCollector.StateToIcon(item.State)));
contentBuilder.AddContent(1, item.Message);
}));

builder.CloseElement();
builder.CloseComponent();
});
}
}
Expand Down
Loading
Loading