Facades (Msg.Auth, Msg.Crud, etc.)
Learn how EasyMessages uses the Facade pattern to provide a clean, discoverable API for creating messages.
What are Facades?
Facades are static classes that provide simple, intuitive methods for creating messages without needing to know message codes or templates.
// Instead of this:
var message = MessageRegistry.Get("AUTH_001");
// Use this:
var message = Msg.Auth.LoginFailed();
Benefits:
- [✓] IntelliSense-driven - Discover messages as you type
- [✓] Type-safe - Compile-time validation
- [✓] Self-documenting - Method names explain what they do
- [✓] No magic strings - No need to memorize message codes
- [✓] Refactoring-friendly - IDEs can find all usages
The Msg Class
All facades are accessed through the static Msg class:
Msg.Auth.* // Authentication messages
Msg.Crud.* // CRUD operation messages
Msg.Validation.* // Validation messages
Msg.System.* // System messages
Msg.Database.* // Database messages
Msg.File.* // File operation messages
Msg.Network.* // Network messages
Msg.Payment.* // Payment messages
Msg.Email.* // Email messages
Msg.Search.* // Search messages
Msg.Import.* // Import messages
Msg.Export.* // Export messages
Msg.Custom(code) // Custom messages
Available Facades
1. Msg.Auth - Authentication & Authorization
Purpose: User authentication, authorization, and session management
// Login scenarios
Msg.Auth.LoginFailed() // AUTH_001 - Invalid credentials
Msg.Auth.LoginSuccessful() // AUTH_003 - Login succeeded
Msg.Auth.LogoutSuccessful() // AUTH_007 - Logout succeeded
// Authorization
Msg.Auth.Unauthorized() // AUTH_002 - No permission
Msg.Auth.Forbidden() // AUTH_002 - Alias for Unauthorized
// Tokens & sessions
Msg.Auth.SessionExpired() // AUTH_004 - Session/token timeout
Msg.Auth.InvalidToken() // AUTH_005 - Invalid JWT
Msg.Auth.InvalidRefreshToken() // AUTH_009 - Cannot refresh
// Account status
Msg.Auth.AccountLocked() // AUTH_006 - Too many attempts
Msg.Auth.PasswordResetRequired() // AUTH_008 - Force password change
Msg.Auth.MfaRequired() // AUTH_010 - Need MFA
Common Usage:
// Login endpoint
[HttpPost("login")]
public IActionResult Login(LoginDto dto)
{
if (!_authService.Validate(dto))
{
var failed = Msg.Auth.LoginFailed();
return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject());
}
var token = _authService.GenerateToken(dto);
var msg = Msg.Auth.LoginSuccessful().WithData(new { token });
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
// Protected resource
[HttpGet("protected")]
public IActionResult GetProtected()
{
if (!User.Identity.IsAuthenticated)
{
var unauth = Msg.Auth.Unauthorized();
return StatusCode(unauth.HttpStatusCode ?? 403, unauth.ToJsonObject());
}
// Return protected data
}
2. Msg.Crud - CRUD Operations
Purpose: Create, Read, Update, Delete operations
// Success operations
Msg.Crud.Created("User") // CRUD_001 - Resource created
Msg.Crud.Updated("Product") // CRUD_002 - Resource updated
Msg.Crud.Deleted("Order") // CRUD_003 - Resource deleted
Msg.Crud.Retrieved("Customer") // CRUD_005 - Resource retrieved
// Failures
Msg.Crud.NotFound("User") // CRUD_004 - 404 Not Found
Msg.Crud.CreationFailed() // CRUD_006 - POST failed
Msg.Crud.UpdateFailed() // CRUD_007 - PUT/PATCH failed
Msg.Crud.DeletionFailed() // CRUD_008 - DELETE failed
// Special cases
Msg.Crud.NoChangesDetected() // CRUD_009 - Update with no delta
Msg.Crud.ConflictDetected() // CRUD_010 - Optimistic concurrency
Parameter Support:
// With resource name
Msg.Crud.Created("User")
// Description: "User has been created successfully."
// Without resource name (generic)
Msg.Crud.Created()
// Description: "Resource has been created successfully."
// Multiple resources
Msg.Crud.Retrieved("Users")
// Description: "Users has been retrieved successfully."
Common Usage:
// REST API CRUD
[HttpGet("{id}")]
public IActionResult GetById(int id)
{
var user = _service.GetById(id);
if (user == null)
{
var notFound = Msg.Crud.NotFound("User").WithMetadata("userId", id);
return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
}
var msg = Msg.Crud.Retrieved("User").WithData(user);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
[HttpPost]
public IActionResult Create(CreateUserDto dto)
{
var user = _service.Create(dto);
var msg = Msg.Crud.Created("User").WithData(user);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
[HttpPut("{id}")]
public IActionResult Update(int id, UpdateUserDto dto)
{
var user = _service.Update(id, dto);
if (user == null)
{
var fail = Msg.Crud.UpdateFailed();
return StatusCode(fail.HttpStatusCode ?? 400, fail.ToJsonObject());
}
var msg = Msg.Crud.Updated("User").WithData(user);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
3. Msg.Validation - Input Validation
Purpose: Data validation errors
// General validation
Msg.Validation.Failed() // VAL_001 - General failure
// Field-specific
Msg.Validation.RequiredField("Email") // VAL_002 - Missing required
Msg.Validation.InvalidFormat("Phone") // VAL_003 - Format mismatch
Msg.Validation.OutOfRange("Age") // VAL_004 - Min/max violation
Msg.Validation.TooShort("Password") // VAL_010 - MinLength
Msg.Validation.TooLong("Description") // VAL_011 - MaxLength
// Type-specific
Msg.Validation.InvalidEmail() // VAL_005 - Email format
Msg.Validation.InvalidPhone() // VAL_006 - Phone format
Msg.Validation.InvalidUrl() // VAL_012 - URL format
Msg.Validation.InvalidDate() // VAL_009 - Date format
Msg.Validation.InvalidFileExtension() // VAL_013 - File type
Msg.Validation.InvalidCharacters() // VAL_015 - Character whitelist
// Password validation
Msg.Validation.PasswordTooWeak() // VAL_007 - Weak password
Msg.Validation.PasswordsMismatch() // VAL_008 - Confirmation fail
// Uniqueness
Msg.Validation.DuplicateValue("Email") // VAL_014 - Already exists
Common Usage:
// Model validation
[HttpPost]
public IActionResult Create(CreateUserDto dto)
{
if (!ModelState.IsValid)
{
var val = Msg.Validation.Failed().WithData(ModelState);
return StatusCode(val.HttpStatusCode ?? 422, val.ToJsonObject());
}
// Custom validation
if (_service.EmailExists(dto.Email))
{
var dup = Msg.Validation.DuplicateValue("Email").WithMetadata("email", dto.Email);
return StatusCode(dup.HttpStatusCode ?? 422, dup.ToJsonObject());
}
var user = _service.Create(dto);
var msg = Msg.Crud.Created("User").WithData(user);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
// Field-level validation
public IActionResult ValidateField(string field, string value)
{
if (string.IsNullOrEmpty(value))
{
var req = Msg.Validation.RequiredField(field);
return StatusCode(req.HttpStatusCode ?? 422, req.ToJsonObject());
}
if (field == "Email" && !IsValidEmail(value))
{
var inv = Msg.Validation.InvalidEmail();
return StatusCode(inv.HttpStatusCode ?? 422, inv.ToJsonObject());
}
return Ok();
}
4. Msg.System - System Messages
Purpose: General system status and errors
// Status messages
Msg.System.Processing() // SYS_002 - In progress
Msg.System.OperationCompleted() // SYS_005 - Operation success
// Errors
Msg.System.Error() // SYS_001 - General error
Msg.System.Maintenance() // SYS_004 - Under maintenance
Msg.System.Unavailable() // SYS_007 - Temporarily down
// Operations & States
Msg.System.Processing() // SYS_002 - Request processing
Msg.System.OperationCompleted() // SYS_005 - Success
Msg.System.RateLimitExceeded() // SYS_006 - Rate limit
Msg.System.Degraded() // SYS_003 - Partial outage
Msg.System.ConfigurationError() // SYS_010 - Invalid config
// Background Tasks
Msg.System.Queued() // SYS_008 - Request queued
Msg.System.Timeout() // SYS_009 - Request timeout
Common Usage:
// Long-running operations
[HttpPost("import")]
public async Task<IActionResult> Import(ImportDto dto)
{
// Start processing (console/log only)
Msg.System.Processing()
.WithData(new { FileName = dto.FileName })
.Log(_logger)
.ToConsole();
try
{
await _service.ImportAsync(dto);
var ok = Msg.System.OperationCompleted()
.WithData(new { Imported = dto.RecordCount });
return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}
catch (Exception ex)
{
var err = Msg.System.Error()
.WithMetadata("error", ex.Message)
.Log(_logger);
return StatusCode(err.HttpStatusCode ?? 500, err.ToJsonObject());
}
}
// Health check
[HttpGet("health")]
public IActionResult Health()
{
if (_service.IsUnderMaintenance())
{
var maint = Msg.System.Maintenance();
return StatusCode(maint.HttpStatusCode ?? 503, maint.ToJsonObject());
}
if (!_service.IsHealthy())
{
var unavail = Msg.System.ServiceUnavailable();
return StatusCode(unavail.HttpStatusCode ?? 503, unavail.ToJsonObject());
}
return Ok(new { status = "healthy" });
}
5. Msg.Database - Database Operations
Purpose: Database connectivity and query errors
Msg.Database.ConnectionFailed() // DB_001 - Cannot connect
Msg.Database.DuplicateEntry("User", "email") // DB_002 - Unique constraint
Msg.Database.ForeignKeyConstraint() // DB_003 - FK violation
Msg.Database.TransactionFailed() // DB_004 - Transaction error
Msg.Database.QueryTimeout() // DB_006 - Query too slow
Msg.Database.Deadlock() // DB_007 - Deadlock
Msg.Database.MigrationPending() // DB_008 - Migration needed
Common Usage:
public async Task<IActionResult> GetData()
{
try
{
var data = await _repository.GetAllAsync();
var msg = Msg.Crud.Retrieved("Data").WithData(data);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}
catch (SqlException ex) when (ex.Number == -2) // Timeout
{
var timeout = Msg.Database.QueryTimeout()
.WithMetadata("query", "GetAll")
.Log(_logger);
return StatusCode(timeout.HttpStatusCode ?? 504, timeout.ToJsonObject());
}
catch (SqlException ex) when (ex.Number == 1205) // Deadlock
{
var deadlock = Msg.Database.Deadlock().Log(_logger);
return StatusCode(deadlock.HttpStatusCode ?? 500, deadlock.ToJsonObject());
}
}
6. Msg.File - File Operations
Purpose: File upload, download, and validation
// Success Msg.File.UploadSuccessful(“document.pdf”) // FILE_001 - Upload success Msg.File.DownloadSuccessful(“report.xlsx”) // FILE_005 - Download success
// Validation Msg.File.InvalidType(“pdf”, “docx”) // FILE_002 - Wrong type Msg.File.TooLarge(“10MB”) // FILE_003 - Too large
// Errors Msg.File.NotFound(“missing.pdf”) // FILE_006 - File missing Msg.File.AccessDenied(“secret.pdf”) // FILE_007 - No access Msg.File.Corrupted(“damaged.pdf”) // FILE_009 - Corrupted Msg.File.VirusDetected(“malware.exe”) // FILE_012 - Malicious file
Common Usage:
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
// Validate type
var allowedTypes = new[] { "pdf", "docx", "txt" };
var extension = Path.GetExtension(file.FileName).TrimStart('.');
if (!allowedTypes.Contains(extension.ToLower()))
{
var inv = Msg.File.InvalidType(allowedTypes).WithMetadata("actualType", extension);
return StatusCode(inv.HttpStatusCode ?? 422, inv.ToJsonObject());
}
// Validate size
const long maxSize = 10 * 1024 * 1024; // 10 MB
if (file.Length > maxSize)
{
var big = Msg.File.TooLarge("10 MB")
.WithMetadata("maxSize", "10 MB")
.WithMetadata("actualSize", $"{file.Length / 1024 / 1024} MB");
return StatusCode(big.HttpStatusCode ?? 413, big.ToJsonObject());
}
// Upload
await _service.UploadAsync(file);
var ok = Msg.File.UploadSuccessful(file.FileName)
.WithData(new { FileName = file.FileName, Size = file.Length });
return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}
7. Msg.Network - Network & API Calls
Purpose: Network connectivity and external API errors
Msg.Network.Timeout() // NET_002 - Request timeout
Msg.Network.BadRequest() // NET_003 - Bad request
Msg.Network.ServerError() // NET_004 - Server error
Msg.Network.ConnectionRefused() // NET_006 - Cannot connect
Common Usage:
public async Task<IActionResult> CallExternalApi()
{
try
{
var result = await _httpClient.GetAsync("https://api.example.com");
return Ok(result);
}
catch (TaskCanceledException)
{
var timeout = Msg.Network.Timeout()
.WithMetadata("endpoint", "https://api.example.com")
.Log(_logger);
return StatusCode(timeout.HttpStatusCode ?? 408, timeout.ToJsonObject());
}
catch (HttpRequestException ex)
{
var unavail = Msg.System.Unavailable()
.WithMetadata("error", ex.Message)
.Log(_logger);
return StatusCode(unavail.HttpStatusCode ?? 503, unavail.ToJsonObject());
}
}
8. Msg.Payment - Payment Operations
Purpose: Payment processing messages
Msg.Payment.Success() // PAY_001 - Payment succeeded
Msg.Payment.Failed() // PAY_002 - Payment failed
Msg.Payment.Pending() // PAY_003 - Awaiting processing
Msg.Payment.InsufficientFunds() // PAY_004 - Not enough balance
Msg.Payment.InvalidCard() // PAY_005 - Card validation
Msg.Payment.Declined() // PAY_006 - Bank declined
Msg.Payment.Refunded() // PAY_007 - Refund processed
9. Msg.Custom - Custom Messages
Purpose: Access custom messages loaded from files or databases
// Load custom messages
MessageRegistry.LoadCustomMessages("messages/custom.json");
// Use custom messages
var payment = Msg.Custom("PAYMENT_001")
.WithParams(new { amount = "$50.00" });
// StatusCode(payment.HttpStatusCode ?? 200, payment.ToJsonObject())
Msg.Custom("NOTIFY_001")
.WithParams(new { userName = "John", action = "logged in" })
.ToConsole();
Common Usage:
// Domain-specific messages
public IActionResult ProcessOrder(OrderDto dto)
{
try
{
_service.ProcessOrder(dto);
var ok = Msg.Custom("ORDER_PROCESSED")
.WithParams(new { orderId = dto.Id, total = dto.Total });
return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}
catch (InventoryException)
{
var err = Msg.Custom("OUT_OF_STOCK")
.WithParams(new { productId = dto.ProductId });
return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject());
}
}
Facade Method Signatures
No Parameters
// Simple, no parameters
public static Message LoginFailed()
{
return MessageRegistry.Get("AUTH_001");
}
// Usage
Msg.Auth.LoginFailed()
Msg.System.Error()
Msg.Validation.Failed()
With Optional Parameters
// Optional parameter for customization
public static Message Created(string? resource = null)
{
var message = MessageRegistry.Get("CRUD_001");
return resource != null
? message.WithParams(new { resource })
: message;
}
// Usage
Msg.Crud.Created() // "Resource has been created successfully."
Msg.Crud.Created("User") // "User has been created successfully."
Msg.Crud.Created("Product") // "Product has been created successfully."
With Required Parameters
// Required parameter
public static Message RequiredField(string fieldName)
{
return MessageRegistry.Get("VAL_002")
.WithParams(new { fieldName });
}
// Usage
Msg.Validation.RequiredField("Email") // "Email is required."
Msg.Validation.RequiredField("Password") // "Password is required."
With Multiple Parameters
// Multiple required parameters
public static Message InvalidType(params string[] allowedTypes)
{
return MessageRegistry.Get("FILE_003")
.WithParams(new { types = string.Join(", ", allowedTypes) });
}
// Usage
Msg.File.InvalidType("pdf", "docx")
// "Only pdf, docx files are allowed."
Msg.File.InvalidType("jpg", "png", "gif")
// "Only jpg, png, gif files are allowed."
IntelliSense Discovery
Facades are designed for discovery through IntelliSense:
Step 1: Type Msg.
Msg. // IntelliSense shows all facades:
// - Auth
// - Crud
// - Validation
// - System
// - Database
// - File
// - Network
// - Payment
// - Custom(code)
Step 2: Select a facade (e.g., Auth)
Msg.Auth. // IntelliSense shows all methods:
// - LoginFailed()
// - Unauthorized()
// - LoginSuccessful()
// - SessionExpired()
// - etc.
Step 3: View documentation
Each method includes XML documentation:
Msg.Auth.LoginFailed() // Hovering shows:
/// <summary>
/// Returns AUTH_001: Invalid username or password.
/// </summary>
Creating Custom Facades
You can extend the Msg class with your own facades:
// Create a partial class in your application
namespace MyApp;
public static partial class Msg
{
public static class Order
{
public static Message Placed()
=> MessageRegistry.Get("ORDER_001");
public static Message Shipped()
=> MessageRegistry.Get("ORDER_002");
public static Message Delivered()
=> MessageRegistry.Get("ORDER_003");
public static Message Cancelled(string reason)
=> MessageRegistry.Get("ORDER_004")
.WithParams(new { reason });
}
}
Usage:
// Your custom facade works just like built-in ones
var placed = Msg.Order.Placed()
.WithData(new { OrderId = 123 });
return StatusCode(placed.HttpStatusCode ?? 200, placed.ToJsonObject());
var cancelled = Msg.Order.Cancelled("Customer request")
.WithMetadata("orderId", 123);
return StatusCode(cancelled.HttpStatusCode ?? 200, cancelled.ToJsonObject());
Best Practices
[✓] DO:
1. Use facades instead of MessageRegistry directly
// Good
Msg.Auth.LoginFailed()
// Bad
MessageRegistry.Get("AUTH_001")
2. Include resource names for clarity
// Good - specific
Msg.Crud.NotFound("User")
Msg.Crud.Created("Order")
// OK - generic
Msg.Crud.NotFound()
Msg.Crud.Created()
3. Chain methods for complete messages
// Good - complete context
var notFound = Msg.Crud.NotFound("User")
.WithMetadata("userId", id)
.WithMetadata("searchedBy", currentUser)
.Log(_logger);
return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
4. Create custom facades for domain-specific messages
// Good - domain-specific facade
public static class Msg
{
public static class Inventory
{
public static Message StockLow(string product, int current, int threshold)
=> Msg.Custom("INV_LOW")
.WithParams(new { product, current, threshold });
}
}
[ ] DON’T:
1. Don’t bypass facades
// Bad - loses type safety and discoverability
var message = MessageRegistry.Get("AUTH_001");
// Good
var message = Msg.Auth.LoginFailed();
2. Don’t forget to pass parameters
// Bad - missing resource name
Msg.Crud.Created() // "Resource has been created successfully."
// Good - specific resource
Msg.Crud.Created("User") // "User has been created successfully."
3. Don’t create facades for every message code
// Bad - one-off usage
public static class Msg
{
public static class MyFeature
{
public static Message OnlyUsedOnce() => ... // [ ] Overkill
}
}
// Good - use Msg.Custom() for rare messages
Msg.Custom("MY_FEATURE_001")
Quick Reference
Most Used Facades
// Authentication
Msg.Auth.LoginFailed()
Msg.Auth.Unauthorized()
Msg.Auth.LoginSuccessful()
// CRUD
Msg.Crud.Created("Entity")
Msg.Crud.Updated("Entity")
Msg.Crud.Deleted("Entity")
Msg.Crud.NotFound("Entity")
Msg.Crud.Retrieved("Entity")
// Validation
Msg.Validation.Failed()
Msg.Validation.RequiredField("Field")
Msg.Validation.InvalidFormat("Field")
// System
Msg.System.Error()
Msg.System.Processing()
Msg.System.OperationCompleted()
// Files
Msg.File.UploadSuccessful("document.pdf")
Msg.File.InvalidType("pdf", "docx")
Msg.File.TooLarge("10MB")
// Custom
Msg.Custom("YOUR_CODE")
Next Steps
- Formatters and Outputs - Convert messages to different formats
- Message Codes Reference - Complete list of all codes
- Create Custom Messages - Add your own messages
Questions? Ask in Discussions