diff --git a/README.md b/README.md index 7135cf0..9035c08 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,16 @@ Danom is a C# library that provides (monadic) structures to facilitate durable p - Implementation of common monads: [Option](#option) and [Result](#result). - Exhaustive matching to prevent null reference exceptions. -- Fluent API for chaining operations. +- Fluent API for chaining operations, including async support. - Integrated with [ASP.NET Core](#aspnet-core-mvc-integration) and [Fluent Validation](#fluent-validation-integration). - API for [parsing strings](#string-parsing) into .NET primitives and value types. -- Supports asynchronous operations. ## Design Goals -- Easy to use API for common monadic operations. -- Efficient implementation to minimize overhead. -- Seamless integration with existing C# code and libraries. +- Provide a safe and expressive way to handle nullable values. - Prevent direct use of internal value, enforcing exhaustive matching. +- Efficient implementation to minimize overhead. +- Opionated monads to encourage consistent use. ## Getting Started @@ -200,6 +199,8 @@ Result resultOrElseWith = Result.Ok(99)); // useful if creating the value is expensive ``` +### Result Errors + Since error messages are frequently represented as keyed string collections, the `ResultErrors` type is provided to simplify Result creation. The flexible constructor allows errors to be initialized with a single string, a collection of strings, or a key-value pair. ```csharp @@ -315,7 +316,7 @@ Since Danom introduces types that are most commonly found in your model and busi ### Fluent Validation Integration -Danom is integrated with [Fluent Validation](https://fluentvalidation.net/) to provide a seamless way to validate your models and return a `Result` or `ResultOption` with the validation errors. +[Fluent Validation](https://fluentvalidation.net/) is an excellent library for building validation rules for your models. A first-class integration is available via [Danom.Validation](src/Danom.Validation/README.md) to provide a seamless way to validate your models and return a `Result` or `ResultOption` with the validation errors. A quick example: @@ -353,9 +354,7 @@ Documentation can be found [here](src/Danom.Validation/README.md). ### ASP.NET Core MVC Integration -Danom is integrated with [ASP.NET Core](https://docs.microsoft.com/en-us/aspnet/core/?view=aspnetcore-5.0) to provide a set of utilities to help integrate the Danom library with common tasks in ASP.NET Core MVC applications. - -Documentation can be found [here](src/Danom.Mvc/README.md). +Danom is integrated with ASP.NET Core via [Danom.Mvc](src/Danom.Mvc/README.md). This library provides a set of utilities to help integrate the core types with common tasks in ASP.NET Core MVC applications. ### ASP.NET Core Minimal API Integration diff --git a/src/Danom.Mvc/README.md b/src/Danom.Mvc/README.md index 4be5236..6334f47 100644 --- a/src/Danom.Mvc/README.md +++ b/src/Danom.Mvc/README.md @@ -2,7 +2,7 @@ [![NuGet Version](https://img.shields.io/nuget/v/Danom.Mvc.svg)](https://www.nuget.org/packages/Danom.Mvc) [![build](https://github.com/pimbrouwers/Danom/actions/workflows/build.yml/badge.svg)](https://github.com/pimbrouwers/Danom/actions/workflows/build.yml) -Danom.Mvc is a library that provides a set of rendering utilities to help integrate the [Danom](../../README.md) library with common tasks in ASP.NET Core MVC applications. +Danom.Mvc is a library that provides a set of utilities to help integrate the [Danom](../../README.md) library with common tasks in ASP.NET Core MVC applications. ## Getting Started @@ -17,7 +17,20 @@ Or using the dotnet CLI dotnet add package Danom.Mvc ``` -## Working With `Option` +## Controller + +The `DanomController` class extends the base controller class to provide a set of methods to help work with `Result`, `Option`, and `ResultOption` types in ASP.NET Core MVC applications. + + +### Option + +The `ViewOption` method is used to render a view based on the presence of an `Option` value. + +If the `Option` is `Some`, the view is rendered with the value. If the `Option` is `None`, the `noneAction` is invoked. By default, the `noneAction` returns a `NotFound` result. + +A custom view name can be provided to render a view with a different name than the action. + +Some examples demonstrating the use of `ViewOption` are shown below: ```csharp using Danom.Mvc; @@ -26,29 +39,32 @@ using Microsoft.AspNetCore.Mvc; public sealed class OptionController : DanomController { - private readonly Option _someOption = Option.Some("Hello world"); - private readonly Option _noneOption = Option.None(); - public IActionResult OptionSome() => ViewOption( - option: _someOption, + option: Option.Some("Hello world"), viewName: "Detail"); // Returns the ASP.NET default `NotFound` result public IActionResult OptionNone() => ViewOption( - option: _noneOption, + option: Option.NoneValue, viewName: "Detail"); public IActionResult OptionNoneCustom() => ViewOption( - option: _noneOption, + option: Option.NoneValue, viewName: "Detail", noneAction: () => NotFound("Not found!")); } ``` -## Working with `Result` +### Result + +The `ViewResult` method is used to render a view based on the presence of a `Result` value. + +By default the `ViewResult` method will render the view with the value if the `Result` is `Ok`. If the `Result` is `Error`, the `errorAction` is invoked. + +A custom view name can be provided to render a view with a different name than the action. ```csharp using Danom.Mvc; @@ -57,58 +73,96 @@ using Microsoft.AspNetCore.Mvc; public sealed class ResultController : DanomController { - private readonly Result _okResult = Result.Ok("Success!"); - private readonly Result _errorResult = Result.Error(["An error occurred."]); - private readonly Result _stringErrorResult = Result.Error("An error occurred."); - public IActionResult ResultOk() => ViewResult( - result: _okResult, + result: Result.Ok("Success!"), viewName: "Detail"); - public IActionResult ResultErrors() => - ViewResult( - result: _errorResult, - viewName: "Detail"); - - // Demonstrating error customization, using a string literal for error output public IActionResult ResultError() => ViewResult( - result: _stringErrorResult, + result: Result.Error("An error occurred."), errorAction: errors => View("Detail", errors), viewName: "Detail"); } ``` -## Working with `ResultOption` +Built into Danom is the `ResultErrors` type, which is particularly well suited for reporting model errors in ASP.NET Core MVC applications. The `ViewResultErrors` method, provided by the `DanomController` class, is a proxy for the `View` method that will inject the `ResultErrors` value into the model state. + +When using `ResultErrors` as the error type, the `ViewResultErrors` will default the `errorAction` to inject the `ResultErrors` value into the model state. ```csharp using Danom.Mvc; using Microsoft.AspNetCore.Mvc; -public sealed class ResultOptionController +public sealed class ResultController : DanomController { - private readonly ResultOption _okResult = ResultOption.Ok("Success!"); - private readonly ResultOption _errorResult = ResultOption.Error("An error occurred."); - private readonly ResultOption _stringErrorResult = ResultOption.Error("An error occurred."); - public IActionResult ResultOk() => - ViewResultOption( - resultOption: _okResult, - viewName: "Detail"); - - public IActionResult ResultErrors() => - ViewResultOption( - resultOption: _errorResult, + ViewResult( + // notice the lack of second type parameter, which is inferred to be ResultErrors + result: Result.Ok("Success!"), viewName: "Detail"); - // Demonstrating error customization, using a string literal for error output public IActionResult ResultError() => - ViewResultOption( - resultOption: _stringErrorResult, - errorAction: errors => View("Detail", errors), + ViewResult( + result: Result.Error("An error occurred."), viewName: "Detail"); + + + public IActionResult ResultErrorView() => + // can be used directly + ViewResultErrors( + errors: new("An error occurred.")); +} +``` + +## View + +### Option + +While not explicitly part of the `Danom.Mvc` library, there are some patterns that make rendering the `Option` type easier in Razor views. Two methods from the base library are especially valuable: `TryGet` and `ToString`. + +The `TryGet` method is used to extract the value from an `Option` type. If the `Option` is `Some`, the value is assigned to the `out` parameter and the method returns `true`. If the `Option` is `None`, the method returns `false`. + +The custom `ToString` method is used to convert the `Option` value to a string. If the `Option` is `Some`, the value is converted to a string. If the `Option` is `None`, the method returns the default value. The method optionally accepts a second parameter for the format string. + +Consider the following type: + +```csharp +public record Person( + string Name, + Option Birthdate, + Option Email); +``` + +The `TryGet` and `ToString` methods can be used in a Razor view to help render the optional properties. + +```cshtml +@model Person + +

@Model.Name

+

Email: @Model.Email.ToString("-")

+ +@if (Model.Birthdate.TryGet(out var birthdate)) +{ + var now = DateTime.Now; + var a = (now.Year * 100 + now.Month) * 100 + now.Day; + var b = (birthdate.Year * 100 + birthdate.Month) * 100 + birthdate.Day; + var age = (a - b) / 10000; + +

You are born on @birthdate, and are @age years old.

+} +else +{ +

You are an ageless wonder!

} ``` + +## Find a bug? + +There's an [issue](https://github.com/pimbrouwers/Danom/issues) for that. + +## License + +Built with ♥ by [Pim Brouwers](https://github.com/pimbrouwers) in Toronto, ON. Licensed under [Apache License 2.0](https://github.com/pimbrouwers/Danom/blob/master/LICENSE).