# .NET + Blazor Stack

Stack principal para desenvolvimento de aplicações web com .NET e Blazor Server.

## Visão Geral

| Aspecto | Tecnologia |
|---------|------------|
| **Backend** | .NET 10 / C# 14 |
| **Frontend** | Blazor Server |
| **Database** | Entity Framework Core 10 + Azure SQL |
| **Hosting** | Azure Container Apps |
| **Background** | Hangfire |
| **AI** | Microsoft Agent Framework (exclusivo) |

## Triggers

Keywords: `blazor`, `razor`, `server-side`, `.net`, `csharp`, `dotnet`

---

## ⚠️ CRITICAL PITFALLS (Read FIRST!)

**ALWAYS consult framework standards before implementing:**
- `framework/standards/blazor-pitfalls.md` - Common issues & solutions
- `framework/standards/blazor-lifecycle.md` - Lifecycle patterns
- `framework/standards/program-cs-checklist.md` - Required Program.cs setup
- `framework/standards/dotnet10-compatibility.md` - Package compatibility

**Quick Checklist:**
- [ ] DI Pattern: Use HttpClient, NOT direct Application layer injection
- [ ] Lifecycle: JSRuntime ONLY in OnAfterRenderAsync(firstRender)
- [ ] Program.cs: app.UseStaticFiles() BEFORE app.UseAntiforgery()
- [ ] File Upload: Specify maxAllowedSize in OpenReadStream()
- [ ] RenderMode: NO @rendermode on MainLayout
- [ ] Package Versions: MudBlazor ≥ 8.15.0 for .NET 10

## Estrutura de Projeto

```
src/
├── {App}.Domain/                  # Entidades, Value Objects, Enums
│   ├── Entities/
│   ├── ValueObjects/
│   ├── Enums/
│   └── Exceptions/
│
├── {App}.Application/             # Serviços, DTOs, Interfaces
│   ├── Services/
│   ├── DTOs/
│   ├── Interfaces/
│   └── Validators/
│
├── {App}.Infrastructure/          # EF Core, External Services
│   ├── Data/
│   │   ├── AppDbContext.cs
│   │   ├── Configurations/
│   │   └── Migrations/
│   ├── Services/
│   └── Extensions/
│
├── {App}.Web/                     # Blazor Server
│   ├── Program.cs
│   ├── Components/
│   │   ├── Layout/
│   │   ├── Pages/
│   │   └── Shared/
│   ├── wwwroot/
│   └── appsettings.json
│
└── tests/
    ├── {App}.UnitTests/
    └── {App}.IntegrationTests/
```

## Configuração do Projeto

### csproj principal

```xml
<!-- {App}.Web.csproj -->
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\{App}.Application\{App}.Application.csproj" />
    <ProjectReference Include="..\{App}.Infrastructure\{App}.Infrastructure.csproj" />
  </ItemGroup>
</Project>
```

### Program.cs

```csharp
var builder = WebApplication.CreateBuilder(args);

// Services
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();

// Database
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Application Services
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddScoped<ICustomerService, CustomerService>();

// Hangfire
builder.Services.AddHangfire(config => config
    .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
builder.Services.AddHangfireServer();

var app = builder.Build();

// Pipeline
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.MapHangfireDashboard("/hangfire");

app.Run();
```

## Componentes Blazor

### Padrões de Componente

