# Vertical Slice in Blazor
Wir haben uns bereits angeschaut, was die [[Vertical Slice Architektur]] ist. In diesem Blog wollen wir uns eine Implementation der Architektur in Blazor anschauen. Das Ziel ist, eine kleine Produktverwaltung zu erstellen.
Der ganze Code ist auch auf einem [Repo](https://github.com/luis-kueng/verticalslice-eample) verfügbar.
## Einrichten
Wir teilen unsere Applikation in drei Projekte auf:
- DataAccess
- Models
- Web (heisst bei mir VerticalSliceExample)
Wir Trennen die Entitäten und den Datenzugriff von unseren Features, um die Abhängigkeit zwischen den Slices zu minimieren.
### Models
*Models* enthält die Entitäten unserer Applikation. Wir wollen eine Produktverwaltung erstellen, wofür wir nur eine Product-Entität brauchen:
```csharp
namespace Models;
public class Product
{
public required string? Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
```
*Models/Product.cs*
Mehr kommt in dieses Projekt eigentlich nicht.
### DataAccess
In diesem Projekt würde die Datenanbindung kommen. Für dieses Beispiel simulieren wir einfach eine Datenbank mit einer Liste von Produkten:
```csharp
using Models;
namespace DataAccess;
public abstract class ProductContext
{
public static List<Product> Products { get; set; }
static ProductContext()
{
Products = new List<Product>()
{
new() { Name = "Book", Price = 15.3m, Quantity = 16 },
new() { Name = "Chair", Price = 73.8m, Quantity = 3 },
new() { Name = "Potato", Price = 0.35m, Quantity = 69 },
new() { Name = "Tomato", Price = 0.10m, Quantity = 132 }
};
}
}
```
*DataAccess/ProductContext.cs*
*DataAccess* sollte nur die direkte Datenanbindung enthalten. Die Geschäftslogik wollen wir stattdessen individuell für die Features gestalten.
### Web
Im Web Projekt kommt unsere eigentliche Applikation. Für das CQRS Pattern brauchen wir ein NuGet Packet namens [MediatR](https://www.nuget.org/packages/MediatR).
Innerhalb des Projekts erstellen machen wir folgende Ordnerstruktur:
```
Components/
├─ Features/
├─ Layout/
├─ Pages/
```
VerticalSliceExample/
Die *Layout* und *Pages* Ordner kennen wir bereits. Der neue *Features* Ordner wird alle unsere Vertical Slices enthalten. Für jeden Slice werden wir einen weiteren Ordner erstellen in welchem sich dessen Dateien befinden.
## Produkte Hinzufügen
Um dieses Feature einzubauen erstellen wir zuerst einen *AddProduct* Ordner. Für ein Feature brauchen wir immer vier Bauteile:
- Anfrage (Command / Query)
- Handler
- Response
- Component
### Command
Jenachdem ob wir eine Lese- oder Schreiboperation durchführen, brauchen wir einen Query oder einen Command. Für dieses Feature wollen wir ein neues Produkt erstellen, weswegen wir einen Command brauchen.
Der Command enthält die nötigen Informationen, damit unser Handler später das Produkt Objekt erstellen kann. Diese Klasse muss das *IRequest* Interface von MediatR mit der *Response* implementieren. Die *Response* erstellen wir als nächstes
```csharp
using MediatR;
namespace VerticalSliceExample.Components.Features.AddProduct;
public class AddProductCommand : IRequest<AddProductResponse>
{
public string? Name { get; set; }
public decimal Price { get; set; }
public int Quantity { get; set; }
}
```
*VerticalSliceExample/Components/AddProduct/AddProductCommand.cs*
### Response
Die *Response* enthält die Informationen die wir zurückgeben wollen. In unserem Fall wollen wir eine Bestätigung, dass die Anfrage erfolgreich war und die Anzahl der Produkte in der Datenbank.
```csharp
namespace VerticalSliceExample.Components.Features.AddProduct;
public class AddProductResponse
{
public bool IsSuccess { get; init; }
public int ProductsInDatabase { get; set; }
}
```
*VerticalSliceExample/Components/AddProduct/AddProductResponse.cs*
### Handler
Der Handler ist für die effektive erstellung des Produkts zuständig. Wir implementieren das *IRequestInterface* um später die *Send* Funktion von MediatR nutzen zu können. Wir übergeben unseren obig erstellten Command und unsere Response.
Unser Handler hat eine Methode *Handle*, welche den request als Parameter nimmt. Wir nutzen diese Methode, um das Produkt zu erstellen, in der Datenbank zu speichern und anschliessend eine Response zurückzugeben.
```csharp
using DataAccess;
using MediatR;
using Models;
namespace VerticalSliceExample.Components.Features.AddProduct;
public class AddProductHandler : IRequestHandler<AddProductCommand, AddProductResponse>
{
public Task<AddProductResponse> Handle(AddProductCommand request, CancellationToken cancellationToken)
{
var product = new Product()
{
Name = request.Name,
Price = request.Price,
Quantity = request.Quantity
};
ProductContext.Products.Add(product);
return Task.FromResult(new AddProductResponse()
{
IsSuccess = true,
ProductsInDatabase = ProductContext.Products.Count
});
}
}
```
*VerticalSliceExample/Components/AddProduct/AddProductHandler.cs*
### Component
Zu guter letzt brauchen wir noch unsere Blazor Komponente mit der wir interagieren können. Was die Komponente angeht, brauchen wir nur ein einfaches Formular. Wir erstellen ein Command Field, an welches wir die Eingabefelder des Formulars binden.
Wenn das Formular abgeschickt wird, rufen wir eine *AddProduct* Methode auf. Innerhalb dieser rufen wir die MrdiatR *Send* Methode mit unserem Command auf. MediatR übernimmt die verwaltung des Handlers für uns, so dass wir nur über die Anfrage bescheid wissen müssen.
Die Methode gibt uns bei Beendung die Response zurück, welche uns mitteilt, ob die Anfrage erfolgreich war.
```csharp
@using MediatR
@inject IMediator Mediatr
<div class="flex flex-col gap-3 w-60">
<div class="flex flex-row justify-between">
<label>Name</label>
<input
class="w-40 border-2 border-slate-500"
type="text"
@bind="_command.Name"/>
</div>
<div class="flex flex-row justify-between">
<label>Preis</label>
<input
class="w-40 border-2 border-slate-500"
type="number"
@bind="_command.Price"/>
</div>
<div class="flex flex-row justify-between">
<label>Menge</label>
<input
class="w-40 border-2 border-slate-500"
type="number"
@bind="_command.Quantity"/>
</div>
</div>
<button class="w-fit h-fit px-3 py-2 bg-black text-white self-center" @onclick="async () => await AddProduct()">
Submit
</button>
@if (_response is { IsSuccess: true })
{
<p class="text-green-500 self-center">
Success!
</p>
}
@code {
AddProductCommand? _command;
AddProductResponse? _response;
protected override Task OnInitializedAsync()
{
_command = new AddProductCommand();
return Task.CompletedTask;
}
private async Task AddProduct()
{
if (_command != null) _response = await Mediatr.Send(_command);
_command = new AddProductCommand();
}
}
```
*VerticalSliceExample/Components/AddProduct/AddProductComponent.cs*
## Fazit
Um ein Feature implementieren zu können, brauchen wir vier Bauteile: Eine Anfrage (Command oder Query), eine Antwort, einen Handler und eine Komponente. Mit MediatR können wir sehr einfach das CQRS Pattern einbinden, was die Verwaltung der Anfragen für uns übernimmt. Alle Teile eines Features befinden sich zusammen in einem Ordner, welche sich wiederum im *Features* Ordner befinden. Nur die Entitäten und der Datenzugriff haben wir ausserhalb erstellt.
Würden wir weitere Features in unsere Applikation einbauen, hätten diese keine Abhängigkeit zum bereits bestehenden Slice, was die Erweiterung stark vereinfacht.