owned this note
owned this note
Published
Linked with GitHub
# [WP] BistroQ - Milestone 1 Report
:::info
This report can be viewed in [HackMD](https://hackmd.io/@CXS0caWySWi1obKmwFzL-Q/tondeptrai) (We strongly recommend to read on the web for better reading experience).
:::
:::info
Link to project: [Google Drive](https://drive.google.com/drive/folders/1ciKlv2l09x2DuuUC523eUsIVdXE5pfft?usp=sharing)
:::
## Table Of Contents
- [Overview](#Overview)
- [Project Setup](#Project-Setup)
- [Backend Setup](#Backend-Setup)
- [WinUI Setup](#WinUI-Setup)
- [Features](#Features)
- [Planning](#Planning)
- [Final](#Final)
- [Completed Works](#Completed-Works)
- [Technical Debt](#Technical-Debt)
- [UI/UX](#UIUX)
- [Design Pattern](#Design-Pattern)
- [Dependency Injection](#Dependency-Injection)
- [Command Pattern](#Command-Pattern)
- [Builder Pattern](#Builder-Pattern)
- [Repository Pattern](#Repository-Pattern)
- [UnitOfWork Pattern](#UnitOfWork-Pattern)
- [Singleton Pattern](#Singleton-Pattern)
- [Architecture](#Architecture)
- [WinUI Architecture](#WinUI-Architecture)
- [Backend Architecture](#Backend-Architecture)
- [Advanced Topics](#Advanced-Topics)
- [Semaphore](#Semaphore)
- [ICommand and RelayCommand](#ICommand-and-RelayCommand)
- [DelegatingHandler](#DelegatingHandler)
- [DI Containers](#DI-Containers)
- [JWT](#JWT)
- [DPAPI](#DPAPI)
- [INotifyDataErrorInfo](#INotifyDataErrorInfo)
- [Git Flow Evidence](#Git-Flow-Evidence)
- [Code Review Evidence](#Code-Review-Evidence)
- [Login Page WinUI](#Login-Page-WinUI)
- [Validator WinUI](#Validator-WinUI)
- [Zone Admin Page WinUI](#Zone-Admin-Page-WinUI)
- [Navigation Pane WinUI](#Navigation-Pane-WinUI)
- [Skeleton API](#Skeleton-API)
- [Authentication/Authorization API](#Authentication/Authorization-API)
- [Category API](#Category-API)
- [Table Zone API](#Table-Zone-API)
- [Hotfix Zone Table API](#Hotfix-Zone-Table-API)
## Overview
Tech stack:
- WinUI .NET 7.0
- ASP.NET Framework .NET 8.0
- MySQL + Entity Framework Core
> [!Important]
>Despite this course mainly focused on WinUI 3 Programming, we would greatly appreciate if you can give us feedback on the backend of our project. We believe that your expertise could provide us with valuable insights and practical knowledge that would enhance our learning experience. Your review would help us a lot in understand real-world applications and improve our skills.
## Project Setup
### Backend Setup
1. Install dependencies
```zsh
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef
```
2. Create ENV
- Create file `.env` based on the file `.env.sample` in the Solution directory.
3. Ensure MySQL and database is working.
4. Run migration
```zsh
dotnet ef database update
```
5. Run project
5.1. If running in an IDE
- Run `BistroQ.API` Project through Visual Studio.
5.2. If running using .NET CLI
```zsh
dotnet restore
dotnet watch run --project BistroQ.API
```
### WinUI Setup
1. Have backend server running successfully.
2. Run `BistroQ` Project through Visual Studio.
### Testing Account
| Role | Username | Password |
| ------- | -------- | -------- |
| Admin | admin | admin |
| Kitchen | Kitchen | Kitchen |
| Cashier | cashier | cashier |
| Client | client1 | client |
| Client | client2 | client |
| Client | client3 | client |
| Client | client4 | client |
| Client | client5 | client |
| Client | client6 | client |
| Client | client7 | client |
| Client | client8 | client |
| Client | client9 | client |
## Features
### Planning
We planned to do these features in Milestone 1:
- Authentication/Authorization: Login, create account for clients.
- Available Table Management: Control the number of available tables and the orders for occupied tables. Take-away and delivery channels are not included in this control.
- Tables Changing: Allow customers to change their table.
- Client can make orders: Have an UI for user to order a dish.
### Final
We were unable to complete all the tasks as planned. Despite our efforts, several tasks remain incomplete or delayed. However, it still meets the requirement of 1/3 of the working hours (approximately 6 hours).
| Task | Working hours (Estimated) | Responsible by |
| ----------------------------------------------------- | ------------------------------ | -------------- |
| API for zones, tables | 0.5 hour | Tiến |
| API for login | 0.5 hour | **Both** |
| API for products, categories | 0.5 hour | Toàn |
| Pagination user control | 0.75 hour | Tiến |
| Login page | 1.25 hour | Toàn |
| Zone management page (CRUD) with sorting, pagination | 0.125 hour *(CRUD excluded)* | Tiến |
| Table management page (CRUD) with sorting, pagination | 0.125 hour *(CRUD excluded)* | Tiến |
| Table management page for cashier (use mock data) | 1 hour | Toàn |
| Generic validation | 1 hour | Toàn |
| Navigation based on user role | 0.5 hour | Toàn |
| Order page for client (use mock data) | 1 hour | Toàn |
| ApiClient for API calls in WinUI app | 0.25 hour | **Both** |
:::info
Total estimated working hours: 7.5 hours
:::
Reason:
- Starting the project from scratch, choosing the right architecture anDd organizing the code was a challenge and time-consuming than expected.
- An additional API needs to be built to support specific functions.
### Completed Works
Authentication/Authorization:
- Works with real users.
- Automatically remember users.
- Logout supported.
- Navigate to user's role-specific page.
- Store token in a secure way.
Available Table Management:
- Cashiers can view the status of tables that are currently occupied.
- Cashiers can also access the order details for each table.
Admin Resource Management:
- CRUD for Zones and Tables.
Client Order:
- Create new order and view current order (No data).
### Technical Debt
These technical debts will be resolved in the next milestone.
:::warning
- Using `async` inside constructor `Shell Page`.
- Mismatch ID data type between resources.
- `Available Table Management` feature is not completed. No animation, lack of highlighted parts.
- WinUI
:::
## UI/UX
We strongly follows the Windows 11 design guidelines, mentioned in WinUI 3 Gallery. (Mica backdrops, spacing, etc.)
:::warning
Some parts of our project still use WPF components because the equivalent components in WinUI 3 are either still in beta or not yet available.
:::
:::info
**Login**
:::
Login Screen when invalid input

Loading animation

Invalid password

:::info
**Admin Zone**
:::
Zones View

Add Zone Form

Added successfully

After added zone

Choose a zone

Edit zone form

Update zone successfully

:::info
**Pagination**
:::
Choosing rows per page

After choose row

Page 2 with 10 rows per page

:::info
**Sorting**
:::
Some fields are sortable

:::info
**Admin Tables**
:::
Admin tables view

Add table form

Pick zone from existing zone

:::info
**Cashier Table**
:::

:::info
Client Order
:::

:::info
Logout Button
:::

## Design Pattern
### **Dependency Injection**
In our application, we need to store tokens. We started by implementing `TokenJsonStorageService` to save tokens in a JSON file. Later, we upgraded to `TokenSecureStorageService` to securely store tokens using DPAPI (Data Protection API).
```csharp!=
// ITokenStorageService.cs
public interface ITokenStorageService
{
Task SaveAccessToken(string accessToken);
Task<string> GetAccessToken();
}
// TokenJsonStorageService.cs
public class TokenJsonStorageService : ITokenStorageService
{
Task SaveAccessToken(string accessToken) { /* Save token in JSON file */ }
Task<string> GetAccessToken(); { /* Retrieve token from JSON */ }
}
// TokenSecureStorageService.cs
public class TokenSecureStorageService : ITokenStorageService
{
Task SaveAccessToken(string accessToken) { /* Save token securely with DPAPI */ }
Task<string> GetAccessToken(); { /* Retrieve token from secure storage */ }
}
// App.xaml.cs
services.AddSingleton<ITokenStorageService, TokenJsonStorageService>(); // Use DI to inject this service
```
### **Command Pattern**
Instead of creating `EventHandler` in `LoginPage`, using `ICommand` class (which implements Command Pattern) in `ViewModel` makes the code easier to test and improves separation of concerns.
```csharp!=
// LoginViewModel.cs
public ICommand LoginCommand { get; }
public LoginViewModel()
{
LoginCommand = new AsyncRelayCommand(async () =>
{
await Login();
});
}
```
```xml!=
<!-- LoginPage.xaml -->
<!-- Clicking this button will execute LoginCommand -->
<Button
Style="{StaticResource AccentButtonStyle}"
Width="60"
Command="{x:Bind ViewModel.LoginCommand}"/>
```
### **Builder Pattern**
To retrieve resources from the database, we use `BaseQueryableBuilder` to construct an `IQueryable` query. This builder allows chaining configurations for filtering, sorting, and paginating data, making it easy to assemble different query conditions.
```csharp!=
// BaseQueryableBuilder.cs
public abstract class BaseQueryableBuilder<T> where T : class
{
protected IQueryable<T> Queryable;
protected BaseQueryableBuilder(IQueryable<T> queryable)
{
Queryable = queryable;
}
public IQueryable<T> Build()
{
return Queryable;
}
}
// ProductQueryableBuilder.cs
public class ProductQueryableBuilder : BaseQueryableBuilder<Product>
{
public ProductQueryableBuilder WithName(string? name) {/* Apply filter for name */}
public ProductQueryableBuilder ApplyPaging(int page, int size) {/* Apply pagination */}
public ProductQueryableBuilder ApplySorting(string? orderBy, string? orderDirection) {/* Apply sorting */}
}
// Usage:
var builder =
new ProductQueryableBuilder(_unitOfWork.ProductRepository.GetQueryable())
.WithName(queryParams.Name)
.ApplySorting(queryParams.OrderBy, queryParams.OrderDirection)
.ApplyPaging(queryParams.Page, queryParams.Size);
```
This can be used to construct complex LINQ queries or queryable objects (`IQueryable`) dynamically based on various conditions.
### **Repository Pattern**
Encapsulates data access logic, keeping it separate from business logic. Implementing this pattern makes it easier to change the ORM if needed or enhance database scaling techniques (Database Replication, Caching, etc).
### **UnitOfWork Pattern**
A `UnitOfWork` class that encapsulates transaction management across multiple repositories for a single operation. For example in the application, `UnitOfWork` coordinates between `OrderRepository` and `ProductRepository` when placing an order. If updating the order and adjusting product stock both succeed, the transaction is committed; if one fails, both are rolled back.
```csharp!=
public interface IUnitOfWork
{
IProductRepository ProductRepository { get; }
IOrderRepository OrderRepository { get; }
ICategoryRepository CategoryRepository { get; }
Task<int> SaveChangesAsync();
Task BeginTransactionAsync();
Task CommitTransactionAsync();
Task RollbackTransactionAsync();
}
```
### **Singleton Pattern**
The Singleton pattern ensures a class has only one instance and provides a global access point to it (Most `Services` in application implements this pattern). Besides, we use a singleton for `JwtSettings` to manage the same authentication settings for all parts of our API.
```csharp!=
public class JwtSettings
{
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int AccessTokenExpiresInMinutes { get; set; }
public int RefreshTokenExpiredInMinutes { get; set; }
public JwtSettings ReadFromEnvironment() { /* Read data when program starts */ }
}
```
## Architecture
:::success
The architecture of the system is designed to promote the principles of separation of concerns, maintainability, and scalability.
:::
### WinUI Architecture
The WinUI application follows `MVVM` architecture, and separates into 3 Projects within a Solution:
- **UI Layer**: Contains Views, ViewModels, Styles and application's configuration.
- **Core Layer**: Implements business logic and stores Models, Dtos.
*Folder Structure*:
```
.
├── BistroQ
│ ├── Activation
│ ├── Assets
│ ├── Behaviors
│ ├── Contracts
│ │ ├── Services
│ │ └── ViewModels
│ ├── Converters
│ ├── Helpers
│ ├── Models
│ ├── Properties
│ │ └── PublishProfiles
│ ├── Services
│ ├── Strings
│ │ └── en-us
│ ├── Styles
│ ├── Validation
│ ├── ViewModels
│ │ ├── AdminTable
│ │ ├── AdminZone
│ │ └── Client
│ └── Views
│ ├── AdminTable
│ ├── AdminZone
│ ├── Client
│ └── UserControls
├── BistroQ.Core
│ ├── Contracts
│ │ └── Services
│ ├── Dtos
│ │ ├── Tables
│ │ └── Zones
│ ├── Helpers
│ ├── Models
│ │ ├── Entities
│ │ └── Exceptions
│ └── Services
│ ├── Auth
│ ├── Http
│ ├── Mock
│ └── Order
└── BistroQ.Tests.MSTest
```
### Backend Architecture
The API follows `Layered Architecture Pattern`, and separates into 4 Projects inside a Solution:
- **Core Layer**: Contains Dtos, Models and Interfaces.
- **Infrastructure Layer**: Also called Data Access Layer, implements Repositories and DbContext.
- **Services Layer**: Implements business logic and application services.
- **API Layer**: Manages HTTP requests, client communication and application's configuration.
*Folder Structure*:
```
.
├── BistroQ.API
│ ├── Configurations
│ ├── Controllers
│ │ ├── Auth
│ │ ├── Category
│ │ ├── Product
│ │ ├── Table
│ │ └── Zone
│ ├── Middlewares
│ └── Properties
├── BistroQ.Core
│ ├── Common
│ │ ├── Builder
│ │ └── Settings
│ ├── Dtos
│ │ ├── Auth
│ │ ├── Category
│ │ ├── Products
│ │ ├── Tables
│ │ └── Zones
│ ├── Entities
│ ├── Enums
│ ├── Exceptions
│ ├── Interfaces
│ │ ├── Repositories
│ │ └── Services
│ ├── Mappings
│ └── Specifications
├── BistroQ.Infrastructure
│ ├── Data
│ ├── Migrations
│ ├── Repositories
│ └── UnitOfWork
├── BistroQ.Services
│ └── Services
└── bistro-q-api
```
*Diagrams*:
:::info
This diagram displays the folder structure above
:::

:::info
The diagram below illustrates the interaction flow for a typical HTTP request from a client application to the API.
:::

## Advanced Topics
### **Semaphore**
`Semaphore` class is used to control access to a pool of resources in concurrency operations. It limits the number of threads that can access the resource at a time. In our application, it’s used for working with files.
Example:
```csharp!
public class TokenSecureStorageService : ITokenStorageService
{
private readonly SemaphoreSlim _semaphore;
private readonly IFileService _fileService;
public TokenSecureStorageService(IFileService fileService)
{
_fileService = fileService;
_semaphore = new SemaphoreSlim(1, 1); // Initialize a semaphore that allows only 1 operation at a time
}
public async Task ClearTokensAsync()
{
await _semaphore.WaitAsync(); // Start the semaphore
try
{
await Task.Run(() =>
{
_fileService.Delete(_folderPath, _fileName);
});
}
finally
{
_semaphore.Release(); // Release the semaphore
}
}
}
```
### **ICommand and RelayCommand**
`ICommand` is an interface that represents a command, used to bind user actions in the UI, such as button clicks, to methods in the `ViewModel`. It helps separate the UI from the business logic. Most of buttons in our application implement this.
`RelayCommand` and `AsyncRelayCommand` is two implementations of `ICommand` provided by `Microsoft Community Toolkit`.
Example:
```csharp=
// Define Command in ViewModel
public partial class AdminZoneViewModel : ObservableRecipient, INavigationAware, IDisposable
{
private readonly IAdminZoneService _adminZoneService;
private readonly INavigationService _navigationService;
[ObservableProperty]
[NotifyCanExecuteChangedFor("EditCommand")]
[NotifyCanExecuteChangedFor("DeleteCommand")]
private ZoneDto? selectedZone;
public IRelayCommand AddCommand { get; }
public IRelayCommand EditCommand { get; }
public IAsyncRelayCommand DeleteCommand { get; }
public AdminZoneViewModel(IAdminZoneService adminZoneService, INavigationService navigationService)
{
_adminZoneService = adminZoneService;
_navigationService = navigationService;
AddCommand = new RelayCommand(NavigateToAddPage);
EditCommand = new RelayCommand(NavigateToEditPage, CanEdit);
DeleteCommand = new AsyncRelayCommand(DeleteSelectedZoneAsync, CanDelete);
}
```
```xml!=
<!-- Bind it in Page.xaml -->
<Button Command="{x:Bind ViewModel.AddCommand}" Margin="0,0,8,0">
<Button.Content>
<!-- -->
</Button.Content>
</Button>
<Button Command="{x:Bind ViewModel.EditCommand}" Margin="0,0,8,0">
<Button.Content>
<!-- -->
</Button.Content>
</Button>
<Button Command="{x:Bind ViewModel.DeleteCommand}" Margin="0,0,8,0">
<Button.Content>
<!-- -->
</Button.Content>
</Button>
```
### **DelegatingHandler**
`DelegatingHandler` is a specialized `HttpMessageHandler` that allows customize the behavior of HTTP requests and responses in a pipeline. In our application, it is used to set request headers and to retry requests if the access token has expired.
:::info
The diagram below show the `Login Http Pipeline` implemented in the application.
:::

Implementation:
```csharp=
public class AuthenticationDelegatingHandler : DelegatingHandler
{
private readonly IAuthService _authService;
public AuthenticationDelegatingHandler(IAuthService authService)
{
_authService = authService;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Add token to initial request
await AddAuthenticationHeader(request);
// Send the request
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
// Some logics
var newResponse = await base.SendAsync(newRequest, cancellationToken);
// Some logics
}
return response;
}
}
```
### **DI Containers**
In .NET, `DI Container` is a built-in software framework that automates the process of managing dependencies and object lifetimes in applications. We use `DI Container` to inject most of dependency injection implementation.
Example:
```csharp!=
// Core Services
services.AddSingleton<IAuthService, AuthService>();
services.AddSingleton<ITokenStorageService, TokenSecureStorageService>();
// In old version:
// services.AddSingleton<IAuthService, MockAuthService>();
// services.AddSingleton<ITokenStorageService, TokenJsonStorageService>();
// Views and ViewModels
services.AddTransient<MainViewModel>();
services.AddTransient<MainPage>();
services.AddTransient<ShellPage>();
services.AddTransient<ShellViewModel>();
```
### **JWT**
The application uses `JWT` for authentication. When users sign in, a `AccessToken` is generated and sent back to the client through Authorization header to authorize future requests. A `RefreshToken` is also created to allow users to request a new `AccessToken` without needing to log in again.
:::info
The diagram below illustrates the authentication flow.
:::

### **DPAPI**
`DPAPI` is a Windows feature that provides a simple way to securely store sensitive data. Currently, user's login result is stored using this feature.
Example:
```csharp!
_entropy = Encoding.UTF8.GetBytes("BistroQ"); // This will be a more secret key and store in .env
// Encryption
private Task<string> EncryptData(string data)
{
var jsonData = JsonSerializer.Serialize(data);
var dataBytes = Encoding.UTF8.GetBytes(jsonData);
var encryptedData = ProtectedData.Protect(
dataBytes,
_entropy,
DataProtectionScope.CurrentUser
);
return Task.FromResult(Convert.ToBase64String(encryptedData));
}
// Decryption
private Task<string> DecryptData(string data)
{
var byteData = Convert.FromBase64String(data);
var decryptedData = ProtectedData.Unprotect(
byteData,
_entropy,
DataProtectionScope.CurrentUser
);
var jsonData = Encoding.UTF8.GetString(decryptedData);
var dataString = JsonSerializer.Deserialize<string>(jsonData);
return Task.FromResult(dataString);
}
```
### **INotifyDataErrorInfo**
`INotifyDataErrorInfo` is an interface in .NET that enables data entity classes to implement custom validation rules and expose validation results asynchronously. The `ValidatorBase` class implements the interface to provide validation infrastructure that all model forms should inherit from. This enables:
- Real-time form validation
- Multiple validation rules per field
- Asynchronous validation support
- WinUI data binding integration
Example:
```csharp!
public partial class ValidatorBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
private readonly Dictionary<string, List<string>> _errors = new();
private readonly Dictionary<string, Func<object?, List<(bool IsValid, string Message)>>> _validators = new();
// EventHandler of two Interfaces
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public event PropertyChangedEventHandler? PropertyChanged;
// Public methods
public bool HasErrors => _errors.Any();
public IEnumerable GetErrors(string? propertyName) { /* Implementation */ }
public void ValidateProperty(string propertyName, object? value) { /* Implementation */ }
protected void AddValidator(string propertyName,
Func<object?, List<(bool IsValid, string Message)>> validators) { /* Implementation */ }
}
```
## Github Project Evidence (for managing tasks and issues)

#### Tasks
The task list and estimated working hours mentioned above. [here](#Features)
## Testing
In this milestone, we are mainly test the UI manually. Link to the testcases sheet: [here]()
- We are planning to write Unit Test/Automation test in the next milestone.
## Git commit history
#### WinUI


#### Backend

## Code Review Evidence
### **Login Page WinUI**





### **Validator WinUI**




### **Zone Admin Page WinUI**






### **Navigation Pane WinUI**




### **Skeleton API**


### **Authentication/Authorization API**



### **Category API**



### **Table Zone API**





### **Hotfix Zone Table API**


### Add Client Order feature

### Client Order Page