```razor
@* Components/Pages/Orders/OrderList.razor *@
@page "/orders"
@inject IOrderService OrderService
@inject NavigationManager Navigation

<PageTitle>Pedidos</PageTitle>

<div class="container mx-auto p-4">
    <div class="flex justify-between items-center mb-6">
        <h1 class="text-2xl font-bold">Pedidos</h1>
        <button @onclick="CreateNew" class="btn btn-primary">
            Novo Pedido
        </button>
    </div>

    @if (_orders is null)
    {
        <Loading />
    }
    else if (!_orders.Any())
    {
        <EmptyState Message="Nenhum pedido encontrado" />
    }
    else
    {
        <DataTable Items="_orders">
            <HeaderTemplate>
                <th>Número</th>
                <th>Cliente</th>
                <th>Total</th>
                <th>Status</th>
                <th>Ações</th>
            </HeaderTemplate>
            <RowTemplate Context="order">
                <td>@order.OrderNumber</td>
                <td>@order.CustomerName</td>
                <td>@order.Total.ToString("C")</td>
                <td><StatusBadge Status="@order.Status" /></td>
                <td>
                    <button @onclick="() => ViewOrder(order.Id)" class="btn btn-sm">
                        Ver
                    </button>
                </td>
            </RowTemplate>
        </DataTable>
    }
</div>

@code {
    private List<OrderDto>? _orders;

    protected override async Task OnInitializedAsync()
    {
        _orders = await OrderService.GetAllAsync();
    }

    private void CreateNew() => Navigation.NavigateTo("/orders/new");

    private void ViewOrder(int id) => Navigation.NavigateTo($"/orders/{id}");
}
```

### Formulários

```razor
@* Components/Pages/Orders/OrderForm.razor *@
@page "/orders/new"
@page "/orders/{Id:int}/edit"
@inject IOrderService OrderService
@inject NavigationManager Navigation

<PageTitle>@(_isEdit ? "Editar" : "Novo") Pedido</PageTitle>

<EditForm Model="_model" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />

    <div class="space-y-4">
        <div>
            <label for="customerId">Cliente</label>
            <InputSelect @bind-Value="_model.CustomerId" id="customerId" class="input">
                <option value="">Selecione...</option>
                @foreach (var customer in _customers)
                {
                    <option value="@customer.Id">@customer.Name</option>
                }
            </InputSelect>
            <ValidationMessage For="() => _model.CustomerId" />
        </div>

        <div>
            <label for="notes">Observações</label>
            <InputTextArea @bind-Value="_model.Notes" id="notes" class="input" />
        </div>

        <div class="flex gap-2">
            <button type="submit" class="btn btn-primary" disabled="@_isSubmitting">
                @if (_isSubmitting)
                {
                    <span class="loading"></span>
                }
                Salvar
            </button>
            <button type="button" @onclick="Cancel" class="btn">Cancelar</button>
        </div>
    </div>
</EditForm>

@code {
    [Parameter] public int? Id { get; set; }

    private OrderFormModel _model = new();
    private List<CustomerDto> _customers = new();
    private bool _isEdit => Id.HasValue;
    private bool _isSubmitting;

    protected override async Task OnInitializedAsync()
    {
        _customers = await CustomerService.GetAllAsync();

        if (_isEdit)
        {
            var order = await OrderService.GetByIdAsync(Id!.Value);
            _model = new OrderFormModel
            {
                CustomerId = order.CustomerId,
                Notes = order.Notes
            };
        }
    }

    private async Task HandleSubmit()
    {
        _isSubmitting = true;

        try
        {
            if (_isEdit)
                await OrderService.UpdateAsync(Id!.Value, _model);
            else
                await OrderService.CreateAsync(_model);

            Navigation.NavigateTo("/orders");
        }
        finally
        {
            _isSubmitting = false;
        }
    }

    private void Cancel() => Navigation.NavigateTo("/orders");
}
```

## Services

