diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..14b065f Binary files /dev/null and b/.DS_Store differ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c91f70..d4993aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,13 +16,7 @@ jobs: matrix: template: - working-dir: modulith - project: Modulith.sln - - working-dir: modulith-proj - project: NewModule/Modulith.NewModule - - working-dir: modulith-proj - project: NewModule/Modulith.NewModule.Contracts - - working-dir: modulith-proj - project: NewModule/Modulith.NewModule.Tests + project: TestModulith.sln name: 'Build project source: ${{ matrix.template.working-dir }}' runs-on: ubuntu-latest @@ -65,13 +59,21 @@ jobs: working-directory: test run: dotnet new modulith -n eShop --with-module Payments - - name: Instance project + - name: Instance basic project working-directory: test/eShop - run: dotnet new modulith-proj --add-module Shipments --to eShop - + run: dotnet new modulith --add basic-module --with-name Shipments --to eShop + - name: Add project reference working-directory: test/eShop run: dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj + + - name: Instance ddd project + working-directory: test/eShop + run: dotnet new modulith --add ddd-module --with-name Billing --to eShop + + - name: Add project reference + working-directory: test/eShop + run: dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj - name: Restore working-directory: test/eShop diff --git a/.github/workflows/qodana_code_quality.yml b/.github/workflows/qodana_code_quality.yml new file mode 100644 index 0000000..83c101b --- /dev/null +++ b/.github/workflows/qodana_code_quality.yml @@ -0,0 +1,20 @@ +name: Qodana +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + +jobs: + qodana: + if: ${{ false }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: 'Qodana Scan' + uses: JetBrains/qodana-action@v2023.3 + env: + QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index d8a33bf..f745d58 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ [![CI](https://github.com/david-acm/modulith/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/david-acm/modulith/actions/workflows/ci.yml) [![NuGet Version](https://img.shields.io/nuget/vpre/Ardalis.Modulith)](https://www.nuget.org/packages/Ardalis.Modulith) -**⚠️This project is a work in progress and will likely receive many API changes before v1.0.0 please keep this in mind when using, there will be breaking changes often.** +**⚠️This project is a work in progress and will likely receive many API changes before v1.0.0. Please keep this in mind when using, as there will be breaking changes often.** + +**🆕 Try UI Module generation with Blazor. Jump to [UI Modules](#ui-module)** (originally hosted at **david-acm/modulith** - thanks David for the contribution!) @@ -10,9 +12,9 @@ Modulith is a `dotnet new template` suite for [Modular Monoliths](https://dometr But, what is a Modular Monolith? Glad you asked. It is a software architecture style to build maintainable applications as a single unit, but in nicely separated modules (Modu-lith, pun intended 🙃). More [about Modular Monoliths](#about-modular-monoliths). -# 🏁 Start here +# 🚀 Quickstart -#### Install by running: +#### Install the tool running: ```pwsh dotnet new install Ardalis.Modulith @@ -56,7 +58,7 @@ Running the solution should show both modules with their default endpoint: #### Direct service registration -However, if you prefer more control and less magic, or you want to modify registration class, remove the `builder.DiscoverAndRegisterModules();` in `program.cs` and add the service registration for each module: +However, if you prefer more control and less magic, or you want to modify registration class, you can remove the `builder.DiscoverAndRegisterModules();` in `program.cs` and add the service registration for each module: ```cs using eShop.Shipments @@ -64,7 +66,6 @@ using eShop.Shipments PaymentsModuleServiceRegistrar.ConfigureServices(builder); ``` - # 🏛️ Solution directory structure The previous command creates the following project structure: @@ -73,7 +74,7 @@ The previous command creates the following project structure: - `Users/` 👈 Your first module - `eShop.Web/` 👈 Your entry point -Inside `Payments`, you will find the project folders: +Inside `Payments`, the second module added, you will find the project folders: - `eShop.Payments/` 👈 Your project code goes here - `eShop.Payments.Contracts/` 👈 Public contracts other modules can depend on @@ -84,16 +85,15 @@ Inside `Payments`, you will find the project folders: Since this is a Modular Monolith, there are a few rules that are enforced to guarantee the modularity: - Every type in `eShop.Payments/` is internal -- This 👆 is enforced by an archUnit test in `eShop.Payments.Tests/` -- The only exception to the last two rules is the static class that configures the services for the module: `UsersModule...Extensions.cs` -- `.Contracts/` and `.Tets/` projects depend on `eShop.Payments/`. The opposite is not possible. This is by design. +- This is enforced by an [ArchUnit](https://github.com/TNG/ArchUnitNET) test in `eShop.Payments.Tests/` +- The only exception to the last two rules is the static class that configures the services for the module: `PaymentsModuleServiceRegistrar.cs` +- `.Contracts/` and `.Tests/` projects depend on `eShop.Payments/`. The opposite is not possible. This is by design. \* *You can always change these rules after you have created the solution to suit your needs. But be mindful of why you are changing the rules. For example, it is ok to add an additional public extensions class to configure the application pipeline, while adding a public contract to `eShop.Payments/` is not. We have a project for those.* - ## Adding a reference automatically to new modules -We support this, but the .Net SDK does not yet. There is an active PR at [dotnet/sdk #40133](https://github.com/dotnet/sdk/pull/40133). Give it a vote if you'd like this feature: +This is only supported on .Net 9 preview 6. If you are running an earlier version you will need to run [these commands manually](#add-a-reference-to-the-new-module). ⚠️ `cd` into the solution folder. I.e. `eShop/`, then run: @@ -101,11 +101,63 @@ We support this, but the .Net SDK does not yet. There is an active PR at [dotnet dotnet new modulith-proj --ModuleName Shipments --existingProject eShop.Web/eShop.Web.csproj ``` -Here `Shipments` is the name of your new module, and `eShop.Web/eShop.Web.csproj` is the path to your web entry project. If you changed this, make sure you update it to the new path and that is relative to the solution folder. +Here `Shipments` is the name of your new module, and `eShop.Web/eShop.Web.csproj` is the path to your web entry project. If you change this, make sure you update it to the new path and that is relative to the solution folder. + +# 🖥️ Modules with UI + +You can generate a solution with a Blazor UI by using the ```--WithUi```: + +``` pwsh +dotnet new modulith -n eShop --with-module Payments --WithUi +``` + +Running the application will show the following blazor app: + +![Screenshot of Blazor app with Payments module]() + +The app uses [MudBlazor](https://www.mudblazor.com/) as the component library. The template includes a menu item and page for the newly created module with UI whose components are defined in the ```eShop.Payments.UI``` project. We include a link to the Swagger UI page in the _API_ menu item. + +The previous command will create a solution with a few additional projects. + +- **eShop.UI:** Is the client project that will be compiled to WebAssembly and executed from the browser. This contains the layout and routes components; but most importantly the ```program.cs``` to register the services for the client side application. +- **eShop.Payments.UI:** Is a razor class library where you can define the components specific to that UI module. +- **eShop.Payments.HttpModels** Contains the DTOs used to send requests from the Blazor client project (```eShop.UI```) to the WebApi endpoints in ```eShop.Shipments```. + +## Adding new modules with UI + +New modules with UI can be added running: + +```pwsh +cd eShop +dotnet new modulith --add basic-module --with-name Shipments --to eShop --WithUi +``` + +⚠️ New modules with UI can only be added to solutions that were instantiated using the ```-WithUI``` parameter. + +However, to allow routing to the newly created module component for the ```Shipments``` module, you need to register the new assembly. + +In blazor WebAssembly, the routeable components that are not present in the executing assembly need to be passed as arguments to the ```Router``` component. In this template, this is done using the ```BlazorAssemblyDiscoveryService```. Simply add the following to the ```GetAssemblies``` array: + +```cs +typeof(ShipmentsComponent).Assembly +``` + +After the modification the class should look like this: + +```cs +public class BlazorAssemblyDiscoveryService : IBlazorAssemblyDiscoveryService +{ + public IEnumerable GetAssemblies() => [typeof(PaymentsComponent).Assembly, typeof(ShipmentsComponent).Assembly]; +} +``` + +For each additional module you create you will need to add a new assembly to this array. + +More about this in [Blazor's documentation page: Lazy Load Assemblies with WebAssembly](https://learn.microsoft.com/en-us/aspnet/core/blazor/webassembly-lazy-load-assemblies?view=aspnetcore-8.0#assemblies-that-include-routable-components) # 📊 About Modular Monoliths -A Modular Monolithic app benefits from the simple deployment of a monolith and the separation of concerns that microservices offer. While avoiding the complexities and maitainability issues they can introduce. When you are ready and *if* you need it, you can split a module as a microservice. Best of both worlds 🌎 +A Modular Monolithic app benefits from the simple deployment of a monolith and the separation of concerns that microservices offer. While avoiding the complexities and maintainability issues they can introduce. When you are ready and *if* you need it, you can split a module as a microservice. Best of both worlds 🌎 This is not a new concept. Martin Fowler [explains it here](https://martinfowler.com/bliki/MonolithFirst.html), and Ardalis teaches it [here](https://ardalis.com/introducing-modular-monoliths-goldilocks-architecture/#:~:text=A%20Modular%20Monolith%20is%20a%20software%20architecture%20that,that%20they%20are%20loosely%20coupled%20and%20highly%20cohesive.). @@ -113,7 +165,7 @@ The templates in this project follow the solution structure as taught by [Ardali # 🛃 Custom templates -No template fits all needs. If you weant to customize the template you can change it in the `working/content` directory and running: +No template fits all needs. If you want to customize the template you can change it in the `working/content` directory and running: *⚠️ Make sure to uninstall the original template* ```pwsh diff --git a/Test-UiWithDotnetCli.ps1 b/Test-UiWithDotnetCli.ps1 new file mode 100644 index 0000000..9abe47c --- /dev/null +++ b/Test-UiWithDotnetCli.ps1 @@ -0,0 +1,19 @@ +cd /Users/davidchaparro/RiderProjects/modulith/test/ +rm -rf /Users/davidchaparro/RiderProjects/modulith/test/** +dotnet new uninstall /Users/davidchaparro/RiderProjects/modulith/working/content/modulith +dotnet new install /Users/davidchaparro/RiderProjects/modulith/working/content/modulith + +dotnet new modulith -n eShop --with-module Payments --WithUi + +cd eShop +dotnet new modulith --add basic-module --with-name Shipments --to eShop --WithUi +dotnet new modulith --add ddd-module --with-name Billing --to eShop + +dotnet build + +dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj +dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj + +dotnet build + +dotnet run --project ./eShop.Web/eShop.Web.csproj diff --git a/test.pwsh b/Test-WithDotnetCli.ps1 similarity index 57% rename from test.pwsh rename to Test-WithDotnetCli.ps1 index a40c7f0..a7ae23d 100644 --- a/test.pwsh +++ b/Test-WithDotnetCli.ps1 @@ -2,8 +2,18 @@ cd /Users/davidchaparro/RiderProjects/modulith/test/ rm -rf /Users/davidchaparro/RiderProjects/modulith/test/** dotnet new uninstall /Users/davidchaparro/RiderProjects/modulith/working/content/modulith dotnet new install /Users/davidchaparro/RiderProjects/modulith/working/content/modulith -dotnet new modulith -n eShop --with-module Payments + +dotnet new modulith -n eShop --with-module Payments cd eShop dotnet new modulith --add basic-module --with-name Shipments --to eShop dotnet new modulith --add ddd-module --with-name Billing --to eShop + +dotnet build + +dotnet add eShop.Web/eShop.Web.csproj reference Shipments/eShop.Shipments/eShop.Shipments.csproj +dotnet add eShop.Web/eShop.Web.csproj reference Billing/eShop.Billing/eShop.Billing.csproj + +dotnet build + +dotnet run --project ./eShop.Web/eShop.Web.csproj diff --git a/qodana.yml b/qodana.yml new file mode 100644 index 0000000..ff45433 --- /dev/null +++ b/qodana.yml @@ -0,0 +1,31 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify IDE code to run analysis without container (Applied in CI/CD pipeline) +ide: QDNET + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +dotnet: + solution: working/content/modulith/TestModulith.sln +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) diff --git a/test b/test index ad068e1..c00c0b5 160000 --- a/test +++ b/test @@ -1 +1 @@ -Subproject commit ad068e1a3b7baf9b1d25381aa23e123fcd2947e4 +Subproject commit c00c0b57e1c9006f48f259ace01912d1e930a7d2 diff --git a/with-ui.png b/with-ui.png new file mode 100644 index 0000000..b82cd8c Binary files /dev/null and b/with-ui.png differ diff --git a/working/.DS_Store b/working/.DS_Store new file mode 100644 index 0000000..def8665 Binary files /dev/null and b/working/.DS_Store differ diff --git a/working/Ardalis.Modulith.csproj b/working/Ardalis.Modulith.csproj index f1f3afd..281024f 100644 --- a/working/Ardalis.Modulith.csproj +++ b/working/Ardalis.Modulith.csproj @@ -11,40 +11,40 @@ dotnet;.NET;dotnet new;dotnet cli;Visual Studio;template;project template;c#;modular;monolith;modular monolith https://github.com/ardalis/modulith - - Template - net8.0 - true - false - content - $(NoWarn);NU5128 - true - README.md - - - - false - - - - MIT - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - + + Template + net8.0 + true + false + content + $(NoWarn);NU5128 + true + README.md + + + + false + + + + MIT + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + diff --git a/working/README.md b/working/README.md index 9e39404..56b22d9 100644 --- a/working/README.md +++ b/working/README.md @@ -1,10 +1,12 @@ [![CI](https://github.com/david-acm/modulith/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/david-acm/modulith/actions/workflows/ci.yml) [![NuGet Version](https://img.shields.io/nuget/vpre/Ardalis.Modulith)](https://www.nuget.org/packages/Ardalis.Modulith) -Modulith is a `dotnet new template` suite for Modular Monoliths. It streamlines the creation of new .Net solutions and the addition of modules to existing ones. +Modulith is a `dotnet new template` suite for Modular Monoliths. It streamlines the creation of new .Net solutions and +the addition of modules to existing ones. -But, what is a Modular Monolith? Glad you asked. It is a software architecture style to build maintainable applications as a single unit, but in nicely separated modules (Modu-lith, pun intended 🙃). - More [about Modular Monoliths](#about-modular-monoliths). +But, what is a Modular Monolith? Glad you asked. It is a software architecture style to build maintainable applications +as a single unit, but in nicely separated modules (Modu-lith, pun intended 🙃). +More [about Modular Monoliths](#about-modular-monoliths). # 🏁 Start here @@ -24,7 +26,6 @@ dotnet new modulith -n eShop --with-module Payments #### Create a new module - ``` pwsh cd eShop dotnet new modulith-proj --add-module Shipments --to eShop @@ -32,7 +33,8 @@ dotnet new modulith-proj --add-module Shipments --to eShop *⚠️ `cd` into the solution folder to add the module inside the solution.* -`Shipments` is the name of your new module. This will create a new module folder with the same three projects as in `Users/`. +`Shipments` is the name of your new module. This will create a new module folder with the same three projects as +in `Users/`. ### Add a reference to the new module @@ -47,8 +49,8 @@ dotnet add eShop.Web/eShop.Web.csproj reference Shipment/eShop.Shipment/eShop.Sh The previous command creates the following project structure: - eShop - - `Users/` 👈 Your first module - - `eShop.Web/` 👈 Your entry point + - `Users/` 👈 Your first module + - `eShop.Web/` 👈 Your entry point Inside `Payments`, you will find the project folders: @@ -62,15 +64,18 @@ Since this is a Modular Monolith, there are a few rules that are enforced to gua - Every type in `eShop.Payments/` is internal - This 👆 is enforced by an archUnit test in `eShop.Payments.Tests/` -- The only exception to the last two rules is the static class that configures the services for the module: `UsersModule...Extensions.cs` +- The only exception to the last two rules is the static class that configures the services for the + module: `UsersModule...Extensions.cs` - `.Contracts/` and `.Tets/` projects depend on `eShop.Payments/`. The opposite is not possible. This is by design. -\* *You can always change these rules after you have created the solution to suit your needs. But be mindful of why you are changing the rules. For example, it is ok to add an additional public extensions class to configure the application pipeline, while adding a public contract to `eShop.Payments/` is not. We have a project for those.* - +\* *You can always change these rules after you have created the solution to suit your needs. But be mindful of why you +are changing the rules. For example, it is ok to add an additional public extensions class to configure the application +pipeline, while adding a public contract to `eShop.Payments/` is not. We have a project for those.* ## Adding a reference automatically to new modules -We support this, but the .Net SDK does not yet. There is an active PR at [dotnet/sdk #40133](https://github.com/dotnet/sdk/pull/40133). Give it a vote if you'd like this feature: +We support this, but the .Net SDK does not yet. There is an active PR +at [dotnet/sdk #40133](https://github.com/dotnet/sdk/pull/40133). Give it a vote if you'd like this feature: ⚠️ `cd` into the solution folder. I.e. `eShop/`, then run: @@ -78,21 +83,30 @@ We support this, but the .Net SDK does not yet. There is an active PR at [dotnet dotnet new modulith-proj --ModuleName Shipments --existingProject eShop.Web/eShop.Web.csproj ``` -Here `Shipments` is the name of your new module, and `eShop.Web/eShop.Web.csproj` is the path to your web entry project. If you changed this, make sure you update it to the new path and that is relative to the solution folder. +Here `Shipments` is the name of your new module, and `eShop.Web/eShop.Web.csproj` is the path to your web entry project. +If you changed this, make sure you update it to the new path and that is relative to the solution folder. # 📊 About Modular Monoliths -A Modular Monolithic app benefits from the simple deployment of a monolith and the separation of concerns that microservices offer. While avoiding the complexities and maitainability issues they can introduce. When you are ready and *if* you need it, you can split a module as a microservice. Best of both worlds 🌎 +A Modular Monolithic app benefits from the simple deployment of a monolith and the separation of concerns that +microservices offer. While avoiding the complexities and maitainability issues they can introduce. When you are ready +and *if* you need it, you can split a module as a microservice. Best of both worlds 🌎 -This is not a new concept. Martin Fowler [explains it here](https://martinfowler.com/bliki/MonolithFirst.html), and Ardalis teaches it [here](https://ardalis.com/introducing-modular-monoliths-goldilocks-architecture/#:~:text=A%20Modular%20Monolith%20is%20a%20software%20architecture%20that,that%20they%20are%20loosely%20coupled%20and%20highly%20cohesive.). +This is not a new concept. Martin Fowler [explains it here](https://martinfowler.com/bliki/MonolithFirst.html), and +Ardalis teaches +it [here](https://ardalis.com/introducing-modular-monoliths-goldilocks-architecture/#:~:text=A%20Modular%20Monolith%20is%20a%20software%20architecture%20that,that%20they%20are%20loosely%20coupled%20and%20highly%20cohesive.). -The templates in this project follow the solution structure as taught by [Ardalis](https://github.com/ardalis) in [his course *Modular Monoliths in DotNet*](https://dometrain.com/bundle/from-zero-to-hero-modular-monoliths-in-dotnet/). +The templates in this project follow the solution structure as taught by [Ardalis](https://github.com/ardalis) +in [his course *Modular Monoliths in +DotNet*](https://dometrain.com/bundle/from-zero-to-hero-modular-monoliths-in-dotnet/). # 🛃 Custom templates -No template fits all needs. If you weant to customize the template you can change it in the `working/content` directory and running: +No template fits all needs. If you weant to customize the template you can change it in the `working/content` directory +and running: *⚠️ Make sure to uninstall the original template* + ```pwsh dotnet new install . ``` diff --git a/working/content/.DS_Store b/working/content/.DS_Store new file mode 100644 index 0000000..309aa83 Binary files /dev/null and b/working/content/.DS_Store differ diff --git a/working/content/modulith-ddd/.template.config/template.json b/working/content/modulith-ddd/.template.config/template.json deleted file mode 100644 index a757dc3..0000000 --- a/working/content/modulith-ddd/.template.config/template.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/template", - "$comment": "See https://aka.ms/template-json-reference for complete configuration description. Complete TODOs below and remove the $comment properties. It is recommended to use the JSON editor that supports schema hints to get more information about defined JSON properties and their description.", - "author": "David Chaparro", - "classifications": [ - "Web", - "Web API", - "API", - "Service", - "Modular", - "Monolith" - ], - "name": "modulith-ddd", - "description": "A project template for creating a Modular Monolithic Web API using FastEndpoints and MediatR", - "precedence": "0", - "identity": "Davidc.Modulith.Ddd.Project.1.0", - "shortName": "modulith-ddd", - "tags": { - "language": "C#", - "type": "solution" - }, - "sourceName": "Modulith", - "preferNameDirectory": true, - "symbols": { - "add-module": { - "type": "parameter", - "datatype": "string", - "defaultValue": "MyModule", - "fileRename": "NewModule", - "replaces": "NewModule", - "isEnabled": true, - "isRequired": true - }, - "to": { - "displayName": "Existing project relative path", - "description": "Path relative to the working directory", - "type": "parameter", - "datatype": "string", - "defaultValue": "ExistingProject/ExistingProject.csproj", - "fileRename": "ExistingProject" - } - }, - "sources": [ - { - "source": ".", - "include": [ - "NewModule/**" - ], - "target": "./" - } - ], - "primaryOutputs": [ - { - "path": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" - }, - { - "path": "NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj" - }, - { - "path": "NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj" - } - ] - , - "postActions": [ - { - "description": "Add projects to solution", - "manualInstructions": [ - { - "text": "Add generated project(s) to solution manually." - } - ], - "actionId": "D396686C-DE0E-4DE6-906D-291CD29FC5DE", - "continueOnError": true - }, - { - "description": "Adding a reference to another project", - "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - "continueOnError": true, - "manualInstructions": [ - { - "text": "Manually add the reference in the web project to the new project" - } - ], - "applyFileRenamesToArgs": [ - "targetFiles", - "reference" - ], - "args": { - "targetFiles": [ - "ExistingProject.Web/ExistingProject.Web.csproj" - ], - "referenceType": "project", - "reference": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" - } - } - ] -} \ No newline at end of file diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Class1.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Class1.cs deleted file mode 100644 index 2dfbde8..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Modulith.NewModule.Contracts; - -public class Class1 -{ - -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj deleted file mode 100644 index 0d123ed..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs deleted file mode 100644 index fa0e688..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs +++ /dev/null @@ -1,28 +0,0 @@ -using ArchUnitNET.Domain; -using ArchUnitNET.Loader; -using ArchUnitNET.xUnit; -using static ArchUnitNET.Fluent.ArchRuleDefinition; - -namespace Modulith.NewModule.Tests; - -public class NewModuleTypesShould -{ - private static readonly Architecture Architecture = - new ArchLoader() - .LoadAssemblies(typeof(AssemblyInfo).Assembly) - .Build(); - - [Fact] - public void BeInternal() - { - var domainTypes = Types() - .That() - .ResideInNamespace("Modulith.NewModule.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleHostApplicationBuilderExtensions)]) - .As("Module types"); - - var rule = domainTypes.Should().BeInternal(); - - rule.Check(Architecture); - } -} \ No newline at end of file diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs deleted file mode 100644 index 8033038..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs +++ /dev/null @@ -1,34 +0,0 @@ -using ArchUnitNET.Domain; -using ArchUnitNET.Fluent; -using ArchUnitNET.Loader; -using ArchUnitNET.xUnit; - -namespace Modulith.NewModule.Tests; - -public class DomainTypesShould -{ - private static readonly Architecture Architecture = - new ArchLoader() - .LoadAssemblies(typeof(AssemblyInfo).Assembly) - .Build(); - - [Fact] - public void NotDependOnApiTypes() - { - var domainTypes = ArchRuleDefinition.Types() - .That() - .ResideInNamespace("Modulith.NewModule.Domain.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleHostApplicationBuilderExtensions)]) - .As("Domain types"); - - var apiTypes = ArchRuleDefinition.Types() - .That() - .ResideInNamespace("Modulith.NewModule.Api.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleHostApplicationBuilderExtensions)]) - .As("Api types"); - - var rule = domainTypes.Should().NotDependOnAny(apiTypes); - - rule.Check(Architecture); - } -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj deleted file mode 100644 index fb97225..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - false - true - - - - - - - - - - - - - - - - - - - - - diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs deleted file mode 100644 index ad17ef3..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs +++ /dev/null @@ -1,34 +0,0 @@ -using FastEndpoints; -using FastEndpoints.Testing; -using FluentAssertions; -using Modulith.NewModule.Api; - -namespace Modulith.NewModule.Tests; - -public class WeatherForecastEndpointShould(NewModuleFixture fixture) : TestBase -{ - [Fact] - public async Task ReturnWeatherForecastDataAsync() - { - var call = await fixture.Client.GETAsync(); - - call.Response.EnsureSuccessStatusCode(); - call.Result.Should().HaveCount(5); - } -} - -public class NewModuleFixture : AppFixture -{ - protected override async Task SetupAsync() - { - Client = CreateClient(); - - await base.SetupAsync(); - } - - protected override async Task TearDownAsync() - { - Client.Dispose(); - await base.TearDownAsync(); - } -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs deleted file mode 100644 index 0265205..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Modulith.NewModule.Api; - -internal interface IWeatherForecastService -{ - WeatherForecastResponse[] GetWeatherForecast(string[] summaries); -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs deleted file mode 100644 index c7618fc..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs +++ /dev/null @@ -1,25 +0,0 @@ -using FastEndpoints; - -namespace Modulith.NewModule.Api; - -internal record WeatherForecastResponse(DateOnly Date, int TemperatureC, string? Summary); - -internal class WeatherForecastEndpoint(IWeatherForecastService weatherForecastService) : EndpointWithoutRequest -{ - public override void Configure() - { - AllowAnonymous(); - Get("/NewModule/weatherforecast"); - } - - public override async Task HandleAsync(CancellationToken ct) - { - string[] summaries = - ["Freezing", "Bracing", "Chilly", "Cool", "Mild", - "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; - - var forecasts = weatherForecastService.GetWeatherForecast(summaries); - - await SendOkAsync(forecasts, ct); - } -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs deleted file mode 100644 index ea4109d..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Modulith.NewModule.Api; - -internal class WeatherForecastService : IWeatherForecastService -{ - public WeatherForecastResponse[] GetWeatherForecast(string[] summaries) - { - - return Enumerable.Range(1, 5) - .Select(random => - new WeatherForecastResponse - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(random)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - } -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/AssemblyInfo.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/AssemblyInfo.cs deleted file mode 100644 index a787985..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Modulith.NewModule.Tests")] -namespace Modulith.NewModule; - -public class AssemblyInfo { } - diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModule.http b/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModule.http deleted file mode 100644 index 66f837a..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModule.http +++ /dev/null @@ -1,6 +0,0 @@ -@Modulith.Web_HostAddress = http://localhost:5183 - -GET {{Modulith.Web_HostAddress}}/NewModule/weatherforecast/ -Accept: application/json - -### diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs deleted file mode 100644 index 1d697f7..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Modulith.NewModule.Api; - -namespace Modulith.NewModule; - -public static class NewModuleModuleHostApplicationBuilderExtensions -{ - public static void AddNewModuleServices(this IHostApplicationBuilder builder) - { - var logger = GetLogger(builder); - builder.Services.AddMediatR( - c => c.RegisterServicesFromAssemblies(typeof(AssemblyInfo).Assembly)); - - builder.Services.AddScoped(); - - logger.LogInformation("⚙️ NewModule module services registered"); - } - - private static ILogger GetLogger(IHostApplicationBuilder builder) - => builder.Services - .BuildServiceProvider() - .GetRequiredService>(); -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Summary.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Summary.cs deleted file mode 100644 index 9d61d89..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Summary.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Modulith.NewModule.Domain; - -public enum Summary -{ - Freezing, - Bracing, - Chilly, - Cool, - Mild, - Warm, - Balmy, - Hot, - Sweltering, - Scorching -} diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Weather.cs b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Weather.cs deleted file mode 100644 index d153dbc..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Domain/Weather.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Modulith.NewModule.Api; - -namespace Modulith.NewModule.Domain; - -internal class Weather(DateOnly date, int temperatureC, Summary summary) -{ - public DateOnly Date { get; init; } = date; - public int TemperatureC { get; init; } = temperatureC; - public Summary Summary { get; init; } = summary; -} - -// ⚠️ Don't do it 🙃. -// The class below references the infra project. -// Uncommenting the Weather class will make the test: -// DomainTypesShould.NotDependOnApiTypes fail. - -// internal class BadWeatherClass -// { -// private readonly WeatherForecastResponse _response; -// -// internal BadWeatherClass(WeatherForecastResponse response) -// { -// _response = response; -// } -// } diff --git a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Modulith.DddModule.csproj b/working/content/modulith-ddd/DddModule/Modulith.DddModule/Modulith.DddModule.csproj deleted file mode 100644 index 6ba615d..0000000 --- a/working/content/modulith-ddd/DddModule/Modulith.DddModule/Modulith.DddModule.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/working/content/modulith-ddd/Directory.Build.props b/working/content/modulith-ddd/Directory.Build.props deleted file mode 100644 index 487fa8e..0000000 --- a/working/content/modulith-ddd/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - True - net8.0 - enable - enable - - diff --git a/working/content/modulith-ddd/Modulith.sln b/working/content/modulith-ddd/Modulith.sln deleted file mode 100644 index d83c82f..0000000 --- a/working/content/modulith-ddd/Modulith.sln +++ /dev/null @@ -1,41 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NewModule", "NewModule", "{A3EF1564-343E-4543-B052-4B917E4447FD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule", "NewModule\Modulith.NewModule\Modulith.NewModule.csproj", "{7F15FFAA-822B-437A-B6F4-BCC2C31FB660}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.Contracts", "NewModule\Modulith.NewModule.Contracts\Modulith.NewModule.Contracts.csproj", "{15EACFA3-099A-4AAB-931A-602509346105}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.Tests", "NewModule\Modulith.NewModule.Tests\Modulith.NewModule.Tests.csproj", "{44CBFA29-6702-4744-8236-BBEF4FA6FFB9}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7F15FFAA-822B-437A-B6F4-BCC2C31FB660}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7F15FFAA-822B-437A-B6F4-BCC2C31FB660}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F15FFAA-822B-437A-B6F4-BCC2C31FB660}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7F15FFAA-822B-437A-B6F4-BCC2C31FB660}.Release|Any CPU.Build.0 = Release|Any CPU - {15EACFA3-099A-4AAB-931A-602509346105}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15EACFA3-099A-4AAB-931A-602509346105}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15EACFA3-099A-4AAB-931A-602509346105}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15EACFA3-099A-4AAB-931A-602509346105}.Release|Any CPU.Build.0 = Release|Any CPU - {44CBFA29-6702-4744-8236-BBEF4FA6FFB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44CBFA29-6702-4744-8236-BBEF4FA6FFB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44CBFA29-6702-4744-8236-BBEF4FA6FFB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44CBFA29-6702-4744-8236-BBEF4FA6FFB9}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {7F15FFAA-822B-437A-B6F4-BCC2C31FB660} = {A3EF1564-343E-4543-B052-4B917E4447FD} - {15EACFA3-099A-4AAB-931A-602509346105} = {A3EF1564-343E-4543-B052-4B917E4447FD} - {44CBFA29-6702-4744-8236-BBEF4FA6FFB9} = {A3EF1564-343E-4543-B052-4B917E4447FD} - EndGlobalSection -EndGlobal diff --git a/working/content/modulith-proj/.template.config/template.json b/working/content/modulith-proj/.template.config/template.json deleted file mode 100644 index 8b16722..0000000 --- a/working/content/modulith-proj/.template.config/template.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/template", - "$comment": "See https://aka.ms/template-json-reference for complete configuration description. Complete TODOs below and remove the $comment properties. It is recommended to use the JSON editor that supports schema hints to get more information about defined JSON properties and their description.", - "author": "David Chaparro", - "classifications": [ - "Web", - "Web API", - "API", - "Service", - "Modular", - "Monolith" - ], - "name": "modulith-proj", - "description": "A project template for creating a Modular Monolithic Web API using FastEndpoints and MediatR", - "precedence": "0", - "identity": "Davidc.Modulith.Proj.Project.1.0", - "shortName": "modulith-proj", - "tags": { - "language": "C#", - "type": "solution" - }, - "sourceName": "Modulith", - "preferNameDirectory": true, - "symbols": { - "add-module": { - "type": "parameter", - "datatype": "string", - "defaultValue": "MyModule", - "fileRename": "NewModule", - "replaces": "NewModule", - "isEnabled": true, - "isRequired": true - }, - "to": { - "displayName": "Existing project relative path", - "description": "Path relative to the working directory", - "type": "parameter", - "datatype": "string", - "defaultValue": "ExistingProject/ExistingProject.csproj", - "fileRename": "ExistingProject" - } - }, - "sources": [ - { - "source": ".", - "include": [ - "NewModule/**" - ], - "target": "./" - } - ], - "primaryOutputs": [ - { - "path": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" - }, - { - "path": "NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj" - }, - { - "path": "NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj" - } - ], - "postActions": [ - { - "description": "Add projects to solution", - "manualInstructions": [ - { - "text": "Add generated project(s) to solution manually." - } - ], - "actionId": "D396686C-DE0E-4DE6-906D-291CD29FC5DE", - "continueOnError": true - }, - { - "description": "Adding a reference to Web project", - "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - "continueOnError": true, - "manualInstructions": [ - { - "text": "Manually add the reference in the web project to the new project" - } - ], - "applyFileRenamesToArgs": [ - "targetFiles", - "reference" - ], - "args": { - "targetFiles": [ - "ExistingProject.Web/ExistingProject.Web.csproj" - ], - "referenceType": "project", - "reference": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" - } - }, - { - "description": "Adding a reference to SharedKernel", - "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", - "continueOnError": true, - "manualInstructions": [ - { - "text": "Manually add the reference in the web project to the new project" - } - ], - "applyFileRenamesToArgs": [ - "targetFiles", - "reference" - ], - "args": { - "targetFiles": [ - "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" - ], - "referenceType": "project", - "reference": "Modulith.SharedKernel/Modulith.SharedKernel.csproj" - } - } - ] -} \ No newline at end of file diff --git a/working/content/modulith-proj/Directory.Build.props b/working/content/modulith-proj/Directory.Build.props deleted file mode 100644 index 487fa8e..0000000 --- a/working/content/modulith-proj/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - True - net8.0 - enable - enable - - diff --git a/working/content/modulith-proj/Modulith.SharedKernel/IRegisterModuleServices.cs b/working/content/modulith-proj/Modulith.SharedKernel/IRegisterModuleServices.cs deleted file mode 100644 index 59fff7e..0000000 --- a/working/content/modulith-proj/Modulith.SharedKernel/IRegisterModuleServices.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Modulith.SharedKernel; - -public interface IRegisterModuleServices -{ - static abstract IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder); - - private static ILogger GetLogger(IHostApplicationBuilder builder) - => builder.Services - .BuildServiceProvider() - .GetRequiredService>(); -} diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Class1.cs b/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Class1.cs deleted file mode 100644 index e40e6c8..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Modulith.NewModule.Contracts; - -public class Class1 -{ - -} diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj b/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj deleted file mode 100644 index 0d123ed..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj b/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj deleted file mode 100644 index b98bd62..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - false - true - - - - - - - - - - - - - - - - - - - diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs b/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs deleted file mode 100644 index 0c0f6f1..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs +++ /dev/null @@ -1,28 +0,0 @@ -using ArchUnitNET.Domain; -using ArchUnitNET.Loader; -using ArchUnitNET.xUnit; -using static ArchUnitNET.Fluent.ArchRuleDefinition; - -namespace Modulith.NewModule.Tests; - -public class NewModuleTypesShould -{ - private static readonly Architecture Architecture = - new ArchLoader() - .LoadAssemblies(typeof(AssemblyInfo).Assembly) - .Build(); - - [Fact] - public void BeInternal() - { - var domainTypes = Types() - .That() - .ResideInNamespace("Modulith.NewModule.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleServiceRegistrar)]) - .As("Module types"); - - var rule = domainTypes.Should().BeInternal(); - - rule.Check(Architecture); - } -} diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule/AssemblyInfo.cs b/working/content/modulith-proj/NewModule/Modulith.NewModule/AssemblyInfo.cs deleted file mode 100644 index a787985..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule/AssemblyInfo.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("Modulith.NewModule.Tests")] -namespace Modulith.NewModule; - -public class AssemblyInfo { } - diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule/Modulith.NewModule.csproj b/working/content/modulith-proj/NewModule/Modulith.NewModule/Modulith.NewModule.csproj deleted file mode 100644 index 6d05af6..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule/Modulith.NewModule.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModule.http b/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModule.http deleted file mode 100644 index 66f837a..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModule.http +++ /dev/null @@ -1,6 +0,0 @@ -@Modulith.Web_HostAddress = http://localhost:5183 - -GET {{Modulith.Web_HostAddress}}/NewModule/weatherforecast/ -Accept: application/json - -### diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs b/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs deleted file mode 100644 index 776a4ed..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Modulith.SharedKernel; - -namespace Modulith.NewModule; - -public class NewModuleModuleServiceRegistrar : IRegisterModuleServices -{ - public static IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder) - { - builder.Services.AddMediatR( - c => c.RegisterServicesFromAssemblies(typeof(AssemblyInfo).Assembly)); - - return builder; - } -} \ No newline at end of file diff --git a/working/content/modulith-proj/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs b/working/content/modulith-proj/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs deleted file mode 100644 index 3705134..0000000 --- a/working/content/modulith-proj/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs +++ /dev/null @@ -1,33 +0,0 @@ -using FastEndpoints; -using static System.DateOnly; -using static System.Random; - -namespace Modulith.NewModule; - -internal record WeatherForecastResponse(DateOnly Date, int TemperatureC, string? Summary); - -internal class WeatherForeCastEndpoint : EndpointWithoutRequest -{ - public override void Configure() - { - AllowAnonymous(); - Get("/NewModule/weatherforecast"); - } - - public override async Task HandleAsync(CancellationToken ct) - { - string[] summaries = - ["Freezing", "Bracing", "Chilly", "Cool", "Mild", - "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; - - await SendOkAsync(Enumerable.Range(1, 5) - .Select(random => - new WeatherForecastResponse - ( - FromDateTime(DateTime.Now.AddDays(random)), - Shared.Next(-20, 55), - summaries[Shared.Next(summaries.Length)] - )) - .ToArray(), ct); - } -} diff --git a/working/content/modulith/.template.config/template.json b/working/content/modulith/.template.config/template.json index 1e38130..94340f1 100644 --- a/working/content/modulith/.template.config/template.json +++ b/working/content/modulith/.template.config/template.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/add", + "$schema": "http://json.schemastore.org/template", "author": "David Chaparro", "classifications": [ "Web", @@ -12,7 +12,7 @@ "name": "modulith", "description": "A solution add for creating a Modular Monolithic Web API using FastEndpoints and MediatR", "precedence": "0", - "identity": "Davidc.Modulith.1.0", + "identity": "Ardalis.Modulith.1.0", "shortName": "modulith", "tags": { "language": "C#", @@ -60,18 +60,43 @@ "type": "parameter", "datatype": "string", "defaultValue": "MyModule", - "fileRename": "NewModule", - "replaces": "NewModule", "isEnabled": "(add != \"solution\")", - "isRequired": "(add != \"solution\")" + "isRequired": "(add != \"solution\")", + "fileRename": "_Module", + "replaces": "_Module", + "forms": { + "global": [ + "ddd", + "new" + ] + } }, "to": { - "displayName": "Existing project relative path", + "displayName": "To", "description": "Path relative to the working directory", "type": "parameter", "datatype": "string", "fileRename": "Modulith", "replaces": "Modulith" + }, + "WithUi": { + "displayName": "With UI", + "type": "parameter", + "description": "Wether to add UI Blazor projects, and configuration", + "datatype": "bool", + "defaultValue": "false" + } + }, + "forms": { + "new": { + "identifier": "replace", + "pattern": "^_", + "replacement": "Ddd" + }, + "ddd": { + "identifier": "replace", + "pattern": "^_", + "replacement": "New" } }, "sources": [ @@ -85,40 +110,64 @@ "NewModule/**/*", ".gitignore", "Directory.Build.props", - "Modulith.sln" + "Modulith.sln", + "Shared/**/*" + ], + "modifiers": [ + { + "condition": "!(WithUi)", + "exclude": [ + "Shared/Modulith.UI/**/*", + "NewModule/Modulith.NewModule.UI/**/*", + "Modulith.Web/wwwroot/**/*", + "Modulith.Web/Components/**/*", + "Modulith.Web/ConventionBuilderExtensions.cs" + ] + } ] }, { "source": ".", "target": "./", "condition": "(add == \"basic-module\")", - "rename": { - "NewModule/": "NewModule/" - }, "include": [ "NewModule/**/*" + ], + "modifiers": [ + { + "condition": "!(WithUi)", + "exclude": [ + "NewModule/Modulith.NewModule.UI/**/*" + ] + } ] }, { "source": ".", "target": "./", "condition": "(add == \"ddd-module\")", - "rename": { - "DddModule": "NewModule" - }, - "modifiers": [ - { - "rename": { - "DddModule": "NewModule" - } - } - ], "include": [ "DddModule/**/*" ] } ], "primaryOutputs": [ + { + "condition": "(WithUi)", + "path": "Shared/Modulith.UI/Modulith.UI.csproj" + }, + { + "condition": "(WithUi)", + "path": "NewModule/Modulith.NewModule.UI/Modulith.NewModule.UI.csproj" + }, + { + "condition": "(WithUi)", + "path": "NewModule/Modulith.NewModule.HttpModels/Modulith.NewModule.HttpModels.csproj" + }, + { + "condition": "(!IsSolution)", + "path": "Shared/Modulith.SharedKernel/Modulith.SharedKernel.csproj" + }, { "condition": "(!IsSolution)", "path": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" @@ -134,7 +183,6 @@ ], "postActions": [ { - "condition": "(!IsSolution)", "description": "Add projects to solution", "manualInstructions": [ { @@ -146,7 +194,7 @@ }, { "condition": "(!IsSolution)", - "description": "Adding a reference to Web project", + "description": "Adding the new module as a reference to Web project", "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", "continueOnError": true, "manualInstructions": [ @@ -166,14 +214,36 @@ "reference": "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" } }, + { + "condition": "(WithUi)", + "description": "Adding the new UI module as a reference to UI shared project", + "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", + "continueOnError": true, + "manualInstructions": [ + { + "text": "Manually add the reference in the UI project to the new project" + } + ], + "applyFileRenamesToArgs": [ + "targetFiles", + "reference" + ], + "args": { + "targetFiles": [ + "Shared/Modulith.UI/Modulith.UI.csproj" + ], + "referenceType": "project", + "reference": "NewModule/Modulith.NewModule.UI/Modulith.NewModule.UI.csproj" + } + }, { "condition": "(!IsSolution)", - "description": "Adding a reference to SharedKernel", + "description": "Adding the new module as a reference to SharedKernel", "actionId": "B17581D1-C5C9-4489-8F0A-004BE667B814", "continueOnError": true, "manualInstructions": [ { - "text": "Manually add the reference in the web project to the new project" + "text": "Manually add the reference in the shared kernel project to the new project" } ], "applyFileRenamesToArgs": [ @@ -185,7 +255,7 @@ "NewModule/Modulith.NewModule/Modulith.NewModule.csproj" ], "referenceType": "project", - "reference": "Modulith.SharedKernel/Modulith.SharedKernel.csproj" + "reference": "Shared/Modulith.SharedKernel/Modulith.SharedKernel.csproj" } } ] diff --git a/working/content/modulith/.template.config/template.jsonc b/working/content/modulith/.template.config/template.jsonc index 9ad6322..f77b37e 100644 --- a/working/content/modulith/.template.config/template.jsonc +++ b/working/content/modulith/.template.config/template.jsonc @@ -8,7 +8,8 @@ "API", "Service", "Modular", - "Monolith" ], + "Monolith" + ], "name": "modulith", "description": "A solution template for creating a Modular Monolithic Web API using FastEndpoints and MediatR", "precedence": "0", @@ -29,7 +30,6 @@ "replaces": "valueToReplace", "isEnabled": false } - }, "sources": [ { @@ -43,7 +43,11 @@ "SampleConstraint": { "$comment": "TODO: The template may define the constraints all of which must be met in order for the template to be used. For more details see https://aka.ms/template-json-reference#symbols#template-constraints. If constraints are not required, remove the property.", "type": "os", - "args": [ "Linux", "OSX", "Windows" ] + "args": [ + "Linux", + "OSX", + "Windows" + ] } }, "primaryOutputs": [ diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Class1.cs b/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Class1.cs index 2dfbde8..0b84a82 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Class1.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Class1.cs @@ -1,6 +1,3 @@ -namespace Modulith.NewModule.Contracts; +namespace Modulith.DddModule.Contracts; -public class Class1 -{ - -} +public class Class1; diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj b/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj index 07b4209..c681e88 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj +++ b/working/content/modulith/DddModule/Modulith.DddModule.Contracts/Modulith.DddModule.Contracts.csproj @@ -1,5 +1,5 @@ - - - + + + diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleFixture.cs b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleFixture.cs index 049b8a9..dd15bb7 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleFixture.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleFixture.cs @@ -1,9 +1,9 @@ using FastEndpoints.Testing; using Modulith.Web; -namespace Modulith.NewModule.Tests; +namespace Modulith.DddModule.Tests; -public class NewModuleFixture : AppFixture +public class DddModuleFixture : AppFixture { protected override async Task SetupAsync() { diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs index c46d338..a8adf94 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DddModuleTypesShould.cs @@ -3,9 +3,9 @@ using ArchUnitNET.xUnit; using static ArchUnitNET.Fluent.ArchRuleDefinition; -namespace Modulith.NewModule.Tests; +namespace Modulith.DddModule.Tests; -public class NewModuleTypesShould +public class DddModuleTypesShould { private static readonly Architecture Architecture = new ArchLoader() @@ -17,12 +17,13 @@ public void BeInternal() { var domainTypes = Types() .That() - .ResideInNamespace("Modulith.NewModule.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleServiceRegistrar)]) + .ResideInNamespace("Modulith.DddModule.*", true) + .And() + .AreNot([typeof(AssemblyInfo), typeof(DddModuleServiceRegistrar)]) .As("Module types"); var rule = domainTypes.Should().BeInternal(); rule.Check(Architecture); } -} \ No newline at end of file +} diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs index b82ef12..4d0c73e 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule.Tests/DomainTypesShould.cs @@ -3,7 +3,7 @@ using ArchUnitNET.Loader; using ArchUnitNET.xUnit; -namespace Modulith.NewModule.Tests; +namespace Modulith.DddModule.Tests; public class DomainTypesShould { @@ -17,13 +17,34 @@ public void NotDependOnApiTypes() { var domainTypes = ArchRuleDefinition.Types() .That() - .ResideInNamespace("Modulith.NewModule.Domain.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleServiceRegistrar)]) + .ResideInNamespace("Modulith.DddModule.Domain.*", true) + .And() + .AreNot([typeof(AssemblyInfo), typeof(DddModuleServiceRegistrar)]) .As("Domain types"); - + + var apiTypes = ArchRuleDefinition.Types() + .That() + .ResideInNamespace("Modulith.DddModule.Api.*", true) + .As("Api types"); + + var rule = domainTypes.Should().NotDependOnAny(apiTypes); + + rule.Check(Architecture); + } + + [Fact] + public void NotDependOnInfraTypes() + { + var domainTypes = ArchRuleDefinition.Types() + .That() + .ResideInNamespace("Modulith.DddModule.Domain.*", true) + .And() + .AreNot([typeof(AssemblyInfo), typeof(DddModuleServiceRegistrar)]) + .As("Domain types"); + var apiTypes = ArchRuleDefinition.Types() .That() - .ResideInNamespace("Modulith.NewModule.Api.*", useRegularExpressions: true) + .ResideInNamespace("Modulith.DddModule.Infrastructure.*", true) .As("Api types"); var rule = domainTypes.Should().NotDependOnAny(apiTypes); diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj b/working/content/modulith/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj index 2668a75..0df92ad 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj +++ b/working/content/modulith/DddModule/Modulith.DddModule.Tests/Modulith.DddModule.Tests.csproj @@ -1,27 +1,27 @@ - - false - true - + + false + true + - - - - - - - - - + + + + + + + + + - - - + + + - - - - + + + + diff --git a/working/content/modulith/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs b/working/content/modulith/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs index 6c0fce8..944c184 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule.Tests/WeatherForecastEndpointShould.cs @@ -1,11 +1,11 @@ using FastEndpoints; using FastEndpoints.Testing; using FluentAssertions; -using Modulith.NewModule.Api; +using Modulith.DddModule.Api; -namespace Modulith.NewModule.Tests; +namespace Modulith.DddModule.Tests; -public class WeatherForecastEndpointShould(NewModuleFixture fixture) : TestBase +public class WeatherForecastEndpointShould(DddModuleFixture fixture) : TestBase { [Fact] public async Task ReturnWeatherForecastDataAsync() @@ -15,4 +15,4 @@ public async Task ReturnWeatherForecastDataAsync() call.Response.EnsureSuccessStatusCode(); call.Result.Should().HaveCount(5); } -} \ No newline at end of file +} diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs b/working/content/modulith/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs index 0265205..27f0795 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/Api/IWeatherForecastService.cs @@ -1,4 +1,4 @@ -namespace Modulith.NewModule.Api; +namespace Modulith.DddModule.Api; internal interface IWeatherForecastService { diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs b/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs index c7618fc..af3209b 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastEndpoint.cs @@ -1,6 +1,6 @@ using FastEndpoints; -namespace Modulith.NewModule.Api; +namespace Modulith.DddModule.Api; internal record WeatherForecastResponse(DateOnly Date, int TemperatureC, string? Summary); @@ -9,17 +9,19 @@ internal class WeatherForecastEndpoint(IWeatherForecastService weatherForecastSe public override void Configure() { AllowAnonymous(); - Get("/NewModule/weatherforecast"); + Get("/DddModule/weatherforecast"); } public override async Task HandleAsync(CancellationToken ct) { string[] summaries = - ["Freezing", "Bracing", "Chilly", "Cool", "Mild", - "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; + [ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", + "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + ]; var forecasts = weatherForecastService.GetWeatherForecast(summaries); - + await SendOkAsync(forecasts, ct); } } diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs b/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs index ea4109d..2d64415 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/Api/WeatherForecastService.cs @@ -1,18 +1,18 @@ -namespace Modulith.NewModule.Api; +using Modulith.DddModule.Infrastructure; -internal class WeatherForecastService : IWeatherForecastService -{ - public WeatherForecastResponse[] GetWeatherForecast(string[] summaries) - { +namespace Modulith.DddModule.Api; - return Enumerable.Range(1, 5) - .Select(random => - new WeatherForecastResponse - ( - DateOnly.FromDateTime(DateTime.Now.AddDays(random)), - Random.Shared.Next(-20, 55), - summaries[Random.Shared.Next(summaries.Length)] - )) - .ToArray(); - } +internal class WeatherForecastService( + ITemperatureService + temperatureService) : IWeatherForecastService +{ + public WeatherForecastResponse[] GetWeatherForecast(string[] summaries) => Enumerable.Range(1, 5) + .Select(random => + new WeatherForecastResponse + ( + DateOnly.FromDateTime(DateTime.Now.AddDays(random)), + temperatureService.GetTemperature(), + summaries[Random.Shared.Next(summaries.Length)] + )) + .ToArray(); } diff --git a/working/content/modulith/DddModule/Modulith.DddModule/AssemblyInfo.cs b/working/content/modulith/DddModule/Modulith.DddModule/AssemblyInfo.cs index a787985..f0627f3 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/AssemblyInfo.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Modulith.NewModule.Tests")] -namespace Modulith.NewModule; +[assembly: InternalsVisibleTo("Modulith.DddModule.Tests")] -public class AssemblyInfo { } +namespace Modulith.DddModule; +public class AssemblyInfo; diff --git a/working/content/modulith/DddModule/Modulith.DddModule/DddModule.http b/working/content/modulith/DddModule/Modulith.DddModule/DddModule.http index 66f837a..065b51c 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/DddModule.http +++ b/working/content/modulith/DddModule/Modulith.DddModule/DddModule.http @@ -1,6 +1,6 @@ @Modulith.Web_HostAddress = http://localhost:5183 -GET {{Modulith.Web_HostAddress}}/NewModule/weatherforecast/ +GET {{Modulith.Web_HostAddress}}/DddModule/weatherforecast/ Accept: application/json ### diff --git a/working/content/modulith/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs b/working/content/modulith/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs index 30ce354..5799d10 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/DddModuleModuleHostApplicationBuilderExtensions.cs @@ -1,29 +1,29 @@ -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Modulith.NewModule.Api; +using Modulith.DddModule.Api; +using Modulith.DddModule.Infrastructure; using Modulith.SharedKernel; -namespace Modulith.NewModule; +namespace Modulith.DddModule; -public class NewModuleServiceRegistrar : IRegisterModuleServices +public class DddModuleServiceRegistrar : IRegisterModuleServices { - public static IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder) + public static IServiceCollection ConfigureServices(IServiceCollection services) { - var logger = GetLogger(builder); - builder.Services.AddMediatR( + var logger = GetLogger(services); + services.AddMediatR( c => c.RegisterServicesFromAssemblies(typeof(AssemblyInfo).Assembly)); - builder.Services.AddScoped(); - - logger.LogInformation("⚙️ NewModule module services registered"); + services.AddScoped(); + services.AddScoped(); - return builder; + logger.LogInformation("⚙️ DddModule module services registered"); + + return services; } - - private static ILogger GetLogger(IHostApplicationBuilder builder) - => builder.Services + + private static ILogger GetLogger(IServiceCollection services) + => services .BuildServiceProvider() - .GetRequiredService>(); + .GetRequiredService>(); } diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Domain/Summary.cs b/working/content/modulith/DddModule/Modulith.DddModule/Domain/Summary.cs index 0a50049..5bf1aee 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Domain/Summary.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/Domain/Summary.cs @@ -1,15 +1,15 @@ -namespace Modulith.NewModule.Domain; +namespace Modulith.DddModule.Domain; internal enum Summary { - Freezing, - Bracing, - Chilly, - Cool, - Mild, - Warm, - Balmy, - Hot, - Sweltering, - Scorching + Freezing, + Bracing, + Chilly, + Cool, + Mild, + Warm, + Balmy, + Hot, + Sweltering, + Scorching } diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Domain/Weather.cs b/working/content/modulith/DddModule/Modulith.DddModule/Domain/Weather.cs index d153dbc..4b4dd7c 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Domain/Weather.cs +++ b/working/content/modulith/DddModule/Modulith.DddModule/Domain/Weather.cs @@ -1,6 +1,4 @@ -using Modulith.NewModule.Api; - -namespace Modulith.NewModule.Domain; +namespace Modulith.DddModule.Domain; internal class Weather(DateOnly date, int temperatureC, Summary summary) { diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/FakeTemperatureService.cs b/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/FakeTemperatureService.cs new file mode 100644 index 0000000..5fa926a --- /dev/null +++ b/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/FakeTemperatureService.cs @@ -0,0 +1,7 @@ +namespace Modulith.DddModule.Infrastructure; + +internal class FakeTemperatureService : ITemperatureService +{ + public int GetTemperature() + => Random.Shared.Next(-20, 55); +} diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/ITemperatureService.cs b/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/ITemperatureService.cs new file mode 100644 index 0000000..846b110 --- /dev/null +++ b/working/content/modulith/DddModule/Modulith.DddModule/Infrastructure/ITemperatureService.cs @@ -0,0 +1,6 @@ +namespace Modulith.DddModule.Infrastructure; + +internal interface ITemperatureService +{ + public int GetTemperature(); +} diff --git a/working/content/modulith/DddModule/Modulith.DddModule/Modulith.DddModule.csproj b/working/content/modulith/DddModule/Modulith.DddModule/Modulith.DddModule.csproj index 6d05af6..9418e20 100644 --- a/working/content/modulith/DddModule/Modulith.DddModule/Modulith.DddModule.csproj +++ b/working/content/modulith/DddModule/Modulith.DddModule/Modulith.DddModule.csproj @@ -5,12 +5,12 @@ - - + + - + diff --git a/working/content/modulith/Directory.Build.props b/working/content/modulith/Directory.Build.props index 487fa8e..d327681 100644 --- a/working/content/modulith/Directory.Build.props +++ b/working/content/modulith/Directory.Build.props @@ -1,8 +1,8 @@ - - True - net8.0 - enable - enable - + + True + net8.0 + enable + enable + diff --git a/working/content/modulith/Modulith.SharedKernel/IRegisterModuleServices.cs b/working/content/modulith/Modulith.SharedKernel/IRegisterModuleServices.cs deleted file mode 100644 index 59fff7e..0000000 --- a/working/content/modulith/Modulith.SharedKernel/IRegisterModuleServices.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; - -namespace Modulith.SharedKernel; - -public interface IRegisterModuleServices -{ - static abstract IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder); - - private static ILogger GetLogger(IHostApplicationBuilder builder) - => builder.Services - .BuildServiceProvider() - .GetRequiredService>(); -} diff --git a/working/content/modulith/Modulith.Web/.DS_Store b/working/content/modulith/Modulith.Web/.DS_Store new file mode 100644 index 0000000..29174cb Binary files /dev/null and b/working/content/modulith/Modulith.Web/.DS_Store differ diff --git a/working/content/modulith/Modulith.Web/Components/App.razor b/working/content/modulith/Modulith.Web/Components/App.razor new file mode 100644 index 0000000..e85b1b3 --- /dev/null +++ b/working/content/modulith/Modulith.Web/Components/App.razor @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/working/content/modulith/Modulith.Web/Components/Pages/Error.razor b/working/content/modulith/Modulith.Web/Components/Pages/Error.razor new file mode 100644 index 0000000..da49173 --- /dev/null +++ b/working/content/modulith/Modulith.Web/Components/Pages/Error.razor @@ -0,0 +1,37 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} \ No newline at end of file diff --git a/working/content/modulith/Modulith.Web/Components/_Imports.razor b/working/content/modulith/Modulith.Web/Components/_Imports.razor new file mode 100644 index 0000000..e0431c5 --- /dev/null +++ b/working/content/modulith/Modulith.Web/Components/_Imports.razor @@ -0,0 +1,13 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using Modulith.Web +@using Modulith.UI +@using Modulith.Web.Components +@using MudBlazor +@using MudBlazor.Services \ No newline at end of file diff --git a/working/content/modulith/Modulith.Web/ConventionBuilderExtensions.cs b/working/content/modulith/Modulith.Web/ConventionBuilderExtensions.cs new file mode 100644 index 0000000..7e8e0f4 --- /dev/null +++ b/working/content/modulith/Modulith.Web/ConventionBuilderExtensions.cs @@ -0,0 +1,23 @@ +using Modulith.UI; +using Modulith.UI.Pages; +using Modulith.Web.Components; + +namespace Modulith.Web; + +public static class ConventionBuilderExtensions +{ + public static void AddBlazorModulesAdditionalAssemblies(this WebApplication app) + { + var discoveryService = app.Services.GetRequiredService(); + + var componentBuilder = app.MapRazorComponents() + .AddInteractiveServerRenderMode() + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(Counter).Assembly); + + discoveryService.GetAssemblies() + .ToList() + .ForEach(a => componentBuilder.AddAdditionalAssemblies(a)); + + } +} diff --git a/working/content/modulith/Modulith.Web/WebApplicationBuilderExtensions.cs b/working/content/modulith/Modulith.Web/ModuleRegistrationExtensions.cs similarity index 59% rename from working/content/modulith/Modulith.Web/WebApplicationBuilderExtensions.cs rename to working/content/modulith/Modulith.Web/ModuleRegistrationExtensions.cs index b5c227d..a9e8eb1 100644 --- a/working/content/modulith/Modulith.Web/WebApplicationBuilderExtensions.cs +++ b/working/content/modulith/Modulith.Web/ModuleRegistrationExtensions.cs @@ -3,32 +3,41 @@ namespace Modulith.Web; -public static class WebApplicationBuilderExtensions +public static class ModuleRegistrationExtensions { private static string SolutionName => "Modulith"; - public static void DiscoverAndRegisterModules(this WebApplicationBuilder webApplicationBuilder) + public static void DiscoverAndRegisterModules(this IServiceCollection services) { - var logger = CreateLogger(webApplicationBuilder); + var logger = CreateLogger(); var discoveredModuleAssemblies = DiscoverModuleAssemblies(logger); var moduleAssembliesLoaded = LoadAssembliesToApp(discoveredModuleAssemblies, logger); - moduleAssembliesLoaded.ForEach(module => RegisterModuleServices(webApplicationBuilder, module, logger)); + moduleAssembliesLoaded.ForEach(module => RegisterModuleServices(services, module, logger)); } - private static ILogger CreateLogger(WebApplicationBuilder webApplicationBuilder) - => LoggerFactory.Create(config => - { - config.AddConsole(); - config.AddConfiguration(webApplicationBuilder.Configuration.GetSection("Logging")); - }).CreateLogger(nameof(WebApplicationBuilderExtensions)); + private static ILogger CreateLogger() + => LoggerFactory.Create(config => { + config.AddConsole(); + }) + .CreateLogger(nameof(ModuleRegistrationExtensions)); + + private static List DiscoverModuleAssemblies(ILogger logger) + { + var solutionAssemblies = GetAllSolutionAssemblies(); + var paths = GetAssembliesPaths(solutionAssemblies); + + var discoveredAssemblies = GetDiscoveredAssemblies(logger, paths); - private static IEnumerable GetAppAssemblies() - => AppDomain.CurrentDomain.GetAssemblies().ToList().Select(a => a.Location).ToArray(); + logger.LogDebug("🧩 Found the following assembly modules"); + discoveredAssemblies.ForEach(d => logger.LogDebug("🧩 {name}", d.Name)); - private static void RegisterModuleServices(WebApplicationBuilder webApplicationBuilder, Assembly assembly, ILogger logger) + return discoveredAssemblies; + } + + private static void RegisterModuleServices(IServiceCollection services, Assembly assembly, ILogger logger) { if (!TryGetServiceRegistrationMethod(logger, assembly, out var method)) { @@ -36,20 +45,20 @@ private static void RegisterModuleServices(WebApplicationBuilder webApplicationB return; } - InvokeServiceRegistrationMethod(logger, webApplicationBuilder, method!); + InvokeServiceRegistrationMethod(logger, services, method!); } - private static void InvokeServiceRegistrationMethod(ILogger logger, WebApplicationBuilder webApplicationBuilder, MethodInfo method) + private static void InvokeServiceRegistrationMethod(ILogger logger, IServiceCollection services, MethodBase method) { try { - var initialServiceCount = GetServicesCount(webApplicationBuilder); - method.Invoke(null, [webApplicationBuilder]); - var finalServiceCount = GetServicesCount(webApplicationBuilder); + var initialServiceCount = GetServicesCount(services); + method.Invoke(null, [services]); + var finalServiceCount = GetServicesCount(services); logger.LogInformation("✅ Registered {serviceCount} services for module: {module}", finalServiceCount - initialServiceCount, - $"{method.DeclaringType?.Assembly.GetName().Name}" ?? method.Name); + $"{method.DeclaringType?.Assembly.GetName().Name}"); } catch (Exception) { @@ -58,18 +67,18 @@ private static void InvokeServiceRegistrationMethod(ILogger logger, WebApplicati } } - private static int GetServicesCount(WebApplicationBuilder webApplicationBuilder) - => webApplicationBuilder.Services.GroupBy(s => s.ServiceType).Count(); + private static int GetServicesCount(IServiceCollection services) + => services.GroupBy(s => s.ServiceType).Count(); private static bool TryGetServiceRegistrationMethod(ILogger logger, Assembly assembly, out MethodInfo? method) { method = default; - if (!TryGetModuleName(logger, assembly, out var moduleName)) return false; - - if (!TryGetServiceRegistrationClass(logger, assembly, out var serviceRegistrationClass)) return false; + if (!TryGetServiceRegistrationClass(logger, assembly, out var serviceRegistrationClass)) + { + return false; + } - var serviceRegistrationMethod = $"Add{moduleName}Services"; method = GetRegistrationMethod(serviceRegistrationClass); if (method == default) { @@ -83,12 +92,6 @@ private static bool TryGetServiceRegistrationMethod(ILogger logger, Assembly ass private static MethodInfo? GetRegistrationMethod(Type? serviceRegistrationClass) => serviceRegistrationClass!.GetMethod(nameof(IRegisterModuleServices.ConfigureServices), BindingFlags.Static | BindingFlags.Public); - private static bool HasCorrectSignature(MethodInfo m) - { - - return m.GetParameters().SingleOrDefault()?.ParameterType == typeof(WebApplicationBuilder); - } - private static bool TryGetServiceRegistrationClass(ILogger logger, Assembly assembly, out Type? serviceRegistrationClass) { serviceRegistrationClass = GetRegisterServicesClass(assembly); @@ -108,54 +111,10 @@ private static bool TryGetServiceRegistrationClass(ILogger logger, Assembly asse private static Type? GetRegisterServicesClass(Assembly assembly) => assembly.GetTypes().FirstOrDefault(t => t.IsClass && t.IsAssignableTo(typeof(IRegisterModuleServices))); - private static bool TryGetModuleName(ILogger logger, Assembly assembly, out string moduleName) - { - moduleName = string.Empty; - if (!TryGetModuleAssemblyName(logger, assembly, out var moduleAssembly)) return false; - - moduleName = moduleAssembly.Split($"{SolutionName}.")[1]; - if (string.IsNullOrEmpty(moduleAssembly)) - { - logger.LogError($"Could not find module {moduleName}"); - return false; - } - - logger.LogDebug($"🐞 Registering services for module '{moduleName}' in '{assembly.GetName().Name}'", - moduleName, moduleAssembly); - - return true; - } - - private static bool TryGetModuleAssemblyName(ILogger logger, Assembly assembly, out string moduleAssembly) - { - moduleAssembly = assembly.GetName().Name ?? string.Empty; - if (string.IsNullOrEmpty(moduleAssembly)) - { - logger.LogError($"Could not find assembly {moduleAssembly}"); - return false; - } - - return true; - } - - private static List DiscoverModuleAssemblies(ILogger logger) - { - var solutionAssemblies = GetAllSolutionAssemblies(); - var paths = GetAssembliesPaths(solutionAssemblies); - - var discoveredAssemblies = GetDiscoveredAssemblies(logger, paths); - - logger.LogDebug("🧩 Found the following assembly modules"); - discoveredAssemblies.ForEach(d => logger.LogDebug("🧩 {name}", d.Name)); - - return discoveredAssemblies; - } - private static List LoadAssembliesToApp(List assemblyModuleNames, ILogger logger) { var addedAssemblies = new List(); - assemblyModuleNames.ForEach(assemblyName => - { + assemblyModuleNames.ForEach(assemblyName => { logger.LogDebug("🐞 Loading module from assembly: {assembly}", $"{assemblyName.Name} {assemblyName.Version}"); try { @@ -173,11 +132,7 @@ private static List LoadAssembliesToApp(List assemblyMod return addedAssemblies; } - private static List GetAssembliesPaths(IEnumerable solutionAssemblies) - { - var loadedPaths = GetAppAssemblies(); - return solutionAssemblies.Where(r => IsModuleAssembly(loadedPaths, r)).ToList(); - } + private static List GetAssembliesPaths(IEnumerable solutionAssemblies) => solutionAssemblies.Where(IsModuleAssembly).ToList(); private static string[] GetAllSolutionAssemblies() => Directory.GetFiles(AppDomain.CurrentDomain.BaseDirectory, $"{SolutionName}.*.dll"); @@ -201,15 +156,13 @@ private static List GetDiscoveredAssemblies(ILogger logger, List loadedPaths, string r) - => !(IsAlreadyLoaded(loadedPaths, r) || IsCurrentExecutingAssembly(r) || IsSharedKernel(r)); + private static bool IsModuleAssembly(string r) + => !(IsWebOrTestAssembly(r) || IsSharedKernel(r) || IsUi(r)); + private static bool IsUi(string s) => s.Contains(".UI.") || s.Contains(".HttpModels."); private static bool IsSharedKernel(string r) => r.Contains("SharedKernel"); - private static bool IsCurrentExecutingAssembly(string r) - => r == Assembly.GetExecutingAssembly().Location; - - private static bool IsAlreadyLoaded(IEnumerable loadedPaths, string r) - => loadedPaths.Contains(r, StringComparer.InvariantCultureIgnoreCase); + private static bool IsWebOrTestAssembly(string r) + => r.Contains(".Web.") || r.Contains(".Tests."); } diff --git a/working/content/modulith/Modulith.Web/Modulith.Web.csproj b/working/content/modulith/Modulith.Web/Modulith.Web.csproj index af894f4..b34249a 100644 --- a/working/content/modulith/Modulith.Web/Modulith.Web.csproj +++ b/working/content/modulith/Modulith.Web/Modulith.Web.csproj @@ -1,14 +1,35 @@ - - - - - - - - - - - + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/working/content/modulith/Modulith.Web/Program.cs b/working/content/modulith/Modulith.Web/Program.cs index d8e322e..a6a0ff4 100644 --- a/working/content/modulith/Modulith.Web/Program.cs +++ b/working/content/modulith/Modulith.Web/Program.cs @@ -1,7 +1,10 @@ using FastEndpoints; using FastEndpoints.Security; using FastEndpoints.Swagger; -using Modulith.NewModule; +#if (WithUi) +using Modulith.UI; +using MudBlazor.Services; +#endif using Modulith.Web; var builder = WebApplication.CreateBuilder(args); @@ -14,11 +17,14 @@ // NewModuleModuleServiceRegistrar.ConfigureServices(builder); // Or use the discover method below to try and find the services for your modules -builder.DiscoverAndRegisterModules(); +builder.Services.DiscoverAndRegisterModules(); + +#if (WithUi) +builder.Services.AddBlazorAssemblyDiscovery(); +#endif builder.Services - .AddAuthenticationJwtBearer(s => - { + .AddAuthenticationJwtBearer(s => { // TODO: Add dotnet secrets s.SigningKey = builder.Configuration["Auth:JwtSecret"]; }) @@ -26,22 +32,39 @@ .SwaggerDocument() .AddFastEndpoints(); +#if (WithUi) +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents() + .AddInteractiveWebAssemblyComponents(); + +builder.Services.AddMudServices(); +#endif var app = builder.Build(); app.UseHttpsRedirection(); +#if (WithUi) +app.UseStaticFiles() + .UseWebAssemblyDebugging(); +#endif // Use FastEndpoints app.UseAuthentication() .UseAuthorization() +#if (WithUi) + .UseRouting() + .UseAntiforgery() +#endif .UseFastEndpoints() .UseSwaggerGen(); +#if (WithUi) +app.AddBlazorModulesAdditionalAssemblies(); +#endif + app.Run(); namespace Modulith.Web { - public partial class Program - { - - } + public partial class Program; } \ No newline at end of file diff --git a/working/content/modulith/Modulith.Web/Properties/launchSettings.json b/working/content/modulith/Modulith.Web/Properties/launchSettings.json index 953fe2c..44f5aa9 100644 --- a/working/content/modulith/Modulith.Web/Properties/launchSettings.json +++ b/working/content/modulith/Modulith.Web/Properties/launchSettings.json @@ -13,8 +13,13 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, + //#if (WithUi) + "launchUrl": "", + //#else "launchUrl": "swagger", + //#endif "applicationUrl": "http://localhost:5183", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -25,6 +30,7 @@ "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:7131;http://localhost:5183", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -33,6 +39,7 @@ "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "swagger", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/working/content/modulith/Modulith.Web/wwwroot/app.css b/working/content/modulith/Modulith.Web/wwwroot/app.css new file mode 100644 index 0000000..0f179df --- /dev/null +++ b/working/content/modulith/Modulith.Web/wwwroot/app.css @@ -0,0 +1,24 @@ +/* + This CSS file matches the color scheme from MudBlazor to Bootstrap when utilized for authentication. + The file remains available at all times for demonstration purposes, + but it is exclusively employed in the 'App.razor' component when authentication is enabled. +*/ + +.btn-primary { + text-transform: uppercase; + --bs-btn-bg: var(--mud-palette-primary) !important; + --bs-btn-hover-bg: var(--mud-palette-primary-darken) !important; +} + +.nav-pills { + --bs-nav-pills-link-active-bg: var(--mud-palette-primary) !important; +} + +.nav { + --bs-nav-link-color: var(--mud-palette-primary) !important; + --bs-nav-link-hover-color: var(--mud-palette-primary-darken) !important; +} + +.full-height { + height: calc(100vh - calc(var(--mud-appbar-height) * 1.5)); +} \ No newline at end of file diff --git a/working/content/modulith/Modulith.Web/wwwroot/favicon.ico b/working/content/modulith/Modulith.Web/wwwroot/favicon.ico new file mode 100644 index 0000000..1239223 Binary files /dev/null and b/working/content/modulith/Modulith.Web/wwwroot/favicon.ico differ diff --git a/working/content/modulith/Modulith.sln b/working/content/modulith/Modulith.sln index f975668..6093a09 100644 --- a/working/content/modulith/Modulith.sln +++ b/working/content/modulith/Modulith.sln @@ -7,13 +7,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.Web", "Modulith.We EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NewModule", "NewModule", "{A3EF1564-343E-4543-B052-4B917E4447FD}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{04A042B2-CF6F-4456-9EB5-DEA178B9B324}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule", "NewModule\Modulith.NewModule\Modulith.NewModule.csproj", "{7F15FFAA-822B-437A-B6F4-BCC2C31FB660}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.Contracts", "NewModule\Modulith.NewModule.Contracts\Modulith.NewModule.Contracts.csproj", "{15EACFA3-099A-4AAB-931A-602509346105}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.Tests", "NewModule\Modulith.NewModule.Tests\Modulith.NewModule.Tests.csproj", "{44CBFA29-6702-4744-8236-BBEF4FA6FFB9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.SharedKernel", "Modulith.SharedKernel\Modulith.SharedKernel.csproj", "{EDFA53B3-520A-4212-AF95-8FE79AE765FF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.SharedKernel", "Shared\Modulith.SharedKernel\Modulith.SharedKernel.csproj", "{EDFA53B3-520A-4212-AF95-8FE79AE765FF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -49,5 +51,6 @@ Global {7F15FFAA-822B-437A-B6F4-BCC2C31FB660} = {A3EF1564-343E-4543-B052-4B917E4447FD} {15EACFA3-099A-4AAB-931A-602509346105} = {A3EF1564-343E-4543-B052-4B917E4447FD} {44CBFA29-6702-4744-8236-BBEF4FA6FFB9} = {A3EF1564-343E-4543-B052-4B917E4447FD} + {EDFA53B3-520A-4212-AF95-8FE79AE765FF} = {04A042B2-CF6F-4456-9EB5-DEA178B9B324} EndGlobalSection EndGlobal diff --git a/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Class1.cs b/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Class1.cs index e40e6c8..8896903 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Class1.cs +++ b/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Class1.cs @@ -1,6 +1,3 @@ namespace Modulith.NewModule.Contracts; -public class Class1 -{ - -} +public class Class1; diff --git a/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj b/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj index 0d123ed..ba4ddb1 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj +++ b/working/content/modulith/NewModule/Modulith.NewModule.Contracts/Modulith.NewModule.Contracts.csproj @@ -1,5 +1,4 @@  - - - + + diff --git a/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/IWeatherForecastService.cs b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/IWeatherForecastService.cs new file mode 100644 index 0000000..3785068 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/IWeatherForecastService.cs @@ -0,0 +1,6 @@ +namespace Modulith.NewModule.HttpModels; + +public interface IWeatherForecastService +{ + Task> GetWeatherForecastAsync(); +} diff --git a/working/content/modulith-proj/Modulith.SharedKernel/Modulith.SharedKernel.csproj b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/Modulith.NewModule.HttpModels.csproj similarity index 67% rename from working/content/modulith-proj/Modulith.SharedKernel/Modulith.SharedKernel.csproj rename to working/content/modulith/NewModule/Modulith.NewModule.HttpModels/Modulith.NewModule.HttpModels.csproj index 8224fd4..3a63532 100644 --- a/working/content/modulith-proj/Modulith.SharedKernel/Modulith.SharedKernel.csproj +++ b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/Modulith.NewModule.HttpModels.csproj @@ -1,9 +1,5 @@  - - - - - + net8.0 enable diff --git a/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/WeatherForecastResponse.cs b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/WeatherForecastResponse.cs new file mode 100644 index 0000000..be50ada --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.HttpModels/WeatherForecastResponse.cs @@ -0,0 +1,6 @@ +namespace Modulith.NewModule.HttpModels; + +public record WeatherForecastResponse(DateOnly Date, int TemperatureC, string? Summary) +{ + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj b/working/content/modulith/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj index b98bd62..59b4052 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj +++ b/working/content/modulith/NewModule/Modulith.NewModule.Tests/Modulith.NewModule.Tests.csproj @@ -1,24 +1,24 @@ - - false - true - + + false + true + - - - - - - - + + + + + + + - - - + + + - - - + + + diff --git a/working/content/modulith/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs b/working/content/modulith/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs index 0c0f6f1..c2fe980 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs +++ b/working/content/modulith/NewModule/Modulith.NewModule.Tests/NewModuleTypesShould.cs @@ -17,8 +17,9 @@ public void BeInternal() { var domainTypes = Types() .That() - .ResideInNamespace("Modulith.NewModule.*", useRegularExpressions: true) - .And().AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleServiceRegistrar)]) + .ResideInNamespace("Modulith.NewModule.*", true) + .And() + .AreNot([typeof(AssemblyInfo), typeof(NewModuleModuleServiceRegistrar)]) .As("Module types"); var rule = domainTypes.Should().BeInternal(); diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/ClientWeatherForecastService.cs b/working/content/modulith/NewModule/Modulith.NewModule.UI/ClientWeatherForecastService.cs new file mode 100644 index 0000000..0191be8 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/ClientWeatherForecastService.cs @@ -0,0 +1,19 @@ +using System.Text.Json; +using Modulith.NewModule.HttpModels; + +namespace Modulith.NewModule.UI; + +public class ClientWeatherForecastService(HttpClient client) : IWeatherForecastService +{ + public async Task> GetWeatherForecastAsync() + { + var response = await client.GetAsync("NewModule/weatherforecast"); + var stringResponse = await response.Content.ReadAsStringAsync(); + var forecasts = JsonSerializer.Deserialize>(stringResponse, new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }); + + return forecasts ?? []; + } +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/ExampleJsInterop.cs b/working/content/modulith/NewModule/Modulith.NewModule.UI/ExampleJsInterop.cs new file mode 100644 index 0000000..4ca73f5 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/ExampleJsInterop.cs @@ -0,0 +1,37 @@ +using Microsoft.JSInterop; + +namespace Modulith.NewModule.UI; + +// This class provides an example of how JavaScript functionality can be wrapped +// in a .NET class for easy consumption. The associated JavaScript module is +// loaded on demand when first needed. +// +// This class can be registered as scoped DI service and then injected into Blazor +// components for use. + +public class ExampleJsInterop : IAsyncDisposable +{ + private readonly Lazy> _moduleTask; + + public ExampleJsInterop(IJSRuntime jsRuntime) + { + _moduleTask = new Lazy>(() => jsRuntime.InvokeAsync( + "import", "./_content/Modulith.NewModule.UI/exampleJsInterop.js") + .AsTask()); + } + + public async ValueTask Prompt(string message) + { + var module = await _moduleTask.Value; + return await module.InvokeAsync("showPrompt", message); + } + + public async ValueTask DisposeAsync() + { + if (_moduleTask.IsValueCreated) + { + var module = await _moduleTask.Value; + await module.DisposeAsync(); + } + } +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/Modulith.NewModule.UI.csproj b/working/content/modulith/NewModule/Modulith.NewModule.UI/Modulith.NewModule.UI.csproj new file mode 100644 index 0000000..0867ff2 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/Modulith.NewModule.UI.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor new file mode 100644 index 0000000..9f58674 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor @@ -0,0 +1,55 @@ +@page "/NewModule/Component" +@inject IWeatherForecastService WeatherForecastService + +
+ This component is defined in the Modulith.NewModule.UI library. +
+ +Weather + +Weather forecast +This component demonstrates fetching data from the server. + +@if (!_forecasts.Any()) +{ + +} +else +{ + + + + Date + + + Temp. (C) + + + Temp. (F) + + + Summary + + + + @context.Date + @context.TemperatureC + @context.TemperatureF + @context.Summary + + + + + +} + +@code { + private IEnumerable _forecasts = []; + + protected override async Task OnInitializedAsync() + { +// Simulate asynchronous loading to demonstrate a loading indicator + await Task.Delay(500); + _forecasts = await WeatherForecastService.GetWeatherForecastAsync(); + } +} \ No newline at end of file diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor.css b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor.css new file mode 100644 index 0000000..c6afca4 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleComponent.razor.css @@ -0,0 +1,6 @@ +.my-component { + border: 2px dashed red; + padding: 1em; + margin: 1em 0; + background-image: url('background.png'); +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleUiModuleServiceRegistrar.cs b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleUiModuleServiceRegistrar.cs new file mode 100644 index 0000000..2e5c3e7 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/NewModuleUiModuleServiceRegistrar.cs @@ -0,0 +1,15 @@ +using Modulith.SharedKernel; +using Modulith.NewModule.HttpModels; +using Microsoft.Extensions.DependencyInjection; + +namespace Modulith.NewModule.UI; + +public class NewModuleUiModuleServiceRegistrar : IRegisterModuleServices +{ + public static IServiceCollection ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + + return services; + } +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/_Imports.razor b/working/content/modulith/NewModule/Modulith.NewModule.UI/_Imports.razor new file mode 100644 index 0000000..a4b4463 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/_Imports.razor @@ -0,0 +1,7 @@ +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using MudBlazor +@using MudBlazor.Services +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using System.Text.Json +@using Modulith.NewModule.HttpModels \ No newline at end of file diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/background.png b/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/background.png new file mode 100644 index 0000000..e15a3bd Binary files /dev/null and b/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/background.png differ diff --git a/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/exampleJsInterop.js b/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/exampleJsInterop.js new file mode 100644 index 0000000..db9d857 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule.UI/wwwroot/exampleJsInterop.js @@ -0,0 +1,6 @@ +// This is a JavaScript module that is loaded on demand. It can export any number of +// functions, and may import other JavaScript modules if required. + +export function showPrompt(message) { + return prompt(message, 'Type anything here'); +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule/AssemblyInfo.cs b/working/content/modulith/NewModule/Modulith.NewModule/AssemblyInfo.cs index a787985..c84fe9b 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule/AssemblyInfo.cs +++ b/working/content/modulith/NewModule/Modulith.NewModule/AssemblyInfo.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Modulith.NewModule.Tests")] -namespace Modulith.NewModule; -public class AssemblyInfo { } +namespace Modulith.NewModule; +public class AssemblyInfo; diff --git a/working/content/modulith/NewModule/Modulith.NewModule/IWeatherForecastService.cs b/working/content/modulith/NewModule/Modulith.NewModule/IWeatherForecastService.cs new file mode 100644 index 0000000..11b3083 --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule/IWeatherForecastService.cs @@ -0,0 +1,9 @@ +#if (WithUi) +using Modulith.NewModule.HttpModels; +#endif +namespace Modulith.NewModule; + +internal interface IWeatherForecastService +{ + Task> GetWeatherForecastAsync(); +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule/Modulith.NewModule.csproj b/working/content/modulith/NewModule/Modulith.NewModule/Modulith.NewModule.csproj index 6d05af6..39c39a8 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule/Modulith.NewModule.csproj +++ b/working/content/modulith/NewModule/Modulith.NewModule/Modulith.NewModule.csproj @@ -5,12 +5,15 @@ - - + + - + + + +
diff --git a/working/content/modulith/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs b/working/content/modulith/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs index 776a4ed..ee49ce9 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs +++ b/working/content/modulith/NewModule/Modulith.NewModule/NewModuleModuleServiceRegistrar.cs @@ -1,16 +1,20 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +#if (WithUi) +using Modulith.NewModule.HttpModels; +#endif +using Microsoft.Extensions.DependencyInjection; using Modulith.SharedKernel; namespace Modulith.NewModule; public class NewModuleModuleServiceRegistrar : IRegisterModuleServices { - public static IHostApplicationBuilder ConfigureServices(IHostApplicationBuilder builder) + public static IServiceCollection ConfigureServices(IServiceCollection services) { - builder.Services.AddMediatR( + services.AddMediatR( c => c.RegisterServicesFromAssemblies(typeof(AssemblyInfo).Assembly)); - return builder; + services.AddScoped(); + + return services; } -} \ No newline at end of file +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule/ServerWeatherForecastService.cs b/working/content/modulith/NewModule/Modulith.NewModule/ServerWeatherForecastService.cs new file mode 100644 index 0000000..090f54e --- /dev/null +++ b/working/content/modulith/NewModule/Modulith.NewModule/ServerWeatherForecastService.cs @@ -0,0 +1,28 @@ +#if (WithUi) +using Modulith.NewModule.HttpModels; +#endif +using static System.DateOnly; +using static System.Random; + +namespace Modulith.NewModule; + +internal class ServerWeatherForecastService : IWeatherForecastService +{ + public Task> GetWeatherForecastAsync() + { + string[] summaries = + [ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", + "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + ]; + + return Task.FromResult(Enumerable.Range(1, 5) + .Select(random => + new WeatherForecastResponse + ( + FromDateTime(DateTime.Now.AddDays(random)), + Shared.Next(-20, 55), + summaries[Shared.Next(summaries.Length)] + ))); + } +} diff --git a/working/content/modulith/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs b/working/content/modulith/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs index 3705134..7217a95 100644 --- a/working/content/modulith/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs +++ b/working/content/modulith/NewModule/Modulith.NewModule/WeatherForeCastEndpoint.cs @@ -1,12 +1,14 @@ using FastEndpoints; -using static System.DateOnly; -using static System.Random; +#if (WithUi) +using Modulith.NewModule.HttpModels; +#endif namespace Modulith.NewModule; +#if (!WithUi) internal record WeatherForecastResponse(DateOnly Date, int TemperatureC, string? Summary); - -internal class WeatherForeCastEndpoint : EndpointWithoutRequest +#endif +internal class WeatherForeCastEndpoint(IWeatherForecastService weatherForecastService) : EndpointWithoutRequest> { public override void Configure() { @@ -14,20 +16,5 @@ public override void Configure() Get("/NewModule/weatherforecast"); } - public override async Task HandleAsync(CancellationToken ct) - { - string[] summaries = - ["Freezing", "Bracing", "Chilly", "Cool", "Mild", - "Warm", "Balmy", "Hot", "Sweltering", "Scorching"]; - - await SendOkAsync(Enumerable.Range(1, 5) - .Select(random => - new WeatherForecastResponse - ( - FromDateTime(DateTime.Now.AddDays(random)), - Shared.Next(-20, 55), - summaries[Shared.Next(summaries.Length)] - )) - .ToArray(), ct); - } + public override async Task HandleAsync(CancellationToken ct) => await SendOkAsync(await weatherForecastService.GetWeatherForecastAsync(), ct); } diff --git a/working/content/modulith/Shared/Modulith.SharedKernel/.DS_Store b/working/content/modulith/Shared/Modulith.SharedKernel/.DS_Store new file mode 100644 index 0000000..9d03c2a Binary files /dev/null and b/working/content/modulith/Shared/Modulith.SharedKernel/.DS_Store differ diff --git a/working/content/modulith/Shared/Modulith.SharedKernel/IRegisterModuleServices.cs b/working/content/modulith/Shared/Modulith.SharedKernel/IRegisterModuleServices.cs new file mode 100644 index 0000000..5f0cd27 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.SharedKernel/IRegisterModuleServices.cs @@ -0,0 +1,8 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Modulith.SharedKernel; + +public interface IRegisterModuleServices +{ + static abstract IServiceCollection ConfigureServices(IServiceCollection services); +} diff --git a/working/content/modulith/Modulith.SharedKernel/Modulith.SharedKernel.csproj b/working/content/modulith/Shared/Modulith.SharedKernel/Modulith.SharedKernel.csproj similarity index 70% rename from working/content/modulith/Modulith.SharedKernel/Modulith.SharedKernel.csproj rename to working/content/modulith/Shared/Modulith.SharedKernel/Modulith.SharedKernel.csproj index 8224fd4..c6dad86 100644 --- a/working/content/modulith/Modulith.SharedKernel/Modulith.SharedKernel.csproj +++ b/working/content/modulith/Shared/Modulith.SharedKernel/Modulith.SharedKernel.csproj @@ -1,13 +1,13 @@  - - - - - + net8.0 enable enable + + + + diff --git a/working/content/modulith/Shared/Modulith.UI/BlazorAssemblyDiscoveryService.cs b/working/content/modulith/Shared/Modulith.UI/BlazorAssemblyDiscoveryService.cs new file mode 100644 index 0000000..79ed6f3 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/BlazorAssemblyDiscoveryService.cs @@ -0,0 +1,10 @@ +using System.Reflection; +using Modulith.NewModule.UI; +using Modulith.UI; + +namespace Modulith.UI; + +public class BlazorAssemblyDiscoveryService : IBlazorAssemblyDiscoveryService +{ + public IEnumerable GetAssemblies() => [typeof(NewModuleComponent).Assembly]; +} diff --git a/working/content/modulith/Shared/Modulith.UI/IBlazorAssemblyDiscoveryService.cs b/working/content/modulith/Shared/Modulith.UI/IBlazorAssemblyDiscoveryService.cs new file mode 100644 index 0000000..6f1f63b --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/IBlazorAssemblyDiscoveryService.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace Modulith.UI; + +public interface IBlazorAssemblyDiscoveryService +{ + IEnumerable GetAssemblies(); +} diff --git a/working/content/modulith/Shared/Modulith.UI/Layout/MainLayout.razor b/working/content/modulith/Shared/Modulith.UI/Layout/MainLayout.razor new file mode 100644 index 0000000..58012b1 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Layout/MainLayout.razor @@ -0,0 +1,27 @@ +@inherits LayoutComponentBase + + + + + + + + + + Modulith + + + + + + + + @Body + + + +@code { + private bool _drawerOpen = true; + + private void DrawerToggle() => _drawerOpen = !_drawerOpen; +} \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/Layout/NavMenu.razor b/working/content/modulith/Shared/Modulith.UI/Layout/NavMenu.razor new file mode 100644 index 0000000..4d66ff1 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Layout/NavMenu.razor @@ -0,0 +1,32 @@ + + Home + Counter + Weather + API + + @foreach (var module in UiModules) + { + + @module + + } + + + +@code +{ + [Inject] + public IBlazorAssemblyDiscoveryService DiscoveryService { get; set; } = default!; + + private string[] UiModules { get; set; } = []; + + protected override void OnInitialized() => UiModules = DiscoveryService.GetAssemblies() + .Select(a => a.GetName().Name?.Split('.')[1] ?? string.Empty) + .Where(n => !string.IsNullOrEmpty(n)) + .ToArray(); + + private string LinkBuilder(string route) => route + "/Component"; +} \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/Modulith.UI.csproj b/working/content/modulith/Shared/Modulith.UI/Modulith.UI.csproj new file mode 100644 index 0000000..c2ac5af --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Modulith.UI.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + true + Default + + + + + + + + + + + + + diff --git a/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor b/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor new file mode 100644 index 0000000..d06ae51 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor @@ -0,0 +1,5 @@ +@page "/api" + + + + \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor.css b/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor.css new file mode 100644 index 0000000..5465a85 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Pages/Api.razor.css @@ -0,0 +1,3 @@ +iframe ::deep { + height: calc(100vh - calc(var(--mud-appbar-height) * 1.5)); +} \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/Pages/Counter.razor b/working/content/modulith/Shared/Modulith.UI/Pages/Counter.razor new file mode 100644 index 0000000..415f3b4 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Pages/Counter.razor @@ -0,0 +1,14 @@ +@page "/counter" + + +Counter + +Counter +Current count: @_currentCount +Click me + +@code { + private int _currentCount; + + private void IncrementCount() => _currentCount++; +} diff --git a/working/content/modulith/Shared/Modulith.UI/Pages/Home.razor b/working/content/modulith/Shared/Modulith.UI/Pages/Home.razor new file mode 100644 index 0000000..728b301 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Pages/Home.razor @@ -0,0 +1,59 @@ +@page "/" + +Home + + +Hello, world! +Welcome to your new app, powered by MudBlazor and the .NET 8 Template! + + + You can find documentation and examples on our website here: + + www.mudblazor.com + + + +
+Interactivity in this Template +
+ + When you opt for the "Global" Interactivity Location,
+ the render modes are defined in App.razor and consequently apply to all child components.
+ In this case, providers are globally set in the MainLayout.
+
+ On the other hand, if you choose the "Per page/component" Interactivity Location,
+ it is necessary to include the
+
+ <MudPopoverProvider />
+ <MudDialogProvider />
+ <MudSnackbarProvider />
+
+ components on every interactive page.
+
+ If a render mode is not specified for a page, it defaults to Server-Side Rendering (SSR),
+ similar to this page. While MudBlazor allows pages to be rendered in SSR,
+ please note that interactive features, such as buttons and dropdown menus, will not be functional. +
+ +
+What's New in Blazor with the Release of .NET 8 +
+Prerendering + + If you're exploring the features of .NET 8 Blazor,
you might be pleasantly surprised to learn that each page is prerendered on the server,
regardless of the selected render mode.

+ This means that you'll need to inject all necessary services on the server,
even when opting for the wasm (WebAssembly) render mode.

+ This prerendering functionality is crucial to ensuring that WebAssembly mode feels fast and responsive,
especially when it comes to initial page load times.

+ For more information on how to detect prerendering and leverage the RenderContext, you can refer to the following link: + + More details + +
+ +
+InteractiveAuto + + A discussion on how to achieve this can be found here: + + More details + + \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/Pages/Weather.razor b/working/content/modulith/Shared/Modulith.UI/Pages/Weather.razor new file mode 100644 index 0000000..22e9680 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Pages/Weather.razor @@ -0,0 +1,70 @@ +@page "/weather" + + +Weather + +Weather forecast +This component demonstrates fetching data from the server. + +@if (_forecasts == null) +{ + +} +else +{ + + + + Date + + + Temp. (C) + + + Temp. (F) + + + Summary + + + + @context.Date + @context.TemperatureC + @context.TemperatureF + @context.Summary + + + + + +} + +@code { + private WeatherForecast[]? _forecasts; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate a loading indicator + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + _forecasts = Enumerable.Range(1, 5) + .Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }) + .ToArray(); + } + + private class WeatherForecast + { + public DateOnly Date { get; init; } + public int TemperatureC { get; init; } + public string? Summary { get; init; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } + +} diff --git a/working/content/modulith/Shared/Modulith.UI/Program.cs b/working/content/modulith/Shared/Modulith.UI/Program.cs new file mode 100644 index 0000000..4de41b9 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Program.cs @@ -0,0 +1,12 @@ +using Modulith.UI; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.Services.AddScoped(_ => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +builder.Services.RegisterNewModuleSpaServices(); + +builder.Services.RegisterClientSideServices(); + +await builder.Build().RunAsync(); diff --git a/working/content/modulith/Shared/Modulith.UI/Routes.razor b/working/content/modulith/Shared/Modulith.UI/Routes.razor new file mode 100644 index 0000000..229d074 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/Routes.razor @@ -0,0 +1,34 @@ +@using System.Reflection +@using NavigationContext = Microsoft.AspNetCore.Components.Routing.NavigationContext +@inject ILogger Logger +@inject IServiceProvider Services + + + + + + + + +@code { + private List _lazyLoadedAssemblies = new(); + + [Inject] + private IBlazorAssemblyDiscoveryService? BlazorAssemblyDiscovery { get; set; } + + private Task OnNavigateAsync(NavigationContext args) + { + try + { + _lazyLoadedAssemblies.AddRange(BlazorAssemblyDiscovery!.GetAssemblies()); + } + catch (Exception ex) + { + Logger.LogError("Error: {Message}", ex.Message); + } + + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/ServiceCollectionExtensions.cs b/working/content/modulith/Shared/Modulith.UI/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..7fd8e74 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/ServiceCollectionExtensions.cs @@ -0,0 +1,99 @@ +using System.Reflection; +using Modulith.SharedKernel; + +namespace Modulith.UI; + +public static class ServiceCollectionExtensions +{ + public static void AddBlazorAssemblyDiscovery(this IServiceCollection services) => services.AddSingleton(); + + public static void RegisterClientSideServices(this IServiceCollection services) + { + var logger = CreateLogger(); + var discoveryService = services.BuildServiceProvider().GetRequiredService(); + + var assemblies = discoveryService.GetAssemblies().ToList(); + + assemblies.ForEach(module => RegisterModuleServices(services, module, logger)); + + } + + private static ILogger CreateLogger() + => LoggerFactory.Create(_ => {}).CreateLogger(nameof(ServiceCollectionExtensions)); + + private static void RegisterModuleServices(IServiceCollection services, Assembly assembly, ILogger logger) + { + if (!TryGetServiceRegistrationMethod(logger, assembly, out var method)) + { + logger.LogError("🛑 An error occurred registering services for assembly: '{assembly}'. Skipping registration", assembly.GetName()); + return; + } + + InvokeServiceRegistrationMethod(logger, services, method!); + } + + + private static void InvokeServiceRegistrationMethod(ILogger logger, IServiceCollection services, MethodBase method) + { + try + { + var initialServiceCount = GetServicesCount(services); + method.Invoke(null, [services]); + var finalServiceCount = GetServicesCount(services); + + logger.LogInformation("✅ Registered {serviceCount} services for module: {module}", + finalServiceCount - initialServiceCount, + $"{method.DeclaringType?.Assembly.GetName().Name}"); + } + catch (Exception) + { + logger.LogError($"An exception occured when invoking {method.Name}. Try calling the method directly from Program.cs"); + throw; + } + } + + private static int GetServicesCount(IServiceCollection services) + => services.GroupBy(s => s.ServiceType).Count(); + + private static bool TryGetServiceRegistrationMethod(ILogger logger, Assembly assembly, out MethodInfo? method) + { + method = default; + + if (!TryGetServiceRegistrationClass(logger, assembly, out var serviceRegistrationClass)) + { + return false; + } + + method = GetRegistrationMethod(serviceRegistrationClass); + if (method == default) + { + logger.LogError($"Could not find extensions method '{nameof(IRegisterModuleServices.ConfigureServices)}'"); + return false; + } + + return true; + } + + + private static MethodInfo? GetRegistrationMethod(IReflect? serviceRegistrationClass) + => serviceRegistrationClass!.GetMethod(nameof(IRegisterModuleServices.ConfigureServices), BindingFlags.Static | BindingFlags.Public); + + private static bool TryGetServiceRegistrationClass(ILogger logger, Assembly assembly, out Type? serviceRegistrationClass) + { + serviceRegistrationClass = GetRegisterServicesClass(assembly); + + if (serviceRegistrationClass == default) + { + logger.LogError("Could not find a public class that implements the interface: {IRegisterServices}", nameof(IRegisterModuleServices)); + return false; + } + + logger.LogDebug("Found '{serviceRegistrationClass}' using it to register modules from: '{assembly}'", + serviceRegistrationClass.Name, + assembly.GetName().Name); + return true; + } + + private static Type? GetRegisterServicesClass(Assembly assembly) + => assembly.GetTypes().FirstOrDefault(t => t.IsClass && t.IsAssignableTo(typeof(IRegisterModuleServices))); +} diff --git a/working/content/modulith/Shared/Modulith.UI/SpaServiceExtensions.cs b/working/content/modulith/Shared/Modulith.UI/SpaServiceExtensions.cs new file mode 100644 index 0000000..c90a90e --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/SpaServiceExtensions.cs @@ -0,0 +1,14 @@ +using MudBlazor.Services; + +namespace Modulith.UI; + +public static class SpaServiceExtensions +{ + public static IServiceCollection RegisterNewModuleSpaServices(this IServiceCollection services) + { + services.AddMudServices(); + services.AddBlazorAssemblyDiscovery(); + + return services; + } +} diff --git a/working/content/modulith/Shared/Modulith.UI/_Imports.razor b/working/content/modulith/Shared/Modulith.UI/_Imports.razor new file mode 100644 index 0000000..3f27ed2 --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using MudBlazor +@using MudBlazor.Services \ No newline at end of file diff --git a/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.Development.json b/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.json b/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/working/content/modulith/Shared/Modulith.UI/wwwroot/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/working/content/modulith/TestModulith.sln b/working/content/modulith/TestModulith.sln index e8279a9..9403fc1 100644 --- a/working/content/modulith/TestModulith.sln +++ b/working/content/modulith/TestModulith.sln @@ -21,7 +21,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.DddModule", "DddMo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.DddModule.Tests", "DddModule\Modulith.DddModule.Tests\Modulith.DddModule.Tests.csproj", "{FEAE1009-1C16-41E9-BD67-945A78D336F4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.SharedKernel", "Modulith.SharedKernel\Modulith.SharedKernel.csproj", "{AF4A5D62-F34F-4AE4-9C61-B9F9CCCCBBC8}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{8FAB2CD9-7E69-4B1B-A3F9-62DD9BFBF897}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.UI", "Shared\Modulith.UI\Modulith.UI.csproj", "{9B49A8FB-53D0-46FF-A023-87C86FAF8392}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.SharedKernel", "Shared\Modulith.SharedKernel\Modulith.SharedKernel.csproj", "{6873DEF4-A6D3-4656-B04F-3791C05E1A55}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.UI", "NewModule\Modulith.NewModule.UI\Modulith.NewModule.UI.csproj", "{495B8AFE-8D00-4356-A369-A5B584C9247F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modulith.NewModule.HttpModels", "NewModule\Modulith.NewModule.HttpModels\Modulith.NewModule.HttpModels.csproj", "{F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -60,10 +68,22 @@ Global {FEAE1009-1C16-41E9-BD67-945A78D336F4}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEAE1009-1C16-41E9-BD67-945A78D336F4}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEAE1009-1C16-41E9-BD67-945A78D336F4}.Release|Any CPU.Build.0 = Release|Any CPU - {AF4A5D62-F34F-4AE4-9C61-B9F9CCCCBBC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AF4A5D62-F34F-4AE4-9C61-B9F9CCCCBBC8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AF4A5D62-F34F-4AE4-9C61-B9F9CCCCBBC8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AF4A5D62-F34F-4AE4-9C61-B9F9CCCCBBC8}.Release|Any CPU.Build.0 = Release|Any CPU + {9B49A8FB-53D0-46FF-A023-87C86FAF8392}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B49A8FB-53D0-46FF-A023-87C86FAF8392}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B49A8FB-53D0-46FF-A023-87C86FAF8392}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B49A8FB-53D0-46FF-A023-87C86FAF8392}.Release|Any CPU.Build.0 = Release|Any CPU + {6873DEF4-A6D3-4656-B04F-3791C05E1A55}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6873DEF4-A6D3-4656-B04F-3791C05E1A55}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6873DEF4-A6D3-4656-B04F-3791C05E1A55}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6873DEF4-A6D3-4656-B04F-3791C05E1A55}.Release|Any CPU.Build.0 = Release|Any CPU + {495B8AFE-8D00-4356-A369-A5B584C9247F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {495B8AFE-8D00-4356-A369-A5B584C9247F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {495B8AFE-8D00-4356-A369-A5B584C9247F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {495B8AFE-8D00-4356-A369-A5B584C9247F}.Release|Any CPU.Build.0 = Release|Any CPU + {F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {7F15FFAA-822B-437A-B6F4-BCC2C31FB660} = {A3EF1564-343E-4543-B052-4B917E4447FD} @@ -72,5 +92,9 @@ Global {E2B3AF03-71F6-421A-8330-2D79411B425E} = {8F446D93-7563-45C6-92FA-9EAAD3385749} {C41D9482-3BCE-492A-A9B8-74ED0D037DE8} = {8F446D93-7563-45C6-92FA-9EAAD3385749} {FEAE1009-1C16-41E9-BD67-945A78D336F4} = {8F446D93-7563-45C6-92FA-9EAAD3385749} + {9B49A8FB-53D0-46FF-A023-87C86FAF8392} = {8FAB2CD9-7E69-4B1B-A3F9-62DD9BFBF897} + {6873DEF4-A6D3-4656-B04F-3791C05E1A55} = {8FAB2CD9-7E69-4B1B-A3F9-62DD9BFBF897} + {495B8AFE-8D00-4356-A369-A5B584C9247F} = {A3EF1564-343E-4543-B052-4B917E4447FD} + {F253A72B-0F2A-40C5-9E4E-C53ABB54BE1D} = {A3EF1564-343E-4543-B052-4B917E4447FD} EndGlobalSection EndGlobal