```csharp
// Application/Services/OrderService.cs
public interface IOrderService
{
    Task<List<OrderDto>> GetAllAsync(CancellationToken ct = default);
    Task<OrderDto> GetByIdAsync(int id, CancellationToken ct = default);
    Task<OrderDto> CreateAsync(CreateOrderRequest request, CancellationToken ct = default);
    Task UpdateAsync(int id, UpdateOrderRequest request, CancellationToken ct = default);
}

public class OrderService : IOrderService
{
    private readonly AppDbContext _context;
    private readonly ILogger<OrderService> _logger;

    public OrderService(AppDbContext context, ILogger<OrderService> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task<List<OrderDto>> GetAllAsync(CancellationToken ct = default)
    {
        return await _context.Orders
            .Include(o => o.Customer)
            .OrderByDescending(o => o.CreatedAt)
            .Select(o => new OrderDto
            {
                Id = o.Id,
                OrderNumber = o.OrderNumber,
                CustomerName = o.Customer.Name,
                Total = o.Total,
                Status = o.Status.ToString()
            })
            .ToListAsync(ct);
    }

    public async Task<OrderDto> CreateAsync(CreateOrderRequest request, CancellationToken ct = default)
    {
        var order = Order.Create(request.CustomerId, request.Items);

        _context.Orders.Add(order);
        await _context.SaveChangesAsync(ct);

        _logger.LogInformation("Order {OrderNumber} created", order.OrderNumber);

        return new OrderDto { /* map */ };
    }
}
```

---

## 🎨 UI Component Libraries

### Recomendação para Projetos AI-First

| Biblioteca | Quando Usar | Vantagens | Desvantagens |
|------------|-------------|-----------|--------------|
| **Fluent UI** ⭐ | Projetos AI-first, Micro-SaaS | Componentes AI nativos, Microsoft integration, Performance (~200KB) | Menos componentes prontos |
| **MudBlazor** | SaaS tradicional, Analytics pesados | 140+ componentes, Charts nativos, Templates prontos | Mais pesado (~500KB), Sem componentes AI |
| **Híbrido** | SaaS complexo com AI | Melhor dos dois mundos | Maior bundle size (~350KB) |

### Fluent UI Blazor (Recomendado)

**Para:**
- Projetos com AI agents/chat
- Integração com Microsoft Agent Framework
- Produtos com UX Copilot-like
- Micro-SaaS que priorizam performance

**Setup rápido:**
```bash
dotnet add package Microsoft.FluentUI.AspNetCore.Components
```

```csharp
// Program.cs
builder.Services.AddFluentUIComponents();
```

**Exemplo de Chat AI:**
```razor
<FluentMessageBar Intent="MessageIntent.Success">
    <FluentLabel Weight="FontWeight.Bold">AI Assistant</FluentLabel>
    <FluentLabel>@_aiResponse</FluentLabel>
</FluentMessageBar>
```

**Guia completo:** [Fluent UI Setup](../../.morph/standards/fluent-ui-setup.md)

### MudBlazor (Complemento)

**Para:**
- Grids complexos com filtros avançados
- Charts e dashboards analytics
- Componentes que Fluent UI não tem

**Setup:**
```bash
dotnet add package MudBlazor
```

**Exemplo de Grid Avançado:**
```razor
<MudDataGrid T="Order" Items="@_orders"
             Filterable="true"
             Groupable="true"
             Dense="true">
    <Columns>
        <PropertyColumn Property="x => x.OrderNumber" />
        <PropertyColumn Property="x => x.Total" Format="C2" />
    </Columns>
</MudDataGrid>
```

### Abordagem Híbrida

**Quando usar:**
- SaaS com AI + analytics complexos
- Dashboards com charts + chat AI
- Aplicações enterprise completas

**Pattern:**
- **Base:** Fluent UI (layout, navegação, AI)
- **Complemento:** MudBlazor (grids, charts)

```razor
<FluentLayout>
    <FluentHeader>...</FluentHeader>
    <FluentMain>
        @* Grid complexo *@
        <MudDataGrid ... />

        @* Chat AI *@
        <FluentMessageBar>AI response</FluentMessageBar>
    </FluentMain>
</FluentLayout>
```

---

## 🎯 Novas Features .NET 10

### State Persistence

**Novo:** Propriedades com `[PersistentState]` são persist idas automaticamente.

```razor
@page "/counter"

<button @onclick="Increment">Count: @Count</button>

@code {
    // Persiste automaticamente em pre-rendering e circuit disconnections
    [PersistentState]
    private int Count { get; set; } = 0;

    private void Increment() => Count++;
}
```

**Quando usar:**
- Componentes com estado que precisam sobreviver a pre-rendering
- Aplicações com circuit disconnections frequentes
- Formulários multi-step

**Não usar para:**
- Dados sensíveis (passwords, tokens)
- Estado que deve ser recalculado (derived state)

### Circuit Pause/Resume

**Novo:** APIs JavaScript para pausar/resumir circuits.

```razor
@inject IJSRuntime JS

@code {
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            await JS.InvokeVoidAsync("setupCircuitManagement");
        }
    }
}
```

```javascript
// wwwroot/app.js
function setupCircuitManagement() {
    // Pausar quando usuário minimiza/troca de aba
    document.addEventListener('visibilitychange', () => {
        if (document.hidden) {
            Blazor.pauseCircuit();  // Libera recursos do servidor
        } else {
            Blazor.resumeCircuit(); // Restaura estado
        }
    });
}
```

**Benefícios:**
- Reduz uso de memória do servidor
- Melhora escalabilidade
- Estado mantido durante pausa

### Blazor Metrics & Observability

**Novo:** Métricas específicas de Blazor Server.

```csharp
// Program.cs
builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.AddSource("Microsoft.AspNetCore.Components.Server");
        tracing.AddAspireTracing();
    })
    .WithMetrics(metrics =>
    {
        metrics.AddMeter("Microsoft.AspNetCore.Components.Server");
        metrics.AddAspireMetrics();
    });
```

**Métricas disponíveis:**
- Page navigations (por rota/componente)
- UI events (eventos e duração)
- Component lifecycle (render diff sizes)
- Active circuits (usuários simultâneos)
- Circuit state (connected/disconnected)

**Visualização:** Aspire Dashboard integrado.

### Hot Reload Aprimorado

**Automático:** 10x mais rápido no Visual Studio 2026.

**Nada a fazer:** Apenas use VS 2026 Preview ou `dotnet watch`.

### Formulários com Validação Aninhada

**Novo:** Validação automática de modelos aninhados.

```csharp
public class Order
{
    [Required]
    public string OrderNumber { get; set; } = null!;

    // Modelo aninhado - validado automaticamente
    public Customer Customer { get; set; } = new();
    public List<OrderItem> Items { get; set; } = new();
}

public class Customer
{
    [Required]
    public string Name { get; set; } = null!;

    [EmailAddress]
    public string Email { get; set; } = null!;

    // Aninhamento profundo - também validado
    public Address ShippingAddress { get; set; } = new();
}
```

```razor
<EditForm Model="@_order" OnValidSubmit="HandleSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />
    <!-- Campos... -->
</EditForm>
```

**Configuração (Program.cs):**
```csharp
builder.Services.AddValidation(); // Habilita validação aninhada
```

---

## 📚 Documentação de Referência

- [Blazor Documentation](https://learn.microsoft.com/aspnet/core/blazor/)
- [Entity Framework Core 10](https://learn.microsoft.com/ef/core/)
- [ASP.NET Core 10](https://learn.microsoft.com/aspnet/core/)
- [Hangfire](https://docs.hangfire.io/)
- **[Passkeys/WebAuthn](../../.morph/standards/passkeys-auth.md)**
- **[Agent Framework](../../.morph/standards/agent-framework-setup.md)**

## Checklist de Projeto

- [ ] Estrutura de camadas (Domain, Application, Infrastructure, Web)
- [ ] EF Core configurado com migrations
- [ ] Blazor Server com componentes reutilizáveis
- [ ] Services com injeção de dependência
- [ ] Logging estruturado
- [ ] Validação com FluentValidation ou DataAnnotations
- [ ] Hangfire para background jobs
- [ ] Dockerfile para containerização

---

*MORPH-SPEC by Polymorphism Tech*
