# Intent Architect - Complete Documentation > This file contains the complete Intent Architect documentation concatenated into a single document for use by LLMs with large context windows. --- # About Applications Applications in Intent Architect represent a _scope of code files_ that we want to automate. It could be a full-stack monolithic application, a microservice, or even just a single folder in which we wish to generate and manage files. Applications are fundamentally composed of installed [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md), [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) and some high-level configuration settings. Creating a new application in Intent Architect can be done by clicking on the "Create a new application" button in the home view to launch the _Create application_ wizard. ![Create Application Start](images/create-application-start.png) The first page of the wizard lists the available [Application Templates](https://docs.intentarchitect.com/docs-md/application-templates/about-application-templates/about-application-templates.md) in the selected Repository (The `All` setting being all the Repositories configured in your user settings. [Learn how to change your default repositories here](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md)). Here you can search and choose which application template you want and directly set key settings like the Application's name, location and [solution](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-solutions/about-solutions.md) name. > [!NOTE] > It is possible to create an empty application by clicking on the `CREATE EMPTY` button instead of `NEXT`. This would create a new application without any Modules or metadata installed. --- # About Modules ## What is a Module Modules are the _building blocks_ and artifacts of pattern reuse in Intent Architect. Typically, the purpose of a Module is to generate and manage a set of code files in a codebase, usually centered around a particular architectural pattern. This could include domain entities, bootstrapping files, ORM mappings, API controllers, etc. Modules share similarities with package systems such as NuGet, NPM, and Maven. However, while those systems primarily focus on code reuse, the primary objective of Modules is _pattern reuse_. Modules have versions and dependencies and do not directly introduce any runtime dependencies. However, they can be configured to introduce package dependencies if the Module designer chooses to do so. ![Application Modules](images/application-modules-installed.png) _An example showing the list of installed Modules in a sample application. The `Intent.Application.MediatR` module is selected, with its details displayed in the pane on the right._ > [!NOTE] > When you select an installed Module, Intent Architect provides a view into its internals (i.e., the Templates, Decorators, and Factory Extensions that comprise it). ## Module Management Modules can be managed at either the **Solution** level (affecting all applications) or the **Application** level. To access the **Module Management** screen: 1. Right-click on your application or solution in the **Solution Explorer**. 2. Select `Manage Modules...`. The Module Management screen includes three tabs: - **Browse** – Discover and install new modules from this tab. - **Installed** – View the list of currently installed modules. - **Updates** – See installed modules with updates available. > [!NOTE] > By default, this screen only displays official releases. To access pre-releases or betas, enable the `Include Prereleases` option. Documentation for modules is available on our [website](https://docs.intentarchitect.com/getting-started/welcome/welcome.html) in the respective module sections. ### Updating Modules It is recommended to be on a clean checkout when updating modules. The nature of the update is indicated by the color of the update indicator icon and aligns with [semantic versioning](https://semver.org/): - **Green** – Patch update. Should be seamless; typically includes bug fixes or small enhancements. - **Blue** – Minor update. Should be seamless; may impact unmanaged code (e.g., upgrading a NuGet package to a new major version). - **Yellow** – Major update. Check the module's release notes for important information about the changes. ![Update Indicator Icon](images/update-indicator.png) The module pane includes **release notes** containing a complete list of changes in each version, along with any upgrade notes (e.g., for major releases). ## Installation Settings When (re)installing a module, you can expand the **Installation Settings** dropdown. ![The installation settings dropdown](images/options-drop-down.png) ![The installation settings dropdown expanded](images/options-drop-down-expanded.png) These settings are generally only relevant to those [building modules](https://docs.intentarchitect.com/docs-md/module-building/module-installation/module-installation.md) who may want modules installed with selective capabilities enabled: - **Enable Factory Extensions** – Controls whether the Software Factory will load any kind of extensions (templates, factory extensions, etc.) from the module. - **Install Application Settings** – Controls whether settings from the module should be added to the [Application Settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) screen. - **Install Designer Metadata** – Controls whether [designer metadata embedded in the module](https://docs.intentarchitect.com/docs-md/application-templates/metadata-installation/metadata-installation.md) is installed into designers during initial installation. - **Install Designers** – Controls whether [designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) are installed into the application. - **Install Template Outputs** – Controls whether [template outputs](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-template-output-targeting/about-template-output-targeting.md) are installed. --- # About Solutions Solutions in Intent Architect serve as a collection of one or more [Applications](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md). They are represented by an `.isln` (Intent Solution) file on the disk-drive. With Intent Architect you can only open Solution files and not Application files; so if you want to open an Intent Architect Application, you have to do that through the context of a Solution. ## Solution Explorer The Solution Explorer is visible on the left side of Intent Architect. ![Solution Explorer](images/solution-explorer.png) _Applications are shown as children on the solution in the tree view._ ## Managing Applications At the top of the Solution Explorer, or the context menu, you will find these commands that can be performed: ### Create New Application Create a new Application such as in the [Tutorial: Building an Application with Intent Architect](https://docs.intentarchitect.com/docs-md/tutorials/building-an-application/building-an-application.md). ### Add Existing Application Browse for an existing Application located outside your current Intent Architect solution and add it to this Solution. > [!NOTE] > It will not import the content into this Solution but will merely make a reference to that location. ### New Folder Create a Solution Folder for organizing your applications as desired within them. ## Managing Modules at a Solution level Through the context menu on the solution, the `Manage Modules...` option can be used to see a solution wide view of all installed modules. This view can be useful for updating modules across all applications at once. ## Solution Settings Change Settings of the Solution such as its name or icon. ## Package as Sample The solution is packaged as a Sample which can be uploaded to an Intent Architect module repository and appear under the "Explore Samples" section on the new Application screen: ![Create New Applications(s) - Explore samples](images/explore-samples.png) --- # Git and SCM guidance This article provides an overview on working with Intent Architect and SCM (Source Control Management) systems (such as Git) including details on which files should or should not be committed and versioned into source code repositories. ## Understanding Intent Architect and SCM Intent Architect is not inherently aware of SCMs, it merely reads and writes files to your file system, allowing users to use their own choice of SCM systems and associated tooling, but Intent Architect is intentionally designed with the use of SCMs in mind. It is intended that Intent Architect files are committed into SCM repositories alongside normal source code files in the same commits. This enables checking out other versions or branches of a code base using your SCM and having the Intent Architect Designer metadata correlate with the committed source code. The other benefit of this approach is that teams practicing automated CI (Continuous Integration) can run the [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) as part of their CI process to provide another quality check. Another aspect of intentionally designing Intent Architect with SCMs in mind is the format and layout of files. Careful consideration has been applied as to whether Designer metadata is saved in its own file per element or embedded in the file of a parent element. For example, in the Domain Designer each Class is saved as a separate file, with all aspects of it (e.g. Attributes, Operations, Constructors, etc) being saved in the same file. This means that you are unlikely to have merge conflicts with other developers unless you are working on the same Class on the designer. If there is a merge conflict, all files are deliberately stored in a clear text format allowing the conflicts to be resolved using regular merge tools. ## Files and Folders to always include When you create a new Application in Intent Architect and you keep the default layout structure settings, the solution folder will have an `intent` subfolder housing all the Intent Architect files that are made up of solution and application information as well as their designer metadata. Should you alter the layout structure when you create a new Application, the files and folders to include to source control are: | File, Extension or Folder | Description | |----------------------------------|-------------| | `.application.config` | Intent Architect Application with basic data such as it's name, icon, etc. | | `.application.deviations.xml` | Tracks files which have customizations necessitating additions/changes to default code management instructions, see [The Software Factory Customizations screen](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) for more information. | | `.application.output.config.xml` | Only created/updated by the Software Factory if it already existed or when it has one or more files it should not generate any reason, for example due to a file being ignored or being once-off generation only. If this file is not committed into source control, then other users and the [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) will not be aware of which files should not be generated. | | `.application.output.log` | Obsolete. Prior to [version 4.5.22](https://docs.intentarchitect.com/docs-md/release-notes/intent-architect-v4.5.md#version-4522) of Intent Architect, was used to track aspects of Software Factory output. For backwards compatibility with older versions of Intent Architect, this file will still be updated by the Software Factory if it already exists, but if it's not present then it will not be created again. Provided that all users working on an Intent Architect application have updated to at least version 4.5.22, this file can be deleted. | | `.isln` | Intent Architect Solution file. | | `Intent.Metadata` folder | Designer Metadata files. | | `intent.repositories.config` | Solution-level asset repository configuration. | | `modules.config` file | Tracks which modules and their versions are installed for the application. | ## Folders which should always be ignored `.intent` folders should always be ignored, they contain data like downloaded/restored modules and data from previous Software Factory output to facilitate functionality such as advanced merging and knowing which files need to be renamed/deleted. ## Configuring Git (`.gitignore` files) As of version [3.1.8](https://docs.intentarchitect.com/docs-md/release-notes/intent-architect-v3.1.md#new-features-added-in-318), Intent Architect has an option during creation of a new Solution or Application to add or update the appropriate `.gitignore` files. If your application or solution was created in a version of Intent Architect less than 3.1.8, you can manually add the following to your `.gitignore` file: ```text # Intent Architect **/.intent/* ``` --- # How to change the output root Applications in Intent Architect have a _Root Output Location_ which is stored as a relative path to the location of the Application's `.application.config` file. To change this location, open the Application's `Settings`. ![application-settings](images/application-settings.png) In the image above we can see that the path of the `SampleApplication.application.config` file is in the `D:\DocSource\SampleSolution\intent\` directory. The `Relative Output Location` is `..\SampleApplication`. Therefore the _Root Output Location_ of this Application is `D:\DocSource\SampleSolution\SampleApplication`. > > [!NOTE] > In an attempt to make Intent Architect OS agnostic, it will automatically convert backslashes to forward slashes. However, we still recommend setting the `Relative Output Location` using forward slashes to avoid confusion. --- # How to manage Repositories Asset Repositories manages the repository locations that are searched when discovering Modules and Application Templates. The official Intent Architect server `https://intentarchitect.com/` is available as a repository hosting all the official (and largely Open Source) Modules for use. Additional sources can be added too which can point to a physical drive, remote/network locations or the URL of a [self-hosted module server](https://docs.intentarchitect.com/tools/module-server/module-server.html). ## Locate the Manage Repositories dialog There are a few ways to locate the Repository Management dialog. ### Profile Menu From any screen within Intent Architect, click on the top right Profile menu and select `Manage Repositories`. ![Profile Menu](images/menu-manage-repositories.png) ### Solution Settings Within the Intent Architect solution settings locate the `Manage Repositories` button like below. ![Solution Settings](images/solution-manage-repositories.png) ### Modules Within any Modules view, locate the gear icon at the top right of the screen. ![Modules](images/modules-manage-repositories.png) ## Repository Management layout ![Default Layout](images/repository-management-diaglog-default.png) At the very least you will always see the Intent Architect repository listed here, it's built-in and cannot be modified or removed on this screen. A new Repository can be added by clicking on the `Add New` button, and one can browse to the desired location by clicking on the Browse button in order to set the Repository location. This location can be any valid path for your operating system, including relative (for solution level repositories) paths, fully qualified paths, UNC paths or the URL for a [self-hosted module servers](https://docs.intentarchitect.com/tools/module-server/module-server.html). The arrow buttons allow you to adjust the order in which Intent Architect searches in each Repository. The top most Repository will be visited first and the bottom most Repository will be visited last. ![Location and Status](images/repository-location-and-status.png) Each Repository has a hyperlink (clicking on it opens up your File explorer on that location) and a counter badge next to it (indicating how many modules it detected at that location). ### Repository Context Each configured Repository has a `Context` dropdown with the following options: | Name | Description | |--------------------|-| | Global | Intent Architect will save this Repository's configuration in your operating system's user folder and it can be used in any Intent Architect Solution for your user. | | Current Solution | Intent Architect will save this Repository's configuration in an `intent.repositories.config` file in the same folder as the Solution's `.isln` file and it will only be used by the current Solution's Applications. | | Exclude from "All" | Results from this repository are excluded when 'All' is chosen on the Modules Management or Application Template screens, but you can still select it directly. | --- # Change Review The **Change Review** screen is a single view of everything that changed between two points in time, presented as one reviewable tree covering model elements, generated code, hand-written code and customizations. While a `git diff` lists changed files, it does not indicate which of them the Software Factory generated, which were written by hand, or which model change caused them. Change Review adds that context, and highlights the files that warrant closer scrutiny when they change: hand-written code, customizations inside generated files, and files a module has marked as important or critical. Specifically: - Changes are grouped by application, and by designer within each application. - Model changes are shown as element-level differences, with created, updated and deleted counts per designer. - Every changed code file is classified as deterministic (generated), deterministic with customizations, or custom (hand-written). - Files with no deterministic baseline are collected into a **Needs attention** list for review and approval. - Where Spec-Driven Development recorded them, changes are linked to the requirements they realize. Taken together, this context narrows a review down to the changes that genuinely need a human. Instead of working through a flat list of files, a reviewer can start from the model change that caused them, treat the deterministic output as already accounted for, and spend their attention on the hand-written code, customizations and critical files that Change Review has flagged. The larger the change set, the more that focus is worth. ![Change review overview](images/change-review-overview.png) > [!NOTE] > Change Review was introduced in Intent Architect 5.2 as the "Changes Review" tab, and renamed to **Change Review** in 5.3, which also added pull request reviews, file classifications and severity. ## Opening a review Change Review always compares two points. Where you open it from decides which two: | Opened from | In plain terms | Compares | | ------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------- | | The flag button in the **Source Control** panel | Everything you have changed but not yet committed | `HEAD` → your working tree | | A branch pill's **Review changes vs this branch** | Everything your branch adds to the base branch, as a pull request would | The merge-base with the base branch → the branch tip | | A commit's **Review changes since this commit** | Everything that has happened since that commit, uncommitted work included | That commit → your working tree | | A commit's **Review changes in this commit** | What that one commit changed, on its own | The commit's parent → the commit | | Two selected commits' **Review changes between** | Everything that changed between the two commits you picked | The older commit → the newer one | | A pull request in the **Git** tab | Everything the pull request would merge in | The pull request's merge-base → its head | The **merge-base** referred to above is the point at which your branch last had the same content as the base branch, in other words where the two diverged. Comparing against it means you see only the work your branch added, and not changes other people have since made to the base branch. You do not have to pick the base branch yourself. If your branch is set to track one on the server, Intent Architect compares against that. If it is not, it uses the first of `origin/main`, `main`, `origin/master`, `master` or `develop` that exists in your repository. Where a branch exists both on your machine and on the server, the server's version is used, because that is the one a pull request would merge into. The range being reviewed is always shown in the toolbar as `refA → refB`. ### The commit gate Committing while there are unreviewed hand-written files or unapproved customizations in the commit raises a **Requires attention** prompt. Its default button is **Review Changes**, which opens Change Review and abandons the commit - you can commit once you have looked. **Commit anyway** proceeds. The prompt considers only the files actually going into the commit, so an unstaged file that needs review does not block a commit that does not contain it. You can turn the prompt off with **Warn about unreviewed changes** in the Source Control panel's `⋮` menu. ![Requires attention prompt](images/change-review-commit-gate.png) ## Reading the screen ### The toolbar Alongside the range and a refresh button, the `⋮` **View options** menu controls: - **File list** - `Flat list` or `Folder tree`. - **Diff** - `Inline` (one column), `Side-by-side` (two columns), and `Wrap long lines`. - **Show** - `Traceability` (the requirement chips, the requirements pill and the Specifications section) and `Build artifacts` (output logs and previous-output files, hidden by default). ### The headline Three informational chips - applications touched, designers touched with `+ ~ -` element counts, and code files touched with changed-line counts - sit on the left. On the right are the actionable pills: the number of requirements realized by the change set, and the **% approved** pill, which jumps to the Needs attention block. ### The composition bar A full-width band showing the split of **changed lines** three ways: | Segment | Meaning | | -------------------------- | ------------------------------------------------------------------- | | **Deterministic** | Generated, and identical to what the Software Factory would produce | | **Deterministic + custom** | Generated, but the diff touched hand-written regions inside it | | **Custom** | Hand-written - nothing in the solution generated it | This is the one-glance answer to "how much of this is machine output?". A change that is 95% deterministic is a very different review from one that is 60% custom. ![Change review headline](images/change-review-headline.png) ## How a file is classified as deterministic or custom Every changed code file gets one of the three classifications above, and the mechanism is worth understanding because it decides what the whole screen emphasizes. Intent Architect keeps a **managed-files inventory** - committed to your repository - recording which files each application generated and, for each one, a hash of its recorded customization regions. Change Review reads that inventory *as it stood at each of the two refs*: - Listed at the ref → the file is **generated**. - Not listed → the file is **custom**. Nothing generated it, so there is no deterministic baseline to judge it against. - Listed, and its customization hash **differs between the two refs** → this change touched hand-written code, so the file reads as **Deterministic + custom**. Two consequences follow from the last rule, and both are deliberate: - A commit that merely regenerates a file carrying long-standing customizations reads as **Deterministic**. The customizations did not change, so the review does not ask you to look at them again. - Because the inventory is committed, the split resolves identically on a fresh clone, on a CI agent and on a teammate's machine - you do not need to have run the Software Factory locally. > [!IMPORTANT] > This classification comes from `Intent.OutputManager.RoslynWeaver`'s code-management directives. If any application in the solution is on a version older than 5.0.0, Change Review shows a persistent banner saying so, because the deterministic/custom split cannot be trusted until the module is updated. ## Needs attention The **Needs attention** block is the triage list. It holds exactly the files that no deterministic baseline vouches for: - **Custom files** - hand-written code, at any git status. An edit or a deletion of hand-written code is as reviewable as its creation. - **Files whose diff touched customizations** - the `Deterministic + custom` rows. - **Repository files** - files that no application owns (docs, CI config, the `.isln`). Nothing in the solution generated them, which is the same reason a custom file qualifies. These are **opted in** rather than shown by default, since lockfiles and CI config would otherwise bury the code beside them. Approved rows stay in the list, shown as approved - the block is a scope, not a queue, so its contents do not shift under you as you work through it. Rows are grouped by **severity**, highest band first, and each band has an **Approve all** action that acts on the rows currently on screen, so the counts on its labels are what it will actually touch. ![Change review needs attention](images/change-review-needs-attention.png) ### Filtering The filter button beside the block narrows the list by **severity** and by **file classification**. The two filters evaluate independently - unticking a severity never hides a file merely for lacking a classification - and the selection is remembered per solution. It records *exclusions* rather than inclusions, so a classification introduced by a module you install next week defaults to visible rather than being silently filtered out by a choice made before it existed. See [File Classifications](https://docs.intentarchitect.com/docs-md/application-development/change-review/file-classifications/file-classifications.md) for how the classification vocabulary is defined. ## Approving Change Review is where you reconcile "what Intent Architect generated" against "what you changed". There are three distinct kinds of sign-off, and the tick in a row means whichever one applies to it. | Kind | Applies to | Recorded in | Lifetime | | -------------------------- | ------------------------------------ | -------------------------------------------- | ----------------------------------------------- | | **Customization approval** | A generated file with custom regions | The application's deviations log (committed) | Until explicitly revoked, or the regions change | | **Custom file approval** | A fully hand-written file | The application's deviations log (committed) | Until explicitly revoked | | **Mark as reviewed** | Any file, in pull request mode only | Machine-local, keyed to the file's git blob | Until the file changes on the branch | Approving a file folds its diff away - you are done reading it. Revoking deliberately does not re-open it. A few behaviours worth knowing: - Approvals are always read from your **current workspace**, so a file you sign off updates on screen straight away, even when you are reviewing an older range of commits. - In pull request mode, a tick turns **amber** when the file has changed since you approved it, meaning your approval is now out of date. Click it to approve the file again. - A **repository file** (such as a skill file, or `.gitignore`) carries no tick outside a pull request. No application owns it, so there is no deviations log to record the sign-off in. Customization approvals are the same records the [The Software Factory Customizations screen](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) manages, so an approval made here shows up there with who approved it and when, and can carry free-form notes. > [!TIP] > The [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md)'s `ensure-no-outstanding-changes` command has a `--check-for-unapproved-customizations` option, so the same approvals can be enforced on CI. ## Model changes The **Designers** section under each application is what makes this a *design* review rather than a diff review. Each designer row carries `+ created`, `~ updated`, `- deleted` element counts, computed per package between the two refs. Expanding it gives a change tree: changed elements, indented under unaffected ancestors that provide context, with single-child folder chains fused into one row. A synthetic **+N more** row appears where the tree is too large to inline. On an element pill you can: - **Hover** it for a field-level popover - every changed field as `before → after`, with `∅` for a created or deleted side - plus git attribution naming the author, commit and date that changed it (or marking it uncommitted). - **Click** it to open the designer's read-only **model diff** at that element. - **Drag** it into the AI chat to attach it, along with the detail of what changed on it, so you can ask the AI Assistant about it. Elements that were deleted can be attached in the same way. ![Change review element popover](images/change-review-element-popover.png) ## Files Under each application, code files and other files are listed separately, and files that belong to no application appear in a **Repository** section of their own. A file row shows its git status, changed-line counts, any [file classification](https://docs.intentarchitect.com/docs-md/application-development/change-review/file-classifications/file-classifications.md) pills, a severity flag, and its deterministic/custom badge. Clicking the row expands its **diff inline**; the row's `⋮` menu offers **Open full diff in editor** and **Go to classification setting**. `Other files` covers designer packages, application and solution metadata, and Software Factory build noise. Build artifacts are hidden until you tick **Build artifacts** in the view options. ## Traceability and the Specifications section When a change was implemented through Spec-Driven Development, Intent Architect records links from each requirement to the model elements and files that realize it. Change Review consumes those links in two directions: - **Inline** - a requirement chip on any changed element or file, with a popover listing the linked requirements. The chip is marked **stale** when a linked requirement's text has changed since the link was recorded. - **Pivoted** - the **Specifications** section reverses the view: spec → requirement → the changes realizing it. This is how you answer "is this requirement actually built?" rather than "what is this file for?". Both are behind the **Traceability** toggle in the view options. ![Change review specifications](images/change-review-specifications.png) ## Reviewing a pull request Open a pull request from the **Pull Requests** list in the **Git** tab and you get the same review screen, covering everything that pull request would merge in. It opens with two sub-tabs: **Conversation**, for the pull request's description and comments, and **Change Review**, for the changes themselves. A pull request review adds the following: - **Comment threads**, which you can attach to a line in a file or to a **model element**, with replies and resolving. A comment on a model element is an ordinary pull request comment, so it appears on the host alongside the rest. - **Mark as reviewed** ticks on each file, alongside the customization and custom-file approvals described above. - **AI review**, which runs over the pull request's changes and collects what it finds into a draft review. You read, edit or drop each finding and submit the review yourself. Running it again skips anything it has already raised, including comments that have since been resolved. - **Conflict resolution** in a temporary copy of the repository that is discarded afterwards, so your own working folder is left alone. Files appear in the review as they are resolved. - **Merge, update branch, close and reopen**, without leaving Intent Architect. Supported hosts are GitHub, Azure DevOps, GitLab and Bitbucket Cloud. See [Pull Requests](https://docs.intentarchitect.com/docs-md/application-development/change-review/pull-requests/pull-requests.md) for the whole pull request surface - connecting to a host, the list, creating one, the conversation, and merging. ![Change review pull request](images/change-review-pull-request.png) ## Banners Change Review surfaces three advisory strips above the content, each with a one-click fix where one exists: | Banner | Meaning | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `Intent.OutputManager.RoslynWeaver 5.0.0 or later required` | The deterministic/custom split cannot be trusted until the module is updated. | | `Intent.Modelers.CodebaseStructure 1.1.0-pre.2 or later required` | File classification pills and severity flags are silently absent. | | `Missing file classifications` | Template Outputs whose module supplies a classification never got it stamped - offers to re-stamp them. | The two module banners stay for as long as the module is out of date, and each offers an **Update module** button that takes you straight to the solution's Modules manager. They only appear when the code on screen is the code you currently have checked out, which means a review of your working tree, or a pull request for the branch you are on. This is because the check reads the version of the module installed right now, which tells you nothing useful about a commit or branch you do not have checked out. ## Change Review for AI agents The same review is available to AI agents deterministically, through the `get_change_review` tool. It returns a compact text report rather than a diff, in one of five sections: | Section | Contents | | -------------- | ------------------------------------------------------------------ | | `overview` | Counts only (the default) | | `files` | Changed code files with their classification and requirement links | | `elements` | Changed designer elements, per designer | | `traceability` | Spec → requirement → realizing changes, plus the untraced lists | | `other` | Metadata, build-artifact and repository-level files | It defaults to the same merge-base baseline the tab opens on, and to `WORKING` as the comparand so committed work and the worktree are both in range. This is what `/sdd-verify` runs against. Requirement-side coverage can only see requirements with no link; it is structurally blind to a changed element or hand-written file that no requirement points at. `get_change_review` answers the change-side question, and answers it by computation rather than by an agent reading a diff and reporting on its own work. ## Related articles - [Pull Requests](https://docs.intentarchitect.com/docs-md/application-development/change-review/pull-requests/pull-requests.md) - reviewing, discussing and merging a pull request on GitHub, Azure DevOps, GitLab or Bitbucket Cloud. - [File Classifications](https://docs.intentarchitect.com/docs-md/application-development/change-review/file-classifications/file-classifications.md) - the classification and severity labels that drive the Needs attention filter. - [The Software Factory Customizations screen](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) - the Software Factory screen that manages the same customization approvals, with notes. - [About Code-Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) - how Intent Architect decides which regions of a file are yours and which are its own. - [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) - enforcing unapproved-customization checks on CI. --- # File Classifications A **File Classification** is a named, color-coded label - `Security`, `API Contract`, `Business Logic` - that you attach to files through your model rather than to files on disk. Together with a **severity** (`low`, `medium` or `high`), it is what lets a reviewer answer the question that matters: out of these 80 changed files, which ones are especially important for review? The reason classifications live in the model is that the model already knows what every file is. A file generated by the `Intent.Application.MediatR.CommandModels` template is an API contract no matter what it is called or which folder it ends up in, and it is that on every branch, on every machine, forever. Classifying the **template** therefore classifies every file it will ever produce - including the ones that do not exist yet. This matters most when an AI agent produced the change. A 2,000-line diff where 1,900 lines are routine generated output and 100 lines touch authorization is not a 2,000-line review - but only if something tells you which 100 lines those are. > [!NOTE] > File Classifications require Intent Architect 5.3 or later, and the `Intent.Modelers.CodebaseStructure` module at version `1.1.0` or later (the **Entries** list described below arrived in `1.1.1`). Where that module is older, Intent Architect skips classification entirely rather than tagging files it has no definitions for. ![file-classifications](images/file-classifications.png) ## How a file gets classified Where a file's classification comes from depends on one thing: whether Intent Architect generated the file. | If the file is | It inherits its classification from | | --------------------------- | ----------------------------------- | | **Generated** by a template | The template that produced it | | **Hand-written** | The folder or project it lives in | **Generated files usually classify themselves.** Modules classify their own templates, so installing a module brings its classifications with it and most of your generated code is labeled before you do anything. You can override what a module shipped, or classify a template its author left alone - see [Overriding a generated file's classification](#overriding-a-generated-files-classification). **Hand-written files are yours to classify.** Nothing generated them, so there is no template to inherit from. You tag a folder or project in the Codebase Structure Designer instead, and the files beneath it pick up the label - see [Classifying hand-written files](#classifying-hand-written-files). Not every file ends up classified, and that is normal. An unclassified file simply shows no pill, and Change Review's filter treats "unclassified" as a category of its own, so you can still include or exclude those files deliberately. Either way, the answer comes from your model and from information Intent Architect commits alongside your code - so a file classifies the same way for everyone on the team, whether that is a fresh clone, a CI agent or a teammate's machine. > [!TIP] > Do not confuse a File Classification with the **Deterministic / Deterministic + custom / Custom** badge on the same row. That badge says who wrote the file; a File Classification says what the file is for. They are independent, and a reviewer usually wants both. ## Classifications in Change Review On the [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md) screen, a classified file row carries: - One **pill per classification** on the file, tinted with that classification's color. - A **severity flag** - amber for `low`, orange for `medium`, red for `high`. The two answer different questions, so they do not always appear together: - **The pills always show.** They describe what the file is for, which is true of the file no matter what this particular change did to it. - **The flag only shows where the change actually touched hand-written code** - rows badged **Custom** or **Deterministic + custom**. So a file that regenerated cleanly, badged **Deterministic**, carries no severity flag even when its template is severity-tagged, and even when the file contains customizations that this change left alone. The flag is there to point at risk the change itself carried, not to remind you that the file is a sensitive one - the pills already do that. ![File Classification](images/file-classification-row.png) ### Filtering by classification and severity The **Needs attention** block can be filtered by severity, by classification, or by both. You untick what you want out of the way, so anything you have not touched stays visible - including a classification a module introduces later. - The classification list covers **every** classification in the solution, not just the ones in front of you. - Files with **no** classification get their own row, so you can keep or drop them deliberately. - The two filters are independent: unticking a severity never hides a file just for lacking a classification. - Your selection is remembered per solution and carries across reviews. ![Needs Attention](images/needs-attention.png) ### Jumping to where a classification is set A file row's context menu has **Go to classification setting**, which opens the designer at the element that assigns the file's classification - the `Template Output` element for a generated file, the folder or project element for a hand-written one. This resolves for an **unclassified** file too, which is usually the point: the most common reason to go looking is to classify something that is not yet classified. In that case Intent Architect opens the element where a classification would be added and tells you so, rather than dropping you on an empty-looking element with no explanation. ## Defining your own classifications Classifications are modeled elements, so you create them in the [Codebase Structure Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/codebase-structure-designer/codebase-structure-designer.md): 1. Right-click the `Root Folder` element (shown as `root` at the top of the designer) and add a **File Classifications** container, if one does not already exist. 2. Right-click that container and choose **New File Classification** (or press `Ctrl+Shift+A`). 3. Name it. The name is what appears on the pill, and is what modules match against - see [Classifying a template's output as a module author](#classifying-a-templates-output-as-a-module-author). Names are matched case-insensitively and trimmed, so `Security` and `security` are the same classification. ![File Classifications](images/file-classifications-container.png) ### Setting a classification's color Every `File Classification` element carries a **File Classification Settings** stereotype with two properties: | Property | Purpose | | ------------------- | ------------------------------------------ | | `Color` | The pill color, e.g. `#C0392B` | | `Color (Dark Mode)` | The pill color when a dark theme is active | Either may be left blank: whichever one you set stands in for the other, and if you set neither, a color is derived deterministically from the classification's name. A classification never renders uncolored, so setting these is purely about making the categories you care about stand out. > [!NOTE] > When a module install needs a classification that does not exist yet, it creates one for you - including creating the `File Classifications` container if there isn't one. Auto-created classifications have no color set, and picking colors for the ones that matter to your team is a worthwhile five minutes. ## Classifying hand-written files Hand-written files are classified by applying the **Custom File Classification** stereotype to a folder or project element in the Codebase Structure Designer. The stereotype holds a list of **Entries**, each of which is one rule: | Property | Meaning | | ---------------- | ------------------------------------------------------------------------------------ | | `Glob` | Which files under this element the rule applies to. Blank means everything (`**/*`). | | `Classification` | One or more `File Classification` elements to apply. | | `Severity` | `0 - None`, `1 - Low`, `2 - Medium` or `3 - High`. | Globs are evaluated against each file's path **relative to the element the stereotype is applied to**, and follow `.gitignore`-style rules: - One pattern per line. - A line beginning with `#` is a comment. - A line beginning with `!` negates. - The last matching line wins. So a rule on your API project of: ```text **/*.cs !**/Generated/** ``` classifies every hand-written C# file in that project except those under a `Generated` folder. When more than one thing could classify a file, the resolution is: **the deepest element containing the file wins**, and within that one element, **the last matching entry wins**. This lets you set a broad default high up your tree and override it precisely further down. > [!IMPORTANT] > `Custom File Classification` is only ever consulted for files that no template generated. Applying it to a folder full of generated code will not change how those files classify - change the template's `Output Classification` instead. ![Custom File Classification Stereotype](images/custom-file-classification-stereotype.png) ### Enabling the stereotype on your own element types `Custom File Classification` can be applied to any element whose settings - or an extension of them - carry the **Allows Custom File Classification** trait. `Root Folder` carries it out of the box, as do the project element types contributed by modules such as `Intent.VisualStudio.Projects`. If you are building a designer of your own and want its folder-like elements to participate, apply that trait to their `Element Settings`, `Element Extension`, `Package Settings` or `Package Extension` in the Module Builder. ## Overriding a generated file's classification The `Output Classification` stereotype on a `Template Output` element is an ordinary stereotype - you can open it and change the `Classification` and `Severity` the module shipped. > [!NOTE] > A classification you set yourself is preserved. Installing, updating or reinstalling the module does not overwrite it, so an override you make here stands until you change it again. If you author the module yourself, it is usually better to ship the classification you want rather than override it in each application that installs it - see [Classifying a template's output as a module author](#classifying-a-templates-output-as-a-module-author). ## Applying missing classifications If a module supplies a classification and severity for a template but the `Template Output` element in your model never received the stereotype - typically because the model predates the module version that introduced it - Change Review shows a **Missing file classifications** bar with a one-click fix. The fix-up is a presence check only: it stamps the stereotype onto elements that have none. An element whose stereotype is present but carries stale values is left alone, so the fix can never quietly discard an override you made on purpose. ![Missing File Classification](images/missing-file-classifications-bar.png) ## Classifying a template's output as a module author If you build modules, classifying your templates is what gives every downstream consumer this behavior for free. In the Module Builder, select a template element and open its **Template Settings** stereotype: | Property | Value | | ---------------- | ----------------------------------------------------------------------------------------- | | `Classification` | One or more classification names, separated by `;` (or `,`), e.g. `API Contract;Security` | | `Severity` | `0 - None`, `1 - Low`, `2 - Medium` or `3 - High` | Run the Software Factory and the values are written into your module's [manifest](https://docs.intentarchitect.com/docs-md/module-building/module-manifest/module-manifest.md) as part of that template's entry. `Severity` is normalized to a plain lower-case token, while `Classification` is written through exactly as you typed it - Intent Architect accepts either separator when reading it back: ```xml ``` When a user installs or updates the module, Intent Architect resolves each name against the `File Classification` elements already in that application - **creating any that do not exist** - and stamps the resulting `Output Classification` stereotype onto the template's `Template Output` element. Withdraw both `` and `` in a later version and the stereotype is removed again on update. A few things to keep in mind when choosing values: - **Name classifications for what a reviewer should think about**, not for your module's internals. `Security` and `API Contract` travel well across modules and merge cleanly with other modules' vocabularies; `MediatRCommandModel` does not. - **Severity is about review attention, not code quality.** Reserve `high` for output where a mistake is expensive and hard to spot - authorization, contracts published to other teams, anything with a compliance boundary. - **Leave a template unclassified rather than guessing.** Unclassified output reads as routine; a wrong but confident label is worse than none at all. ## Related articles - [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md) - the screen these classifications drive, and how it reads a change set. - [Codebase Structure Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/codebase-structure-designer/codebase-structure-designer.md) - the designer that hosts `Template Output`s, folders and `File Classification` elements. - [About Template Output Targeting](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-template-output-targeting/about-template-output-targeting.md) - how a template's output is routed to a `Template Output` element in the first place. - [Module Manifest (.imodspec)](https://docs.intentarchitect.com/docs-md/module-building/module-manifest/module-manifest.md) - the module manifest (`.imodspec`) that carries `` and ``. - [About Stereotype Definitions](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/about-stereotype-definitions/about-stereotype-definitions.md) - background on stereotypes generally. --- # Pull Requests Intent Architect can list, create, review, discuss and merge pull requests without leaving the application, on **GitHub**, **Azure DevOps**, **GitLab** and **Bitbucket Cloud**. The reason it does is [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md). A pull request raised from an Intent Architect solution carries three different kinds of change in one change set: designer metadata, the code the Software Factory generated from it, and code written by hand. **Intent Architect knows which is which** - it generated that code, and it holds the model change that caused it. **The Git host does not.** All it has is the files, so its web diff renders all three the same way: as text. That distinction is what makes such a change reviewable. It separates the generated output you can take as read from the handful of files that genuinely need a human - and until 5.3 it was lost the moment the review moved to a browser. Reviewing a pull request here is therefore the *same* Change Review screen, pointed at the pull request's changes instead of your working tree. Everything that screen does - element-level model diffs, the deterministic/custom split, [file classifications](https://docs.intentarchitect.com/docs-md/application-development/change-review/file-classifications/file-classifications.md) and severity, requirement traceability - applies unchanged. What this article covers is the pull request layer on top of it: finding pull requests, creating them, the conversation, and the actions that land them. ![Pull requests in the Git tab](images/pull-requests-overview.png) _The Pull Requests list in the Git tab, with a pull request open on its **Conversation** tab._ > [!NOTE] > Pull request support was introduced in Intent Architect 5.3. ## Connecting to a Git host Intent Architect works out which host a repository is on from its remote URL, preferring `origin` where there is more than one - so a repository whose `origin` is on Azure DevOps is treated as an Azure DevOps repository even if it also has a GitHub remote. This happens per repository. A solution can span several, and each one has its own host, credential and pull requests - nothing here is solution-wide. ### Credentials Intent Architect stores no credential of its own. It asks Git for the one your usual credential helper already holds - Git Credential Manager, the macOS Keychain, libsecret - and never prompts for it. **If you already `git push` to the host over HTTPS, you are signed in here with no setup.** Where nothing is stored, the list is replaced by a **Connect** panel asking for a personal access token. The token goes into your operating system's credential manager, so `git push` picks it up too. The panel links to the right token page for the host, and for GitHub and GitLab an ⓘ button lists the exact permissions the token needs - a token created without them will list pull requests and then fail on the first thing you click. > [!WARNING] > **Sign out** on a repository's row erases the credential from that shared store, so `git push` will ask for it again as well. This is why it is a two-step confirm rather than a single click. A read-only token still lists pull requests and shows their diffs. Submitting a review and merging need write access, and are withheld without it. ### Differences between hosts The screens are the same whichever host you are on. A handful of capabilities are not: | Capability | Notes | | --------------------------------- | ------------------------------------------------------------------------------------------------------ | | **Assignees** | Azure DevOps and Bitbucket Cloud have none, so **Assigned to you** shows pull requests awaiting your review instead - the list says so when it does. | | **Reopening a closed pull request** | Not supported on Bitbucket Cloud, so the action is absent there rather than offered and failing. | | **Checks** | Shown where the host reports them. On GitHub a fine-grained token needs `Commit statuses: Read-only` for them to appear. | ## The Pull Requests list The list lives in the **Pull Requests** section of the **Git** tab, above Branches / Tags. Each repository in the solution gets its own row, showing which host it is on, the account you are signed in as, and its own sign-in and sign-out actions. Under each repository, pull requests are bucketed: | Bucket | Contents | | ------------------ | ------------------------------------------------------------------------------ | | **On this branch** | The open pull request raised _from_ the branch you currently have checked out | | **Open** | Everything else that is open | | **Merged** | Merged pull requests, when the filter asks for them | | **Closed** | Closed-without-merging pull requests, when the filter asks for them | **A bucket only appears when it has something in it.** In particular, **On this branch** is absent unless the branch you are standing on has an open pull request raised from it - which is the usual reason not to see it. It is a shortcut to that one pull request rather than a state of its own, so a pull request listed there is not repeated under **Open**. Each row shows the pull request's state as a coloured icon - open, draft, merged or closed - along with its title, number, author, and when the host last saw activity on it. A repository with nothing to list keeps its row and says why - not on a supported host, not signed in, an error, or genuinely no pull requests. While its list is still loading the row stays blank, rather than claiming there is nothing there. ### Filtering The filter button offers two things at once. The **Filter** rows are presets: - Open pull requests (the default) - Your pull requests - Assigned to you - Review requested from you The **Show** checkboxes below then widen the states on screen - open, merged, closed - independently. Presets are applied by the host as it builds the list, rather than by Intent Architect afterwards. Hosts return only their most recently updated pull requests, so filtering after the fact would quietly miss older ones. ![Pull requests filter](images/pull-request-filter.png) ## Creating a pull request **New pull request** (the `+` in the section header) opens a page of its own for the solution repository's checked-out branch. A branch row's context menu is the way in from any other branch, or from another repository. The page opens fully populated - the branch, a suggested title, a suggested base, and whether the branch still needs pushing - so the button can say **Push and create** up front, rather than an unpushed branch turning into a rejection after you have written a description. | Part of the form | What it does | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **base ← compare** | Two ref pickers, each with a **Branches** and a **Tags** tab. The repository's default branch carries a `default` badge. Changing either re-counts the range. | | **Counts strip** | `N commits · M files changed`, with additions and deletions. **Open Change Review** follows the same range through to what actually changed. | | **Title** | Pre-filled with the branch's suggested title. | | **Description** | Markdown, with **Write** and **Preview** tabs. Preview renders through Intent Architect's document viewer, so a Mermaid diagram is a diagram here rather than a code fence. | | **Draft** | Creates the pull request as a draft. | | **Push and create** | Reads **Create pull request** when the branch is already up on the remote. Pushing is never silent, and never done when it isn't needed. | ![New Pull Requests](images/pull-request-new.png) Two things the form deliberately does not treat as errors: - **A pull request is already open from this branch.** The form tells you, and offers to open that one instead. It does not stop you - raising a second pull request from the same branch is still allowed if that is what you want. - **The counts could not be read.** A message appears where the counts would have been, and creating carries on as normal - the counts are informational only. Tags are offered in both pickers because comparing a branch against a release tag is worth doing, and the counts and **Open Change Review** both handle one. You cannot *create* a pull request from or onto a tag - hosts only accept branches - so the form says so beside the picker. ### Drafting the description with AI The ✨ control beside the description box offers two summaries of the range: | Option | What you get | | ----------------- | -------------------------------------------------------------------------------------- | | **Quick summary** | The gist in markdown, at most three short sections. Returns quickly. | | **Rich write-up** | A full sectioned write-up with Mermaid diagrams. Thinks first, so it takes longer. | It replaces the description, and the title as well - unless you have edited the title yourself, in which case yours is kept. The button reads **Cancel** for as long as a request is in flight, and cancelling aborts the model call rather than merely discarding its answer. ## Reviewing a pull request Selecting a pull request opens it in the Git tab's right-hand pane; it can also be opened as a standalone Change Review tab. Either way the screen has two sub-tabs: - **Conversation** - the pull request itself: its description, its comments, and every action that manages it. - **Change Review** - the changes, as [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md) renders them. ### What gets reviewed The review covers the pull request's own changes, measured from the point where its branch left the base branch. Anything else that has landed on the base branch since it was raised is left out. You do not need the branch locally, so pull requests **from forks** work, and so do **merged** ones - including where the source branch has since been deleted. Occasionally a merged pull request's changes can no longer be separated from the base branch's, usually after a squash or a fast-forward merge; you then get a short notice saying so, rather than an empty review. ### The head card Pinned to the top of the **Conversation** tab, and the one thing that stays on screen while you read: | Row | Contents | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Title row** | Number and title, then badges: checks, draft, the review decision, the pull request's state, and whether it can be merged. A link out to the host sits at the far end. | | **Meta row** | Author, `head → base`, commit and file counts, and `n of m files reviewed` - how much of it *you* have ticked off, on this machine. | | **Status line** | One line showing the most important thing right now: failed checks, a re-check in progress, why merging is blocked, or confirmation that it merges cleanly. | | **Action row** | The AI control, **Update branch** / **Resolve conflicts**, **Merge**, and **Close** or **Reopen**. | ![Pull Request header](images/pull-request-header.png) Below it the description renders through the document viewer, then the timeline, then the composer. ### The timeline Every review that said something, interleaved with every comment thread, oldest first - the order a conversation is read. A file-anchored thread appears here *and* in its diff row on the **Change Review** tab, so a reply or a resolve on either surface is immediately true on both. Comments you have staged but not yet submitted appear in the same sequence, marked **Pending**. A review being written should read in the same order as one that has been submitted. ## Comment threads A thread can be anchored to a **line in a file**, or to a **model element**. Commenting on a model element posts an ordinary pull request comment - anchored to that element's entry in the designer metadata - so it appears on the host alongside every other comment, and anyone reading the pull request in a browser sees it. Inside Intent Architect it is shown against the element itself, named as the element rather than as a line of a metadata file. The reverse works too: a comment left on a generated line of code carries a chip naming the model element that produced it, and clicking the chip opens that element in its designer. ![Pull Request line comment](images/pull-request-line-comment.png) Other behaviour worth knowing: - Unresolved threads arrive expanded and resolved ones collapsed. Anything you toggle away from that keeps your choice across a reload. - Posted comments and their resolved state always come from the host, and are re-read after every change - so what you see here is what everyone else sees. - A thread the diff cannot place on a line - because it has none, or the file has changed too much - is collected into a panel below the diff rather than quietly disappearing. ## The pending review Comments can be **posted immediately**, or **staged into a pending review** and sent together with a verdict. Staging is what the composer's **Start a review** does, and every comment after that stages by default. The composer sits at the bottom of the Conversation tab and is always open on an open pull request. Its verdict is one of: | Verdict | Notes | | ------------------- | -------------------------------------------------------------------------------------------- | | **Comment** | The default. May be submitted with no body when there are staged comments - those are the review. | | **Approve** | May be submitted with nothing written. | | **Request changes** | Always needs its reason in words. | Approve and Request changes are withheld on your own pull request, because every host refuses them there. Submitting sends every staged comment, the verdict and the body **as one review**, so the pull request's followers get one notification instead of one per comment. Afterwards you are told what became of each staged comment: one the host could not attach to its line is added to the review body instead, and one it rejected stays staged so you can deal with it. ![A pending review](images/pull-requests-pending-review.png) _Findings staged into a pending review - read, edited or dropped before anything reaches the host._ A few properties of the draft: - **It outlives the tab.** The verdict body is autosaved, so closing the tab (or the application) does not lose what you typed. - **It follows the repository, not the folder.** Every clone and worktree of the same repository shares one draft, so a review staged in one is the same review you submit from another. - **It stays on your machine.** Drafts are never committed, and nothing reaches the host until you submit. - **You are told when the pull request moves on.** If new commits have arrived since the comments were staged, the bar says so - the lines they name may no longer be the lines they were written about. **Discard** throws the whole pending review away, behind a confirm. It is the one action here that destroys work nobody else can see. ### Marking files as reviewed In pull request mode each file row also carries a **Mark as reviewed** tick. This is a private, per-file sign-off: it stays on your machine, is tied to the exact version of the file you ticked, and is never sent to the host - so submitting or discarding a review leaves the ticks standing. If the file changes on the branch afterwards, the tick turns **amber** to say your sign-off is out of date. The `n of m files reviewed` figure in the meta row counts these. These sit alongside the customization and custom-file approvals Change Review already manages, which are a different thing entirely: those are committed to the repository and outlive the pull request. See [Approving](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md#approving) for the three kinds side by side. ## Using AI on a pull request There are two AI actions: one reviews the pull request, and one works through the review comments it (or anyone else) left. Both are usually available at once, so rather than two buttons competing for attention, the head card carries one control whose main half is whichever action is the sensible next step - the caret keeps the other a click away. ### Review with AI Runs the same review Change Review uses, over exactly the changes the pull request contains, as an AI Assistant conversation in that pull request's repository. Findings land as **staged comments in your pending review** - not posted one at a time. You read each one on the diff, edit or drop what you disagree with, and submit the whole review yourself with your own verdict. Its summary becomes the review body. Two properties matter more than they look: - **It never says the same thing twice.** Running it again skips anything it has already raised, including findings on threads that have since been resolved. Repeated reviews turning into a pile of duplicate comments is the reason this kind of feature usually gets switched off. - **It remembers what it has already reviewed.** Asking for a review of a commit it has already read prompts you to confirm first, rather than quietly reviewing it twice. It is offered on your own pull request too. Hosts accept comments from the author, and reviewing your own work before asking someone else to is the most useful moment for it. ### Address review with AI Works through the pull request's unresolved comment threads, makes its changes in a **separate working copy** rather than yours, commits them, and drafts a reply on each thread. It never pushes. Pushing is the row below, which you click. That row appears whenever the run has commits the pull request's branch does not, and offers: - **Review changes** - a Change Review of what the run did, so you read it before it goes anywhere. - **Push** - puts the commits onto the pull request's branch. - **Fetch & rebase** - when the branch has moved on underneath it. A rebase that hits conflicts pauses rather than failing, and is finished from that conversation's own Source Control panel. The link between a pull request and its fix conversation is remembered, so the row survives closing the tab or restarting Intent Architect. > [!TIP] > AI agents reach the same conversation through the `get_pull_request_review_threads` and `stage_pull_request_review` tools, which let a run read outstanding threads and answer them without a separate `gh` / `glab` / `az` install. See [Agent Tools](https://docs.intentarchitect.com/docs-md/integrated-ai/tooling/index.md) for how tools are made available to an agent. ## Updating the branch and resolving conflicts One button covers both reasons a pull request's branch needs the base branch merged into it, and names the reason you are actually looking at: | Label | When | | -------------------------------- | -------------------------------------------------------------------------------------------------------- | | **Resolve conflicts** | The host reports the pull request as conflicting. Resolving is the primary action - **Merge** is not even on offer until it is done. | | **Update branch from ``** | It merges cleanly but is behind the base, and a branch-protection rule wants it current first. | Both do the merge in a **temporary copy of the repository**, not in your own working folder - which is never switched, never dirtied, and does not have to be clean first. The copy is discarded afterwards. ![Resolving conflicts](images/pull-requests-resolve-conflicts.png) _Conflicts resolved away from your own working folder, with the AI Assistant working through them file by file._ The resolution panel opens under the head card and lists each conflicted file. Files appear in the review as they are resolved, so what you are about to commit is readable as you go. Resolution is per file - by hand, or handed to AI - and **Commit merge** is the only thing that reaches the remote. **Discard** throws the whole session away. A resolution in progress survives closing the tab, and closing Intent Architect - reopening the pull request picks it back up. Two cases where the action is withheld: - **A fork.** Updating pushes the pull request's branch, which lives in the contributor's repository rather than yours. Rather than offer a button that would fail, the card explains whose side the fix is on. - **While a resolution is already in progress.** It is the same action, already running. > [!NOTE] > Pushing a merge is not what makes a host decide a pull request is mergeable - hosts work that out a few seconds later, in their own time. Intent Architect keeps checking for about a minute afterwards, so **Merge** appears on its own rather than needing the tab reloaded by hand. ## Merging, closing and reopening **Merge** opens a form rather than a yes/no confirm, because the commit it produces is what the base branch's history keeps: - The **merge methods** offered are the ones the repository allows - create a merge commit, squash and merge, rebase and merge. Whichever you pick becomes your default for next time. - **Commit title** and **extended description** are prefilled with the host's own defaults for the chosen method, and switching method to compare the two never overwrites text you have typed. Rebase has no message to set, so those fields are hidden for it. **Close pull request** takes an optional comment. The comment is posted *first*, and the pull request is only closed if it landed - a rejection whose stated reason silently vanished is worse than one that never happened, and the second is something you can simply retry. **Reopen** takes its place on a closed pull request, and is the one action here that happens on the press rather than opening a confirm. Nothing needs guarding: Close is the next button along and puts it straight back. No host will reopen a *merged* pull request, so it is never offered on one. All three update the Pull Requests list straight away, so a pull request you just merged or closed stops being listed as open. ## Where things are stored | Thing | Where | Committed? | | ---------------------------------------------- | ------------------------------------------------------------------ | ---------- | | Host credential | Your operating system's credential manager | No | | Pending review (staged comments, verdict body) | `%AppData%/Intent Architect/pr-reviews`, per repository and pull request | No | | Per-file **Mark as reviewed** ticks | Beside the pending review, same folder | No | | Posted comments, reviews, resolved state | The Git host | n/a | | Customization and custom-file approvals | The application's deviations log | **Yes** | ## Related articles - [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md) - the review screen a pull request opens into, and everything it does with model changes, classifications and traceability. - [File Classifications](https://docs.intentarchitect.com/docs-md/application-development/change-review/file-classifications/file-classifications.md) - the classification and severity labels the review filters on. - [Understanding and resolving merge conflicts involving Intent Architect metadata files](https://docs.intentarchitect.com/docs-md/application-development/for-developers/understanding-and-resolving-merge-conflicts/understanding-and-resolving-merge-conflicts.md) - resolving conflicts in designer metadata. - [Git and SCM guidance](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/git-and-scm-guidance/git-and-scm-guidance.md) - which Intent Architect files belong in source control in the first place. - [AI Assistant](https://docs.intentarchitect.com/docs-md/application-development/ai-assistant/index.md) - the AI Assistant that runs the review and fix conversations. --- # About Code-Management Simply put, code-management is a system that allows us to tell Intent Architect which parts of the codebase are managed by Intent Architect and which parts are managed by the developers, at a very granular level of control. It's the system that ensures practicality, and that we never need to convolute or distort our codebase in order to accommodate this code-automation tool. In this sense, code-management is a unique approach to code-automation that completely avoids the typical compromises. It utilizes abstract syntax tree parsing and intelligent algorithms to merge user-written code with automatically generated code. Code-management allows developers to control the automation systems of Intent Architect from a high-level all the way to the granular level of the members within each managed code file (e.g. classes, fields, functions, methods, etc.). As a simple example, a developer may configure a C#, Java, Kotlin or TypeScript file such that they are managing the implementation of one method within a class, while Intent Architect will manage the remaining methods. They may then choose to change this configuration, perhaps deciding to take over management of the entire class or just one of the other methods. The configuration of each file is completely controlled by the developer, ensuring that the automation system never gets in the way. ![Code-Management Example](images/code-management-example-dark.png) _An example of how Intent Architect would change a C# `Startup.cs` file that is partially managed by the developer and partially by Intent Architect. Changes are staged before being accepted by the developer, allowing them to diff the changes before accepting or rejecting them._ --- # C# Code Management This article explains how to control [Code Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) (Code Weaving) behaviour for C# files when "RoslynWeaver" (the C# code management extension of the `Intent.OutputManager.RoslynWeaver` Module) is used. ## Overview of how it works The RoslynWeaver parses C# files into an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) and applies code management logic on a node-by-node basis. An individual node on the _abstract syntax tree_ is referred to as a _syntax node_. _Syntax nodes_ may have one or more children which are also _syntax nodes_. ![Example of an abstract syntax tree of a C# file](images/syntax-tree-example.png) _Example of an abstract syntax tree of a C# file._ The RoslynWeaver compares the generated content from the template with the existing file (if there is one) on a node-by-node basis. Instructions in the form of C# attributes are used by the RoslynWeaver for it to determine for a particular _syntax node_ what content it should ignore, replace with content generated by the template or perhaps remove entirely. ## Code management instructions Instructing the RoslynWeaver on how to treat particular _syntax nodes_ is done using code management instructions in your source code, such as e.g. `[IntentManaged(Mode.Ignore)]`, `[IntentIgnore]`. ### Management modes Both the `[IntentManaged(…)]` and `[DefaultIntentManaged(…)]` attributes have a mandatory parameter which accepts a `Mode` enum value of one of the following: | Enum Value | Description | |------------|-------------| | `Fully` | Intent has **full** control over the particular _syntax node_, any deviations in the existing file's _syntax node_ are overwritten with the content generated by the template. Descendant _syntax nodes_ can be opted-out of being fully managed having an `[IntentManaged(…)]` attribute applied to them. | | `Merge` | Intent will add and remove Intent generated code for the _the syntax node_ but will never remove code which was manually added¹. | | `Ignore` | Intent must **ignore** this _syntax node_ and not remove or overwrite it with content generated by the template. Code management instructions on descendant _syntax nodes_ are likewise ignored, i.e. it is not possible to opt-out of being ignored as a descendant. | [1] Prior to version 4.7.0 of the `Intent.OutputManager.RoslynWeaver` module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode. ### The `[DefaultIntentManaged(…)]` attribute The `[DefaultIntentManaged(…)]` attribute can be used to set the "default" (or "fallback") management mode for _syntax nodes_ which otherwise have no code management instructions of their own. A `[DefaultIntentManaged()]` assembly attribute without additional options must be specified at least once at the top of the file and can also be applied any additional number of times with any combination of additional options either as assembly attributes at the top of the file or to _type declaration syntax nodes_ (classes, enums, interfaces, etc), for example: ```csharp [assembly: DefaultIntentManaged(Mode.Fully)] [assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)] [DefaultIntentManaged(Mode.Fully, Body = Mode.Ignore, Targets = Targets.Constructors | Targets.Methods)] public class Class { } ``` #### DefaultIntentManaged `Targets` property The `[DefaultIntentManaged(…)]` attribute's `Targets` property can be set to have a value of one or more `Targets` enum flags to specify that the instruction should only "target" particular _syntax node_ types. To specify multiple target _syntax node_ types, use the [bitwise logical OR operator `|`](https://learn.microsoft.com/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#logical-or-operator-), e.g. `Targets = Targets.Constructors | Targets.Methods` will make the instruction apply to both _Constructors_ and _Methods_. The following `Targets` are available: - `Classes` - `Constructors` - `Delegates` - `EnumMembers` - `Enums` - `Fields` - `Interfaces` - `Methods` - `Namespaces` - `OperatorConversions` - `Operators` - `Properties` - `Records` - `Structs` - `TopLevelStatements` - `Usings` ### The `[IntentManaged(…)]` attribute _Type declaration syntax nodes_ (e.g. classes, enums, interfaces, etc) and _type member declaration syntax nodes_ (e.g. fields, methods, properties, etc) can have an `[IntentManaged(…)]` attribute applied to them to control their code management behaviour. #### `[IntentManaged(…)]` when applied to type **member** declaration syntax nodes The `[IntentManaged(…)]` attribute can be applied to _type member declaration syntax nodes_ (e.g. fields, methods, properties, etc) to control the behaviour of that member in particular, for example to have the code management ignore a method which you made manual changes to, you can add an `[IntentManaged(Mode.Ignore)]` attribute to it like so: ```csharp [IntentManaged(Mode.Ignore)] public void ChangeCountry(string country) { throw new NotImplementedException(); } ``` When the RoslynWeaver sees this, it will know not to modify (or remove) this method in any way during code merging. #### `[IntentManaged(…)]` when applied to **type** declaration syntax nodes The `[IntentManaged(…)]` attribute can be applied to _type declaration syntax nodes_ (e.g. classes, enums, interfaces, etc) to control the following: - **Signature:** Controls access modifiers, generic type parameters and what is being derived from and/or implemented. - **Body:** Controls whether or not members (e.g. fields, methods, properties, etc) of the class should be added/removed (it does not control the _content_ of the members): - **Fully:** Members are added to/removed from the existing file to ensure they match the template output. - **Merge:** Any members from the template output which are missing from the existing file will be added, additional members which were previously added by Intent will be removed¹ and any additional members which where manually user added will not be removed. - **Ignore:** No members will be added to or removed from the existing file. - **Comments:** Controls comments of the _type declaration_, falls back to `Signature` behaviour if unspecified. - **Attributes:** Controls attributes of the _type declaration_, falls back to `Signature` behaviour if unspecified. When a _type declaration syntax node_ has `[IntentManaged(Mode.Ignored)]` applied to it, descendant nodes (i.e. members, such as fields, properties, etc.) all become ignored and will stop being updated or removed. Additionally, code management instructions on descendant nodes are also ignored and disregarded meaning it is not possible to "opt-out" of being ignored as a descendant. ¹ Prior to version 4.7.0 of the `Intent.OutputManager.RoslynWeaver` module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode. ### The `[IntentInitialGen]` attribute This instruction is useful for having a syntax node which can be initially generated by a template, and then changed or even removed by users without the Software Factory trying to update or put the it back into the file. When `[IntentInitialGen]` is on a syntax node in a template, it is generated during the initial generation (creation) of a file and is then essentially treated as "Ignored" on subsequent generations. Furthermore, if the syntax node is deleted then it won't be re-generated on subsequent updates to the file. During initial generation of a file, the instruction is removed from syntax node so this instruction will never be visible except to template authors. ### Code management attribute properties The `[IntentManaged(…)]` and `[DefaultIntentManaged(…)]` attributes have additional properties which can provide finer grained control of code management for a particular _syntax node_. By default each of these properties has the same `Mode` as the default parameter of the attribute, so: ```csharp [IntentManaged(Mode.Fully, Body = Mode.Fully, Signature = Mode.Fully, Comments = Mode.Fully, Attributes = Mode.Fully)] ``` is equivalent to: ```csharp [IntentManaged(Mode.Fully)] ``` The following table documents the available attribute properties and how the RoslynWeaver interprets them when applied to different _syntax node_ types: | _Syntax node_ type | Attribute property | Description | |---------------------|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | (All) | Attributes | By default the `Signature` parameter determines this parameter's setting but this parameter instructs the RoslynWeaver to treat the _syntax node_'s Attribute differently. | | (All) | Comments | By default the `Signature` parameter determines this parameter's setting but this parameter instructs the RoslynWeaver to treat the _syntax node_'s Comments differently. | | Class | Signature | Instructs the RoslynWeaver to treat the definition of a Class (class name, inheritance, etc.) differently to the default parameter setting. | | Class | Body | Instructs the RoslynWeaver to treat the members of a Class (methods, properties, etc.) differently to the default parameter setting. | | Constructor, Method | Signature | Instructs the RoslynWeaver to treat the definition of a Method (method name, parameters, return type, etc.) differently to the default parameter setting. | | Constructor, Method | Body | Instructs the RoslynWeaver to treat the implementation part of a method (where the code goes) differently to the default parameter setting. | | Field, Property | Signature | Instructs the RoslynWeaver to treat the definition of a Field (field name, type, etc.) differently to the default parameter setting. | | Field, Property | Body | Instructs the RoslynWeaver to treat the value differently to the default parameter setting. | ### Shorthand attributes > [!NOTE] > Shorthand attributes were introduced in version `4.2.0` of the `Intent.OutputManager.RoslynWeaver` module, ensure you have at least this version of the module installed for them to be able to work. In situations where you are using a simple `[IntentManaged(Mode.)]` attribute, you can use any of the following attributes: - `[IntentFully]` (equivalent to `[IntentManaged(Mode.Fully)]`) - `[IntentIgnore]` (equivalent to `[IntentManaged(Mode.Ignore)]`) - `[IntentMerge]` (equivalent to `[IntentManaged(Mode.Merge)]`) Additionally, the following attributes can be used to override particular [code management properties](#code-management-attribute-properties): - `[IntentFullyAttributes]` - `[IntentFullyBody]` - `[IntentFullyComments]` - `[IntentFullySignature]` - `[IntentIgnoreAttributes]` - `[IntentIgnoreBody]` - `[IntentIgnoreComments]` - `[IntentIgnoreSignature]` - `[IntentMergeAttributes]` - `[IntentMergeBody]` - `[IntentMergeComments]` - `[IntentMergeSignature]` These attributes can also be combined, for example to have a method have its signature fully managed, but the rest of it ignored, you can combine them as follows: ```csharp [IntentIgnore] [IntentFullySignature] public void ChangeCountry(string country) { throw new NotImplementedException(); } ``` You can also combine the attributes into a single list, the following code is functionally identical to the previous example: ```csharp [IntentIgnore, IntentFullySignature] public void ChangeCountry(string country) { throw new NotImplementedException(); } ``` ### "Tag Mode" attributes > [!NOTE] > File level "Tag Mode" attributes were introduced in version `4.2.0` of the `Intent.OutputManager.RoslynWeaver` module, ensure you have at least this version of the module installed for them to be able to work. These attributes manage the "Tag Mode" for the current file. #### Explicit Tag Mode - `[assembly: IntentTagMode(TagMode.Explicit)]` or - `[assembly: IntentTagModeExplicit]` The RoslynWeaver will only look at the existing file for code management attributes except in the case where the generated template output will add new _syntax nodes_ to the existing file. #### Implicit Tag Mode - `[assembly: IntentTagMode(TagMode.Implicit)]` or - `[assembly: IntentTagModeImplicit]` When a _syntax node_ has no code management attribute of its own, the RoslynWeaver will attempt to find the corresponding _syntax_ node in the template generated content and use its code management attribute instructions. If the RoslynWeaver sees that the existing file's _syntax node's_ code management attribute instructions are identical to that of the _syntax node_ in the generated template output, it will remove it from existing file. This mode is useful if you want to keep the amount of code management attributes in your files to an absolute minimum. > [!TIP] > If you would like to make "implicit tag mode" the default for all files, this can be done with the [tag mode application setting](#tag-mode). ### Block statement code management behaviour Statements within code block _syntax nodes_ (e.g. method body, constructor body, delegate body, etc) can also support certain code management capabilities. > [!NOTE] > Support for management of statements was added in version `4.0.0` of the `Intent.OutputManager.RoslynWeaver` module, ensure you have at least this version of the module installed for them to be able to work. #### Fully mode You can add your own statements to a code block by adding a comment above with a code management instruction, for example: ```csharp // Template generated content: [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; } // Content in your file, added after initial generation: [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; // IntentIgnore var myVariable = "myVariable"; } ``` With the above, even though the body of the method is "fully" managed, the `var myVariable = "myVariable";` will not be removed due to having `// IntentIgnore` above it. This also works for statements which have statement blocks, with the above example you could have alternatively added an `if` statement: ```csharp [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; // IntentIgnore if (SomeCondition) { var myStatement1 = "myStatement1"; var myStatement2 = "myStatement2"; } } ``` #### Merge mode Inline with merge behaviour of other _syntax nodes_, the RoslynWeaver will add any statements which are on the template and missing from your file, it will remove any statements which are no longer being generated by the template and it will always leave code manually added by a user: ```csharp // Template generated content: [IntentManaged(Mode.Fully)] public void Method() { if (_flag) { // Do something } var variable1 = "variable1"; } // Content in your file (will not be changed by the software factory): [IntentManaged(Mode.Fully)] public void Method() { if (_flag) { // Do something } var variable1 = "variable1"; var myVariable = "myVariable"; } ``` If at a later time, the template content changes to add and remove statements, then previously generated statements will be removed and new ones will be added, all without touching the statements which were manually added: ```csharp // Template generated content: [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; for (var i = 1; i < 10; i++) { _counter++; } } // Content in your file before running the software factory: [IntentManaged(Mode.Fully)] public void Method() { if (_flag) { // Do something } var variable1 = "variable1"; var myVariable = "myVariable"; } // Content in your file after running the software factory: [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; for (var i = 1; i < 10; i++) { _counter++; } var myVariable = "myVariable"; } ``` > [!NOTE] > Prior to version 4.7.0 of the `Intent.OutputManager.RoslynWeaver` module, it was not possible for it to identify what was previously generated by Intent so it would never delete anything when in merge mode. ##### Updating variable values Block statement merge mode also allows you to update variable values: ```csharp // Template generated content: [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "variable1"; } // Content in your file (will not be updated by the software factory): [IntentManaged(Mode.Fully)] public void Method() { var variable1 = "my alternative value"; } ``` ##### Updating other kinds statements For other kinds of statements that you would like to update, you can use the `// [IntentFully(Match = "…")]` or `// [IntentIgnore(Match = "…")]` comments to specify how Intent Architect should know which statement to replace: ```csharp // Template generated content: [IntentManaged(Mode.Merge)] public void Method() { SomeOtherMethod(argument); } // Content in your file (will not be updated by the software factory): [IntentManaged(Mode.Merge)] public void Method() { // [IntentIgnore(Match = "SomeOtherMethod")] SomeOtherMethod(argument, additionalArgument); } ``` In the above example, the `SomeOtherMethod` value for the commented out attribute lets Intent know to correlate this statement in your existing file with the first statement in the template output starting with that string. Template authors can also add this line to their templates which lets Intent know how to try correlate the template expression with that in the existing file. When `// [IntentFully(Match = "…")]` only exists on the template, it is not included in the template output. ##### Nested block statements As with [fully mode](#fully-mode), if your statement was an if statement with a block statement, that would be retained too. #### The `// IntentInitialGen` instruction This instruction is useful for having a statement which can be initially generated by a template, and then changed or even removed by users without the Software Factory trying to update or put the it back into the file. When `[IntentInitialGen]` is on a statement in a template, it is generated during the initial generation (creation) of a file and is then essentially treated as "Ignored" on subsequent generations. Furthermore, if the statement is deleted then it won't be re-generated on subsequent updates to the file. During initial generation of a file, the instruction is removed from statement so this instruction will never be visible except to template authors. Consider the below template content: ```csharp [IntentFully, IntentMergeBody] public int DoSomeCalculationFor(Guid id) { // IntentInitialGen throw new NotImplementedException(); } ``` When the method is initially generated it inserts the the `throw new NotImplementedException();` statement but without the code management instruction: ```csharp [IntentFully, IntentMergeBody] public int DoSomeCalculationFor(Guid id) { throw new NotImplementedException(); } ``` If the user deletes the statement while adding their own logic, Intent Architect will not try to bring it back, for example: ```csharp [IntentFully, IntentMergeBody] public int DoSomeCalculationFor(Guid id) { return _calculationService.Calculate(id); } ``` #### Method chains In both fully and merge mode of a block statement, you can add to a chain to a method chain using `// IntentIgnore` above it, for example: ```csharp void Method() { Member .GeneratedChain1() // IntentIgnore .ManuallyAddedChain() .GeneratedChain2(); } ``` ## Module Settings ![RoslynWeaver Settings](images/roslynweaver-settings.png) The `Intent.OutputManager.RoslynWeaver` Module has the following settings which will instruct Intent Architect to behave in certain ways: ### Usings Sorting This will instruct Intent Architect to order the `using directives` located above or within a `namespace` scope within a C# file. | Option | Description | |-----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------| | None | The order of the using directives will remain unchanged. | | Alphabetical | The using directives will be sorted alphabetically in ascending order. | | Alphabetical, place 'System' directives first | The using directives will be sorted alphabetically except it will give first priority to `System` based using directives. | ### Tag Mode _Tag Mode_ can be used to control inclusion of `[IntentManaged(…)]` and `[DefaultIntentManaged(…)]` attributes in the output file. | Option | Description | |---------------|-------------| | Explicit | The RoslynWeaver will only look at the existing file for `[IntentManaged(…)]` attributes except in the case where the generated template output will add new _syntax nodes_ to the existing file. | | Implicit | When a _syntax node_ has no `[IntentManaged(…)]` attribute of its own, the RoslynWeaver will attempt to find the corresponding _syntax_ node in the template generated content and use its `[IntentManaged(…)]` attribute instructions. If the RoslynWeaver sees that the existing file's _syntax node's_ `[IntentManaged(…)]` is identical to that of the _syntax node_ in the generated template output, it will remove it from existing file. This mode is useful if you want to keep the amount of `[IntentManaged(…)]` attributes in your files to an absolute minimum. | | Template Only | Like with _Implicit_ mode, when a _syntax node_ has no `[IntentManaged(…)]` attribute of its own, the RoslynWeaver will attempt to find the corresponding _syntax_ node in the template generated content and use its `[IntentManaged(…)]` attribute instructions. After running the code management logic, **RoslynWeaver will remove all `[IntentManaged(…)]` and `[DefaultIntentManaged(…)]` attributes from the file**. This mode is useful if you want absolutely no `[IntentManaged(…)]` or `[IntentDefaultManaged(…)]` attributes in your code base. **WARNING: This mode removes all code management instructions from your code files, ensure you have a backup of your files (or have a commit in your source control management) in case you wish to rollback this change.** | ### Usings Placement This will instruct Intent Architect where to place the `using` directives within a C# file. | Option | Description | |--------------------------|------------------------------------------------------------------------| | Default | All using directives will be placed at the top of the C# file. | | Move to inside namespace | All using directives will be placed within the scope of a `namespace`. | ## Code Management in C# Project files Intent Architect automatically manages NuGet packages in `.csproj` files. Each module declares the packages its generated code depends on along with a minimum required version, and the Software Factory ensures the referenced version is always **at least** that version, upgrading the reference where it is lower. It will never downgrade a package, and packages which you added to the csproj file yourself are left alone. There may be some use cases where Intent Architect will want to alter the referenced packages (such as `removing` unused references, or `upgrading` a package to the minimum required version), but you would like Intent Architect to ignore these changes. The `IntentIgnore` attribute can be used in the csproj file to instruct Intent Architect to ignore the package reference. In the below snippet from a csproj file, the `IntentIgnore` attribute will prevent Intent Architect from removing or updating the version number of the `Microsoft.EntityFrameworkCore` NuGet package: ``` xml ``` > [!WARNING] > An ignored package reference is excluded from the minimum version enforcement described above, so a module which requires a later version of that package will no longer be able to upgrade it. If you pin a version below what an installed module requires, the code it generates may not compile. ## Frequently asked questions ### How can I disable the RoslynWeaver from formatting my C# files? By default the RoslynWeaver will automatically format files under [code management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md). If this is undesired you can disable this behaviour by using the `.WithAutoFormatting(...)` extension method with the first parameter set to `false` in the `DefineFileConfig` method of your template: ```csharp protected override CSharpFileConfig DefineFileConfig() { return new CSharpFileConfig( className: "MyClass", @namespace: OutputTarget.GetNamespace()) .WithAutoFormatting(false); } ``` ### Why isn't the RoslynWeaver removing extraneous `using directives`? By default RoslynWeaver uses `Merge` as the management mode for `using directives`, where it will never remove nodes unless they were previously generated by the software factory but aren't any more. This can be overridden by adding an `[assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)]` in your file, typically just beneath the existing `[DefaultIntentManaged(…)]` attribute, for example: ```csharp [assembly: DefaultIntentManaged(Mode.Fully)] [assembly: DefaultIntentManaged(Mode.Fully, Targets = Targets.Usings)] ``` ### Why are are _class_ members (such as _methods_, _properties_, _fields_, etc) being overwritten by Intent even though the _class_ has `[IntentManaged(Mode.Merge)]` / `[IntentManaged(Mode.Fully, Body = Mode.Merge)]` on it? Generally, the `IntentManaged` attribute [does not effect descendant _syntax nodes_](#intentmanaged-when-applied-to-type-declaration-syntax-nodes) (an exception being `[IntentManaged(Mode.Ignore)]`), it only applies to the _syntax node_ it's directly applied to (the class in this case), allowing you to "opt-out" of being fully managed. When you do not specify the `Body` mode specifically for an `IntentManaged` attribute, then the all its properties (including `Body`) use the default constructor's mode. For _type declarations_ (e.g. _class_, _interface_, _enum_, _record_, etc), the `Body` mode is used to control whether or not members (i.e. methods, fields, properties, etc) are added/removed, this is what Intent Architect will do under the following `Body` modes: - **Fully:** Members are added to/removed from the existing file to ensure they match the template output. - **Merge:** Any members from the template output which are missing from the existing file will be added, additional members which were previously added by Intent will be removed and any additional members which where manually user added will not be removed. - **Ignore:** No members will be added to or removed from the existing file. ### How does Intent know what was previously generated and is now no longer being generated? After pressing Apply in the software factory, for all files managed by the software factory (except for files which are unchecked in the Changes view), the Software Factory will save the template output (i.e. the "pre-merge" output) for each file in the `.intent/previous_output` of the current Intent Architect application. ### Why didn't Intent automatically delete something which was no longer being generated even though it was in merge mode? The `.intent` folder which [contains the previous template output](#how-does-intent-know-what-was-previously-generated-and-is-now-no-longer-being-generated) is [not committed to source control](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/git-and-scm-guidance/git-and-scm-guidance.md) so as to prevent these files causing noise in your SCM logs and possible pull requests. This means that if you have a fresh checkout of your source code, or for some reason you haven't applied any software changes on your machine prior to making an update which should delete something, the previous output may be missing or out of date (for example to due it last being run by a different user on a different machine), so it may not be aware of some things which were actually generated by Intent. --- # Java Code Management This article explains how to control [Code Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) (Code Weaving) behaviour for Java files. The following table describes the various instructions that may be used to control which parts of the codebase Intent Architect manages, and which parts are managed by the developers, at a very fine-grained level of control. | Instruction | Description | |----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `@IntentIgnore` | Instructs Intent Architect to ignore this declaration (i.e. do not update or remove it). | | `@IntentIgnoreBody` | Instructs Intent Architect to ignore the body of the declaration, but allows it to manage the signature. This instruction is typically applied over business-logic placeholder methods throughout the codebase. | | `@IntentMerge` | Instructs Intent Architect to manage this declaration, allowing adding, updating, but not removing, of its child members. This instruction is typically used in areas of business logic to ensure that if the developer creates additional methods within a call, that those methods are not deleted. | | `@IntentManage` | Instructs Intent Architect to manage this declaration, allowing adding, updating and removing of its child members. This instruction is useful in cases where Intent Architect is not fully managing a declaration and you would like it to. | | `@IntentManageClass` | Instructs Intent Architect to manage this class, allowing adding, updating and removing of its child members. Additional management options can be supplied to this instruction to instruct Intent Architect how to treat `methods`, `properties` and `constructors`. | > [!NOTE] > Instructions can be applied as `annotations` to any of the following declarations: `class`, `interface`, `constructor`, `method`, or `property`. ## Examples of applying these annotations Decorating an entire class to be managed by the developer would be accomplished by using `@IntentIgnore`: ```java @Service @AllArgsConstructor @IntentIgnore // Intent Architect will not modify this class public class UsersServiceImpl implements UsersService { private UserRepository userRepository; private ModelMapper mapper; @Override @Transactional(readOnly = false) public UUID Create(UserCreateDTO dto) { var user = new User(); userRepository.save(user); return user.getId(); } } ``` A method can be decorated by the `@IntentIgnoreBody` annotation if Intent Architect should manage the method signature but the developer is responsible for the implementation of the method: ```java @Service @AllArgsConstructor public class UsersServiceImpl implements UsersService { private UserRepository userRepository; private ModelMapper mapper; @Override @Transactional(readOnly = false) @IntentIgnoreBody // Intent Architect will not modify the implementation code below public UUID Create(UserCreateDTO dto) { var user = new User(); userRepository.save(user); return user.getId(); } } ``` --- # JSON Code Management This article explains how to control [Code Management / Merging](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) behaviour for `.json` files when using the `Intent.Code.Weaving.Json` module. > [!NOTE] > **For Module Authors**: For JSON weaving to occur and code management instructions to be applied, the JSON template must set `codeGenType` to `JsonMerger`: > ``` csharp > public override ITemplateFileConfig GetTemplateFileConfig() > { > return new TemplateFileConfig( > fileName: "SampleJson", > fileExtension: "json", > codeGenType: CodeGenType.JsonMerger); > } > ``` ## Overview of how it works The merger parses `.json` files into a graph of nodes and recursively applies code management logic on a node-by-node basis. The merger compares the generated content from the template with the existing file (if there is one) on a node-by-node basis. Instructions are used by the merger for it to determine for a particular node what content it should ignore, replace with content generated by the template or perhaps remove entirely. ## Code management instructions Instructing the merger on how to treat particular nodes is done using code management instructions in your source code, within JSON, these are instructions like `// IntentIgnore` above nodes. ## Management modes - **`// IntentFully`** - Intent has full control over the particular node, any deviations in the existing file's node are overwritten with the content generated by the template. Descendant nodes can be opted-out of being fully managed having an `// Intent` instruction applied to them. - **`// IntentMerge`** - Intent will add and remove Intent generated nodes for the node but will never remove nodes which were manually added. - **`// IntentIgnore`** - Intent must ignore this node and not remove or overwrite it with content generated by the template. Code management instructions on descendant nodes are likewise ignored, i.e. it is not possible to opt-out of being ignored as a descendant. ## Default code management behaviour By default, templates are in `Merge` mode. This default can be changed in the [settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) for your application: ![JSON Merger Settings](images/json-merger-settings.png) ## Node matching The merger correlates nodes between existing and generated files by their field names on objects and for array items by their order or scalar value content. ### Matching scalar values in arrays For cases where you want to replace a particular value in an array, you can add `(Match = "")` to the above instructions, e.g. `// IntentIgnore(Match = "SomeValue")`. For example, the following is normally generated by a template: ```json { "names": [ "Alice", "Bob", "Charles" ] } ``` If you wanted to replace `Bob` with a value, you can do the following: ```json { "names": [ "Alice", // IntentIgnore(Match = "Bo") "John", "Charles" ] } ``` As `Bob` in the generated value starts with the string of `Bo` the items correlate. ### Matching objects in arrays by field value For cases where you have an array of objects, you may want to match objects based on field names, this can be done by adding the following instruction above the array: ```json // IntentMatchBy(""[, ""[, ...]]) ``` Where ``, ``, etc are names of fields you the merger to match on. Which instructs the merger that it should match objects when all the specified field names have matching values. For example consider the following generated file: ```json { "customers": [ { "customerNumber": "00001", "name": "Generated Name 1" }, { "customerNumber": "00002", "name": "Generated Name 2" } ] } ``` And you want to override just the generated name to have a different value, so you ignore the field: ```json { "customers": [ { "customerNumber": "00001", // IntentIgnore "name": "Modified name" }, { "customerNumber": "00002", "name": "Generated Name 2" } ] } ``` The above won't work reliably as the merger also needs to be instructed that it should match items by the `customerNumber` field, which can be done as follows: ```json { // IntentMatchBy("customerNumber") "customers": [ { "customerNumber": "00001", // IntentIgnore "name": "Modified name" }, { "customerNumber": "00002", "name": "Generated Name 2" } ] } ``` As stated above, you can match on multiple fields, in the example below both `regionCode` and `customerNumber` fields must be the same for the objects to be considered a match. ```json { // IntentMatchBy("regionCode", "customerNumber") "customers": [ { "regionCode": "01", "customerNumber": "00001", // IntentIgnore "name": "Modified name" }, { "regionCode": "02", "customerNumber": "00001", "name": "Generated name for customer in region code 2" } ] } ``` If a template generates an `// IntentMatchBy` instruction it is not included in the output file but the merger will still respect it. This allows for keeping generated JSON looking clean while still allowing objects to be correlated predictably in arrays. --- # Razor Code Management This article explains how to control [Code Management / Merging](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) behaviour for `.razor` files when using the `Intent.Code.Weaving.Razor` module. ## Overview of how it works The Razor Merger parses `.razor` files into an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) and applies code management logic on a node-by-node basis. An individual node on the _abstract syntax tree_ is referred to as a _syntax node_. _Syntax nodes_ may have one or more children which are also _syntax nodes_. The Razor Merger compares the generated content from the template with the existing file (if there is one) on a node-by-node basis. Instructions are used by the Razor Merger for it to determine for a particular syntax node what content it should ignore, replace with content generated by the template or perhaps remove entirely. ## Code management instructions Instructing the Razor Merger on how to treat particular syntax nodes is done using code management instructions in your source code, within Razor syntax, these are instructions like `@Intent.Ignore` above elements. Within `@codeblock` directives, the Razor Merger is delegating to the RoslynWeaver, please refer to its [article](https://docs.intentarchitect.com/docs-md/application-development/code-management/code-management-csharp/code-management-csharp.md) for information on controlling C# Code management behaviour. ## Management modes - **`@Intent.Fully([""])`** - Intent has full control over the particular syntax node, any deviations in the existing file's syntax node are overwritten with the content generated by the template. Descendant syntax nodes can be opted-out of being fully managed having an `@Intent.` instruction applied to them. - **`@Intent.Merge([""])`** - Intent will add and remove Intent generated code for the syntax node but will never remove code which was manually added. - **`@Intent.Ignore([""])`** - Intent must ignore this syntax node and not remove or overwrite it with content generated by the template. Code management instructions on descendant syntax nodes are likewise ignored, i.e. it is not possible to opt-out of being ignored as a descendant. Each of the above can be suffixed with the following: - **`Body`** - Override only the body mode behaviour of the syntax node, generally body refers to inner syntax nodes or the content of a syntax node. - **`Signature`** - Override only the signature mode behaviour of the syntax node, generally this refers to aspects like HTML element / Directive attributes of a syntax node. Each of the above can optionally have [path](#path-syntax) argument specified which like the [`MoveHere`](#intentmoveherepath) instruction can be used to move the element's location from where it was in the generated content. The following instructions can be used to instruct the code weaver how to manage specific particular attributes on a markup element: - **`@Intent.FullyAttributes("attribute1", ["attribute2", ...])`** - Fully manages the specified attributes. - **`@Intent.MergeAttributes("attribute1", ["attribute2", ...])`** - Separates the value of the attribute by space and will merge them. By default `class` attributes are in merge mode. - **`@Intent.IgnoreAttributes("attribute1", ["attribute2", ...])`** - Ignores the specified attributes. ### Management mode examples In the below, the `class` attribute in the `
` will be ignored, but not the content: ```razor @Intent.Fully() @Intent.IgnoreAttributes("class")
content
``` The next example shows how management modes and their suffixes can be combined. The `
` will be "Fully" controlled, while its body mode is overridden to be ignored, i.e. the Razor Merger should always update the attributes to match that generated by the template, but it should never update its body ("content" in this case). ```razor @Intent.Fully() @Intent.IgnoreBody()
content
``` The following example shows how with some Razor Components, placing Razor expressions within them will cause a compilation error. In such cases the instructions can be placed in Razor comments: ```razor @* @Intent.Fully() *@ @* @Intent.IgnoreBody() *@
content
``` The `@` prefix is optional for instructions inside razor comments so the following is equivalent: ```razor @* Intent.Fully() *@ @* Intent.IgnoreBody() *@
content
``` ### `InitialGen` instructions These instructions are useful for having syntax which can be initially generated by a template, and then changed or even removed by users without the Software Factory trying to update or put the it back into the file. When an `InitialGen` instruction is on a syntax node in a template, it is generated during the initial generation (creation) of a file and is then essentially treated as "Ignored" on subsequent generations. Furthermore, if the syntax node is deleted then it won't be re-generated on subsequent updates to the file. During initial generation of a file, the instruction is removed from syntax node so this instruction will never be visible except to template authors. The following instructions are available and are applied the same way as normal management instructions: - **`@Intent.InitialGen()`** - All aspects of the syntax are ignored after initial generation. - **`@Intent.InitialGenAttributes()`** - Attributes of the syntax are ignored after initial generation. - **`@Intent.InitialGenBody()`** - The body of the syntax is ignored after initial generation. - **`@Intent.InitialGenSignature()`** - The signature of the syntax are ignored after initial generation. ### `Intent.Skip("", ["", "", ...])` Will skip over insertion of template content at the specified [paths](#path-syntax). For example, you have deleted generated content and the code weaver keeps trying to bring it back: ```diff My Page +

Page Heading

+ +
+

Generated content

+

Manually added content.

``` You can add `@attribute [Intent.Skip("h1", "/div[@id='generated']")]` to the top of the file and the code weaver will then know to "skip over" insertion of items generated at those paths: ```razor @attribute [Intent.Skip("h1", "/div[@id='generated']")] My Page

Manually added content.

``` ### `Intent.MoveHere("")` Adding an `Intent.MoveHere("")` instruction allows moving an element from a completely structurally different location in the generated output at the specified [path](#path-syntax). For example, you're wanting to move the `

` element in the example below to the outer scope, but the code weaver keeps trying to add it back to where it was: ```diff @page "/page"

Heading 1

+

Heading 1

Generated content

``` You can add `@Intent.MoveHere("/div[@id='main']/h1")` above the `h1` element to instruct the code weaver that the element has been moved from elsewhere in the generated output: ```razor @page "/page" @Intent.MoveHere("/div[@id='main']/h1")

Heading 1

Generated content

``` ### Path syntax This section discusses the syntax for the path arguments used by [management mode](#management-modes), [Skip](#intentskippath1-path2-path3-) and [MoveHere](#intentmoveherepath) instructions. The path supports a URL like path separated by forward-slashes (`/`), for example, `/div/div/h1` would match the `

` in the following: ```razor

Heading 1

``` The path syntax supports the following very small subset of [XPath](https://developer.mozilla.org/en-US/docs/Web/XML/XPath): #### `attribute` Axis This will match elements with attribute names prefixed with an `@` with the specified value, for example the string `/div/div[@id='additional']` will match the 2nd nested `
` in the following: ```razor
Main content.
Additional content.
``` You can also refer to the MDN documentation on the [`attribute` Axis](https://developer.mozilla.org/en-US/docs/Web/XML/XPath/Reference/Axes#attribute) for more information. #### [`position` Function](https://developer.mozilla.org/en-US/docs/Web/XML/XPath/Reference/Functions/position) This will match the element at the specified 1-based index position, for example the string `/div/div[position()=3]` will match the 3rd div in the following: ```razor
1st
2nd
3rd
``` You can also refer to the MDN documentation on the [`position` Function](https://developer.mozilla.org/en-US/docs/Web/XML/XPath/Reference/Functions/position) for more information. ## Default code management behaviour By default, templates are in `Merge` mode. This default can be changed in the [settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) for your application: ![Razor Merger Settings](images/razor-merger-settings.png) ## Syntax node matching The Razor Merger applies a heuristic to try "match" syntax nodes in your "existing" file with a corresponding syntax node generated by the template it is merging with. ### Matching by identity There are cases where syntax nodes can't be easily differentiated and the Merger's default match may not be correct, this is particularly common with HTML Elements and Directives which appear numerous times within the same parent syntax node or root of the document. In such cases, an "identity" can be assigned to the syntax node which will force the Razor Merger to correlate the Syntax Node only with syntax nodes with a matching identity. An "identity" can be assigned to a syntax node using any of the following ways: - **An `id=""` HTML element attribute** - As this is a default attribute for HTML element it can be a "natural" identifier to use on elements if it's present. - **An `intent-id=""` HTML element attribute** - This takes precedence over the `id` HTML element attribute and is useful for scenarios an HTML element's `id` is not "stable", i.e. if it's expected that the template output's `id` may change based on other factors. - **An `@Intent.Id("")` or `@* @Intent.Id("") *@` or `@* Intent.Id("") *@` instruction above the syntax node** - This takes precedence over both of the HTML element attributes and was created with Directives in mind as trying to apply unknown attributes to them causes a compilation error. ### Matching by other attribute types If a syntax node doesn't have an identifier, a match is performed in order by the following attributes: - `@bind-Value` - `@bind` - `Value` ### Component specific attribute matching configuration Module authors can configure matching behaviour for particular element/component tag names by using the `ConfigureRazorTagMatchingFor` extension method on any instance of `ISoftwareFactoryExecutionContext`. > [!TIP] > > The `IApplication` interface and the `ExecutionContext` property on template base types are two common places which are `ISoftwareFactoryExecutionContext` and this extension method can be used. The extension method takes an `Action` argument which allows fluent style configuration to occur. The following methods are available: #### AllowMatchByDescendant The tag may be matched by a descendant at the specified path. Consider you're trying matching MudGrid in the following existing file: ```razor @if (Model is not null) { } ``` With the one in the following generated file: ```razor ``` We can specify the following in a factory extension in our module: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { application.ConfigureRazorTagMatchingFor("MudGrid", c => c.AllowMatchByDescendant(["MudItem"])); } ``` The Razor Weaver will then know that for a `MudGrid` that it can consider it matched with a `MudGrid` when both have at least one direct descendant under their `MudItem` with is considered a match. #### AllowMatchByNameOnly The tag may be matched by its name alone rather than requiring content also be matched. ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { application.ConfigureRazorTagMatchingFor("MudDialogProvider", c => c.AllowMatchByNameOnly()); } ``` #### AllowMatchByAttributes The tag may be match by the one or more specified attribute names. ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { application.ConfigureRazorTagMatchingFor("MudDialogProvider", c => c.AddTagNameAttributeMatch("@bind-Date", "otherAttribute", ...)); } ``` --- # TypeScript Code Management This article explains how to control [Code Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) (Code Weaving) behaviour for TypeScript files. The following table describes the various instructions that may be used to control which parts of the codebase Intent Architect manages, and which parts are managed by the developers, at a very fine-grained level of control. | Instruction | Description | |------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `@IntentIgnore()` | Instructs Intent Architect to ignore this declaration (i.e. do not update or remove it). | | `@IntentIgnoreBody()` | Instructs Intent Architect to ignore the body of the declaration, but allows it to manage the signature. This instruction is typically applied over _business-logic placeholder methods_ throughout the codebase. | | `@IntentMerge()` | Instructs Intent Architect to manage this declaration, allowing adding, updating, but not removing, of its child members. This instruction is typically used in areas of business logic to ensure that if the developer creates additional methods within a call, that those methods are not deleted. | | `@IntentManage()` | Instructs Intent Architect to manage this declaration, allowing adding, updating and removing of its child members. This instruction is useful in cases where Intent Architect is not fully managing a declaration and you would like it to. | | `@IntentManageClass()` | Instructs Intent Architect to manage this class, allowing adding, updating and removing of its child members. Additional management options can be supplied to this instruction to instruct Intent Architect how to treat `methods`, `properties` and `constructors`. | > [!NOTE] > Instructions can be applied as `decorators` (or `comments` if preferred, or where a decorator is not allowed) to any of the following declarations: `class`, `interface`, `constructor`, `method`, `property`, `function`, or `constant`. --- # YAML Code Management This article explains how to control [Code Management / Merging](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) behaviour for `.yaml` files when using the `Intent.Code.Weaving.Yaml` module. > [!NOTE] > **For Module Authors**: For YAML weaving to occur and code management instructions to be applied, the YAML template must set `codeGenType` to `YamlMerger`: > ``` csharp > public override ITemplateFileConfig GetTemplateFileConfig() > { > return new TemplateFileConfig( > fileName: "SampleYaml", > fileExtension: "yaml", > codeGenType: CodeGenType.YamlMerger); > } > ``` ## Overview The YAML merger parses `.yaml` files into a graph of nodes and recursively applies code management logic to each node. It compares the generated content from templates with the existing file (if present) on a node-by-node basis. Special instructions guide the merger on whether to ignore, merge, replace, or remove specific nodes. ## Code Management Instructions Instructions are embedded directly in the YAML as comments (e.g., `# IntentIgnore`) above the relevant node. These tell the merger how to treat that node during the merge process. ## Node-Level Management Modes - **`# IntentFully`** - Intent Architect has full control over the node. Any manual changes in the existing file will be overwritten. Child nodes can override this by using a different instruction. - **`# IntentMerge`** - Intent Architect will add and remove *generated* nodes, but will not delete manually added content. - **`# IntentIgnore`** - Intent Architect will skip the node entirely, leaving it untouched. All child nodes are ignored as well, and cannot override this behavior. ## File-Level Management Modes Due to YAML’s structure, placing `# IntentIgnore` at the top of a file creates ambiguity - does it apply to the whole file or just the first node? To resolve this, **file-level** instructions have been introduced: - **`# IntentFullyFile`** - Intent Architect has full control over the entire file. Any changes will be overwritten by the template, unless overridden on specific nodes. - **`# IntentMergeFile`** - Intent Architect adds and removes generated nodes, but preserves manually added content. - **`# IntentIgnoreFile`** - The entire file is ignored by the merger. ### Example Comparison ``` yaml # IntentFully name: John Smith age: 33 ``` In this example, only the name field is controlled by Intent Architect. ``` yaml # IntentFullyFile name: John Smith age: 33 ``` Here, the entire file is managed by Intent Architect. ## Default behaviour By default, templates operate in `Merge` mode. This default can be changed in the [settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) for your application: ![Yaml Merger Settings](images/yaml-merger-settings.png) ## Node Matching The merger correlates nodes in the existing file with nodes in the generated content using: - Field names for object properties. - Order or scalar value for array items. ### Matching Scalar Values in arrays For cases where you want to replace a particular value in an array, use the `(Match = "")` parameter to the above instructions, e.g. `# IntentIgnore(Match = "SomeValue")`. ``` yaml names: - Alice - Bob - Charles ``` If you wanted to replace `Bob` with a value, you can do the following: ``` yaml names: - Alice # IntentIgnore(Match = "Bo") - John - Charles ``` As `Bob` in the generated value starts with the string of `Bo` the items correlate. ### Matching Objects in Arrays by Field Value Use `# IntentMatchBy` above an array to specify how array items should be matched. Consider the following *generated* file: ``` yaml customers: - customer: customerNumber: 00001 name: Generated Name 1 - customer: customerNumber: 00002 name: Generated Name 2 ``` And you want to override just the generated name to have a different value, so you ignore the field: ``` yaml customers: - customer: customerNumber: 00001 # IntentIgnore name: Modified Name - customer: customerNumber: 00002 name: Generated Name 2 ``` The above won't work reliably as the merger also needs to be instructed that it should match items by the `customerNumber` field, which can be done as follows: ```yaml # IntentMatchBy("customerNumber") customers: - customer: customerNumber: 00001 # IntentIgnore name: Modified Name - customer: customerNumber: 00002 name: Generated Name 2 ``` You can also match on **multiple fields**. In the example below both `regionCode` and `customerNumber` fields must be the same for the objects to be considered a match. ```yaml # IntentMatchBy("regionCode", "customerNumber") customers: - customer: regionCode": 01 customerNumber: 00001 # IntentIgnore name: Modified Name - customer: regionCode": 02 customerNumber: 00002 name: Generated name for customer in region code 2 ``` > [!NOTE] > If a template includes `# IntentMatchBy` in the generated content, it may not be output in the generated YAML file, but the merger will still apply the matching logic internally. --- # Practical C# Code Management Examples This guide provides practical examples for common code management scenarios in Intent Architect. Each example demonstrates how to handle real-world situations where you need to customize or extend generated code. For a comprehensive explanation of code management concepts, see the [C# Code Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/code-management-csharp/code-management-csharp.md) documentation. ## Code Management Modes Intent Architect creates and maintains large portions of your codebase through continuous code generation. When you need to customize the generated code, you can use _Code Management Instructions_ to control how Intent Architect handles specific sections of this code. This is achieved by adjusting the `Mode` for a section of code: - **Fully** Mode: Intent Architect owns, governs and controls the generated code completely. - **Ignore** Mode: The developer owns and controls the code completely. Intent Architect will not touch this code. - **Merge** Mode: The Intent Architect generated code is intelligently merged with developer added code. --- ## When to use Code Management The various code management techniques described below can be leveraged when: - You want to put an exception or small tweak into the pattern output by Intent Architect. - You want to extend the code which Intent Architect is generating. - You want to completely customize the code Intent Architect is generating, either in an isolated fashion, or by opening the code up for broader changes. The techniques can be applied in a number of ways depending on what you want to do and the intention you want to convey with the code management applied. --- ## Choosing the Right Code Management Technique Intent Architect provides multiple code management techniques: `[IntentIgnore]`, `[IntentManaged(Mode.Ignore)]`, `// IntentIgnore` (along with other variants). The choice depends on where you're applying the instruction: - **Attribute-based technique** (e.g., `[IntentIgnore]` or `[IntentManaged(Mode.Ignore)]`) - This should be the default technique and used most of the time (such as above classes, methods, properties, or any other C# element that supports attributes). - **Comment-based techniques** (e.g., `// IntentIgnore`) - This should only be used when a C# attribute is not supported (such as inside method bodies or above individual lines of code). > [!NOTE] > Each file generated by Intent Architect will also have a `[DefaultIntentManaged]` attribute defined at the top of the file. This indicates the default instruction for the entire file and can be overridden at lower levels - either by modifying the existing code management attribute on the relevant code block (class, method, etc.) if present, or by adding it if not present. --- ## Practical Examples ### Intent Architect wants to remove my method **Scenario:** You've manually added a method to a class, but Intent Architect wants to remove it during the Software Factory execution. ![Method removal](images/method-removal.png) **Cause:** The class is fully managed (`[IntentManaged(Mode.Fully, Signature = Mode.Fully)]`), and Intent Architect doesn't recognize the custom method as part of the template output. #### Solution 1: Apply Method-Level Ignore Attribute **Use when:** You want to keep individual custom methods while keeping the class fully managed, or when you want to indicate that the class is _not_ open for extension generally. Add the `[IntentIgnore]` attribute above the custom method: [!code-csharp[](images/method-ignore.cs?highlight=1)] #### Solution 2: Use Class-Level Merge Mode **Use when:** You are adding multiple custom methods to the same class, or when you want to indicate that the class is _generally_ open for extension with custom methods. Change the class-level attribute to `[IntentManaged(Mode.Merge, Signature = Mode.Fully)]`. This instructs Intent Architect to merge generated methods with any developer-added methods. [!code-csharp[](images/class-merge.cs?highlight=1)] --- ### Intent Architect wants to remove my custom code from a method (single line) **Scenario:** You want to add custom code to an Intent Architect generated method, but Intent Architect wants to remove it during the Software Factory execution. ![Code Line removal](images/code-line-removal.png) **Cause:** The method body is fully managed (`[IntentManaged(Mode.Fully, Body = Mode.Fully)]`), and Intent Architect doesn't recognize the custom code as part of the template output. #### Solution 1: Use Method Body Merge Mode (Single Line) **Use when:** You are adding multiple custom lines throughout a method body, or when you want to indicate that the method is _generally_ open for extension and you anticipate it will be extended. Change the method attribute to `[IntentManaged(Mode.Fully, Body = Mode.Merge)]`. This instructs Intent Architect to merge generated code with custom code within the method body. The method signature and attributes remain fully managed. [!code-csharp[](images/method-body-merge.cs?highlight=1)] #### Solution 2: Use Statement-Level Ignore **Use when:** You want to preserve specific lines of custom code while keeping the method body fully managed, or when you want to indicate that the method is _not_ open for extension generally. If you need to add multiple lines to a method but still want to indicate that the method is not open for extension generally, an option is to extract those lines into their own method and add a single line to the Intent Architect generated method that invokes the new custom method. Add the `// IntentIgnore` comment above the specific line or block you want to preserve. Since attributes cannot be applied to individual statements, the comment-based approach is required. [!code-csharp[](images/statement-ignore.cs?highlight=4)] Even though the body remains in `Fully` mode, the `// IntentIgnore` comment overrides management for that specific statement. --- ### Intent Architect wants to remove my custom code from a method (multiple lines) **Scenario:** You want to add **multiple lines** of custom code to an Intent Architect generated method, but Intent Architect wants to remove it during the Software Factory execution. ![Code Line removal](images/code-multi-line-removal.png) **Cause:** The method body is fully managed (`[IntentManaged(Mode.Fully, Body = Mode.Fully)]`), and Intent Architect doesn't recognize the custom code as part of the template output. #### Solution 1: Use Method Body Merge Mode (Multi-line) **Use when:** You are adding multiple custom lines throughout a method body, or when you want to indicate that the method is _generally_ open for extension and you anticipate it will be extended. Change the method attribute to `[IntentManaged(Mode.Fully, Body = Mode.Merge)]`. This instructs Intent Architect to merge generated code with custom code within the method body. The method signature and attributes remain fully managed. [!code-csharp[](images/method-body-merge-multi.cs?highlight=1)] #### Solution 2: Use Code Block-Level Ignore **Use when:** You want to preserve multiple consecutive lines of custom code while keeping the method body fully managed, and you want to keep the custom code alongside the generated code. Add the `// IntentIgnore` comment above a code block (enclosed in braces `{ }`) to ignore the entire block. Since attributes cannot be applied to individual statements, the comment-based approach is required. [!code-csharp[](images/statement-ignore-block.cs?highlight=4)] Even though the body remains in `Fully` mode, the `// IntentIgnore` comment overrides management for that specific code block statement. #### Solution 3: Extract to own method **Use when:** You want to preserve multiple consecutive lines of custom code while keeping the method body fully managed, and the custom code can be executed without requiring to be with the generated code. Extract the custom code into a separate method and add a single `// IntentIgnore` comment above the method invocation line. [!code-csharp[](images/custom-method-extract.cs?highlight=4,5,19-27)] Even though the body remains in `Fully` mode, the `// IntentIgnore` comment overrides management for that specific invocation statement. --- ### I want to add to a code block generated by Intent Architect **Scenario:** You need to add statements to a code block generated by Intent Architect, but the additions are removed during Software Factory execution. ![Adding a custom code line](images/custom-code-line-add.png) > [!NOTE] > This example covers _adding statements_ to a generated block. For _modifying existing statements_, see [Updating Generated Code Blocks](#i-want-to-update-a-code-block-generated-by-intent-architect). #### Solution 1: Method Body Merge Mode **Use when:** You are adding multiple custom statements across the method, or when you want to indicate that the method is generally open for extension and you expect it to be extended substantially. Change the method attribute to `[IntentManaged(Mode.Fully, Body = Mode.Merge)]`. This allows Intent Architect to merge generated code with custom additions throughout the method body. [!code-csharp[](images/code-block-add-merge.cs?highlight=1)] #### Solution 2: Use Statement Block Merge **Use when:** You want to add statement(s) to a specific code block while keeping other parts of the method fully managed, and when you want to indicate that the method is _not_ open for extension generally. Add the `// IntentMerge` comment above the specific code block. This instructs Intent Architect to merge generated statements with your additions within that block only. [!code-csharp[](images/code-block-add-merge-comment.cs?highlight=7)] --- ### I want to update a code block generated by Intent Architect **Scenario:** You need to modify a statement within a code block generated by Intent Architect, but the changes are reverted during Software Factory execution. ![Updated a generated code line](images/custom-code-line-update.png) **Cause:** When you modify an existing generated statement (like changing `x => x.Active && x.Qty > 0`), Intent Architect cannot merge the code because the statement itself is being replaced, not added to. > [!NOTE] > This example covers _modifying existing statements_. For _adding new statements_, see [Adding to Generated Code Blocks](#i-want-to-add-to-a-code-block-generated-by-intent-architect). #### Solution: Use Statement-Level Ignore **Use when:** You need to modify a code block generated by Intent Architect, and when you want to indicate that the method is _not_ open for extension generally. Add the `// IntentIgnore` comment above the modified statement to preserve your changes: [!code-csharp[](images/code-block-update-ignore.cs?highlight=4)] You can apply this to individual statements within larger blocks. In the example below, only the `Description = GetProductDescription(request),` line is ignored: [!code-csharp[](images/code-block-update-ignore-2.cs?highlight=11)] --- ### I want to add to a method chain generated by Intent Architect **Scenario:** You need to add a method call to a fluent method chain generated by Intent Architect, but the addition is removed during Software Factory execution. ![Method chain update](images/statement-chain-add.png) #### Solution: Use Inline Ignore Comment **Use when:** You need to add a specific method call to a fluent method chain while keeping other parts of the chain managed by Intent Architect. Add the `// IntentIgnore` comment before the specific method call you want to preserve within the chain: [!code-csharp[](images/chain-ignore.cs?highlight=2)] --- ### I want to override a statement Intent Architect generated with my own statement **Scenario:** You want to replace a line of code generated by Intent Architect, with your own code, but the modification is being undone during the Software Factory execution. ![Ignore Match](images/ignore-match.png) #### Solution: Use Inline Ignore Comment with Match **Use when:** You want to replace a line of code that Intent Architect has generated with a custom line of code, but want to keep the rest of the method fully managed. Add the `// IntentIgnore` comment before the specific line you want to replace, with a `Match` parameter that specifies which generated line of code you want your line to replace. The `// IntentIgnore(Match="var products = ")` instruction specifies: The following line should be ignored by the Software Factory, and it will replace a line of generated code which starts with `var products = `. [!code-csharp[](images/ignore-match.cs?highlight=4)] --- ### I want to add an interface or attribute to a Intent Architect generated class **Scenario:** You want to add an _interface_ or _attribute_ to an Intent Architect generated class, but the added code element is removed during the Software Factory execution. ![Attribute Interface removal](images/attribute-interface-removal.png) #### Solution 1: IntentMerge Attribute **Use when:** You want to keep the manually added _interface/attribute_, as well as any Intent Architect generated _interfaces/attributes_, while indicating that the class _is_ open for extension generally. Add the `[IntentMerge]` attribute to the class: [!code-csharp[](images/class-att-interface-intentmerge.cs?highlight=6)] #### Solution 2: IntentManaged Attribute with Targeted Merge **Use when:** You want to keep the manually added _Interface/Attribute_, as well as any Intent Architect generated _interfaces/attributes_, while indicating that the class is _not_ open for extension generally. The `[IntentManaged]` attribute can be added to the class, which allows for a more targeted approach to allow merging on specific aspects of the class. In the example below, the class remains `fully` managed, which the exception of `class signature` (the interfaces) and the `class attributes` which are both in `merge` mode. [!code-csharp[](images/class-att-interface-merge.cs?highlight=6)] --- --- # Intent Architect Development Environment Setup ## Enable Long Name Paths (Windows Only) Enabling long path support in Windows is recommended for software development. Descriptive file and folder names often result in deep directory structures, which can exceed the default 260-character limit and cause build or runtime errors. For more information on configuring Long Name Paths, read further [here](https://docs.intentarchitect.com/docs-md/getting-started/long-path-support-windows-only/long-path-support-windows-only.md). ## Anti-Virus Whitelisting To avoid negatively affecting developer productivity, we take the performance of the Software Factory very seriously. Over time, we have applied many optimizations and continue to identify new areas for improvement. However, we have found that on some developer machines, environmental factors or configurations can significantly impact Software Factory execution speed. For more information on environmental factors that can slow down Software Factory execution, read further [here](https://docs.intentarchitect.com/docs-md/application-development/software-factory/environmental-factors-which-can-slow-down-software-factory-execution/environmental-factors-which-can-slow-down-software-factory-execution.md). ## Default Diff Tool Reviewing changes in Intent Architect is done through a third-party diff tool. We recommend using [Visual Studio Code](https://code.visualstudio.com/download) because it is lightweight, fast, and works directly with the underlying files-allowing for interactive editing in diff windows. ## User Settings The `User Settings` section of the application allows you to configure various preferences, such as: - Theming - Default `Solution Location` - Default IDE - Default Diff Tool For more information on configuring user settings, read further [here](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md). --- # How Intent Architect Solutions are structured on the File System This article covers how Intent Architect solutions are structured on your local file system. ## Overview Intent Architect solutions are created in two separate steps: 1. **Solution Creation** - Creates the solution structure with the `intent` folder and all solution-level configuration 2. **Application Creation** - Applications are then added to the solution as separate steps --- ## Creating a Solution The solution creation wizard allows you to specify the following settings: ![Solution configuration](./images/default-solution-setup.png) The main settings you'll provide are: * **Location** - where the solution folder will be created * **Solution Name** - the name of the solution When you proceed through the wizard, Intent Architect creates a folder structure: `{Location}\{Solution Name}`. This folder will contain: * The `intent` folder - containing all solution-level configuration and Intent Architect data * The `.isln` file - the solution file that you double-click to open the solution in Intent Architect ### The Intent Architect solution file (`.isln`) The Intent Architect solution file (`.isln` file extension) is the entry point for your solution, very analogous to a Visual Studio solution file. When you create a solution, Intent Architect creates an `.isln` file at the following location: ```text {Location}\{Solution Name}\intent\{Solution Name}.isln ``` Double-clicking this file will open the solution in Intent Architect. --- ## Creating and Adding Applications After you have created a solution, you can add applications to it. When you add an application to your solution, Intent Architect will: 1. Create a new application folder (`{Application Name}`) at the solution root 2. Create application-specific metadata and configuration in the `intent` folder 3. Generate the initial project structure based on your selected application template Each application is independent and can have its own technology stack, architecture, and design specifications. You can add multiple applications to a single solution, and each will have its own folder at the solution root. --- ## Solution Folder Structure Once you've created your solution and added applications to it, the overall folder structure looks like this: ![Parent Folder Structure](./images/intent-solution-layout.png) The structure follows your selected options: `{Location}\{Solution Name}`. Within this folder, you'll find: * `intent` - this folder contains all the Intent Architect data for this solution (including the `.isln` file) * `{Application Name}` folders - one for each application you've added, containing the source code for that application ### Folder structure basics When you create a solution, Intent Architect creates a folder `{Location}\{Solution Name}` which contains all solution-related content. Initially: * The `intent` folder contains all solution-level configuration and Intent Architect data When you add applications to the solution: * Each `{Application Name}` folder contains the full source code for that application The `intent` folder contains: * Solution / Application settings and Intent Architect configuration information * Designer Metadata - all designer related data (domain models, service models, etc.) * Module manifests - details on what specific modules and their versions are being used Each `{Application Name}` folder contains the full source code for the application. In a .NET application, this includes: * Visual Studio solution file (`{ApplicationName}.sln`) * Various `CSProj` files and their related artifacts > [!NOTE] > Every additional Application you add to your Intent Architect solution will add an additional folder to the solution root, with that application's source code in it. ### Application source code Once you have added an application to your solution, looking inside the `{Application Name}` folder will show all the source code for that application. (Assuming you have run the Software Factory and applied the changes) If you have worked with C# solutions before, this should look familiar to you. Here we can see a C# solution file (e.g. MyApplication.sln) which contains C# projects. This C# solution is the source code realization of the Intent Architect application and its designs. ![Application Source Code](./images/application-source.png) > [!NOTE] > If you are wondering why the C# solutions / projects are generated the way they are, this has been configured in the [`Codebase Structure Designer`](https://docs.intentarchitect.com/docs-md/application-development/modelling/codebase-structure-designer/codebase-structure-designer.md) or `Folder Designer` with in Intent Architect. > ![Codebase Structure Designer configuration](./images/codebase-structure-designer.png) ### Intent Architect Solution data Investigating the `intent` folder, you will find the following: ![intent folder contents](./images/intent-folder.png) This folder contains the following items: * `.intent\modules` folder - this folder is the `Module Cache` and contains copies of downloaded and installed modules the solution is running. * `Intent.Metadata` folder - this folder contains Intent Architect solution-level related data. * Application-specific folders (e.g., `MyApplication`, `AnotherApplication`, etc.) - each contains Intent Architect application-specific metadata for that application. * `.gitignore` file - this file is configured so that the module cache folder, mentioned above, does not get committed into version control. * `{Solution Name}.isln` file - the Intent Architect solution file, for this solution. Double clicking this file will open the solution in Intent Architect. (This is very analogous to a .sln file for your C# IDE) ### Module cache This folder is very analogous to a NuGet package folder, it is a directory on your solution where Intent Architect modules it has downloaded are cached for use by the Intent Architect Applications. If you take a look at what's in this folder, you will see folders, corresponding to the Modules you have installed across your various Applications. ![Module Cache View](./images/modules-cache.png) > [!NOTE] > Similar to NuGet, this folder is a cache and does not need to be version controlled and can be cleared if required. The Applications keep track of what modules they need and at what specific version (modules.config). There is a `.gitignore` configured to ensure the actual module binaries don't get committed into version control. ### Intent Architect Application data Within the `intent` folder, each application you add to the solution will have its own folder (e.g., `MyApplication`, `MyService`, etc.). Each application folder contains application-specific data: * `{Application Name}.application.config` - this file contains all the application-specific configuration information. * `Intent.Metadata` folder - this folder contains all the Metadata described in the installed `Designer`s for this application. * `modules.config` file - this file contains which modules, and at what specific version, are being referenced by the application. ### Application Metadata folder This Folder contains all the Metadata described in the installed `Designer`s, for example let's say you have the following 3 designers installed: * Domain Designer * Services Designer * Codebase Structure Designer Then this folder would contain 3 sub-folders, one for each designer where each of these children would contain all the Metadata for their designer. ![Designer Metadata](./images/designer-metadata.png) --- # Software Development Life Cycle (SDLC) Best Practices for Intent Architect Users ## Version Control ### Commit your Intent Architect design with the source code When using Intent Architect, you are modeling your system design and applying that design to your codebase. This design evolves naturally over time alongside your codebase. In the same way we commit and version our code, we should also commit and version our design to ensure they remain aligned. For example, if you switch to a different branch or roll back to a previous version of the codebase, you want to be able to work with the corresponding version of the design. The Intent Architect designs are stored in the **intent** folder. It is best practice to commit this **intent** folder into version control alongside the codebase. This is the default folder layout for an Intent Architect solution with multiple .NET applications: - 📁 `` - The root folder which should be committed in your SCM (e.g. Git) - 📁 `intent` folder - 📁 `.intent` folder - Should not be committed and is added to `.gitignore` by default, contains cache of installed modules which Intent Architect will automatically download if it's missing. - 📁 `Application1` folder - Intent Architect data for Application1 - 📁 `.intent` folder - Should not be committed and is added to `.gitignore` by default, contains cache of data from the last Software Factory execution to enable smarter code merging or knowing which files can have some of their processing skipped if they are unchanged since the previous execution. - 📁 `Intent.Metadata` folder - Metadata for the application - 📄 `Application1.application.config` file - Basic details of the Intent Architect application, such as its name, relative output location, etc. - 📄 `Application1.application.output.config.xml` file - Only created/updated by the Software Factory if it already existed or when it has one or more files it should not generate any reason, for example due to a file being ignored or being once-off generation only. If this file is not committed into source control, then other users and the [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) will not be aware of which files should not be generated. - 📄 `Application1.application.output.log` - Obsolete. Prior to [version 4.5.22](https://docs.intentarchitect.com/docs-md/release-notes/intent-architect-v4.5.md#version-4522) of Intent Architect, was used to track aspects of Software Factory output. For backwards compatibility with older versions of Intent Architect, this file will still be updated by the Software Factory if it already exists, but if it's not present then it will not be created again. Provided that all users working on an Intent Architect application have updated to at least version 4.5.22, this file can be deleted. - 📄 `Application1.application.deviations.log.xml` file - Tracks code management [customizations](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) for the application. - 📄 `modules.config file` - 📄 `.isln` file - The Intent Architect solution file containing basic data such as its name and the Intent Architect applications within it. - 📁 `Application2` folder - Intent Architect data for Application2 - 📁 `[...]` folders - Intent Architect data for any additional applications - 📁 `Application1` folder - Output for Application1 - 📁 `Project1` folder - For the `Project1` Visual Studio project - 📁 `[...]` - Any folders within the project - 📄 `Project1.csproj` file - 📄 `[...]` - Any other files within the project root - 📁 `Project2` folder - For the `Project2` Visual Studio project - 📁 `[...]` - Folders for any additional Visual Studio projects - 📄 `Application1.sln` file - Visual Studio solution file - 📁 `Application2` folder - Output for Application2 - 📁 `[...]` - Output for any additional applications ### Merge Conflicts on Intent Architect Metadata Files Everything you design within Intent Architect is persisted inside the `intent` folder as `.xml` files. As with any files stored in a repository, it's possible for the same file to be edited differently across branches or by multiple users. In that case you may encounter a **merge conflict** that must be resolved. This is similar to resolving conflicts in a `.csproj` file. Resolving the conflicts is generally very straightforward once you understand the largely self-evident cleartext file format. We strive to keep our metadata files human-readable and appropriately sized to minimize conflicts, in particular different concepts (e.g. `Class`, `DTO`, `Service`) each have separate files meaning that unless multiple developers are working on the exact same concept, they shouldn't interfere with each other at all. To reduce the frequency and complexity of merge conflicts, apply standard development practices: - **Pull frequently** – Regularly fetch and merge changes from the main branch to stay in sync. - **Keep branches short-lived** – Work in small, focused branches and merge them quickly. - **Communicate with your team** – Coordinate when multiple people are working on related areas. - **Avoid large commits** – Make atomic commits that are easier to review and merge. - **Rebase instead of merging (when appropriate)** – Keeps history clean and makes conflicts easier to manage. - **Use tools for visual diffing** – Tools like Beyond Compare, Meld, or IDE-integrated tools help clarify changes. For more details, read [Understanding and Resolving Merge Conflicts involving Intent Architect Metadata Files](https://docs.intentarchitect.com/docs-md/application-development/for-developers/understanding-and-resolving-merge-conflicts/understanding-and-resolving-merge-conflicts.md) ### Working with Pull Requests For teams using **Pull Requests (PRs)** as part of their SDLC, here are some key aspects to consider: You typically do not need to review any of the Intent Architect metadata files-i.e., files located in the `intent` folder. These files serve as input to the code generation process, so reviewing the resulting codebase changes as you normally would is sufficient. Since much of the code in a PR may be generated by Intent Architect, it's helpful to optimize your PR review process by focusing on: - **Non-generated code** – such as business logic (e.g., service endpoint implementations or domain logic) and any custom code files added manually. - **Customizations** – areas where **Code Management** instructions have been used to customize generated code. As you become more familiar with the patterns being automated, you'll develop an intuition for which aspects of the PR require closer inspection. Intent Architect also offers a **Customization Tracking** feature to support this process. It highlights areas of the codebase where Customizations exist, making them easy to identify and review to ensure nothing is overlooked. For more details on Customization Tracking, see [The Software Factory Customizations screen](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md). > [!NOTE] > To use the Customization Tracking feature during PR reviews, you must have the PR checked out locally. > > ```bash > git fetch origin pull/123/head:pr-123 > git checkout pr-123 > ``` ## CI/CD Tooling ### Intent Architect design and codebase should be synchronized when committing to version control Since you commit your Intent Architect design to version control alongside your codebase, it's best practice to ensure that your design work has been applied to the codebase **before committing**. Ultimately, you want the committed design and codebase to always be in sync. Failing to do so is analogous to committing code that doesn't compile - something CI/CD processes aim to prevent. You can use the `Software Factory CLI tool` with the `ensure-no-outstanding-changes` option as part of your CI/CD pipeline to enforce this behavior. [Software Factory CLI tool documentation](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) ### Automate Governance of Architectural Customizations (Optional) If you're using the [customizations tracking feature](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) and its approval functionality, you can integrate governance checks into your CI/CD pipeline. Run the `Software Factory CLI tool` with the `ensure-no-outstanding-changes` and `--check-for-unapproved-customizations` options. This will cause the build to break if there are any unapproved customizations. [Software Factory CLI tool documentation](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md) There are multiple ways to configure this, but a popular and effective setup is: - Allow customizations in a `development` branch. - Enforce approval checks in a `release` branch. This approach allows developers to work freely in development while ensuring that Customizations are reviewed before promotion. ## Upgrading and Installing Modules Upgrading and installing modules can result in changes to your codebase. It is best practice to perform these operations on a **clean checkout** of your codebase - that is, all code committed or stashed (or the similar operation in your source control of choice). This helps isolate the impact of the upgrade and verify your codebase's readiness. This is similar to manually upgrading NuGet packages - you'd typically do this from a clean state to ensure smooth upgrades. On occasion, a module upgrade may result in many files being changed by the Software Factory, but as the change in almost all the files are related to the exact same change to the pattern, typically the changes are incredibly quick to review. Your codebase is a mix of Intent Architect–managed code and custom code. While the tool upgrades managed code automatically, some custom code may need manual adjustments. If you are not in a position to perform the manual adjustment at that time, you can easily roll back the modules / changes, and plan for when the upgrades should take place. If you use pull requests as part of your SDLC, ensure that you do module upgrades as their own PR so as to not mix "functional" changes with regular module upgrade changes, this ensures that the work for reviewers is as easy as possible. [Module Management documentation](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) ## Have your team run the same version of Intent Architect We recommend that all team members use the same version of Intent Architect. Most of our users are part of teams working on shared solutions, so it's ideal for all developers to use the same **major and minor** version (e.g., 4.4.x) to ensure a consistent and predictable experience. Teams should coordinate when upgrading product versions to avoid compatibility issues. ## Custom Module deployment If you build your own Intent Architect modules, you will need to consider how you deploy these modules so that your teams can discover and use your modules. Module discovery is done through a Repository configuration which can be setup globally per Intent Architect solution. This can be particularly useful if you have custom modules which you want share / distribute either with-in your own development team or with external parties. These repositories can be either: - A URL to a module server, by default solution's are configured to point to the Intent Architect official module server and you can also host your own. - A UNC Path, e.g. a local file folder or a mapped drive. For more information on configuring Module Repositories, refer to the [How to manage Repositories](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/how-to-manage-repositories/how-to-manage-repositories.md) article. ### Module Server If you have custom modules which you wish to distribute and don't want to go the UNC Path route, you can host your own Module Server to distribute your modules, this is very analogous to setting up a custom NuGet hosting solution for distributing your own NuGet packages. For more information on deploying a Module Server, refer to the [Module Server](https://docs.intentarchitect.com/docs-md/tools/module-server/module-server.md) article. ## Configure your Development Environment When working with Intent Architect there are some best practices we recommend for configuring your development environment, these are detailed in the [Intent Architect Development Environment Setup](https://docs.intentarchitect.com/docs-md/application-development/for-developers/development-environment-setup/development-environment-setup.md) article. ## General FAQ This section addresses common questions and scenarios you may encounter when working with Intent Architect, providing practical guidance for typical uncertainties. ### What does installing this module / adjusting this setting / performing this modelling do? If you are unsure what effect: - installing a module - adjusting an application setting - performing modelling in one of the designers the best approach is to perform these operations on a clean checkout of your codebase (i.e., with all changes committed or stashed). This helps isolate the impact of performing the action on your code base. Once you perform one of the above mentioned actions, you can then run the Software Factory and evaluate the changes - allowing you to draw a correlation between the changes you made in Intent Architect and the code generated. As the action was performed on a clean checkout, if you do not require the changes made, you can do the following: - If the **Software Factory execution is applied**, as the operation was performed on a clean checkout, the code can be reverted using your source control tool of choice. - If the **Software Factory execution is not applied**: - The Intent Architect metadata updates can be reverted using your source control tool of choice - The module can be uninstalled - The setting can be reverted - The modelling can be undone using the `Undo` feature (`Ctrl-z`) --- # Understanding and resolving merge conflicts involving Intent Architect metadata files Everything you design within Intent Architect is persisted inside the `intent` folder as `.xml` files. As with any files stored in a repository, it's possible for the same file to be edited differently across branches or by multiple users. In that case you may encounter a **merge conflict** that must be resolved. This is similar to resolving conflicts in a `.csproj` file. Resolving the conflicts is generally very straightforward once you understand the largely self-evident cleartext file format. We strive to keep our metadata files human-readable and appropriately sized to minimize conflicts, in particular different concepts (e.g. `Class`, `DTO`, `Service`) each have separate files meaning that unless multiple developers are working on the exact same concept, they shouldn't interfere with each other at all. To reduce the frequency and complexity of merge conflicts, apply standard development practices: - **Pull frequently** – Regularly fetch and merge changes from the main branch to stay in sync. - **Keep branches short-lived** – Work in small, focused branches and merge them quickly. - **Communicate with your team** – Coordinate when multiple people are working on related areas. - **Avoid large commits** – Make atomic commits that are easier to review and merge. - **Rebase instead of merging (when appropriate)** – Keeps history clean and makes conflicts easier to manage. - **Use tools for visual diffing** – Tools like Beyond Compare, Meld, or IDE-integrated tools help clarify changes. > [!NOTE] > For a practical walkthrough of version control fundamentals in Intent Architect, see this video tutorial: [Version Control Fundamentals](https://docs.intentarchitect.com/docs-md/tutorials/fundamentals/fundamentals-6-version-control.md). ## Understanding a merge conflict by example Assume you have a `Customer` entity, and this design has already been committed to the `development` branch. ![Modelled Customer](./images/customer-start.png) Now you want to make a change to the `Customer`. In this example, an **IsActive** `Attribute` has been added. ![Modelled Customer added IsActive](./images/added-is-active.png) Now let’s assume another developer made a different change-adding an `Email` attribute to the `Customer`-and pushed it into the `development` branch. ![Modelled Customer added Email](./images/added-email.png) When you try to merge your changes into `development`, you'll encounter a merge conflict that looks something like this: ![Merge Conflict Overview](./images/merge-conflict-overview.png) Looking at the files involved, we can categorize the conflicts into two groups: - **Codebase files** – `...Customer.cs` and `...CustomerConfiguration.cs` - **Intent Architect metadata files** – `Customer__cnjlkkwn.xml` ### Merging Codebase Files Let’s look at `Customer.cs` as an example. ![Customer Merge](./images/customer-file-merge.png) This is a standard merge conflict that developers are accustomed to resolving. In this case, the resolution is simply to add both properties to the `Customer` class. > [!NOTE] > If these codebase files are **fully automated**-i.e. the code is fully generated with no customizations-it doesn’t really matter how you resolve these conflicts, as the files will be regenerated once you’ve resolved the metadata conflicts and re-run the Software Factory. ### Merging Intent Architect Metadata Files Now let’s look at merging the `Customer__cnjlkkwn.xml` file. Even before viewing its contents, there’s useful context available in the filename. ```text ...\intent\SampleApplication\Intent.Metadata\Domain\SampleApplication.Domain\Elements\Class\Customer__cnjlkkwn.xml ``` Here’s a breakdown of the folder and filename structure: ```text ...\intent\{Application Name}\Intent.Metadata\{Designer Name}\{Package Name}\Elements\{Element Type}\{Instance Name}__cnjlkkwn.xml ``` Extracting the Data | No | Concept | Value | |----|------------------|--------------------------| | 1 | Application Name | SampleApplication | | 2 | Designer | Domain | | 3 | Package Name | SampleApplication.Domain | | 4 | Element Type | Class | | 5 | Instance Name | Customer | Visually, we can locate this in Intent Architect as follows: ![Understanding the file name visually in Intent Architect](./images/understanding-filename-visually.png) In this example, you already know the nature of the conflict because the scenario was explained. However, even without that context, you could reasonably infer-from the filename alone-that both you and another developer made changes to the `Customer` entity in the `Domain` designer of the `SampleApplication` application. Looking at the actual merge conflict, we would see the following: ![Customer Metadata Merge](./images/customer-metadata-merge.png) Before diving into the resolution, let's explore this file a bit to understand what we're looking at. This is what we have modelled: ![Customer Model Visual](./images/model-to-xml.png) And this is what the **Customer metadata file** looks like: ```xml Customer Customer false ... Id Id: guid ... ... ... Name Name: string ... Surname Surname: string ... ``` Looking at the XML metadata file, you can see how it structurally maps to the visuals you see in the **Tree View**. If you select elements like the `Entity` or an `Attribute` and inspect the **Property Pane**, you'll start to notice values that match those in the XML file. ![Visual Mapping of XML to UI](./images/xml-to-model-mapping.png) Now that we understand what’s going on in the file, let’s revisit the merge conflict: ![Customer Metadata Merge](./images/customer-metadata-merge.png) With a better understanding of the file, it’s clear that two attributes were added: `IsActive` (added by you) and `Email` (added by another developer), each attribute is encapsulated in a `...` XML element so what we need is both versions of the ``s and their nested elements. Now you’re well positioned to resolve the conflict-by keeping both attributes. ### After Resolution, Run the Software Factory Once the conflicts are resolved, go back to Intent Architect. It will prompt you to reload the designers since the underlying files have changed. In the Domain Designer, you’ll now see the updated `Customer` in the domain model: ![Resolved merge conflict](./images/resolved-model.png) Re-run the Software Factory to ensure the generated codebase is correctly aligned with the design after merging. --- # Manage Agents **Manage Agents** answers one question: _what are all of my agents doing right now, and which of them needs me?_ For a single task, a solution window is the natural place to work: the chat sits beside the model it is changing, in the source control checkout you are already working in, with the designers, Software Factory and Change Review it needs right there. That way of working is unchanged - see [AI Assistant](https://docs.intentarchitect.com/docs-md/application-development/ai-assistant/index.md). The trade-offs show up when you need to **run several agents at the same time**. A solution window is bound to one solution and one checkout, so parallel tasks either queue behind each other in a single chat panel, or spread across several windows with no single place that shows what is running, what has finished, and what is blocked waiting on an answer. Tasks that do run concurrently share that one working directory, so their uncommitted changes land on top of each other and neither is reviewable on its own. Manage Agents is a separate top-level window - a peer of the solution window - that gathers **every** agent conversation across **every** repository and solution on your machine, including the ones you started from a solution window. Each conversation can run in its own Git worktree, so tasks stay out of each other's way, and around the chat sits a full workspace: designers, files, diffs, terminals, Git, Software Factory runs and Change Review, all scoped to the conversation you are looking at. ![Manage Agents shell](images/manage-agents-shell.png) > [!NOTE] > Manage Agents was introduced in Intent Architect 5.3. ## Opening Manage Agents The **Manage Agents** button on the Home screen is the only way in. It carries two live pills, so you can see from Home whether anything wants your attention before opening the window at all: | Pill | Meaning | | ----- | --------------------------------------------------- | | ▶ _n_ | _n_ conversations are running a turn right now | | ? _n_ | _n_ conversations are parked waiting on your answer | ![Manage Agents Home button](images/manage-agents-home-button.png) The window opens with no solution of its own. Each conversation carries its own folder and solution instead, which is what lets one window host tasks across several repositories at once. Where a conversation's folder does resolve to an Intent solution, an **Open \** chip appears in the toolbar and opens that solution in a _separate_ Intent Architect instance, leaving this window and the rest of your tasks undisturbed. ## The board The left-hand **Chats** panel is the board. It reads the whole conversation store, so it lists every conversation on your machine - including the ones you started from a solution window's AI Assistant panel, which are ordinary rows here like any other. > [!NOTE] > A conversation is not owned by the window that started it. Conversations live in one shared store, so the same chat can be opened and continued from either surface. A solution window's own history is scoped to that solution, which is usually what you want while working in it; the board is the unscoped view of the same store. ![Manage Agents board](images/manage-agents-board.png) ### What a row tells you Each row is two lines, and between them they answer "is this worth opening?": - **The status dot**, whose colour and fill are the row's state (see below). - **The title**, renamable in place with `F2` or from the row menu. - **The category tag**, a coloured tag glyph after the title, on a row you have categorised - see [the row menu](#the-row-menu). - **The time**, the conversation's last activity - or `unsaved` for a chat that has not been persisted yet. - **The checkout subline** - the folder the agent actually ran in and the branch it is on _right now_, with a distinct glyph for a repository, a linked worktree, and a plain folder outside Git. The row only says what the group header above it has not already said. - **The churn badge** - `+additions` / `-deletions` of uncommitted work in that checkout, against `HEAD`. Nothing is drawn for a clean checkout, so a badge always means there is something there. While Git is creating, restoring or removing a row's worktree, the subline is replaced by a spinner naming that activity - the folder and branch it would otherwise show are exactly what is still being settled. ### Status dots One vocabulary, used by the dot's tooltip everywhere a conversation appears: | Status | Means | | -------------------------- | ----------------------------------------------------------- | | **In progress** | A turn is running | | **Awaiting approval** | A tool call or plan approval is parked | | **Waiting on your answer** | The agent asked you a question - typing is what unblocks it | | **Completed** | The last run finished | | **Failed** | The last run ended in an error | | **Cancelled** | The last run was stopped | | **Ready to start** | The conversation exists but has never run | Two modifiers ride on top. A dot **fills** when a run finished that you have not opened yet (or you marked the row unread yourself), and the tooltip gains `- new` / `- marked unread`. A conversation with backgrounded shell commands or sub-agents still going gains `- background work running`, which persists even when the turn itself reads as finished. ### Grouping, sorting and filtering The `⋮` **view menu** on the board's toolbar controls how the list is built: | Section | Options | | ------------ | -------------------------------------------------------------------------------------------------------------------------------- | | _(top)_ | **Refresh** - re-read the conversation index | | **Sort** | **Created**, **Created (oldest first)**, **Updated** (most recently active at top) | | **Group** | **Group by workspace** (one group per repository, or folder outside one), **Group by time** (Today, Yesterday, Previous 7 days…) | | **Show** | **Show recent chats** (active in the last 7 days), **Show all chats** | | **Archived** | **Show archived chats** - off by default | | _(bottom)_ | **AI Configuration…** | Beside it are a search box and a collapse/expand-all toggle. Choices are remembered across sessions. > [!NOTE] > In **Group by workspace**, a repository gathers _all_ of its checkouts under one header - a linked worktree is not a level underneath the repository it was cut from, it is a row whose subline names it. When the recency scope is hiding rows, the empty state says so - _"N older - pick Show all chats from the view menu"_ - rather than claiming there is nothing there. ### Organising the board - **Drag a group header** to put the board in the order you think in (workspace grouping only - a time bucket's position is the calendar's). - **Drag a row into a group of your own**, created from the row menu's **Move to group ▸ New group…**. A custom group holds chats from any repository; **Move to group ▸ \** sends a row home again. - **Categorise a row** from the row menu to tag it with one of six colours. A category cuts _across_ the grouping rather than being part of it, so it is the way to mark a handful of related chats that a group cannot hold together - and it survives switching between workspace and time grouping. Nothing groups or filters by it; the chip is the whole feature. - **Collapse** any group by clicking its header. - Every group header carries a **+** (start a new chat here) and an **archive** button on hover. - The list deliberately **holds still while your pointer is over it**, so a row cannot move out from under a click. ### The row menu Right-click any row, on the board or in the docked history picker: | Action | Notes | | ------------------------------- | ------------------------------------------------------------------------------------------------- | | **Rename** | Same as `F2` | | **Mark as Read / Unread** | Manual, and distinct from the automatic unseen-completion state | | **Mark All as Read** | Disabled when nothing is unread | | **Move to group** | Board only, and only while grouping by workspace | | **Categorise** | Board only, in every grouping mode - tags the row with one of six colours, or **Clear category** | | **Archive / Unarchive** | List state only - the session, the worktree and the history are all untouched | | **Restart ACP host** | Rebuilds the conversation's agent session and respawns its subprocess, resuming prior turns | | **Gather Diagnostics** | Collects this conversation's chat file, agent logs, Software Factory logs and MCP logs into a zip | | **Archive and Delete Worktree** | Only for a row whose worktree is still on disk - see below | | **Delete** | Destroys the conversation, its session and its worktree | **Archive** is also the hover button on the row itself. The two destructive actions are menu-only, deliberately: neither is something a button beside a title should do in one click. > [!TIP] > **Categorise** and **Gather Diagnostics** both arrived in 5.3.2. Gather Diagnostics is the fastest way to hand a misbehaving agent run to support - the zip covers sessions that ran before the last restart too, trimmed to that conversation's own window. ## Choosing where an agent runs Before a conversation's first turn, the composer shows a row of **dispatch chips**. They are what make parallel agents safe. ![Manage Agents dispatch chips](images/manage-agents-dispatch-chips.png) | Chip | What it sets | | ------------ | --------------------------------------------------------------------------------------------------------------------- | | **Folder** | Where the agent runs. Offers the folders work has recently run in and the ones you have picked, plus **Open folder…** | | **Branch** | The ref the work is based on | | **Worktree** | Whether the agent gets an isolated Git worktree of its own, on a session branch | | **Solution** | Which `.isln` the task models against - shown **only** where the folder is governed by several | Three coupling rules are worth knowing, because they are enforced rather than advisory: 1. **Picking a branch that is not the checked-out one forces a worktree on.** The only other way to honour that choice would be to check the branch out in your own working tree behind your back. 2. **A folder that is in no Git repository offers neither branch nor worktree.** The chips present as unavailable rather than disappearing. 3. **The location freezes once the conversation has sent its first message.** It is keyed into the agent session, so it cannot move without discarding that session. From then on the chips _report_ where the task actually landed rather than offering a choice. Worktrees are offered for ACP agents only (Claude Code, Codex and the rest) - Intent Architect's own in-product personas drive the open solution's designers and have no concept of running in a checkout of their own. ### Session branches and the worktree root A worktree run gets its own **session branch**, named `agent/` by default. Click the name on the worktree line to rename it before the first turn; invalid branch names are rejected in place with the reason shown. Worktrees are created on the **first turn**, not when you tick the chip - until then, the task's Source Control and Codebase panels correctly say there is no folder yet rather than showing the parent checkout's. They live under `~/.worktrees` by default. To put them elsewhere, set **Worktree Location** under _User Settings_ - see [How to change user settings](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md). ### Approving a plan into a worktree When an agent's plan comes up for approval in this window, the approval card can also offer **Implement in a separate worktree**. Taking it creates the worktree at that moment and moves the plan document into it, so implementation starts on a clean branch rather than on top of whatever you happened to be doing. The option is offered only in the Manage Agents window, and only when the model that will implement is an ACP agent. ### Releasing a worktree **Archive and Delete Worktree** removes the worktree folder and archives the conversation; the chat and its history survive. When the checkout has uncommitted changes, you are told exactly what is at stake before it proceeds: - Uncommitted changes in the worktree **are lost**. - Commits already made on the session branch **are kept**. - The session branch is removed **only if it is already merged into a remote**; otherwise it stays in the repository. ![Manage Agents delete worktree](images/manage-agents-delete-worktree.png) ## The workspace around the chat The centre of the window is not just a chat. It is a **tab strip scoped to the conversation you selected**, so each task keeps its own tabs and you can move between tasks without losing your place. The chat itself is the pinned first tab. Beside it can sit: - **Designers** - opened in the conversation's own solution, hosted by this window. - **Files**, editable, with rendered Markdown preview. - **Diffs** - Source Control diffs, Software Factory diffs and baseline diffs. - **Terminals**, rooted in the conversation's folder. - **Software Factory Output** for a run this window owns. - **Change Review**, over refs resolved against the conversation's own repository. ![Manage Agents tabs](images/manage-agents-tab-well.png) ### The right panel An activity bar on the right edge switches between panels, all of them the solution window's own, pointed at the **selected conversation's** folder and solution: | Panel | Shows | Needs a solution | | ------------------ | --------------------------------------------------------------------------------- | ---------------- | | **Changes** | What this conversation changed - model elements and files, grouped by application | No | | **Source Control** | Git for the conversation's own checkout | No | | **SF Changes** | Files staged by Software Factory runs for the conversation's solution | Yes | | **Codebase** | The Codebase Explorer tree over the conversation's workspace folder | No | | **Specifications** | `intent/.specs` under the conversation's workspace root | Yes | Panel buttons carry a badge - a count, or a spinner while it is recomputing - and clicking the lit one collapses the panel to the bar. A task that has not settled on a folder yet (a worktree requested but not yet created) shows an empty state rather than borrowing the previous task's repository. ### The toolbar and status bar The toolbar is the solution window's, minus what needs an open solution: navigation, save and save-all, undo/redo, Search Everywhere, New Terminal, the **Open \** chip, **Tasks** from the conversation's `tasks.json`, and **Open in IDE** (which opens the folder the agent runs in, not the solution's). The status bar along the bottom carries the same Software Factory taskbar entries a solution window shows - a run started by an agent's tool call is otherwise invisible, and this is where you watch it, open its output, or stop it. On the right sit three fleet counters: **running**, **needs answer** and **solutions in store**. > [!NOTE] > A Software Factory run started from this window is tied to the conversation that started it. Its taskbar entry names the chat, folder and branch, so two runs of the same application in different worktrees do not share an Output tab or restart each other. ## Agents that reach the model The reason all of this is hosted here rather than in a bare chat list is that a task dispatched from this window can still read and change its own Intent model. This window answers designer requests **on behalf of the conversations it hosts** - opening designers in the background, in the asking conversation's own scope, resolved against _that_ conversation's solution rather than whatever is on screen. An agent working in a worktree of a repository nobody has open does not have to ask you to open a solution first. A folder governed by several `.isln` files is a first-class case rather than an error: the composer's **solution** chip lists the candidates (with each solution's own icon, since sibling solutions often differ only by a suffix), marks the default and remembers your choice for that folder - for the dispatch about to be made _and_ for the agent's own MCP server. ## Keyboard shortcuts These work here exactly as they do in a solution window: | Shortcut | Action | | ----------------------------------- | ------------------------------------------- | | `Ctrl + N` | New chat | | `F2` | Rename the selected conversation | | `Ctrl + T` | Search Everywhere, over the task's solution | | `` Ctrl + ` `` | New terminal, in the task's folder | | `Ctrl + Tab` / `Ctrl + Shift + Tab` | Switch tabs | | `Ctrl + W` / `Ctrl + Shift + W` | Close tab / close all tabs | | `Alt + ←` / `Alt + →` | Navigate backward / forward | | `Ctrl + S` | Save the active tab | | `Ctrl + Shift + V` | Toggle rendered Markdown preview | See [Keyboard Shortcuts](https://docs.intentarchitect.com/docs-md/application-development/user-interface/keyboard-shortcuts/keyboard-shortcuts.md) for the full list. ## Related articles - [AI Assistant](https://docs.intentarchitect.com/docs-md/application-development/ai-assistant/index.md) - the AI Assistant itself: modes, providers and configuration. - [Change Review](https://docs.intentarchitect.com/docs-md/application-development/change-review/change-review/change-review.md) - reviewing what a task actually changed, in the model and in code. - [How to change user settings](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md) - where the worktree scratch root is configured. - [Terminal & Tasks](https://docs.intentarchitect.com/docs-md/application-development/software-factory/terminal-and-tasks/index.md) - the `tasks.json` behind the Tasks toolbar. - [Release notes: Intent Architect version 5.3](https://docs.intentarchitect.com/docs-md/release-notes/intent-architect-v5.3.md) - the release that introduced Manage Agents. --- # About Designers Designers in Intent Architect allow you describe your [application's](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md) design as visual models and hierarchical concepts. For example, Designers could be used to describe: the entities in a domain, the services that make up the applications API, events that are published and subscribed, etc. Designers are added to an [Application](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md) when a [Module](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) that has designer configuration is installed. You can therefore choose which Designers you would like to use in your Application. ![Domain Designer](images/designers-domain.png) _An example of a sample application showing a Domain Designer with a modelled domain._ Designers serve as a **blueprint** of your system. They can be used to describe any aspect of your Application. The following are some examples of what they are used to capture: - **Codebase Structure** - Visual Studio Projects in .NET, Folder Structures in other languages, etc. - **Entities** - Entities and their relationships to one another, Domain Driven Design (DDD) concepts (Aggregate Roots, Entities and Value Objects), etc. - **Database Schemas** - Tables, Documents, Foreign keys, Indices, etc. - **Services** - RESTful web services, SOAP services, security settings, transactional settings, etc. - **Client Proxies** - Web client proxies, Synchronous Query Proxies, etc. - **Eventing** - Messages, Topics, Queues, Subscriptions, etc. Often used to support a Microservices architectures. - **Workflows** - Workflow Diagrams, Process Diagrams, etc. - **Front-End Structure** - Components, Routing, Modules, View Models, Views, etc. Intent Architect Designer behaviour and element visuals are configuration driven and [module authors are able to make Designers](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/about-designer-extensions/about-designer-extensions.md) perfectly suited to just about any scenario. --- # About Packages A Package in Intent Architect is a file system folder containing a `.pkg.config` file and various subfolders with `.xml` files for persistence of metadata modeled in Intent Architect [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md). Designers can reference any number of Packages and Packages can reference other Packages. ![Example of a designer with packages](images/example-of-designer-with-packages.png) _In the example in the picture above, the `Domain` Designer has two Packages loaded/referenced, `SampleApplication.Domain` and `SampleApplication.AdditionalEntities`._ ## Creating new or adding existing Packages to a designer To create a new Package or add an existing Package to a Designer use the `Create new package` and `Add existing package` buttons on the Designer toolbar: ![The create and add existing buttons](images/package-save-and-open-buttons.png) When "adding an existing package" a Designer reference is created which is referring relatively to the chosen path and the actual Package content itself is not copied. If the added Package is in a folder outside of the source code management (e.g. Git) repository for the Application, it may make more sense to first copy it to the [default packages location](#default-package-locations). ## Adding references to other packages Before a type or element in one package can refer to or use types or elements in another package, it requires a reference to the package containing the type or element, otherwise the element or type will not be available for selection. As an example, in the screenshot above the `SampleApplication.AdditionalEntities` package has a `BaseEntity` inside of it. If we were to try use it for inheritance from the `SampleApplication.Domain` package, it is not on the list of items which can be selected: ![BaseEntity not available for selection](images/base-entity-not-available-for-selection.png) To make it available for selection we need to add a reference to the other package. Right-click the `References` node and then select the `Add Package Reference...` option: ![The Add Package Reference... option](images/add-package-reference-option.png) Then ensure the package you want referenced is checked, in this case `SampleApplication.AdditionalEntities`, and then press `OK`: ![Package Reference Manager](images/package-reference-manager.png) The `SampleApplication.AdditionalEntities` package is now present if you were to expand the `References` node: ![The "SampleApplication.AdditionalEntities" package now visible under References](images/package-now-visible-under-references.png) And BaseEntity can now be selected as an option for inheritance: ![BaseEntity now available as an option for inheritance](images/base-entity-now-available-as-an-option-for-inheritance.png) ## The `Is External` checkbox All packages in Intent Architect have a checkbox option labelled `Is External`. When checked, the Software Factory will generally ignore the concepts modelled in that package. For example in the below picture there are two packages. `OnlineShop.Customers.Domain` has the `Is External` checkbox option selected and this is also visible by the `[external]` text next to the package name in the tree view. In this case, entities within it (`Address` and `Customer`) will not be generated by the Software Factory, while entities in the "non-external" `OnlineShop.Invoice.Domain` (`Invoice`, `Line` and `Product`) package will. ![Example of Is External in use](images/is-external-example.png) ## Default Package locations By default, Package folders are placed in the `Intent.Metadata/` folder which is relative to an application's `.application.config` file. One can easily open an Application's `.application.config` file's containing folder by clicking the path link beneath the Application's name on the [Application Settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) screen of the application: ![Application config location on the settings screen](images/application-config-location.png) --- # About Stereotypes Intent Architect's Designers enables modelling of [Stereotypes](https://en.wikipedia.org/wiki/Stereotype_(UML)) which can be used to extend the vocabulary of Designer Elements. Stereotypes allow "decoration" of elements and are analogous to decoration features in programming languages, such as [`C# Attributes`](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/) or [`Java Annotations`](https://en.wikipedia.org/wiki/Java_annotation). Common use of Stereotypes is specifying technology or business domain specific metadata for Elements in the Intent Architect designer, such as a Class having a business requirement of being audited, or a Service Operation's HTTP method. ## A practical example For example, Intent Architect's standard Services Designer only allows modelling generic Services and generic Operations on them. ![What Intent Architect's standard Services Designer looks like](images/services-designer-vanilla.png) After installing the `Intent.Metadata.WebApi` [module](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md), an `Http Settings` Stereotype can be applied on Operations and allows specifying via which HTTP Verbs should be accessible. This Stereotype enriches the data model for the operation allowing us to describe the Http related aspects of the service operation. The Stereotype can be located on the Properties pane located to the right of the Designer: ![What the Services Designer looks like with API Metadata](images/services-designer-api-metadata.png) Stereotypes not only offer a form of "tagging" on Elements found in Intent Architect Designers, but they also allow one to capture additional information using properties. These properties can hold different data types, allowing for a richer way of specifying more information on the Element at hand. ## Making your own Stereotypes can be defined by the use of Stereotype Definitions, refer to this [article](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/about-stereotype-definitions/about-stereotype-definitions.md) for further details. ## See also - [How to use Stereotypes](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/how-to-use-stereotypes/how-to-use-stereotypes.md) --- # Codebase Structure Designer The **Codebase Structure Designer** is used for configuring the file system and folder structure into which files for an application are to be generated into or laid out. This designer will allow you to create new Folders, new [Output Anchors](#output-anchors) and to change the location of [Template Outputs](#template-outputs). ![Screenshot of the Codebase Structure Designer](images/screenshot.png) ## Template Outputs `Template Output`s are used by the Software Factory to determine for each template where it should be generated to on your file system. They are automatically created/removed/renamed by Intent Architect when Modules are installed, updated or uninstalled and cannot be manually created or renamed. By reorganizing them into different folders within the `Codebase Structure` Designer, you are able to control the layout and structure of your codebase. Depending on the template, their output may go into file system sub-folders from their `Template Output`, for example DTOs placed in folders in the Services Designer will be generated into corresponding sub-folders on your file system. As long as Module authors ensure that their templates use the default of suggested APIs to dynamically work out relative locations of related templates, an application's codebase structure can be completely customized. Initial placement of `Template Output`s are controlled by [Output Anchors](#output-anchors). Once an `Output Template` is placed, its location within your codebase structure is not changed when a module is updated or reinstalled, this is to allow users to customize their codebase structure without a module update undoing it. To force a `Template Output` to be re-placed at its default location you will need to completely uninstall and then install the module again. ## Runtime Environments When you are using the Visual Studio concepts in Codebase Structure, you can add `Runtime Environment` elements under a project to tell Intent Architect which environment-specific configuration files should be generated. To add one: 1. Right-click the relevant project in the Codebase Structure designer. 2. Select **Add Runtime Environment**. 3. Give the environment a name such as `Dev`, `Prod` or `Tst`. ![Add Runtime Environment](images/runtime-environments.png) Each `Runtime Environment` you model allows Intent Architect to generate a matching environment-specific `appsettings` file alongside the base `appsettings.json` file. ![Generated appsettings files](images/appsettings-generation.png) This is also what template authors rely on when they call APIs such as `ApplyAppSetting(..., runtimeEnvironment: "Prod")`. The `runtimeEnvironment` value must match a modeled `Runtime Environment` element on the target project, otherwise the request will not be applied to an environment-specific file. > [!NOTE] > If you are configuring environment-specific `appsettings` output from a template or factory extension, see [How to Update `appsettings.json` Files](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/how-to-update-appsettings-json-files/how-to-update-appsettings-json-files.md) for the code-side usage. ## Output Anchors `Output Anchor`s are arbitrary tags used to control the initial placement of a [Template Outputs](#template-outputs) at the time Modules are being installed or updated. They allow high level configuration of `Template Output` locations without each individual `Template Output`'s location needing to be individually considered when creating a new application or later installing additional modules into it. For example, if you have a domain-oriented module, when it's installed the `Template Output`s for its various templates will be automatically placed relative to the location of the `Output Anchor` named `Domain`. In this way Module authors are able to target generalized logical locations for their templates as opposed to specific ones. ## Registration filtering Template Outputs in the Codebase Structure designer can control where templates generate their files. The **Registration Filter** allows you to conditionally include or exclude a particular template instance from generating to a particular Template Output based on properties of the model element the template instance is running against. ### Configuring the Registration Filter The Registration Filter is set via the **Template Output Settings** stereotype on a Template Output element. Select the Template Output in the designer and look for the **Registration Filter** property in the Properties panel. If the Registration Filter field is left empty, the Template Output matches all model instances (i.e., the template runs for every element of the relevant type). ### Duplicating Template Outputs for conditional routing When you want different registration behaviour for different subsets of a model type, you must **duplicate the Template Output** - one copy per branch. For example, to route to different folders based on a package name, you would have two Template Outputs with the same template name but different filters: ```text Controllers/ Intent.AspNetCore.Controllers.Controller [filter: np(Operations.FirstOrDefault().InternalElement.Package.Name) == "MyApp.Services.Command"] Intent.AspNetCore.Controllers.Controller [filter: np(Operations.FirstOrDefault().InternalElement.Package.Name) == "MyApp.Services.Query"] ``` ### Ensuring filters are mutually exclusive Each model instance must match **at most one** Template Output's filter. If a template's model is matched by more than one Template Output filter, the template will attempt to be registered multiple times causing an error in the Software Factory. Design filters so they are collectively exhaustive over the cases you care about and mutually exclusive with each other. ### Expression syntax Registration Filters use [System.Linq.Dynamic.Core](https://dynamic-linq.net/) (Dynamic LINQ) expression syntax. The expression must evaluate to a `bool`. #### Useful operators and functions | Feature | Syntax | Description | | --- | --- | --- | | Null propagation | `np(expr)` | Returns `null` instead of throwing if any part of `expr` is `null`. Essential when navigating optional relationships. | | Equality | `==`, `!=` | Standard equality comparison. | | Logical | `&&`, `\|\|`,`!` | Boolean logic. | | String methods | `.StartsWith()`, `.Contains()`, `.EndsWith()` | Standard string instance methods. | | LINQ methods | `.Any()`, `.All()`, `.FirstOrDefault()`, `.Where()` | Standard LINQ extension methods on collections. | Full documentation for the expression language is available at: #### Example filters ```text // Match only when a package has a specific name np(Operations.FirstOrDefault().InternalElement.Package.Name) == "MyApp.Services.Command" // Match when the element itself has a specific name prefix Name.StartsWith("Create") // Match when there is at least one operation with a specific tag np(Operations.Any(o => o.Name == "Execute")) ``` ### Model instance type The expression runs against a **model instance** of the type that corresponds to the template's registered model. The type varies by Template Output name, which maps to the template's registered ID (i.e., the value of the Template Output's `Name` property matches the template's registered ID). To find the exact model type and its available properties for a given template: 1. Identify the Template Output's name - this is the template ID (e.g., `Intent.AspNetCore.Controllers.Controller`). 2. Search for that template in the source repositories, e.g.: - [Intent.Modules](https://github.com/IntentArchitect/Intent.Modules) - [Intent.Modules.NET](https://github.com/IntentArchitect/Intent.Modules.NET) 3. Locate the template class and inspect its generic type parameter (e.g., `IntentTemplateBase`) - that is the model type passed to the filter expression. 4. Inspect the model class to discover the properties and navigation paths available in the filter expression. > [!NOTE] > Model properties generally align with what is visible in the Intent Architect designers, but the source repositories are the authoritative reference for exact property names and navigation chains. ## See also - **[Visual Studio Module](https://docs.intentarchitect.com/modules-dotnet/intent-visualstudio-projects/intent-visualstudio-projects.html)** - Extends the Codebase Structure Designer with Visual Studio concepts such as solutions and projects. --- # Modeling the Domain ## Adding a Diagram You can add `Diagram`s to your `Domain Package`. By default, `Application Template`s typically include a default one. Diagrams are useful for: - Visualizing your domain. - Visually modeling your domain. - Visualizing subsets or logical areas of a large domain. 1. Right-click on the **Domain** package and select **New Diagram**. 2. If the designer was in Tree-view, it will switch to a diagram view. 3. Rename the diagram by right-clicking it in the Tree-view and selecting **Rename**. 4. Add domain elements to your diagram: - From the context menu, select **Add to Diagram** and choose `Elements` to add to the `Diagram`. - Drag domain elements, like `Class`s, from the Tree-view onto the diagram. - Model new domain elements. 5. Optionally, create multiple diagrams for different perspectives. The Tree-view remains the source of truth. > [!TIP] > Hold down **CTRL** while dragging elements from the Tree-view to include directly associated elements. ![Diagram View](./images/add-diagram.png) ## Creating an Entity An `Entity` is a core building block in domain modeling that represents a uniquely identifiable object within a system, such as a Customer or Order. Entities are essential because they encapsulate business rules and behavior, ensuring the system consistently reflects the real-world concepts they model. > [!NOTE] > While we typically do domain modeling in diagrams, it's important to realize that the Tree-view is the source of truth. Diagrams are simply a visualization of the domain or a part of the domain. 1. `Add an Entity` to the diagram. 2. Give your `Entity` a name. > [!NOTE] > The `Entity`'s element type is `Class`. You can now model various aspects of your entity: - **Attributes**: Model the data of your `Entity`. - **Associations**: Model the relationships between your `Entity`s. - **Constructors**: Model the construction of your `Entity`. - **Operations**: Model the behavior of your `Entity`. ### Capturing Attributes To model the data of an `Entity`: 1. `Add Attribute` (Ctrl + Shift + A) on a class. 2. Enter the name of the `Attribute`. 3. Press **Tab** and select the `Type` for the attribute. > [!TIP] > Pressing F2 on an attribute allows you to rename and change its type. > [!TIP] > If you have an `Attribute` selected, pressing **CTRL+ENTER** will add a new `Attribute` to the class and begin editing it. This is very useful for rapidly capturing attributes. ### Capturing Constructors To model constructors of an `Entity`: 1. `Add Constructor` (Ctrl + Shift + U) on a class. 2. [Capture any parameters](#capturing-parameters-for-constructors-and-operations) as required. ### Capturing Operations To model operations (mnethods) of an `Entity`: 1. `Add Operation` (Ctrl + Shift + O) on a class. 2. Enter the name of the `Operation`. 3. Press **Tab** and select the `Return Type` for the operation. 4. [Capture any parameters](#capturing-parameters-for-constructors-and-operations) as required. ### Capturing parameters for constructors and operations To model parameters for a [constructor](#capturing-constructors) or an [operation](#capturing-operations) on a class: 1. `Add Parameter` (Ctrl + Shift + A) on a class. 2. Enter the name of the `Parameter`. 3. Press **Tab** and select the `Type` for the parameter. > [!TIP] > Pressing F2 on an parameter allows you to rename and change its type. > [!TIP] > If you have an `Parameter` selected, pressing **CTRL+ENTER** will add a new `Parameter` and begin editing it. This is very useful for rapidly capturing parameters. ### Adding Entity Associations To model the relationships an `Entity` has with other `Entity`s: 1. Ensure both `Entity`s are on the diagram; use `Add to Diagram` if needed. 2. Right-click on the `Entity` that will own the relationship, hover your mouse over **New Association**, and select the type of relationship you want from the submenu (e.g., `Many to One`). 3. Left-click on the related `Entity` to create the association between them. > [!NOTE] > For more details on modeling domain `Association`s, [click here](#modeling-entity-associations). ![Basic Domain](./images/create-domain.png) ## Inheritance with Entities To inherit one `Entity` from another: 1. Right-click on the `Entity` that will inherit and select **New Inheritance**. 2. Select the parent `Entity`. ![Inheritance Entity](./images/inheritance-entity.png) ## Creating a Domain Contract A `Domain Contract` encapsulates a specific intent or operation to be performed within a domain, bundling the data required for that operation into a single object. They help maintain clear and explicit boundaries in the domain, promoting separation of concerns and ensuring the domain logic operates on well-defined inputs. Domain `Data Contract`s are sometimes referred to as (domain) Data Transfer Objects or Command Objects. > [!NOTE] > The `Domain Contract`'s element type is `Data Contract`. 1. `Add Domain Contract` in the Tree-view. 2. Name your `Domain Contract`. 3. Right-click the `Domain Contract` and select **Add Attribute** to define its data. 4. Add complex data types as needed: - *Domain Contract* for modeling nested structures. - *Enum* for modeling enumerations. ![Domain Contract](./images/domain-contracts.png) ## Modeling Entity Associations Entity `Association`s are modeled using a UML style visualization, and define the relationships between `Entity`s. ![Domain Association](./images/domain-association.png) Visually the `Association`s communicates the following: -**Multiplicity**, the numbers above the `Association` indicate the multiplicity, 1 to Many (1 -*) in the example above. -**Ownership**, denoted by the diamond shape, its color indicates the type of ownership. -**Navigability**, arrows on the line show navigability from one entity to another, `Order` will have a `OrderItems` property. ### Multiplicity When adding an association you can use the sub-menu to choose the multiplicity you want e.g. 1 to Many, 1 to 1 etc. You can adjust the multiplicity, in the `Properties pane` by changing the `Is Nullable` and `Is Collection` properties on the Association Ends (Source End and Target End). Multiplicity for an `Association End` is configured as follows: | Multiplicity | IsNullable | IsCollection | |--------------|------------|--------------| | **1** | ⬜ | ⬜ | | **0..1** | ✅ | ⬜ | | **\*** | ⬜ | ✅ | ### Ownership When creating an Association the `Entity` you create it from will be the owner, you can see this visually by the diamond on the `Association` line attached to the owning `Entity`. There are two types of ownership which can be modeled and this ownership is inferred by the multiplicity. These ownership types are `Composition` (black diamond) and `Aggregation` (white diamond). #### Composition relationship 1. When attempting to represent real-world whole-part relationships, e.g. an engine is a part of a car. 2. When the container is destroyed, the contents are also destroyed, e.g. a university and its departments. *- [Wikipedia](https://en.wikipedia.org/wiki/Class_diagram#Composition)* > [!TIP] > An entity should never have multiple `Composition` relationships pointint to different owners, this is a logical modeling error as an Entity can only have 1 owner. #### Aggregation relationship 1. When representing a software or database relationship, e.g. car model engine ENG01 is part of a car model CM01, as the engine, ENG01, may be also part of a different car model.[8] 2. When the container is destroyed, the contents are usually not destroyed, e.g. a professor has students; when the professor dies the students do not die along with them. *- [Wikipedia](https://en.wikipedia.org/wiki/Class_diagram#Composition)* | Relationship | Ownership Type | |---------------|----------------| | 0..1 -> 0..1 | white diamond | | 0..1 -> 1 | white diamond | | 0..1 -> * | white diamond | | 1 -> 0..1 | black diamond | | 1 -> 1 | black diamond | | 1 -> \* | black diamond | | \* -> 0..1 | white diamond | | \* -> 1 | white diamond | | \* -> \* | white diamond | > [!TIP] > You can convert a `black diamond` relationship to a `white diamond` by changing the owner of the relationship, i.e. create the relationship from the other `Entity`. ### Navigability By default `Navigability` is configured to be from `Owner` to target Entity. If you want bi-directional navigation, simply check **Navigable** on the `Source End` of the association in the `Properties pane`. Modeling `Entity` associations can be a bit of an art who "owns" a relationship can be subjective and should be driven by your use cases. ## Modeling Multiple Domains with Different Persistence Concerns You can model and connect your application to multiple domains by adding additional **Domain Packages** in the *Domain Designer*. To create multiple domains: 1. **Create a New Domain Package** - In the *Domain Designer*, right-click in the **tree view** and select **New Package**. - Alternatively, use the **Create New Package** button in the toolbar. ![New Domain Package](./images/new-package.png) 2. **Name the Domain Package** - Provide a meaningful name for the Domain Package. ![New Domain Package Name](./images/new-package-name.png) 3. **Define the Database Type** - Apply the appropriate **stereotype** to specify the type of database the domain will use. - Available options depend on the installed modules. ![Domain Type](./images/package-stereotype.png) > [!TIP] > The available **database type** options are determined by the installed modules. For example, to use the `Document Database` stereotype, a corresponding implementation modules like `Intent.MongoDb` or `Intent.CosmosDB` must be installed. Similarly, for the `Relational Database` stereotype, a module such as `Intent.EntityFrameworkCore` must be installed. 4. **Configure Database Settings** - Set any required settings for the selected database type. ![Database Provider](./images/provider-settings.png) > [!TIP] > If a single implementation module (e.g. `Intent.MongoDb` or `Intent.CosmosDB` for example) is installed, it will automatically be used as the default provider, eliminating the need to set it explicitly. Each domain can now be modeled separately in their own respective package as per the instructions detailed in this page. --- # Invoking HTTP Endpoints In distributed architectures, services often need to communicate with one another. Writing service clients manually can be time-consuming, error-prone, and inconsistent. A _Service Proxy_ simplifies this process by allowing developers to reference _already defined services_ in other Intent Architect applications and automatically generate strongly typed clients that adhere to the specified service contract. The generated `Service Proxy` acts as an intermediary between an application and an external service, providing a strongly typed API that abstracts away the complexity of request configuration and client setup. This article explains how to model `Perform Invocation` relationships which are then realized in generated code as Service Proxies which will use HTTP to communicate with target endpoints. > [!NOTE] > > Historically, [Modeling Service Proxies](https://docs.intentarchitect.com/docs-md/application-development/modelling/services-designer/modeling-service-proxies/modeling-service-proxies.md) was required to invoke HTTP Endpoints, although that method is still fully supported, going forward the approach of using `Perform Invocation` as described in this article is recommended. This article will describe the process of creating a Service Proxy from an example `eShop.Invoicing` application to an `eShop.Customers` application in the following solution: ![Intent Architect Solution Explorer showing the two applications](images/solution-explorer.png) The Services designer for the `eShop.Customers` application has the following CQRS requests modeled: ![Screenshot of the Services designer in the eShop.Customers application](images/customers-services-designer.png) ## Adding the Customer CQRS requests to a diagram We will start by adding the CQRS requests from the `eShop.Customers` applications onto a diagram in the `eShop.Invoicing` application's Services designer. In the Services designer of the `eShop.Invoicing` application: - [Add a package reference](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-packages/about-packages.md) to the `eShop.Customers.Services`. ![Package references manager screen showing the packages which need to be selected](images/package-references-manager.png) This will make the CQRS requests from the referenced package available for use in our designer. - Right-click the package on the designer and select the `New Folder` option: ![New Folder context menu option on a package](images/package-new-folder-option.png) - Give the folder a name, such as `Customers`. - Right-click the folder and select the `New Diagram` option: ![New Diagram context menu option on a folder](images/new-diagram-option.png) - Give the diagram a name such as `Customers`. - Right-click the background of the diagram and select the `Add to Diagram` option: ![Add to Diagram context menu option](images/add-to-diagram-option.png) - You can filter by `Customer` and then select all the Commands and Queries and then press DONE: ![Items selected to be added to the diagram](images/add-to-diagram-dialog.png) ![Items added to the Diagram](images/screenshot-showing-items-added-to-diagram.png) ## Creating a request and have it invoke a CQRS request over HTTP - Right-click the diagram and choose the `New Query` option: ![New Command context menu option](images/new-command-option.png) - Give the Command a name such as `GetCustomerByIdCommand`. - Right-click the Command and select the `Invoke Service` option: ![Invoke Service context menu option](images/invoke-service-option.png) - Click the `GetCustomersByIdQuery` to set it as the Target End. ![Invoke Service association with target end set](images/invoke-service-target-end-selected.png) - Right-click the association line and select the `Map Call Operation` option: ![Map Call Operation context menu option](images/map-call-operation-option.png) - Double click the `GetCustomerByIdQuery` element in the right-pane to map the command from the left-pane to it: ![Mapping created between GetCustomerByIdCommand and GetCustomerByIdQuery](images/command-mapped-to-query.png) - Right-click the `CommandCustomerByIdCommand` in the left pane and select the `Add Property` option: ![Add Property context menu option](images/add-property-option.png) - Give it a name of `Id` and type of `Guid`: ![The new property given a name and type](images/id-property-name-and-type.png) - You can now double click the `Id: guid` in the right-pane to specify that its field needs to be populated from the `Id` on the command in the left pane: ![Mapping created between the Id properties](images/properties-mapped.png) - Press DONE. ## Run the Software Factory Run the Software Factory and review the proposed changes: ![Proposed software factory changes](images/proposed-software-factory-changes.png) Reviewing the changes, observe the following in particular: - `ICustomersService` is being is being created and registered up in `HttpClientConfiguration` against the also created `CustomersServiceHttpClient`. - Other related contract files such as the Command and its referenced DTOs are being created. - The `GetCustomerByIdCommandHandler` has an implementation as follows: ```csharp public class GetCustomerByIdCommandHandler : IRequestHandler { private readonly ICustomersService _customersService; [IntentManaged(Mode.Merge)] public GetCustomerByIdCommandHandler(ICustomersService customersService) { _customersService = customersService; } [IntentManaged(Mode.Fully, Body = Mode.Fully)] public async Task Handle(GetCustomerByIdCommand request, CancellationToken cancellationToken) { var result = await _customersService.GetCustomerByIdAsync(request.Id, cancellationToken); } } ``` ## Invoking Service Operations The procedure for invoking traditional Service Operations is essentially the same as the above where `Invoke Service` associations are created with service operations as their target end: ![Screenshot of diagram showing Invoke Service association to the operation of a traditional service](images/invoke-traditional-service-operation-on-diagram.png) ![Mapping configuration for invoking a traditional service operation](images/invoke-traditional-service-operation-mapping-screen.png) ## Service Proxy for a 3rd party service You can have a proxy created for a third-party services and invoke it, provided the service definition is modeled in Intent Architect. Invoking the service follows the same steps described [above.](#invoking-http-endpoints) To represent a third-party service, you must create a **separate application** in your Intent Architect solution. Use the _External API_ application template for this purpose: - Right-click the solution in Intent Architect and select **Create New Application**. - Choose the `External API` application template. ![External API](images/external-api-template.png) - Give the application a clear, descriptive name to identify it as a third-party service. - An empty Intent Application will be created and added to your solution, pre-configured with the necessary modules to model an external API. ### Using Intent.OpenApi.Importer If the third-party service provides a OpenApi document, you can import it directly into the `Services Designer` using the [Intent.OpenApi.Importer](https://docs.intentarchitect.com/modules-dotnet/intent-openapi-importer/intent-openapi-importer.html) module. Once imported, follow the steps outlined above to [invoke the service](#invoking-http-endpoints). > [!NOTE] > > The [Intent.OpenApi.Importer](https://docs.intentarchitect.com/modules-dotnet/intent-openapi-importer/intent-openapi-importer.html) module is automatically installed when creating an application using the `External API` template. ### Manually Modeling the Service If no OpenApi document is available, you can manually model the third-party service in the `Service Designer`. Once modeled, follow the same steps above to [invoke the service](#invoking-http-endpoints). ## Summary This article guided you through using the `Invoke Service` association to invoke HTTP endpoints. ## Next steps You can try invoking other endpoints in the same way as described above. --- # Message-Based Integration Modeling ## What is Message-Based Integration / Event-Driven Architecture Message-based integration is a design approach for systems to communicate asynchronously. It uses a message broker, such as RabbitMQ, Kafka, or Azure Service Bus, to exchange messages. This approach decouples systems, allowing each service to operate independently. It improves scalability, fault tolerance, and flexibility in system design. By exchanging data or triggering actions without direct dependency on other systems’ availability or implementation, message-based integration fosters resilience. It is especially useful in distributed systems requiring real-time or asynchronous processing, such as processing orders, handling events, or coordinating microservices. ## How to Model Message-Based Integrations Within Intent Architect, you can model your application’s Message-Based Integration or Event-Driven Architecture. The diagram below illustrates this type of design: ![Message Based Integration](./images/message-based-integration.png) This example demonstrates the following: - The application subscribes to a `CustomerCreated` integration message. - This triggers an `AccountCreatedCommand` service endpoint. - The process publishes an `AccountCreated` integration message for subscribed applications. You can model this design in the Services Designer with these simple steps: 1. **Model the message contracts**: Design the data contracts that flow between your systems. 2. **Model where these messages are published**: Specify which parts of the application send integration messages to other systems. 3. **Model who subscribes to the messages**: Configure which integration messages your application wants to process. > [!NOTE] > Integration message are typically categorized as either an `Integration Event` or an `Integration Command`. Within Intent Architect the model type which represents a Integration Event is a `Message`. ## Integration Events vs Integration Commands When modeling integration messages, you can use either model Integration Events (`Message`) or `Integration Command`s. While similar from a modeling perspective, they have distinct characteristics. The table below summarizes their differences: | Aspect | Integration Event | Integration Command | |---------------------------|-----------------------------------|-----------------------------| | **Technical realization** | Topic | Queue | | **Purpose** | Notifies about an occurrence | Instructs an action | | **Recipients** | Multiple subscribers (potentially)| Single recipient | | **Expectations** | No action mandated; subscribers decide how to react | Sender expects the action to be performed | | **Ordering of messages** | Unordered | Naturally ordered (queued) | In general, Integration Events are more commonly used, and many systems adopt a purely event-driven paradigm. ## Integration Message Naming Conventions You can use any naming convention for integration messages. Within the Intent Architect ecosystem, visual distinctions between concepts like `Message`, `Integration Command`, and `Domain Event` make them easy to identify. However, when working in an IDE, naming overlaps may cause confusion. For example, having both a `CustomerCreated` domain event and a `CustomerCreated` integration message could be unclear. To address this, you can apply suffix-based naming conventions, such as `CustomerCreatedDomainEvent` or `CustomerCreatedDE`. ## Making Integration Messages Available in Other Applications When modeling Message-Based Integrations, you define the message contracts your application publishes. These contracts are stored as `Message`s and `Integration Command`s in an `Eventing Package` within the publishing application. To use these messages in a subscribing application: 1. In the `Services Designer`, under the `Service Package`, right-click `References` and select **Add a Package Reference**. 2. In the `Package Reference Manager` dialog, select the package containing the integration messages, e.g., `PublishingApp.Eventing.Messages`. ![Add Package Reference](./images/add-package-reference.png) You can now use these integration messages in the diagrams of the subscribing application. ## Publishing an Integration Event You can publish an Integration Event from any of the following elements: - Service `Command` - Service `Operation` - `Domain Event Handler` - `Integration Event Handler` ### Creating and Publishing a New Integration Event 1. On a diagram, hover over the element and click the `Suggestion` icon :bulb:. 2. Select **Publish Integration Event**. - This adds a new `Message` and associates it with the selected element. 3. Enter a name for the `Message` and press **Enter**. 4. Use the **Advanced Mapping Dialog** to map the structure of the `Message`: - Double-click elements on the left-hand side to add and map them to your `Message`. ![Publishing a Message from a Service Command](./images/publish-event-from-command.png) ### Publishing an Existing Integration Event 1. Add the existing `Message` to the diagram using **Add to Diagram**. 2. Right-click on the service `Command` and select **Publish Integration Event**. Link it to the `Message`. 3. Right-click the `Publish Integration Event` association and select **Map to Message**. 4. Map the relevant data between the service `Command` and the `Message`. > [!TIP] > If the `Message` you want to use isn’t available in the **Add to Diagram** dialog, ensure the package containing the `Message` is referenced in the `Services Package`. ## Create an Integration Event To create a new Integration Event (`Message`): 1. Add a `Integration Event` to any diagram in the `Services Designer`. This typically represents an integration message your application will publish. 2. Name your `Integration Event`. 3. Right-click the `Integration Event` and select **Add Property** to define its data. 4. Add complex data types as needed: - *Eventing DTO* for modeling nested structures. - *Enum* for modeling enumerations. ![Modeled Message](./images/message-modeling.png) > [!NOTE] > `Message`s are modeled in an `Eventing Package` to facilitate sharing between applications. > [!TIP] > Using the **Advanced Mapping Dialog** while [publishing the integration message](#publishing-an-integration-event) can speed up message structure definition. ## Subscribing to an Integration Event If your application (`SubApp`) wants to subscribe to a `Message` published by another application (`PubApp`): > [!NOTE] > These steps assume you already have a package reference to the publishing application's messages. If you don't, [add a package reference to the publishing application's integration messages](#making-integration-messages-available-in-other-applications). ### Subscribing with a new `Integration Event Handler` 1. On a diagram, select **Add to Diagram** and choose the `Message` to subscribe to. 2. Hover over the `Message`, click the `Suggestion` icon :bulb:, and select **Create Integration Event Handler**. ![Message Subscribed](./images/subscribe-integration-message.png) > [!TIP] > If the `Message` you want to subscribe to doesn’t exist in the `Eventing Package` (e.g., it’s from outside the Intent Architect ecosystem), model the `Message` as described in [Create an Integration Message](#create-an-integration-event). ### Subscribing with an existing `Integration Event Handler` 1. On a diagram, select **Add to Diagram** and choose the `Message` you want to subscribe to. 2. Right-click on the `Integration Event Handler` and select **Subscribe to Integration Event**. 3. Left-click on the `Message` you want the `Integration Event Handler` to handle. ![Message subscribed existing handler](./images/subscribe-existing-handler.png) ## Publishing an Integration Command You can publish an 'Integration Command` from any of the following elements: - Service `Command` - Service `Operation` - `Domain Event Handler` - `Integration Event Handler` ### Creating and Publishing a New `Integration Command` 1. On the diagram, hover over the element and click the `Suggestion` icon :bulb:. 2. Select **Publish Integration Command**. - This adds a new `Integration Command` and associates it with your `Command`. 3. Enter the name of your `Integration Command` and press Enter. 4. Use the **Advanced Mapping Dialog** to project the structure of your message: - Double-click elements on the left-hand side to add and map them to the `Integration Command`. ![Publishing an Integration Command from a Service Command](./images/publish-integration-command-from-command.png) ### Publishing an Existing `Integration Command` 1. On the diagram, select **Add to Diagram**, and choose the existing `Integration Command` you want to send. 2. Right-click on the service `Command`, select **Publish Integration Command**, and left-click the `Integration Command` to link them. 3. Right-click on the `Publish Integration Command` association and select **Map to Message**. 4. Map the relevant data between the service `Command` and the `Integration Command`. > [!TIP] > If the `Integration Command` you are looking for is not available in the **Add to Diagram** dialog, ensure you have added the `Package` containing the `Integration Command` as a reference to the `Services Package`. ## Create an Integration Command To create an `Integration Command`: 1. Add a new `Integration Command` to any diagram in the `Services Designer`. This typically represents an integration message your application will send. 2. Name your `Integration Command`. 3. Right-click on the `Integration Command` and select **Add Property** to start modeling the data structure of the message. 4. Add additional data types as needed: - **Eventing DTO** - for modeling complex child objects. - **Enum** - for modeling enumerations. ![Modeled Integration Command](./images/integration-command-modeling.png) > [!NOTE] > `Integration Command`s are modeled in an `Eventing Package` to facilitate sharing between applications. > [!TIP] > Using the **Advanced Mapping Dialog** while [publishing the integration message](#publishing-an-integration-command) can speed up message structure definition. ## Subscribing to an Integration Command If your application (`SubApp`) wants to subscribe to an `Integration Command` published by another application (`PubApp`): > [!NOTE] > These steps assume you already have a package reference to the publishing application's messages. If you don't, [add a package reference to the publishing application's integration messages](#making-integration-messages-available-in-other-applications). ### Subscribing with a new `Integration Event Handler` 1. On a diagram, select **Add to Diagram** and choose the existing `Integration Command` you want to subscribe to. 2. Hover over the `Integration Command`, click the `Suggestion` icon :bulb:, and select **Create Integration Event Handler**. ![Integration Command subscribed](./images/subscribe-integration-command.png) > [!TIP] > If the `Integration Command` you want to subscribe to does not exist in an `Eventing Package` (e.g., it is outside the Intent Architect ecosystem), you can model it manually as [described here](#create-an-integration-command). ### Subscribing with an existing `Integration Event Handler` 1. On a diagram, select **Add to Diagram** and choose the `Integration Command` you want to subscribe to. 2. Right-click on the `Integration Event Handler` and select **Subscribe to Integration Command**. 3. Left-click on the `Message` you want the `Integration Event Handler` to handle. ![Integration Command subscribed existing handler](./images/subscribe-integration-command-existing-handler.png) ## Invoking Application Services from Integration Event Handlers When subscribing in an integration message, you may want to delegate the handling of that message to an existing Service Endpoint. ## Invoking CQRS Commands from Integration Event Handlers 1. On a diagram, select **Add to Diagram** and choose the `Command` you want to delegate to. 2. Right-click on the `Integration Event Handler` and select **Send Command**. 3. Left-click on the `Command` you want the `Integration Event Handler` to delegate to. 4. Right-click on the linking `Association` and select **Map to Command**. ## Invoking Traditional Service operations from Integration Event Handlers 1. On a diagram, select **Add to Diagram** and choose the `Service` you want to delegate to. 2. Right-click on the `Integration Event Handler` and select **Call Service Operation**. 3. Left-click on the service `Operation` you want the `Integration Event Handler` to delegate to. 4. Right-click on the linking `Association` and select **Map to Service Operation**. ## Realizing Your Message-Based Integration in Technologies Your message-based integration can be realized in a specific technology depending on the installed modules. Options include: - [MassTransit](https://github.com/IntentArchitect/Intent.Modules.NET/blob/development/Modules/Intent.Modules.Eventing.MassTransit/README.md) (supports RabbitMQ, Azure Service Bus, Amazon SQS) - [Kafka](https://github.com/IntentArchitect/Intent.Modules.NET/blob/development/Modules/Intent.Modules.Eventing.Kafka/README.md) - [Solace](https://github.com/IntentArchitect/Intent.Modules.NET/blob/development/Modules/Intent.Modules.Eventing.Solace/README.md) [mi](#migrating-from-the-eventing-designer) ## Migrating from the Eventing Designer As of version `6.0.0` of the `Intent.Modelers.Eventing` module, the Eventing Designer is no longer available and all message based integration modelling will need to be performed in the Services designer going forward. If you were modelling message based integration using the Eventing Designer then a straight forward manual migration will need to be performed or you will otherwise get an error when running the software factory directing you to this page. You should perform the migration before upgrading to `6.0.0` or newer of the `Intent.Modelers.Eventing` module. - Open the Eventing Designer and make a note of the following: - The name of each message to which the current application is a subscriber. For example, in the following image, the `EventingMigration` is an "Application" element and it has one subscriber, `SubscribedEvent`, indicated by the `subscribe:` prefix: ![Tree view of the eventing designer](images/migrating-from-the-eventing-designer/eventing-designer-tree-view.png) - Select the Eventing package (the root most node) for the current application and in the properties pane select the context menu button and click the `Copy file path to clipboard option`: ![The Eventing Package's copy file path to clipboard option](images/migrating-from-the-eventing-designer/copy-file-to-clipboard-option.png) - Open the Services Designer - Click the `Add Existing Package` button on the toolbar: ![Add Existing Package toolbar button](images/migrating-from-the-eventing-designer/add-existing-package-toolbar-button.png) - You can paste the complete path from your clipboard into the "File name" field and simply press "Open": ![Open package dialogue](images/migrating-from-the-eventing-designer/open-package-dialogue.png) - You will see the following warning on which you can press OK to proceed: ![Warning dialogue](images/migrating-from-the-eventing-designer/warning-dialogue.png) - Delete any "Application" elements which are distinguishable by having a grey box icon and when selected they have `Application (not found)` in the properties pane: ![Application elements to delete](images/migrating-from-the-eventing-designer/application-elements-to-delete.png) - Click the package and uncheck the `Is External` option: ![Is External option](images/migrating-from-the-eventing-designer/is-external-option.png) - You can now save the designer and on the Confirmation dialogue warning of undetected errors click the "YES" button. - There should no longer be errors in the designer: ![No more warnings in the designer](images/migrating-from-the-eventing-designer/warnings-now-gone-from-designer.png) - Right-click on `References` under the `Services` package and select the `Add Package Reference...` option: ![Add Package Reference... option](images/migrating-from-the-eventing-designer/add-package-reference-option.png) - Ensure you have the opened package selected in the list: ![Package Reference Manager](images/migrating-from-the-eventing-designer/package-reference-manager.png) - You can now model Integration Message Handlers as per the [Subscribing to an Integration Event](#subscribing-to-an-integration-event) section of this article. > [!NOTE] > > The generated files for the Integration Message Handlers may not align with the existing handler implementations and you may need to either try change the names to align or alternatively manually copy the implementations from the old to the new files manually in your IDE. ## Common Problems ### New Integration Event or Command not Generating If using the `New Integration Event/Command` menu item or the `Publish Integration Event/Command` suggestion does not result in a new element being added, the issue is most likely due to a missing `Eventing Package` in the Services Designer. To resolve this: - Add a new `Eventing package` to your Services Designer: ![Add package](images/add-package.png) - Give the package a relevant name: ![New Eventing package](images/new-eventing-package.png) - In your `Service Package`, right-click `References` and select **Add Package Reference**. ![Add reference](images/new-service-reference.png) - Select the previously created `Eventing Package` ![Add Eventing Package](images/eventing-package-selected.png) Once the reference is added, you will be able to successfully create and publish Integration Events and Integration Commands. --- # Modeling Service Proxies ## What is a Service Proxy? In distributed architectures, services often need to communicate with one another. Writing service clients manually can be time-consuming, error-prone, and inconsistent. A _Service Proxy_ simplifies this process by allowing developers to reference _already defined services_ in other Intent Architect applications and automatically generate strongly typed clients that adhere to the specified service contract. The generated `Service Proxy` acts as an intermediary between an application and an external service, providing a strongly typed API that abstracts away the complexity of request configuration and client setup. Initially, Intent Architect required explicit modeling of Service Proxy elements as described in this article, but we have since made it possible for Service Proxies to be implicitly generated based on `Perform Invocation` relationships directly to Service Operations or CQRS requests which allows for quicker and simpler modelling. For more information on this approach please refer to the [Invoking HTTP Endpoints](https://docs.intentarchitect.com/docs-md/application-development/modelling/services-designer/invoking-http-endpoints/invoking-http-endpoints.md) article. > [!NOTE] > Although explicit modeling of Service Proxies as described in this article is still fully supported by Intent Architect, the alternative approach as described in the [Invoking HTTP Endpoints](https://docs.intentarchitect.com/docs-md/application-development/modelling/services-designer/invoking-http-endpoints/invoking-http-endpoints.md) article is the recommended approach going forward. ## Configuring Service References Before creating a `Service Proxy`, the service to be proxied must be added as a reference in the `Services Designer`. In the example below, we have an **Order Service** and a **Product Service**, where we will generate a **Product Service Proxy** inside the **Order Service**. ![Application setup](images/application-setup.png) The **Product Service** has services modeled and exposed: ![Services exposed](images/services-exposed.png) In the `Services Designer` of the **Order Service** (the consuming service), a reference must be added to the **Product Service Services Package** (the consumed service): ![Service reference](images/service-reference.png) ![Designer reference](images/designer-reference.png) Once this reference is established, a `Service Proxy` can be created for the **referenced service** (Product Service in this case). ## Creating a Service Proxy Once the consuming service (the Order service in this example) has a reference to the service to be consume (Product Service), a `service proxy` can be created in the `Services Designer` of the **consuming service**. 1. Right-click on the `Service Package` (the root) or on a `folder`, and select `New Service Proxy` > [!NOTE] > If the `New Service Proxy` menu item is not available, the most likely cause is that a required module (e.g. `Intent.Integration.HttpClients` for .NET HTTP client proxies) has not been installed. 2. Select the Service for which you are creating the proxy: ![Service selection](images/service-select.png) 3. Choose the operations to include in the service proxy, then click **DONE**: ![Operation selection](images/operation-select.png) > [!TIP] > If a `Service Operation` or `Command/Query` is greyed out and unable to be selected, check the following: > > - Ensure the operation, command, or query has been **exposed as an HTTP endpoint**. Service proxies can only be created for exposed services. > - Verify that the command or query is **inside a folder in the Services Designer of the consumed service**. Service proxies cannot be created for commands and queries placed directly in the root of the Services package. ## Updating a Service Proxy Since a`Service Proxy` is based on an existing service, its details (parameters, return type, etc.) cannot be changed directly. Instead, modifications must be made in the underlying service, and then the proxy must be updated to reflect those changes. However, an existing `Service Proxy` can be modified to include additional operations or remove existing ones: 1. Right click on the `Service Proxy` and select `=> Service Reference` 2. Select the operations to include/exclude and click `DONE` ## Invoking a Service Proxy The `Advanced Mapping` feature in the _Services Designer_ allows for easy invocation and mapping of `Service Proxy` operations. Additionally, several `accelerators` can generate CQRS operations or service operations that automatically map to proxy operations. ### Manual Mapping to Invoke Proxy Operation To manually configure a call to a `Service Proxy` operation, use `Call Service Operation`. ![Call Service Operation](images/call-service-operation.png) Select the `Proxy Operation` to invoke: ![Select Service Operation](images/select-operation.png) Next, map the input parameters for the selected operation: ![Select Service Operation](images/proxy-mapping.png) Then, adjust the execution order if needed. In the example below, the call to the service proxy operation has been moved up to occur **before** persisting the entity to the domain. ![Action order](images/adjust-order.png) The return values from the service call can now be mapped for subsequent operations, such as persisting the entity: ![Result Mapping](images/proxy-result-mapping.png) ### Auto Mapping from CQRS Operations An `accelerator` is available to automatically create CQRS operations and map them to service proxy operations. Right-click on the `Service Proxy` or specific `Service Proxy Operation(s)`, then select **Create CQRS Operations** / **Create CQRS Operation**. This generates CQRS operations that call the `Proxy Service`, with parameters and return types matching those of the proxy service. ![CQRS Operations](images/cqrs-proxy.png) > [!NOTE] > The `Create CQRS Operations` and `Create Service` options are available at a **Proxy Service** level. These options will accelerate the creation of the `commands/queries/operations` that don't already exist. Selecting them again will have no effect if all `commands/queries/operations` have already been created. > > [!NOTE] > The `Create CQRS Operation` and `Create Service Operations` options are available at the **Proxy Operation** level. These options will accelerate the **creation or updating** of the selected `command/query/operation`. If selected for a proxy operation which is already linked to a proxy `command/query/operation`, the proxy `command/query/operation` synced with the source `command/query/operation`. Attributes, attribute types, and default values will be updated to match the source as closely as possible. Be aware this could result in a loss of manual changes which have been made to linked proxy `command/query/operation`. ### Auto Mapping from Service An `accelerator` is also available to automatically create service operations and map them to proxy operations. Right-click on the `Service Proxy` or specific `Service Proxy Operation(s)`, then select **Create Service** / **Create Service Operation**. This generates service operations that call the `Proxy Service`, with parameters and return types matching those of the proxy service. ![Service Operations](images/service-proxy.png) > [!NOTE] > The `Create CQRS Operations` and `Create Service` options are available at a **Proxy Service** level. These options will accelerate the creation of the `commands/queries/operations` that don't already exist. Selecting them again will have no effect if all `commands/queries/operations` have already been created. > > [!NOTE] > The `Create CQRS Operation` and `Create Service Operations` options are available at the **Proxy Operation** level. These options will accelerate the **creation or updating** of the selected `command/query/operation`. If selected for a proxy operation which is already linked to a proxy `command/query/operation`, the proxy `command/query/operation` synced with the source `command/query/operation`. Attributes, attribute types, and default values will be updated to match the source as closely as possible. Be aware this could result in a loss of manual changes which have been made to linked proxy `command/query/operation`. ## Service Proxy for a 3rd party service You can have a proxy created for a third-party services and invoke it, provided the service definition is modeled in Intent Architect. To represent a third-party service, you must create a **separate application** in your Intent Architect solution. Use the _External API_ application template for this purpose: - Right-click the solution in Intent Architect and select **Create New Application**. - Choose the `External API` application template. ![External API](images/external-api-template.png) - Give the application a clear, descriptive name to identify it as a third-party service. - An empty Intent Application will be created and added to your solution, pre-configured with the necessary modules to model an external API. ### Using Intent.OpenApi.Importer If an OpenApi document is available for the third-party service, the [Intent.OpenApi.Importer](https://docs.intentarchitect.com/modules-dotnet/intent-openapi-importer/intent-openapi-importer.html) module can import the service definition directly into the `Services Designer`. Once imported, follow the steps outlined above to [create a service proxy](#creating-a-service-proxy). > [!NOTE] > > The [Intent.OpenApi.Importer](https://docs.intentarchitect.com/modules-dotnet/intent-openapi-importer/intent-openapi-importer.html) module is automatically installed when creating an application using the `External API` template. ### Manually modeling the service If no OpenApi document is available, you can manually model the third-party service in the `Service Designer`. Once modeled, follow the same steps above to [create a service proxy](#creating-a-service-proxy). --- # Modeling Services The **Services Designer** in Intent Architect is a powerful tool for modeling "Application Services" in your applications. This module enables developers to define how an application can be interacted with at the service level, allowing for the creation of both internal services and publicly exposed endpoints. ## What is an Application Service? An application service is a layer in an application's architecture that serves as an intermediary between the domain layer (business logic) and the presentation layer (e.g., user interface or API). It orchestrates use cases and workflows, delegating detailed domain logic to the domain layer. ### Key Characteristics of an Application Service - **Coordinates Use Cases**: Encapsulates specific use cases or workflows, such as "Register a User" or "Place an Order." - **Delegates Domain Logic**: Delegates core business logic to domain entities or domain services. - **Handles Input/Output**: Processes input from the presentation layer (e.g., HTTP requests) and returns output (e.g., HTTP responses or data transfer objects). - **Transaction Management**: Manages transaction boundaries, such as starting, committing, or rolling back database transactions. - **Separates Layers**: Prevents the presentation layer from interacting directly with the domain layer, maintaining separation of concerns. - **Interacts with Infrastructure**: Uses repositories, mappers, or other infrastructure components to fetch or persist data. ## Service Modeling Paradigms Intent Architect provides two paradigms for modeling application services: **CQRS (Command Query Responsibility Segregation)** and **Traditional Services**. This flexibility allows you to design services tailored to your system's architectural requirements, whether you prioritize scalability and clarity or prefer a unified approach. Modeling services focuses on defining the flow of data into and out of your application (data contracts). You can also optionally model implementations for your services. ### CQRS Paradigm - Separates read and write responsibilities into distinct models optimized for their respective purposes. - **Commands** handle state-changing operations, focusing on business logic and domain consistency. - **Queries** handle data retrieval, often accessing read-optimized data stores or projections. - Ideal for systems with complex requirements or high scalability demands. - Use case-centric. ![CQRS Paradigm](./images/cqrs-paradigm.png) ### Traditional Service Paradigm - Combines read and write logic into a single service. - Simplifies development by using a unified data model and service structure. - Common in systems with straightforward requirements or minimal scalability concerns. ![Traditional Service Paradigm](./images/traditional-service-paradigm.png) Intent Architect enables you to effectively model services using either approach. ## Exposing an Application Service By default, application services are only available internally. To expose these services for external consumption, you must take explicit action. This involves making critical decisions: - Which service endpoints will be exposed? - Over which technology will they be exposed? - What are the technology-specific configurations (e.g., security, addressing)? The methods available for exposing services depend on the modules installed. For example, if you have the `Intent.Metadata.WebApi` module installed, you can **Expose as HTTP Endpoint** to expose services over HTTP using REST conventions. ## Creating a CQRS Command 1. Add a `Command` to a diagram in the **Services Designer**. 2. Name the `Command`, typically suffixed with `Command` (e.g., `CreateCustomerCommand`). 3. Right-click the `Command` and select **Add Property** to define its data. 4. Add complex data types as needed: - **DTO** for nested structures. - **Enum** for enumerations. 5. *Optional*: Define the return type of the `Command` in the property pane or by pressing **F2**. ![Modeled CQRS Command](./images/create-cqrs-command.png) ### Implementing the Command Once applied to your codebase: 1. Right-click on the `Command` and select **Open in IDE -> {CreateCustomerCommandHandler.cs}**. 2. Implement your business logic in the `Handle` method. > [!NOTE] > Many service implementations are predictable and repetitive. Intent Architect can generate these implementations for you: [Modeled Service Implementations](#modeled-implementations). > [!TIP] > Quickly model or bootstrap your services using the [CQRS CRUD Accelerator](#create-crud-cqrs-operations-accelerator). ## Creating a CQRS Query 1. Add a `Query` to a diagram in the **Services Designer**. 2. Name the `Query`, typically suffixed with `Query` (e.g., `GetCustomerByIdQuery`). 3. Right-click the `Query` and select **Add Property** to define its data. 4. Add complex data types as needed: - **DTO** for nested structures. - **Enum** for enumerations. 5. Select return type of the `Query` in the property pane or by pressing **F2** (typically a `DTO`). ![Modeled CQRS Query](./images/create-cqrs-query.png) ### Implementing the Query Once applied to your codebase: 1. Right-click on the `Query` and select **Open in IDE -> {QueryHandler.cs}**. 2. Implement your business logic in the `Handle` method. > [!NOTE] > Many service implementations are predictable and repetitive. Intent Architect can generate these implementations for you: [Modeled Service Implementations](#modeled-implementations). > [!TIP] > Quickly model or bootstrap your services using the [Traditional Service CRUD Accelerator](#create-crud-traditional-service-accelerator). ## Creating a Traditional Application Service To create a service with operations: 1. Right-click on the diagram and select **New Service**, then provide a unique name. 2. Right-click the service and select **Add Operation**, then provide a name. 3. Right-click the operation and select **Add Parameter**. Specify its name and type. If it represents an inbound payload, select the corresponding DTO. 4. Leave the type as `void` for operations with no return value, or choose an appropriate return type. ![Modeled Traditional Service](./images/service-with-operations.png) ### Implementing the Service Once applied to your codebase: 1. Right-click the `Service` and select **Open in IDE -> {OrganizationsService.cs}**. 2. Implement your business logic in the method corresponding to your modeled `Operation` (e.g., `CreateOrganization`). > [!NOTE] > Many service implementations are predictable and repetitive. Intent Architect can generate these implementations for you: [Modeled Service Implementations](#modeled-implementations). > [!TIP] > Quickly model or bootstrap your services using the [Traditional Service CRUD Accelerator](#create-crud-traditional-service-accelerator). ## Creating a DTO To create a DTO: 1. Right-click on the **Service Package** or a containing folder and select **New DTO**, then provide a unique name. 2. Right-click the DTO and select **Add Field**. Specify the name and type. ![Normal DTOs](./images/normal-dtos.png) ## Inheriting from a DTO To inherit one DTO from another: 1. Right-click on the DTO that will inherit and select **New Inheritance**. 2. Select the parent DTO. ![Inheritance DTO](./images/inheritance-dto.png) ## Mapping an Outbound DTO To map outbound DTOs: 1. Right-click on the DTO that will receive the mapped information and select **Map From Domain**. 2. In the dialog, specify the domain entity and select the attributes to include in the outbound DTO. 3. Check the desired attributes and click **Done**. This links your domain data to the DTO. ![Outbound Mapping DTO](./images/outbound-mapping-dto.png) ## Adding a Diagram to the Services Designer To enhance visual organization: 1. Right-click on the **Services** package and select **New Diagram**. 2. If the designer was in Tree-view, it will switch to a diagram view. 3. Rename the diagram by right-clicking it in the Tree-view and selecting **Rename**. 4. Drag services from the Tree-view onto the diagram to create visual representations. 5. Optionally, create multiple diagrams for different perspectives. The Tree-view remains the source of truth. ![Diagram View](./images/diagram-view.png) > [!TIP] > Hold down **CTRL** while dragging elements from the Tree-view to include directly associated elements. ## Using Accelerators to Rapidly Model Services Accelerators are macros or scripts that automate repetitive modeling tasks, saving time and ensuring consistency. ### Create CRUD CQRS Operations Accelerator This accelerator models a CQRS service with a CRUD implementation, including the following: - **Commands**: - Create Entity Command - Update Entity Command - Delete Entity Command - **Queries**: - Get Entity by Id Query - Get All Entities Query - `Commands` based on the `Entity`'s operations. 1. Right-click on the **Services Package**, and select **Create CRUD CQRS Operations**. 2. Select the domain `Entity` to model the service around. > [!NOTE] > Not all entities types will be available for selection. The following entities types will be available: - **Aggregate roots** - **Collection composites that are entities** of the aggregate root The following entities will **not** be available for selection: - **One-to-one composites** of the aggregate root - **Value Objects**, even when they appear as collection composites under the root ![Generated CRUD CQRS Service Example](./images/accelerator-crud-cqrs.png) > [!NOTE] > You can also run this accelerator on a **Folder** in the **Services Designer**. ### Create CRUD Traditional Service Accelerator This accelerator models a Traditional Service with CRUD implementations, including the following operations: - Create Entity - Update Entity - Delete Entity - Get Entity by Id - Get All Entities - `Operations` based on the `Entity`'s operations. 1. Right-click on the **Services Package**, and select **Create CRUD Traditional Service**. 2. Select the domain `Entity` to model the service around. > [!NOTE] > Not all entities types will be available for selection. The following entities types will be available: - **Aggregate roots** - **Collection composites that are entities** of the aggregate root The following entities will **not** be available for selection: - **One-to-one composites** of the aggregate root - **Value Objects**, even when they appear as collection composites under the root ![Generated CRUD Traditional Service Example](./images/accelerator-crud-traditional.png) ### Paginate Accelerator Pagination enables large datasets to be returned in smaller, more manageable chunks. The `Paginate` accelerator is available for any **Operation** or **Query** that returns a collection. 1. Right-click on the qualifying **Operation** or **Query**. 2. Select the **Paginate** menu item. Two primary types of pagination are available, dependant on the underlying database provider: - **Offset-based pagination**: Common in SQL-based systems. Uses page numbers and sizes. - **Cursor-based pagination**: Often used in NoSQL or distributed systems. Uses tokens for consistent, ordered traversal. > [!NOTE] > If the provider only supports a single pagination type, a single `Paginate` menu item will be available. If multiple types are supported, multiple options will appear under the `Paginate` menu. #### Offset-based Pagination When **offset-based pagination** is applied, the following occurs: - The return type of the `Operation` or `Query` is changed from `TReturnType` to `PagedResult`. - Three parameters/properties are added to the `Operation`/`Query`: - **PageNo**: Specifies the page number to retrieve, based on the *PageSize*. - **PageSize**: Specifies how many records should be included in a single page. - **OrderBy**: Specifies how data should be sorted before pagination. This is optional, and defaults to database ordering if omitted. It will order data in **ascending order** by default. > [!NOTE] > If using the default CRUD implementation, the `PageNo` parameter is *1-based* by default (first page = 1), renaming the parameter to `PageIndex` will make it *0-based* (first page = 0). > [!TIP] > The CRUD modules treat the `OrderBy` as a dynamic LINQ statement. The `OrderBy` parameter supports, a single entity property (e.g., `name`), multiple entity properties (e.g., `created, name`), sorting directions for each property (e.g., `name desc`, `created desc, name asc`). Examples of valid `OrderBy` formats: - `name` - `name asc` - `name desc` - `created, name` - `created desc, name asc` ![Pagination](./images/paginate.png) The `PagedResult` returned by the `Operation` or `Query` contains the following fields: - `TotalCount`: Total number of records available - `PageCount`: Total number of pages - `PageSize`: Number of records per page - `PageNumber`: Current page number - `Data`: Collection of returned data records #### Cursor-based Pagination When **cursor-based pagination** is applied, the following occurs: - The return type of the `Operation` or `Query` is changed from `TReturnType` to `CursorPagedResult`. - Three parameters/properties are added to the `Operation`/`Query`: - `PartitionKey`: The partition key to be queried - `PageSize`: The number of records to include per page - `CursorToken`: The optional opaque token used to fetch the next set of results. If not set, the first set of results is retrieved. > [!NOTE] > A default query mapping is applied to filter by `PartitionKey`. You may remove this parameter if the query is intended to span all partitions. ![Cursor Pagination](./images/cursor-paginate.png) The `CursorPagedResult` returned by the `Operation` or `Query` contains the following fields: - `PageSize`: Number of records per page - `CursorToken`: Token used to retrieve the next set of results - `HasMoreResults`: Indicates if there are more results available - `Data`: Collection of returned data records ## Modeled Implementations ### Create Entity Action This action allows you to model the creation of a domain `Entity` (`Class`) using either an `Object Initializer` or a `Constructor`. It can be applied to a `Command`, a service `Operation`, or a `Domain Event Handler Association` (referred to as the `Element` below). #### Creating a Domain Entity using Object Initialization 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to create. 2. Right-click on the `Element` and select **Create Entity**. 3. Connect the `Element` to the `Entity` by left-clicking it. This opens the `Create Entity Mapping` dialog to map data from the `Element` to the `Entity`. 1. Double-click the `Entity` in the right-hand panel. A purple line appears, indicating the creation of the `Entity` using an `Object Initializer`. 2. Map data from the `Element` to the `Entity`: - **Double-click the `Entity` again** to map all attributes and add missing ones. - **Double-click an `Entity` attribute or `Element` property** to automatically map (or create and map) them. - **Drag an an `Entity` attribute or `Element` property to it's counter part***, to map them. - **Drag multiple mappable items from either side to the other side's background**, this will batch map the items, adding items if applicable. ![Create Entity Action Mapping](./images/create-entity-action-object-mapping.png) > [!TIP] > If no mappable property exists, you can define an expression (e.g., `true`, `0`, `""`) in the text box next to the attribute. ![Create Entity Action ](./images/create-entity-action-object-initializer.png) > [!TIP] > To revisit the mapping screen, right-click on the `Create Entity Action` or the association linking the `Element` and `Entity`, and select **Map Entity Creation**. #### Creating a Domain Entity using a Constructor 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to create. 2. Right-click on the `Element` and select **Create Entity**. 3. Connect `Element` to a `Constructor` on the `Entity` Left-click on the `Constructor`. This will open the `Create Entity Mapping` Dialog, this dialog helps you map data from the `Element` to the `Entity`. 1. Double-Click the `Constructor` in right hand panel. This will add purple line between the `Element` and the `Constructor`, this represents how the `Entity` will be created i.e. using this `Constructor`. 2. Map how the data from the `Element` to the `Constructor`, this can be done in several ways: - **Double-click the `Constructor` again**, this map all the `Constructor` parameters to the corresponding `Element` properties, adding missing ones where required. - **Double-click an `Constructor` attribute or `Element` property** to automatically map (or create and map) them. - **Drag an an `Constructor` attribute or `Element` property to it's counter part***, to map them. ![Create Entity Action Mapping](./images/create-entity-action-constructor-mapping.png) > [!TIP] > If no mappable property exists, you can define an expression (e.g., `true`, `0`, `""`) in the text box next to the attribute. ![Create Entity Action ](./images/create-entity-action-constructor.png) > [!TIP] > To revisit the mapping screen, right-click on the `Create Entity Action` or the association linking the `Element` and `Entity`, and select **Map Entity Creation**. ### Update Entity Action This action models updates to a domain `Entity` (`Class`) using its `Attributes` or an entity `Operation`. It applies to a `Command`, a service `Operation`, or a `Domain Event Handler Association` (referred to as the `Element` below). #### Updating a Domain Entity Using Its Properties 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to update. 2. Right-click on the `Element` and select **Update Entity**. 3. Connect the `Element` to the `Entity` by left-clicking the `Entity`. This opens the `Update Entity Mapping` dialog, where you can: 1. Map data from the `Element` to the `Entity`: - **Select `Entity` attributes, drag them to the background of the left-hand side**, mapping the `Entity` attributes to the corresponding `Element` properties. - **Double-click an `Entity` attribute or `Element` property** to automatically map (or create and map) them. - **Drag an individual `Element` property onto a `Entity` attribute**, this will map the two elements. ![Update Entity Action Mapping](./images/update-entity-action-mapping.png) 2. Click **Map Entity Query** to define how the `Entity` should be retrieved: - Map the primary key of the `Entity` to corresponding `Element` properties. ![Update Entity Action ](./images/update-entity-action-properties.png) > [!TIP] > Revisit the mapping screen by right-clicking the `Update Entity Action` or the association and selecting **Map Entity Update**. #### Updating a Domain Entity using a Domain Entity Operation 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to create. 2. Right-click on the `Element` and select **Update Entity**. 3. Connect `Element` to an `Operation` on the `Entity` by left-clicking the `Operation`. This opens the `Update Entity Mapping` dialog, where you can: 1. Map how the `Operation` will be invoked : 1. Double-click the `Operation` in right hand panel. This will add purple line between the `Element` and the `Operation`, this represents the invocation of the `Operation`. 2. Map data from the `Element` to the `Operation`: - **Double-click the `Operation` again**, this map all the `Operation` parameters to the corresponding `Element` properties, adding missing ones where required. - **Double-click an `Operation` parameter or `Element` property** to automatically map (or create and map) them. - **Drag an individual `Element` property onto a `Operation` parameter**, this will map the two elements. ![Update Entity Action Operation Mapping](./images/update-entity-action-operation-mapping.png) 2. Click **Map Entity Query** to define how the `Entity` should be retrieved: - Map the primary key of the `Entity` to corresponding `Element` properties. ![Update Entity Action using an Operation](./images/update-entity-action-operation.png) > [!TIP] > Revisit the mapping screen by right-clicking the `Update Entity Action` or the association and selecting **Map Entity Update**. ### Delete Entity Action This action allows you to model the deletion of a domain `Entity` (`Class`). It can be applied to a `Command`, a service `Operation`, or a `Domain Event Handler Association` (referred to as the `Element` below). 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to delete. 2. Right-click on the `Element` and select **Delete Entity**. 3. Connect the `Element` to the `Entity` by left-clicking the `Entity`. ![Delete Entity Action](./images/delete-entity-action.png) > [!NOTE] > For `Domain Event Handler` you will need to configure how the Domain Entity is queries, simply right-click on the `Delete Entity Action` (Connecting Association) and select **Map Entity Filter** and map the domain `Entity`s primary key. ### Query Entity Action This action allows you to model the querying of a domain `Entity` (`Class`). It can be applied to a `Query`, `Command`, service `Operation`, or `Domain Event Handler Association` (referred to as the `Element` below). ### How to Query a single Entity 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to query. 2. Right-click on the `Element` and select **Query Entity**. 3. Connect the `Element` to the `Entity` by left-clicking the `Entity`. 4. Select the `Association` you just created (`Query Entity Action`). 5. Right-click and select **Map Entity Query**. This opens the `Query Entity Mapping` dialog, where you model the criteria for Entity Select: 6. Map the filter criteria for querying your Entity, here are a few ways you could do this - Double-click the primary key of the `Entity`. - Map the primary key of the `Entity` to corresponding `Element` properties. - Map one or more `Entity` attributes, which uniquely identify the `Entity`, to corresponding `Element` properties. 7. Click **Done** to complete the mapping (even if no filter criteria are applied). ![Query Entity Action](./images/query-entity-action-single.png) ### How to Query a Collection of Entities 1. On a diagram, select **Add to Diagram** and choose the domain `Entity` you want to query. 2. Right-click on the `Element` and select **Query Entity**. 3. Connect the `Element` to the `Entity` by left-clicking the `Entity`. 4. Select the `Association` you just created (`Query Entity Action`). 5. In the **Properties** pane, check **Is Collection** (shortcut: `Alt + C`). You can also rename the Name **entity** to "entities" or similar (this will be the name of the variable the query results are assigned to). 6. Right-click the association and select **Map Entity Query**. This opens the `Query Entity Mapping` dialog, where you model the criteria for Entity Select: 7. *Optionally* [Map Filter criteria](#how-to-apply-mapped-filters-to-a-query) for the `Entity` selection. 8. Click **Done** to complete the mapping (even if no filter criteria are applied). ![Query Entity Action Collection](./images/query-entity-action-collection.png) ### How to apply mapped filters to a Query 1. On a Query that has a field used for filtering, right click on the Query action and select **Map Entity Query**. This opens the `Query Entity Mapping` dialog. 2. Map one or more `Entity` attributes, which uniquely identify the `Entity`, to corresponding `Query` properties. You can double-click on an `Entity` attribute to create a corresponding field on the `Query` element that is also mapped. 3. Click **Done** to complete the mapping. ![Mapping Field for Query](images/query-for-mapped-filters-mapping.png) > [!NOTE] > Currently, filter criteria mapping only supports "==" conditions. More complex filters must be implemented in code. ### Service Returns and Query Entity Actions `Query Entity Action` is designed specifically for modeling the querying of data and is not directly tied to the **Return Type** of the `Element` it is modeled on. Suppose you are modeling a `Query` named `GetCustomersQuery`. This query would likely return a collection of `CustomerDto`s. The implementation of the `GetCustomersQueryHandler` would involve the following steps: 1. Query the database to get a list of `Customer`s. 2. Transform the `Customer`s to `CustomerDto`s and return the `DTO`s. The `Query Entity Action` focuses solely on step 1 - querying the database. It is unrelated to step 2, which involves transforming the data into a different return type. When using `Query Entity Action` alongside our **CRUD** modules, these modules employ heuristic algorithms to handle the wiring for step 2 automatically. This mapping can be established using the **Map From Domain** context menu option on the `DTO`. This ensures that the transformation from the queried type (`Customer`) to the return type (`CustomerDto`) is recognized and automated. The return type and query type must also share the same value for **Is Collection**. If the query retrieves a collection of entities, the return type should also be a collection. Similarly, if the query retrieves a single entity, the return type should not be a collection. ### Call Service Operation Action The **Call Service Operation Action** allows you to model the invocation of `Service` or `Domain Service` operations. It can be applied to a `Command`, `Query`, service `Operation`, or `Domain Event Handler Association` (collectively referred to as the `Element` below). 1. On a diagram, select **Add to Diagram** and choose a `Service` or `Domain Service` you want to invoke. 2. Right-click on the `Element` and select **Call Service Operation**. 3. Connect the `Element` to the `Operation` you want to invoke, by left-clicking the `Operation`. This opens the `Service Operation Mapping` dialog, where you can: 1. Map data from the `Element` to the `Operation` invocation: - **Double-click the `Operation` again**, this map all the `Operation` parameters to the corresponding `Element` properties, adding missing ones where required. - **Double-click an `Operation` attribute or `Element` property** to automatically map (or create and map) them. - **Drag an an `Operation` attribute or `Element` property to it's counter part**, to map them. ![Service Operation Call Mapping](./images/service-operation-call-mapping.png) ![Service Operation Call](./images/service-operation-call.png) ### Processing Actions > [!WARNING] > This feature is experimental, unsupported, and NOT recommended for use. It is likely to change or be removed in the future. **Add Processing Action** allows you configure additional implementation details through additional mappings. These mappings are able to link any existing **actions** together. Here are a few examples of what is possible. - Invoke an Operation on a Domain Entity you are updating. - Map data from the result of a **Service Operation Call** onto an entity. --- # Modeling Service Security ## Securing Services Securing your services is as simple as applying a `Secured` stereotype to the service endpoint (`Command`, `Query`, or `Operation`). 1. Select your service endpoint and press F3 (**Apply Stereotype**). 2. Select the `Secured` stereotype. Secured services require consumers to be authenticated to access them, further authorizations can be configured by setting up `Role`s or `Policy`s against these endpoints. ![Modeled Secured Service](images/modeled-secured-service.png) > [!NOTE] > There is an application setting, "API Settings -> Default API Security" which configures whether endpoints are secured or unsecured by default. ### The `Secured` and `Unsecured` stereotypes This package provides a `Secured` and `Unsecured` stereotype which can be applied to the Service Packages, Services, Service Operations and CQRS Operations: ![Secured and Unsecured stereotypes](images/secured-and-unsecured-stereotypes.png) The `Secured` stereotype allows specifying that a resource is only accessible to authorized users and optionally that the authorized user must be within a Role or adhere to a security Policy. ![The "Secured" stereotype showing an example of using comma separated names](images/secured-stereotype-comma-separated-names.png) Depending on whether [common Roles and Policies are defined](#defining-common-roles-and-policies), Roles and Policies are specified either by entering a comma separated list of names (as per the example above), or alternatively by selecting one or more pre-defined items from a dropdown as per the example below: ![The "Secured" stereotype showing an example of using pre-defined common names](images/secured-stereotype-common-names.png) #### Role and Policy Behavior When configuring the `Secured` stereotype: - **Multiple Roles**: If multiple roles are specified, the authorization check will succeed if the user belongs to any one of the listed roles. This defines a logical OR condition across the roles. - **Multiple Policies**: If multiple policies are required, the user must satisfy all specified policies for authorization to succeed. This defines a logical AND condition across the policies. To enforce multiple policies, apply the Secured stereotype multiple times-each instance specifying a different policy. #### Unsecured The Unsecured stereotype can be used to specify that a resource does not require a user to authenticated in order to be accessed. It is only necessary to apply this if you wish a particular resource to "opt-out" of being secure if a parent element/package or application setting would otherwise cause the resource to be secured implicitly. #### Applying multiple Secured stereotypes to a resource It is possible to have multiple `Secured` stereotypes applied to the same resource, either explicitly on the resource itself or through a parent of the resource (such as its parent Service or containing Service Package) also having the stereotype applied to it. For most technologies when there are multiple Secured stereotypes applied it is required that the security requirement of _all_ the Secured stereotypes are met. > [!NOTE] > > This can be technology dependent, for example with ASP.NET Core Controllers the above is true, while with FastEndpoints [it is not possible to specify that an authenticated user be within _all_ roles or meet the requirements of _all_ policies](https://fast-endpoints.com/docs/security). ### Defining common Roles and Policies Although it is sometimes convenient to simply enter comma separated values for Roles and Policies, in some circumstances it may be preferable to pre-define the available Roles and Policies and then only allow selection from these specifically. This has advantages such as: - Being able to see a central list of available Role and Policy names. - Allows centrally updating the name of a Role or Policy. - Avoids mistakes of incorrectly using a mistyped or invalid Role/Policy names. To define Roles and Policy names, you need to first have a security configuration added to your package, right-click a Services Package and select the _Add Security Configuration_ option: ![The _Add Security Configuration_-_ option](images/add-security-configuration-menu-option.png) > [!NOTE] > > If you don't see the option on the context menu, there may already be a `Security` element in the package, meaning there is no need to make an additional one. On the Security element you then have the option to _Add Role_, _Add Policy_ and if you had already used comma separated roles there is an option to _Convert from "text based" Roles/Policies_ which will update all existing Secured stereotypes to use pre-defined Role/Policy names, creating new ones as needed: ![Security element context menu options](images/security-element-menu-options.png) --- # Type Definitions Reference any Types you have defined in your codebase inside your designers in Intent Architect by using `Type Definitions`. As an example let's assume you're building an Orders application in .NET and you would like to introduce an Enum `Country` to your `Address` Entity as an `Attribute` in your Domain designer, however in your codebase there already is a `Country` Enum that is quite lengthy due to the 249 countries defined in it. Trying to reproduce it in your designer is time consuming. ```c# namespace OrderApplication.Domain.Enums; public enum Country { [Display(Name = "Afghanistan", ShortName = "AF", Description = "AFG:004")] Afghanistan = 4, [Display(Name = "Albania", ShortName = "AL", Description = "ALB:008")] Albania = 8, [Display(Name = "Algeria", ShortName = "DZ", Description = "DZA:012")] Algeria = 12, [Display(Name = "American Samoa", ShortName = "AS", Description = "ASM:016")] AmericanSamoa = 16, [Display(Name = "Andorra", ShortName = "AD", Description = "AND:020")] Andorra = 20, // ... Additional countries omitted for brevity ... [Display(Name = "United Kingdom", ShortName = "GB", Description = "GBR:826")] UnitedKingdom = 826, [Display(Name = "United States", ShortName = "US", Description = "USA:840")] UnitedStates = 840, [Display(Name = "Zimbabwe", ShortName = "ZW", Description = "ZWE:716")] Zimbabwe = 716 } ``` Rather than redefining it in your Domain designer, you can introduce this Type without defining a whole Enum element. Right click on the Domain package and select `New Type`. ![Create new Type](images/create-new-type.png) Perform the following steps on this new `Type`: - Name it `Country`. - Right click on the type and select `Apply Stereotype`. - Select the `C#` stereotype. - In the `C#` stereotype properties specify the `Type` to be `Country`, the `Namespace` to be `OrderApplication.Domain.Enums` and ensure `Is Primitive` is `checked`. ![C# Stereotype](images/csharp-stereotype.png) In the `Address` Entity, replace the `Country` Attribute's type from `string` to `Country`. ![Country type on Address](images/country-type-on-address.png) Now when the code gets generated you will see the `Address` Entity class looking like this: ```c# using Intent.RoslynWeaver.Attributes; using OrderApplication.Domain.Common; using OrderApplication.Domain.Enums; [assembly: IntentTemplate("Intent.Entities.DomainEntity", Version = "2.0")] namespace OrderApplication.Domain.Entities; public class Address : IHasDomainEvent { public Address() { Line1 = null!; Line2 = null!; City = null!; PostalCode = null!; } public Guid Id { get; set; } public string Line1 { get; set; } public string Line2 { get; set; } public string City { get; set; } public string PostalCode { get; set; } public Country Country { get; set; } public List DomainEvents { get; set; } = []; } ``` The `Country` type is now the return type for the `Country` property and the namespace `OrderApplication.Domain.Enums` has been added as a using directive for referencing that type. --- ## Angular UI Modeling with AI This article explains how to use Intent Architect to rapidly build professional Angular UIs, leveraging both deterministic (pattern-based) and non-deterministic (AI/LLM-driven) code generation. The high-level workflow is: - **Design and generate your View Models**: Define which services to interact with and how UI navigation should work. ![Model View Model](./images/basic-viewmodel-design.png) - **Implement with AI**: Use the "Implement with AI" accelerator to have your chosen LLM generate the View. Intent Architect manages the context and LLM interactions for you. Review the proposed changes as a code diff, similar to a regular Software Factory execution. ![AI Generates View](./images/ai-review.png) - **Review and validate the code**: Since LLMs are non-deterministic, always review the generated code. With a bit of luck, you’ll get a screen like this: ![Generated Front ends](./images/genarate-search.png) > [!NOTE] > While the samples use Material Components, the approach is not Material-specific. You can configure for other component libraries as needed. > [!NOTE] > The Angular modules and application template are currently in **beta**. If you encounter issues, please let us know. See [Known Issues and Snags](#known-issues-and-limitations) for current limitations. --- ## Design and Generate Your View Model To get started, create an Angular Web Application in Intent Architect (using the Angular Web Application architecture template) ![Angular Web Application](./images/angular-template.png) > [!NOTE] > The Angular modules and architecture template are currently in beta. Make sure `Include prerelease` is checked to see the templates and modules. > [!NOTE] > A comprehensive Angular Sample application is also available. To use it, select **Explore Samples** on the left, then choose `Angular Web Application`. When creating the application, ensure the `Angular AI`, `Material`, and `Http Clients` components are all selected (these are ticked by default): ![Angular Web Application](./images/angular-components.png) ### Initial Code Generation and Module Installation Run the **Software Factory** to generate the initial application structure and infrastructure code. Before running the application, install the referenced modules. Open a console window at your application's output location (a shortcut is provided at the bottom of the Software Factory execution window). Install the modules: ```cmd npm i ``` Then start the application: ```cmd ng serve ``` ### Modeling Pages 1. Add a `Page` to a diagram in the **UI Designer**. 2. Name the `Page` to describe its function (e.g., `CustomerSearch`, `CustomerAdd`). 3. *(Optional)* Adjust the route in the property pane. ![Page Added](./images/add-page.png) ### Adding Route Parameters to a Page If your page needs **Route Parameters** (e.g., `customers/edit/{customerId}`): 1. Right-click the `Page` → **Add Property**. 2. Name the property (e.g., `CustomerId`) and set its type (e.g., `Guid`). 3. Apply the `Route Parameter` stereotype to the property (press **F3**). ![Page with Route Parameters Added](./images/add-page-with-route.png) The page route updates automatically based on the route parameters. --- ### Modeling a Dialog 1. Add a `Page` to a diagram in the **UI Designer**. 2. Name the `Page` with a **Dialog** suffix (e.g., `CustomerAddDialog`). ![Dialog Added](./images/add-dialog.png) If your dialog needs parameters: 1. Right-click the **Dialog** → **Add Property**. 2. Name the property (e.g., `CustomerId`) and set its type (e.g., `Guid`). 3. Apply the `Route Parameter` or `Bindable` stereotype (press **F3**). ![Dialog with Route Parameters Added](./images/add-dialog-with-parameters.png) --- ### Modeling UI Navigation #### Navigation to a Page 1. Right-click the `Page` (or Dialog) → **Add Navigation**. 2. Connect the navigation arrow to the destination by left-clicking the target. #### Navigation to a Dialog 1. Right-click the `Component` (Page or Dialog) → **Add Operation**. 2. Name the operation (e.g., `AddNewCustomer`). 3. Right-click the operation → **Show Dialog**. 4. Connect the navigation to the dialog by left-clicking the destination. ![Navigation Modeled](./images/navigation.png) If your `Component` has `Route Parameters`, a mapping dialog will open for you to bind those parameters. Usually, you add these parameters to your navigation `Operation`. ![Map Route Parameters](./images/map-route-parameters.png) --- ### Connecting UI Components to Services in Other Applications UI `Component`s interact with services to retrieve data or trigger changes in the system. Model this as follows: To use external services in your UI application: 1. In the `User Interface Designer`, under the `User Interface Package`, right-click `References` → `Add a Package Reference`. 2. In the `Package Reference Manager`, select the package containing the services (e.g., `OtherApplication.Services`). ![Add Package Reference](./images/add-package-reference.png) You can now use these external services in your UI diagrams. 1. In the `Component`'s suggestions, click `Call Backend Service`. 2. In the **Call Backend Service** dialog, select the service endpoint to call. > [!NOTE] > If you don't see the desired services, remember to add a package reference to the `Service Package` containing those services in the UI Designer. ![Call Backend Service to Fetch Data](./images/call-backend-service-fetch.png) Depending on the service type: - For `Query`s: The result is added to the `Component` as a property, and request parameters are modeled as `Operation` parameters. - For `Command`s: A corresponding `Model Definition` is created and added to the `Component`, mapped to the command for invocation. For `Command`s, you typically want a separate model for view concerns. > [!NOTE] > You also need the appropriate integration module for remote communication. Currently, only `Intent.Angular.HttpClients` is supported (selected by default for new Angular applications). #### Pages using a Query and Command For pages that use both a `Query` and a `Command` (for example, an Edit Page with a `Query` to retrieve the record by Id and a `Command` to update it), there is some additional configuration required. When you use the `Call Backend Service` suggestion and select both the `Query` and `Command`, two properties are created: - A property to store the response from the `Query` (this will be of type `Dto`) - A property to store the information to be passed to the `Command` and bound to the UI controls (this will be of type `Model Definition`) Perform the following steps to ensure the correct end-to-end data flow. Instead of having the data returned from the `Query` populate the `Dto`, it should populate the `Model Definition`: - On the `Call Service Operation Action` to the **GetByIdQuery** (the dashed line between the page and the `Query`), right-click and select `Map Response`. - In the right panel, delete the `Dto` property completely (including its mappings), and then configure the mappings from the response on the left-hand side to the `Model Definition` on the right-hand side. This ensures that the data returned from the `Query` call is stored in the `Model Definition` property, which is then bound to the UI controls and used when invoking the update `Command`. Here is an example of what the `Query` mapping should look like after being updated. Instead of the response being mapped to a `Dto`, it is mapped to the model. ![Update Mapping Example](images/update-mapping.png) --- ## Implementing Your View with AI > [!TIP] > Install the `Intent.AI.Angular` module and connect Intent Architect to your preferred LLM API. Ensure [User Settings](https://docs.intentarchitect.com/modules-common/intent-common-ai/intent-common-ai.html#user-settings) are complete, including a valid API key. Prompt results will vary by provider and model-experiment to find what works best for you. Once your **View Model** design is ready, use the Angular AI accelerator to generate the remaining implementation details. > [!NOTE] > Always apply your **Software Factory** before running AI prompts, as the generated code is used as input/context for the AI. ### Generate Your View with AI 1. Run and apply the **Software Factory**. 2. Right-click the `Component` → **Implement with AI**. 3. *(Optional)* Adjust settings in the AI Prompt dialog. 4. Click **Done** - Intent Architect will generate and submit a prompt to the LLM (this may take a while). 5. Review the AI's proposed changes as a code diff. 6. Click **Apply** to accept the changes. ![Implement with AI](./images/implement-ai-dialog.png) > [!NOTE] > AI is non-deterministic. While we strive for predictability, results will vary. Test and adjust as needed. --- ### Implementing Your Layout with AI 1. Run and apply the **Software Factory**. 2. Right-click the `Layout` → **Implement with AI**. 3. *(Optional)* Adjust settings in the AI Prompt dialog. 4. Click **Done** - Intent Architect will generate and submit a prompt to the LLM. 5. Review the AI's proposed changes as a code diff. 6. Click **Apply** to accept the changes. For a `Layout`, the AI will generate a menu structure based on navigation items. You can manually add or adjust menu items (such as ordering) as needed in the layout html file. --- ### Application Styling By default, the application uses two stylesheets - `styles.scss` and `theme.scss` - which leverage Material themes. You can update or modify these files (and component-specific stylesheets) to suit your branding. If you use the default implementation, you can adjust the primary and accent color themes in `Application Settings`: ![Color Theme](./images/color-theme.png) --- ### Improving AI Results You may get good results with the default AI prompt, but you can further improve outcomes with these techniques: #### Add User Prompt Context Usually, you don't need to provide extra context, but if the LLM repeats mistakes or needs more guidance (e.g., how to refactor code), add instructions in the AI dialog. Examples: - "Ensure buttons/actions exist for the new navigations I added." - "Refresh the grid if the add customer dialog closes successfully." - "Ensure you have controls for adding and removing addresses." #### Use a Template Select a template to guide the AI. Templates provide: - Additional rules and guidance - Sample implementations ![Select a Template](./images/select-a-template.png) Several pre-configured Material templates exist for different Pages and Dialogs. Templates are auto-selected based on your `Component` naming convention (this can be adjusted). | Template | Keywords | |-------------------------|--------------------------------------| | Page - Search Entity | search, find, list, lookup | | Page - Add Entity | add, create, new, insert, register | | Page - Edit Entity | edit, update, modify, change | | Page - View Entity | view, details, detail, show | | Dialog - Add Entity | dialog, add, create, new, insert | | Dialog - Edit Entity | dialog, edit, update, modify, change | > [!NOTE] > [Templates can be customized, extended, or replaced](#angular-ai-prompt-augment-and-customization-through-templates). You can even adapt them for a different component library. #### Provide an Example If you have a similar screen, select it in **Example Components** to submit its code as guidance for the AI. Sometimes, the LLM may generate different layouts for **Add Entity** and **Edit Entity** pages. Using an *Example Component* and extra context (e.g., "*ensure the layout of the edit page is the same as the add page*") helps align layouts. --- ## Keep Your ViewModel Managed This approach combines deterministic and non-deterministic code generation: - Everything you model is generated deterministically by the **Software Factory** in the **ViewModel** (e.g., `customer-add.component.ts`). - The AI/LLM generates code in the **View** (e.g., `customer-add.component.html` and `customer-add.component.scss`) and may also update the **ViewModel**. Considerations: - The **ViewModel** uses *Merge* mode by default, allowing both systems to operate together. - If the AI changes deterministic code in the **ViewModel**, the Software Factory may not merge changes automatically and could undo or duplicate code blocks. - Ideally, refactor code and/or add explicit [**Code Management**](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) instructions so Intent Architect can merge code automatically. - If merging is not possible, use the `@IntentIgnore()` decorator. --- ## Known Issues and Limitations We are actively working to address the following issues: - **Multiple AI agent execution**: `Implement with AI` runs per page/component. If you make a change affecting multiple pages (e.g., adding an "Add Entity" page along with a navigation to a "Search" page), you must run `Implement with AI` on each page for full functionality. - **Import removal**: If page A references page B, an `import` is added to page A. If page B is deleted or the reference is removed, the `import` is not automatically removed, which may cause build errors. Manually remove any unused `import` statements. - **Traditional Services Support**: Only CQRS Services are officially supported. Traditional Services may partially work but are not fully tested. Full support is in progress. - **Limited Weaving Capabilities** (vs. C#): TypeScript weaving is less mature than C#. Some *IntentMerge* scenarios may not work as expected; use *IntentIgnore* as needed. --- ## Angular AI Prompt Augment and Customization Through Templates The Angular AI prompting can be further extended through **AI Prompt Templates**. Out of the box these come pre-configured for Material, but the system is simple and extensible. Embrace it, extend it, or completely re-configure it - the choice is yours. Look in the following folder within your Intent Architect Solution: ```cmd .\intent\AI.Prompt.Templates\{application name}\Intent.Modules.AI.Angular.Generate ``` You will find: - `prompt.md` (generic prompt rules, limitations and instructions) - `prompt-config.json` (prompt configuration) - Folders containing sample code files for the various templates - Template specific markdown (template specific rules, limitations or instructions ![AI Prompt Config Folder](./images/ai-prompt-folder.png) --- ### Prompt Overview The main prompt (`prompt.md`) is a readable markdown file, which can be adjusted to suite your specific rules and requirements. This file contains the generic rules, limitations and instructions for the prompt, including: - Component Libraries (including versions) - Styling guides and rules (using Material) - Rules for when and how to modify existing code - Rules for navigation between components #### Template Specific Prompt Each template also has its own markdown file (e.g. `add-entity.md`) which contain any additional template specific rules to be passed to the LLM. ### JSON Schema Overview The `prompt-config.json` file defines templates which define **reusable AI prompt blueprints** for common scenarios (e.g., Search Page, Add Dialog). They include: - **id** → Unique identifier - **name** → Human-friendly label - **description** → What the template does - **applicability** → Keywords that help Intent Architect pick the most appropriate template - **template-folder** → The folder containing sample files #### Example Template: *Search Entity Page* ```json { "id": "SearchEntity", "name": "Page - Search Entity", "description": "Search Entity", "applicability": { "key-words": [ { "word": "search", "weight": 3 }, { "word": "list", "weight": 3 } ] }, "template-folder": "SearchEntity" } ``` --- ### Example Template Types The schema already defines several templates: - **Page Templates** - `SearchEntity` → Generates search/listing pages - `AddEntity` → Generates entity creation pages - `EditEntity` → Generates entity update pages - `ViewEntity` → Generates read-only entity views - **Dialog Templates** - `AddEntityDialog` → Generates dialogs for adding entities - `EditEntityDialog` → Generates dialogs for editing entities Each template includes its **own rules** to ensure compliance with Material and project conventions. --- ### Extending Configuration To extend the configuration: 1. **Add new rules** in `prompt.md` (global) or under a specific `template` markdown. 2. **Create a new template** in `prompt-config.json` by adding an object under `templates`. - Define keywords under `applicability`. - Specify a `template-folder` with an example implementation. #### Example A: Bulk Import Entities The entry in `prompt-config.json`: ```json { "id": "BulkImportEntities", "name": "Page - Bulk Import Entities", "description": "Upload a CSV/Excel file, preview parsed rows, validate, and commit in bulk.", "applicability": { "key-words": [ { "word": "import", "weight": 3 }, { "word": "upload", "weight": 3 }, { "word": "bulk", "weight": 3 }, { "word": "csv", "weight": 2 }, { "word": "excel", "weight": 2 }, { "word": "batch", "weight": 2 } ] }, "template-folder": "BulkImportEntities", "metadata": {}, "rules": [ ] } ``` Examples rules defined in `bulk-import-entities.md`: ```markdown ### Form generation rules - Provide a file input and a server-side parse action. Reuse existing parse, validate, and commit methods where available. - Use official enum values for component parameters. Do not use raw strings. ### Save behavior - Disable `Commit` until there are no blocking validation errors. - Show success and error toasts/dialogs using existing notification services where present. ``` ##### Example B: Upsert Template The entry in `prompt-config.json`: ```json { "id": "AddOrUpdateEntity", "name": "Page - AddOrUpdate Entity", "description": "Create a new entity or update an existing one, using a single page flow (upsert).", "applicability": { "key-words": [ { "word": "add", "weight": 3 }, { "word": "create", "weight": 3 }, { "word": "new", "weight": 2 }, { "word": "insert", "weight": 2 }, { "word": "register", "weight": 2 }, { "word": "update", "weight": 3 }, { "word": "edit", "weight": 3 }, { "word": "modify", "weight": 2 }, { "word": "change", "weight": 2 }, { "word": "upsert", "weight": 3 }, { "word": "save", "weight": 2 } ] }, "template-folder": "AddOrUpdateEntity" } ``` Examples rules defined in `add-or-update-entity.md`: ```markdown ### General behavior - Reuse existing backing methods if present (e.g., SaveEntityAsync, UpdateEntityAsync, LoadEntityAsync). Do not invent new ones if appropriate methods already exist. - If an Id or key is present in the model or route, treat the page as Update; otherwise treat as Add. ### Form generation rules - Always use official enum values for component parameters (no raw strings). ``` ✅ With this setup, you can tailor AI prompt behavior, enforce conventions, and even use a different Angular component library. --- ## Blazor UI Modeling with AI This article will show you how you can use Intent Architect to rapidly build professional-looking UIs, using a combination of both deterministic (pattern reuse) and non-deterministic (LLMs driven by Intent Architect) code generation techniques. How this works at a high level is as follows: - Design and generate your **View Model**s. This includes aspects like which Services to interact with and where UI navigations are going. This _can_ be done manually, but the recommended approach is to ask the `Intent Architect AI Assistant` to model for you. ![Model View Model](./images/basic-viewmodel-design.png) - Ask the AI to implement your pages. If you asked the AI to model your **View Model**s in the step above, the implementation will happen automatically. Alternatively, if you modeled your **View Model**s manually, you can ask the AI to generate the **View** implementation. Intent Architect handles the context engineering and manages the LLM interactions on your behalf. ![AI Generates View](./images/ai-review.png) - Review and validate the code. As always when dealing with LLMs, you will want to review and validate the code. By their nature LLMs are non-deterministic, but with a little bit of luck you should end up with a screen similar to this: ![Generated Front ends](./images/genarate-search.png) > [!NOTE] > While the samples here use MudBlazor, there is nothing inherently MudBlazor-specific about the implementation, and you can adjust the configuration for other component libraries. --- ## Design and Generate Your View Model To start, create a Blazor Application in Intent Architect. > [!NOTE] > Intent Architect has two Architecture Templates for quickly setting up a Blazor application: > - **Blazor Server** (Fullstack ASP.NET core Server) > - **Blazor Web Application (Standalone)** (Standalone UI which composes one or more Service Applications) ### How to Model Pages There are two approaches to model pages: using the AI Assistant (recommended) or manually in the UI Designer. #### Using the AI Assistant (Recommended) Ask the `Intent Architect AI Assistant` to add/model your pages. Describe what pages you need and their purpose, and the AI will create them for you with the appropriate structure, navigations and service interactions. ![AI Add Pages](./images/ai-add-pages.png) #### Manual Approach 1. Add a `Page` to a diagram in the **UI Designer**. 2. Name the `Page`, typically describing its function (e.g., `CustomerSearch` or `CustomerAdd`). 3. _Optional_: Adjust the route in the property pane. ### Adding Route parameters to your page If your page requires **Route Parameters** (e.g., `customers/edit/{customerId}`), you have two options: #### Using the AI Assistant - Page Route If you ask the `Intent Architect AI Assistant` to model pages that need route parameters, it will automatically add them for you. Based on the implied context (the services the page interacts with) the route parameters can usually be inferred, however you can also describe the page and its parameters in your request, and the AI will model them accordingly or ask clarifying questions. #### Manual Approach - Page Route 1. Right-click on the `Page` → **Add Property**. 2. Name the property (e.g., `CustomerId`) and set its type (e.g., `Guid`). 3. Apply the `Route Parameter` stereotype to the property using **F3**. ![Page with Route Parameters Added](./images/add-page-with-route.png) The page route will automatically update based on the route parameters. --- ### How to Model a Dialog There are two approaches to model dialogs: using the AI Assistant (recommended) or manually in the UI Designer. #### Using the AI Assistant (Recommended) - Dialog Ask the `Intent Architect AI Assistant` to add and model your dialogs. Describe what dialogs you need and their purpose, and the AI will create them for you with the appropriate structure and service interactions. If your dialog requires parameterization, the AI will automatically handle adding and configuring the necessary route parameters for you. ![AI Add Dialogs](./images/ai-add-dialogs.png) #### Manual Approach - Dialog 1. Add a `Dialog` to a diagram in the **UI Designer**, using the `New Dialog` context menu option. If your dialog requires parameterization, you can model that as follows: 1. Right-click on the **Dialog** → **Add Property**. 2. Name the property (e.g., `CustomerId`) and set its type (e.g., `Guid`). 3. Apply the `Route Parameter` stereotype using **F3**. ![Dialog with Route Parameters Added](./images/add-dialog-with-parameters.png) --- ### How to Model a Reusable Component There are two approaches to model reusable components: using the AI Assistant (recommended) or manually in the UI Designer. #### Using the AI Assistant (Recommended) - Component Ask the `Intent Architect AI Assistant` to add and model your reusable components. Describe what components you need and their purpose, and the AI will create them for you with the appropriate structure and service interactions. #### Manual Approach - Component 1. Add a `Component` to a diagram in the **UI Designer**. 2. Name the `Component`, typically describing its function (e.g., `AddressComponent` or `HeaderComponent`). If your component requires parameters or properties, you can model those by: 1. Right-click on the **Component** → **Add Property**. 2. Name the property and set its type. 3. Apply any relevant stereotypes using **F3** if needed. --- ### How to Model UI Navigations UI navigations define how users move between pages in your application. Intent Architect supports two types of navigations: direct page navigations and dialog interactions. #### Page Navigations 1. Right-click on the `Page` → **Add Navigation**. 2. Select the destination `Page` from the dropdown or create the connection visually in the diagram using the arrow tool. ![Navigations Modeled](./images/navigation.png) Route parameter mappings are handled automatically by the LLM during implementation, so you don't need to configure explicit mappings when your destination `Page` has `Route Parameters`. #### Show Dialogs 1. Right-click on the `Page` or `Dialog` → **Show Dialog**. 2. Select the `Dialog` you want to display from the dropdown or create the connection visually in the diagram using the arrow tool. ![Dialogs Modeled](./images/show-dialog.png) Similar to page navigations, parameter mappings for dialogs are automatically configured by the LLM during implementation, even when your `Dialog` has `Route` or `Binding Parameters`. --- ### How to Model Component Composition Component composition allows you to reuse UI components within pages, dialogs, or other components. This promotes consistency and reduces duplication across your UI. 1. Right-click on the `Page`, `Dialog`, or `Component` → **Add Component**. 2. Select the reusable `Component` you want to compose from the dropdown or use the arrow tool to create the composition visually in the diagram. 3. *(Optional)* Add guidance in the `Comment` section of the composition to instruct the LLM on how the component should be placed or configured. ![Component Modeled](./images/add-component.png) --- ### Modeling Layout/Menus Layouts define the overall structure and navigation menus of your application. Pages can be exposed in menus (sidebar, header, footer) by creating navigations from the layout to those pages. #### Using the AI Assistant (Recommended) - Menu When you ask the AI Assistant to model your application, it will automatically: - Identify which pages should appear in menus based on their names and purposes - Create navigations from the Main Layout to appropriate pages - Prompt you to confirm if certain pages should be navigable from the main layout If you want to control where a menu item appears (in which menu section), you can add guidance in the `Comment` section of the navigation (e.g., "show in sidebar"). ![Menu Context](./images/menu-context.png) #### Manual Approach - Menu To add a page to the application menus: 1. Right-click on the **Main Layout** → **Add Navigation**. 2. Select the `Page` you want to expose in the menu. 3. *(Optional)* Add guidance in the `Comment` section of the navigation to specify which menu section it should appear in (e.g., "sider", "header", "footer"). The LLM will use this context to correctly place the menu item during view implementation. --- ### Modeling Service Interactions UI `Component`s interact with backend services to retrieve data and perform operations. Intent Architect simplifies this by automatically linking components to appropriate services based on context. #### Using the AI Assistant (Recommended) - Services When you ask the AI Assistant to model your pages, dialogs, or components, it will automatically identify and link to the relevant backend services based on the page name and description. The AI handles all mapping, property creation, and model definition configuration for you. #### Manual Approach - Services If you're modeling service interactions manually: 1. Right-click on the `Component` → **Call Backend Service**. 2. Select the service endpoint you want to call from the **Add to Diagram** dialog. All the complex setup—such as creating the appropriate `Model Definition`s, configuring property mappings, and handling the data flow between queries and commands—is handled automatically by the LLM when you ask it to implement your views. You simply need to express your intent by linking the component to the services it should use. > [!NOTE] > If you are not seeing the Services you want to call, [add a package reference to the `Service Package` which contains those Services in the UI Designer](#connecting-your-ui-components-to-services-in-other-applications). --- ### Connecting Your UI Components to Services in Other Applications When modeling service invocations, you may want to connect to Services defined in applications beyond your UI application. The `Connect to Service` functionality is available on the UI Package, which provides a quick way to reference available services. ![Connect to Service](./images/connect-to-service.png) You can also manually configure a package reference: 1. In the **UI Designer**, under the **UI Package**, right-click **References** → **Add a Package Reference**. 2. In the `Package Reference Manager` dialog, select the package containing the Services (e.g., `OtherApplication.Services`). ![Add Package Reference](./images/add-package-reference.png) You can now use these external services in the diagrams of the UI application. > [!NOTE] > You will also need the appropriate integration module installed to make the remote communication work (e.g., `Intent.Blazor.HttpClients` for a WASM UI talking to an external REST Web API, or `Intent.Integration.HttpClients` for a server-side Blazor application talking to an external REST Web API). --- ## Implement Your View with AI Once you have modeled your **View Model** (pages, dialogs, components, navigations, and service interactions), you can ask the AI Assistant to implement your views. ### Automatic Implementation When you ask the AI Assistant to model and implement your UI, it will automatically generate the view implementation based on: - Your modeled information (service invocations, navigations, properties) - Any guidance in the `Comment` sections of your components - Your prompt instructions The AI processes all this context to generate the appropriate Blazor code. ### Explicit Implementation If a page wasn't automatically implemented, or if you want to regenerate a view, you can ask the AI Assistant to implement it: 1. Ask the AI Assistant to implement your page, dialog, or component (e.g., "Implement the CustomerSearch page"). 2. *(Optional)* Provide any additional context you feel might be relevant. ![Implement with AI](./images/ai-implementation.png) > [!TIP] > AI by its nature is non-deterministic — results will vary based on the context and randomness in the LLM. Review the generated code, test it, and make adjustments as needed. If you're not satisfied with the results, try regenerating with adjusted prompts or by providing additional guidance in component comments. --- ## Implement Your Menu with AI Once you have modeled your **Main Layout** (either using the AI Assistant or manually), you can ask the AI Assistant to implement the updated menu and layout. The AI will automatically generate the menu/layout implementation based on: - Your modeled information (service invocations, navigations, properties) - Any guidance in the `Comment` sections of your components - Your prompt instructions When the AI Assistant models a page and confirms it is navigable from the menu, it will typically auto-regenerate and reconcile the menu based on your model. If you want to manually trigger a menu implementation update, you can ask the AI Assistant to do so: ![Implement the menu](./images/ai-implementation-menu.png) --- ## Compilation Issues When modeling is done through the AI Assistant, it will run the **Software Factory** to apply changes and build your application as part of the process to ensure there are no compilation issues. If any issues are detected, the AI Assistant will automatically investigate and make the required code updates to resolve them. If the AI Assistant doesn't automatically resolve an issue, you can paste the error details, stack trace, or a screenshot of the error into the AI Assistant and ask it to resolve the problem. --- ## Improving the Results of AI The AI Assistant can generate high-quality code out of the box, but following these practices will help you get even better results and reduce the need for manual revisions. ### Use Descriptive Page and Component Names The AI uses naming conventions to understand what you're building. Instead of generic names like `Page1` or `Component1`, use descriptive names that clearly indicate the purpose: **Good Examples:** - `CustomerSearch` — AI recognizes this as a search/list page - `CustomerAdd` — AI recognizes this as a form for adding a customer - `CustomerEdit` — AI recognizes this as an edit page with fetch and update logic - `AddressInput` — AI recognizes this as a reusable component for address input **Why it matters:** The AI uses these naming conventions to automatically select the appropriate skill/template and create the correct structure (queries, commands, navigation, etc.) without needing additional guidance. This alone can eliminate many manual fixes. ### Add Helpful Comments to Components Comments on your components are included in the AI prompt and help guide code generation. Use them to provide context and specific requirements: **Examples:** - On a search page: *"Display customers in a data grid with sorting and filtering. Allow users to click a row to edit or delete the customer."* - On an edit dialog: *"Load the customer details and allow updates to name, email, and phone number. Disable the ID field."* - On a component: *"Reusable component for selecting a date range. Should have a start date and end date picker. Include validation to ensure start date is before end date."* - On a navigation: *"Show this menu item in the sidebar under 'Admin'."* **Why it matters:** Comments guide the AI on: - Features to include - Validation rules - UI placement and behavior - Business logic requirements This reduces iterations and helps the AI generate code closer to your requirements on the first try. ### Using Skills and Samples Intent Architect provides out-of-the-box skills with associated sample files for implementing common UI patterns. These pre-configured skills are found in the `.agents` folder and help guide the AI Assistant: | Skill | Used for | |-------------------------|--------------------------------------| | Page - Search Entity | search, find, list, lookup | | Page - Add Entity | add, create, new, insert, register | | Page - Edit Entity | edit, update, modify, change | | Page - View Entity | view, details, detail, show | | Dialog - Add Entity | dialog, add, create, new, insert | | Dialog - Edit Entity | dialog, edit, update, modify, change | The AI automatically selects the best skill based on your naming conventions. You can also [customize or create your own skills](#customizing-or-creating-your-own-skills) for your specific needs. ### Provide Additional Prompt Context You typically don't need to provide additional context, but if the AI is making the same mistakes or you need more specific guidance, you can provide extra instructions when prompting. Examples: - *"Ensure buttons/actions exist for the new navigations I added."* - *"Refresh the grid if the add customer dialog closes successfully."* - *"Ensure you have controls for adding and removing addresses."* ### Reference Existing Examples If you already have a similar screen you want the new one to be based on, you can tell the AI Assistant to use it as a reference. You can even attach the razor/cs file as an attachment to the prompt for concrete guidance about style and structure. --- ## Updating Styling from a New `design.md` As part of the default Blazor templates, a `design.md` file is generated alongside your application. It documents the out-of-the-box style sheet - colors, typography, spacing and component conventions - and is automatically included as context whenever the AI generates a **View**. This is what keeps styling consistent across all your AI-generated pages. If your design changes (e.g., a new brand palette, updated component variants, or a refreshed style guide), you don't need to touch each `Component` individually. Instead: 1. Replace the existing `design.md` with your updated version or include it as an attachment to the prompt. 2. Give the AI Assistant a prompt such as: *"A new `design.md` is available - update the stylesheets with the new design values."* A dedicated skill picks up on this, extracts what's required from `design.md`, and updates the relevant CSS files accordingly. Because styling is centralized in the CSS files rather than duplicated per `Component`, this single AI Task updates styling application-wide - there is no need to run it against each Page or Dialog separately. > [!NOTE] > Because `design.md` is included automatically as AI prompt context, any *new* Views you generate after replacing it will already reflect the updated design. The AI Task above is only needed to retrofit the CSS for styling that was already generated before the change. --- ## Customizing or Creating your own skills Intent Architect comes with pre-configured templates and samples for common UI patterns (search pages, add dialogs, etc.). You can either customize these existing skills or create your own entirely new skills to tailor the AI-driven code generation to your specific needs. ### Customizing Existing Skills If you want to modify an existing skill or sample implementation: 1. Locate the skill in the `.agents` folder within your Intent Architect application. 2. Update the skill files (markdown guidance and code samples). 3. Once you've customized a skill, Intent Architect will recognize it as a custom version and will no longer attempt to overwrite it with default updates. This allows you to maintain your customizations across Intent Architect updates while still benefiting from other new features. ### Creating Your Own Skills You can create completely custom skills from scratch by: 1. Using the existing skill structure as a template in the `.agents` folder. 2. Creating a new folder for your skill with your skill definition file and sample implementations. 3. Following the same structure as the pre-configured templates. 4. Once created, your custom skills will automatically be available to the AI Assistant when generating views. This approach allows you to: - Enforce your organization's UI patterns and conventions - Provide specific guidance for complex components - Maintain consistency across all AI-generated code - Reuse solutions for common UI scenarios in your application > [!TIP] > Start by copying an existing skill template and modifying it to fit your needs. This ensures you follow the correct structure and format that the AI will understand. --- # Viewing Code Intent Architect designers can display generated files associated with a particular element by right-clicking on it. There are two options available: ## View Code in Intent Architect View the file directly within Intent Architect in a code viewer: ![View Code context menu option](images/view-code-context-menu-option.png) The code viewer displays the generated file with syntax highlighting: ![View Code result](images/view-code-result.png) ## Open in IDE Open the generated files associated with a designer element in your preferred IDE: ![Open in IDE context menu option](images/open-in-ide-context-menu-option.png) In the screenshot above there are 4 files which Intent Architect determined are associated with the selected element. > [!NOTE] > > Intent Architect will not show or update the list of files associated with an element until [Software Factory](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-software-factory-execution/about-software-factory-execution.md) changes have been applied. > > The list of associated files is stored in an application's `.intent` folder which [should not be committed into source control management systems (such as Git)](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/git-and-scm-guidance/git-and-scm-guidance.md#folders-which-should-always-be-ignored), so if you've just checked out a repository or recently pulled changes into your branch, re-run the Software Factory and apply changes to ensure your local list is fully up to date. > [!NOTE] > > If you already have the Visual Studio Solution open in your IDE, "Open In IDE" will open the file in that IDE instance. > [!NOTE] > > If your IDE is running as Administrator your Intent Architect will also need to be running as Administrator for it to discover the running instance. ## How does Intent Architect choose which IDE to use? The IDE to use is controlled by the "Preferred IDE" option in [user settings](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md) which by default is set to "Auto": ![Preferred IDE option in user settings](images/preferred-ide-user-setting.png) When set to "Auto", Intent Architect will check in common locations on your computer for supported IDEs and use the first one it can locate based on the following priority: - Microsoft Visual Studio - JetBrains Rider - Visual Studio Code If there is a particular IDE you would prefer that Intent Architect uses, you can change the option to one of the following: - JetBrains IntelliJ IDEA > [!NOTE] > Intent Architect is not able to auto-detect the path for IntelliJ's executable and its path will need to be manually specified in the "IntelliJ IDEA Executable" field. - JetBrains Rider Intent is generally able to auto-detect the location of Rider's executable, but you can manually specify the path if required. - Microsoft Visual Studio - Microsoft Visual Studio Code - Other You can have Intent use any other IDE of your choosing by specifying its executable location and the launch arguments where Intent Architect will substitute `%FILE_PATH%` with the full path of the file to open. --- # About Software Factory Execution The Software Factory Execution is the process that executes the installed [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) with the metadata from the [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) within an [Application](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md). The result is changes to the codebase which are **staged** before being accepted or rejected. ![Software Factory Changes View](images/software-factory-changes-view.png) A core principal of Intent Architect is that it will not make changes to your codebase without your explicit consent which is why a diff is shown for the selected file - like a _pull request_ - giving your the opportunity to review or update the changes. The Software Factory Execution is initiated from within an Application by clicking on the _Run Software Factory_ button in the top tool bar. The execution is typically kicked off after completing some design changes, or installing / updating Modules. ![Play Button](images/software-factory-execution-play-button.png) _Run the Software Factory from the top tool bar._ You can also run the Software Factory from the application's context menu: ![Run from Context Menu](images/software-factory-context-menu-run.png) _Run the Software Factory from the application context menu_ The Software Factory can also be minimized to the task bar. In this state the Software Factory will subscribe to changes for the application and automatically run in the background, notifying the user when it has pending changes. ![Running in the Background](images/software-factory-minimized.png) _Minimized Software Factory running in the background._ ## See also - [Synchronizing code changes to design](https://docs.intentarchitect.com/docs-md/application-development/software-factory/synchronize-code-to-design/synchronize-code-to-design.md) - [Inspecting codebase customizations](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md) --- # About Template Output Targeting ## Overview `Output Targeting` refers to how Intent Architect determines where a Template's output should be placed on the file system during software execution through configuration within the Codebase Structure designer and the template itself. The `Codebase Structure` designer supports "Output Configuration" which lets Intent Architect know during module installation where `Template Output`s should be placed. The Software Factory Execution uses these `Template Outputs` to determine the output paths for template instances. ![Output Config Template Output](images/output-config-template-output-side-by-side.png) _Example showing Template Output items in the Codebase Structure designer_ ## Template Outputs `Template Output`s in the designers are used by the Software Factory to know where on the file system that template output should be written, in particular under which sub-folder. As modules are installed, for each template within them, a `Template Output` is automatically created with its name being the value of the `TemplateId` property of the template as specified during module building. ## Unassigned Template Output The Codebase Structure designer will show unassigned Template Outputs in red: ![Unassigned Template in Output Configuration Designer](images/output-config-vs-unassigned-template.png) Running the Software Factory while these templates are unassigned will result in errors during execution about there being unassigned templates. Module authors can control where their "Template Output"s should be placed by default during module installation so that things work automatically, this is covered in this [article](https://docs.intentarchitect.com/docs-md/module-building/templates-general/configuring-a-templates-default-output-location/configuring-a-templates-default-output-location.md). ## See also - [Configuring a Template's default output location](https://docs.intentarchitect.com/docs-md/module-building/templates-general/configuring-a-templates-default-output-location/configuring-a-templates-default-output-location.md) --- # The Software Factory Customizations screen It can be useful to know which files may not be following the architecture which your installed modules normally generates. Towards this, the Software Factory is able to track customizations of code management instructions, i.e. where instructions such as `IntentIgnore` are added to files which aren't present in default template output: ![The customizations screen on the Software Factory](images/customizations.png) Clicking on a file in the list shows a diff by default comparing the unmerged template output (left) with the current file (right). Editing and saving the file in the right pane will trigger re-running the code merging for that particular file allowing fixing of customizations without having to open the file through in a different editor (such as your IDE). Customizations can be approved by right-clicking it and selecting "Approve customization" which will also record who approved the customization and when. To see when a customization was approved, hover over the file in the list and read the tooltip showing approval details. ![Approval Details](images/customization-approval.png) If the particular customization(s) for the file changes, the approval is revoked, approvals can also be manually revoked using the context menu. The final option available on the context menu is the ability to update "Notes" on a customization, allowing you to enter any free form text which you feel may be relevant to a particular customization. > [!TIP] > The [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md)'s `ensure-no-outstanding-changes` command has a `--check-for-unapproved-customizations` option that can be used on CI environments to automatically detect unapproved customizations pushed into source control. --- # Environmental factors which can slow down Software Factory execution So as to avoid negatively affecting developer productivity, we take the performance of the Software Factory very seriously. Over time we have applied many optimizations to the Software Factory and continue to do so as we find areas where it's possible. However, we have found that on some developer machines, environmental factors / configurations can negatively impact the Software Factory execution speed. Firstly, to ensure you're getting the best and shortest possible execution times, please ensure you're running the latest version of Intent Architect as well as the latest versions of modules. Otherwise, continue reading for additional advice. ## Windows' Dev Drive Windows 11 users are able to use Microsoft's [Dev Drive](https://learn.microsoft.com/en-us/windows/dev-drive/) feature which is intended to improve the performance of many developer related tasks and tools by using a different file system type and adjusting the way that Windows Defender works. Intent Architect's Software Factory has been found to run faster when solutions are running off of a Dev Drive. ## Other common causes of slowness ### "Initializing Software Factory Execution" step is slow A common area of slowness in the Software Execution is the very first step, "Initializing Software Factory Execution". While this step should take less than a second, on some machines we have observed it taking multiple seconds and in extreme cases, tens of seconds. The identified cause of this slowness is the interference of computer anti-virus software in the loading process of Module DLLs from the disk drive. To avoid this interference, consider adding some or all of the following exclusions to your anti-virus software: - The folder in which Intent Architect is installed (e.g. `C:\Program Files\Intent Architect v4`). - The folder containing your Intent Architect Solution and Applications, essentially the folder into which the "source code" for your project is checked out. - The Intent Architect software factory executable: `Intent.SoftwareFactory.Host.exe` > [!WARNING] > Adding exclusions to your anti-virus protection may expose you to additional risks. If your computer is administered by an IT department, their approval and assistance may be required to add exclusions. #### Adding Exclusions to Windows Defender - Open Windows Security by clicking on the Start menu and selecting the shield icon to open Windows Security. Alternatively, you can search for "Windows Security" in the search bar. ![Search Windows Security](images/search-windows-secutiry.png) - In the Windows Security window, click on "Virus & threat protection" on the left sidebar. ![Virus and threat protection](images/virus-and-threat-protection.png) - Scroll down to the "Virus & threat protection settings" section and click on "Manage settings." ![Manage Settings](images/manage-settings.png) - In the settings window, scroll down to the "Exclusions" section. Here, you will find options to add exclusions for files, folders, file types, and process exclusions. ![Add or Remove exclusions](images/add-or-remove-exclusions.png) - Add Exclusions: - For Folders: Click on "Add or remove exclusions" under the "Folders" section. In the window that opens, click on "Add an exclusion," then browse to the folder you want to exclude (e.g. `C:\Program Files\Intent Architect v5` or `C:\Dev`) and select it. Click "Select Folder" and then "OK." - For Processes: Click on "Add an exclusion" under the "Processes" section. Type the name of the process you want to exclude (e.g. `Intent.SoftwareFactory.Host.exe`) and click "Add." - After adding the exclusions, you will be prompted to confirm. Click "OK" to save your changes. --- # Synchronize code to design Intent Architect is able to take changes manually made to code files and apply them back to your design in Intent Architect designers. Synchronization supports structural aspects of source code files, here are some (non-exhaustive) examples: - Added/removed/updated properties on domain entities can add/remove/update attributes in the Domain Designer. - Added/removed/updated methods on domain entities can add/remove/update operations, including parameters, in the Domain Designer. - Added/removed/updated properties on DTOs can add/remove/update attributes in the Services Designer. > [!NOTE] > > Only templates which have implemented the required synchronization logic will offer this capability. We are progressively applying this logic to additional templates, in the meantime please reach out to our support team if there are particular templates you are looking to have this supported on sooner rather than later. ## Using the feature For example a `Notes` property has been added to an entity in your IDE and when running the Software Factory it wants to remove it, but it is also showing the "Synchronize code to design" button next to the change: ![Code to design synchronization available](images/synchronize-code-to-design.png) > [!NOTE] > > Only templates which have implemented the required synchronization logic will show this icon. We are progressively applying this logic to additional templates, in the meantime please reach out to our support team if there are particular templates you are looking to have this supported on sooner rather than later. On clicking the icon the Software Factory minimizes, the relevant designer will be opened, focussed and the changes applied. In the following screenshot notice the dirty indicator on the `Notes` attribute: ![Designer with code synchronized into it](images/synchronize-code-to-design-applied.png) You can then save and the Software Factory will no longer want to remove those changes from the file. ## See also - [For Module authors: Implementing reverse synchronization for C# templates](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/synchronize-code-to-design/synchronize-code-to-design.md) --- # Troubleshooting Software Factory errors It is possible when running the Software Factory that one or more errors can occur, when this happens the Software Factory remains on the Execution tab for you to be able to review the logs to resolve them. ![An example of an error](images/error-example.png) > [!TIP] > > You can quickly drill into the logs to the first error by clicking the "\ Error(s)" button above the log. > > ![Errors button](images/errors-button.png) Errors can occur for a variety of reasons, the most common being invalid modelling in a designer (e.g. an element was intentionally deleted in the domain designer but it was still mapped to from the services designer). Sometimes errors can occur due to a bug in a Module or Intent Architect itself. Sometimes a single error can cause a cascade of other errors, so it is recommended to look at the first error, resolve that, then run the Software Factory to see if there are more errors to still be resolved, again, by looking at the first error and repeating until all errors are resolved. ## Element Errors An _Element Error_ is typically very easy to resolve, it is a special kind of error which occurs when the Software Factory has been able to determine that an error occurred when processing a particular element from a designer, very often due to an invalid modelling scenario in a designer. The bolded text on these errors can be clicked on which will cause Intent Architect to navigate to the designer and the element within for which the error occurred: ![Clickable portion of the error](images/clickable-portion-of-error.png) Very often just looking at the element that was navigated to in the designer is enough to quickly see how to resolve the issue and prevent the same error for the next Software Factory execution. In this example it has jumped to the following and if we click on the red triangle with the exclamation mark it has some additional information: ![Element with error](images/element-with-error.png) In this case we can click on the blue arrow which will open the mapping screen: ![Mapping screen with error](images/mapping-screen-with-error.png) We can see from the error that the source mapping could not be found, in this example it is because `PhoneNumbers` is not a valid attribute on the entity (it is `PhoneNumber`). We can see this error message by also clicking on the red exclamation mark. A solution here is to rename the mapping value: ![Rename field](images/rename-field.png) We can then press _DONE_ on the dialogue to apply the changes. In this example after doing the above actions the red triangle is now gone and we can see there are no more errors: ![No more errors](images/no-more-errors.png) We can now save the designer, run the Software Factory, and we should see it no longer shows errors during execution. ### What to do if there are no errors visible in the designer Sometimes when you navigate to the element causing the error it may not show an error in the designer and there is no obvious problem, in such cases you should look at additional details on the error by expanding its log tree node and treating it as you would a ["general" error](#general-errors). ## "General" errors ![Additional error information](images/additional-error-information.png) Sometimes looking at just the error text can be enough to find a resolution yourself, other times you may want to contact [Intent Architect support](https://github.com/IntentArchitect/Support) or the module's author with the error details to receive additional help. Errors can be copied to your clipboard by right-clicking the particular log entry and selecting the "Copy" option or using the Ctrl+C keyboard shortcut: ![Copying the error](images/copying-errors.png) Since Intent Architect version 5 you also have the option to ask AI to attempt to fix the issue if this button is available. ![Try fix with AI error](images/try-fix-with-ai-errors.png) --- # How to change user settings To access the user settings: 1. Click your user Display Name dropdown at the top right-hand corner of the screen. 2. Click on `Settings` ![User Settings](images/user-settings.png) ## Theme Choose between dark theme or light. By default Intent Architect will use your operating systems' theme settings. ## Updates From time to time, Intent Architect may release pre-release versions of the product. To be notified and able to update to a new pre-release, enable the `Enable updating to pre-release versions` toggle. It is recommended that you keep this setting disabled as pre-releases can be less stable than official ones. ## Default Application locations This is the default location that Intent Architect will use when creating a new Application from the home view. ## Diff Tools Intent Architect supports the use of any Diff tool that can be executed from the CLI. By default (if blank), Intent Architect will use Visual Studio Code if it has been installed. It will fall back to using Visual Studio, and finally, if neither can be found, a Diff tool will need to be specified manually here. ## Preferred IDE The IDE to use when using the [Open in IDE](https://docs.intentarchitect.com/docs-md/application-development/modelling/viewing-code/viewing-code.md) context menu option from within designers. --- # How to use the Change Icon Dialogue Intent Architect allows specifying an icon in many different places and this is always done using the Change Icon Dialogue, this how-to will guide you on how to use it. ## Opening the Dialogue There are various different places where the Change Icon Dialogue can be opened from, for example by clicking the icon on the Create Application screen: ![Choose application icon](images/choose-application-icon.png) Or on a stereotype definition / property: ![Choose stereotype icon](images/choose-stereotype-icon.png) And some other places too with the same/similar paradigm. ## Overview of the dialogue The dialogue has the following parts: - A preview of the icon. - "Tabs" which can be used to specify which type of icon to use. - Content specific to the icon type chosen: ![Overview of the dialogue](images/overview.png) 1. Icon preview. 2. Different icon types. 3. Content here changes depending on the icon type (2.) selected. ## Different Icon Types You can use the "Tabs" to choose which type of icon to choose. ### Import This is the easiest way to use any existing image as an icon. You can use Ctrl-V/Cmd+V to paste an image from your clipboard or drag and drop an existing image from a browser window or click on the area to open a browse dialogue. ### URL You can put any valid URL to an image in here. You should only ever use URLs to domains which you personally control or you can alternatively use a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). ![URL Icon Type](images/icon-type-url.png) ### Font Awesome Intent Architect uses [Font Awesome](https://fontawesome.com/) and enables use of any [these icons](https://fontawesome.com/icons): ![Font Awesome Icon Type](images/icon-type-font-awesome.png) ### Boxed Character This makes basic dynamic icons which are a letter with background color of your choice: ![Boxed Character Icon Type](images/icon-type-boxed-character.png) --- # Keyboard Shortcuts At any point you can bring up the Keyboard shortcuts legend by pressing `Ctrl + .`. This will give an overview of all the available shortcuts for the current view you're on. > [!NOTE] > > For macOS users the `Ctrl` button is substituted by the `⌘` (command) key. ![Keyboard shortcuts legend](images/keyboard-shortcuts-legend.png) ## Special usages ### Create new element of selected type When on an element the `Ctrl + Enter` shortcut can be used to make a new element of the same type, this can be very useful when needing to create lots of elements of the same type, for example domain class attributes or DTO fields. For example, while having typed in the name of an element, before having even pressed enter to complete the change: ![alt text](images/ctrl-enter-step-1.png) Press `Ctrl + Enter` which completes the creation of the previous element, creates a new element of the same type and is ready for you immediately start typing the name for the new element: ![alt text](images/ctrl-enter-step-2.png) ### Mouse Panning To pan a diagram click and hold the middle button on it and move your mouse. If you're on a touch pad or middle-clicking is not conveniently available, you can alternatively `Ctrl + Left-Click` and move your mouse on the diagram. --- # Telemetry collection Intent Architect includes a telemetry collection feature designed to help improve performance, stability, and user experience. By gathering essential usage statistics, the Intent Architect team can better understand how the tool is being used and make informed decisions about future enhancements. ## What data is collected? Intent Architect collects only basic statistical and error data, including: - **Software Factory Execution Time** - The duration of the Software Factory execution. - **Designer Load Time** - How long it takes for Designers to load. - **Code Generation Metrics** - The total amount of code generated. - **Modeling Activity** - The number of elements modeled within the designers. - **Application Name** - The specific Intent Architect application in use. - **Account Identifier** - The identifier of the current user account. - **Errors** - Errors/exceptions which occur in Intent Architect itself. - **AI Usage Metrics** - Purely metrics on usage, used only to track usage against your free daily AI credits. ## What data is NOT collected? Intent Architect prioritizes user privacy and ensures that no proprietary or sensitive information is transmitted. Specifically, data such as the following is not collected: - **Element Names** - The names of modeled elements, services, classes, or any other defined types. - **Generated or Written Code** - Any source code, whether automatically generated or manually written, remains entirely private and is never sent through telemetry. - **AI Chat Content** - The AI usage we send to our server is purely metrics on usage and only for the purpose of tracking free AI usage. It does NOT include any actual content of the chat itself. - **Usage metrics on your AI provider** - Any usage on your own AI provider subscription or API keys will not be recorded by Intent Architect. ## What are the benefits of telemetry collection? Telemetry data allows the Intent Architect team to: - Optimize performance by identifying slow processes. - Enhance user experience by improving Designer load times. - Track feature usage to focus development efforts on the most impactful areas. - Diagnose potential issues before they become widespread. --- # The About Dialog The About Dialog: - Shows the version of Intent Architect you are running and a link to the release notes for it. - Shows the license assigned to your account at the time you logged into Architect Architect. - Shows if a new update is available along with its version and a link to its release notes. ![The about dialog](images/the-about-dialog.png) ## Accessing the About Dialog The About Dialog can be accessed by clicking on your user display name in the top-right corner of the window and then clicking the `About` option: ![The about menu option](images/menu-option.png) --- # About Application Templates Application Templates allow specifying a pre-defined set of options of which [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) and Metadata (for [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md)) to automatically install into a new [Application](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md) during its creation. To illustrate this, from the home screen create a sample .NET Core Application using the `Clean Architecture .NET` Application Template. Fill out the Application's name, location, and the Solution name, then click `NEXT`. The wizard then moves to next page which displays the high-level _Components_ that make up the Application Template. ![Application Template Components](images/application-template-components.png) Each block is a _Component_ and each represents one or more Modules that must be installed into the new Application. Each Component may be included or excluded as required. To see exactly which Modules will be installed depending on our selection of Components, we can expand the Component list on the right side of the page. ![Application Template Modules](images/application-template-modules.png) _This screenshot of the component list on the left of the screen shows exactly which modules would be installed from the selected Components._ To create the Application using the selected Application Template and its selected Components, click on the `CREATE` button. Intent Architect will download and install the Modules correlating to the selected Components, as well as create any default metadata that is required. When the installation is complete you will see a 'Ready' message as illustrated below. ![Application Template Installation](images/application-created.png) _The modules downloaded in the example above are specifically related to the `Clean Architecture .NET` Application Template._ > [!NOTE] > Although this example is based in .NET, this process would work in the same way for other Application Templates that are designed for other languages. ## See also - [How to create Application Templates](https://docs.intentarchitect.com/docs-md/application-templates/how-to-create-application-templates/how-to-create-application-templates.md) - [Metadata installation](https://docs.intentarchitect.com/docs-md/application-templates/metadata-installation/metadata-installation.md) --- # How to create Application Templates This how-to guide will walk you through creating your own [Application Template](https://docs.intentarchitect.com/docs-md/application-templates/about-application-templates/about-application-templates.md) using the `Intent Application Template Builder` [Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md). ## Create a new Application Create a new `Intent Application Template Builder` application and give it the name `MyApplicationTemplates`: ![Create a new Application](images/my-app-templates-creation.png) - Click `NEXT`. - Leave the ticked options as is and click `CREATE`: ![Installation dialog](images/application-template-ready.png) Once the `Application Installation` is complete, you will see a 'Ready' message. ## Create a new Package - Click on the `App Templates` Designer on the left. - Click `CREATE NEW PACKAGE`. - Give the package a name of `My Application Template`. - Click `DONE`. - Click the root Package element and in the Properties pane on the right: - For `Display Name` enter `My Application Template`:

## Specify a Group and a Component - Right-click the root Package element and click the `New Group` option. - Give the new element a name of `Group 1`. - Right-click the `Group 1` element and click the `Add Component` option. - Give the new element a name of `Component 1`. - Click the root `Component 1` element and in the Properties pane on the right: - For `Icon` use the ellipses to [choose an icon](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-use-the-change-icon-dialogue/how-to-use-the-change-icon-dialogue.md):

## Add a Module - Right-click the `Component 1` element and click the `Add Module` option. - Give the new element a name of `Intent.Modelers.Domain`. > [!IMPORTANT] > This must be the exact name of a [module](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) which is to be installed by the Application Template. - Click the root `Intent.Modelers.Domain` element and in the Properties pane on the right: - For `Version` enter `3.4.0`. > [!IMPORTANT] > This must be a valid version for the Module name chosen above. ![Add a Module](images/add-module.png) ## Run the Software Factory to generate the module Run the Software Factory and optionally review the proposed changes: ![Staging files](images/software-factory-run.png) Click `APPLY CHANGES`. Before you dismiss the Software Factory Execution dialog (after it's completed applying all the changes) expand the second last item called `Finalizing Execution`, then `Execution Life Cycle` to see that a `dotnet build` was executed on the generated `Visual Studio` solution: ![Software Factory: dotnet build](images/application-template-sf-execution.png) Take note of the following line in the output for the following step: ```text Successfully created application template C:\Dev\.Intent-modules\My Application Template.1.0.0.iat ``` > [!IMPORTANT] > This output location may be different depending on your environment configuration. Click on the close button. ## Test your Application Template Ensure you have added the output path as noted in the above step as a repository in the [Repository Manager](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/how-to-manage-repositories/how-to-manage-repositories.md). Add a new Asset Repository with the name `My Modules` and address `C:\Dev\.Intent-modules` (this is the folder where your `.iat` file is located), then click `Save`. ![Manage Repositories](images/repository-config.png) In the Solution Explorer click to add a new application. Ensure your module repository is selected in the drop down and you should see your application template: ![Select your Application Template](images/testing-select-application-template.png) Give it a name and click `NEXT`. ![Application Template Components](images/testing-select-application-template-components.png) Notice that it's showing all the elements we created in the [Specify a Group and a Component](#specify-a-group-and-a-component) and [Add a Module](#add-a-module) steps. ## Defining user configurable settings Application Templates also allow defining of user configurable options, for example in the following screen shot, the contents of the highlighted box is configurable: ![An example of user configurable options](images/settings-example.png) ### Settings Configurations In the above screenshot, the `Basic Settings` is a Settings Configuration which can one or more Fields. An Application Template can have multiple Settings Configurations defined with each one having in a separate "heading" for itself. To create a Settings Configuration, right-click the Package node in the Application Template Designer and use the `New Settings Configuration` option. > [!NOTE] > If you try run the Software Factory to test the updated Application Template you will not see any effect yet as the New Application wizard hides Settings Configurations with no fields or where all fields for it are `Hidden`. ### Field Configurations Each Settings Configuration can have one or more `Field Configuration`s within it. A Field Configuration's `Name` is used as the display label presented on the wizard's UI and a Field Configuration's `Value` setting is the logical setting name that can be referred to for consuming the value of the Field Configuration which was captured by the user in the wizard. To create a Field Configuration, right-click a `Settings Configuration` and use the `Add Field` option. #### Control Type The `Control Type` property is used to control what kind of control is used in the wizard UI for the field. The following options are available: | Control Type | Description | | ------------ | ----------- | | Text Box | A single line text field. | | Number | Allows capturing of numeric only values. | | Checkbox | A checkbox for capturing boolean values. | | Switch | An alternative to the `Checkbox` control for capturing boolean values which instead looks like a switch. | | Text Area | A text box which allows capturing text with multiple lines. | | Select | A combo-box which allows selecting a single [](#field-options). | | Multi-Select | A combo-box which allows selecting multiple [](#field-options). | | Hidden | The field will not display in the wizard UI, but its default value can still be consumed. | #### Is Required When set, the wizard UI will not allow the user to proceed unless a value for the Field Configuration has been captured. #### Hint Text which appears below the field which can be used to further explain how the captured value for the Field Configuration should be used or what it is for. #### Default Value Used to set the default value for the Field Configuration. ### Field Options Are presented as available options for `Select` and `Multi-Select` [Control Types](#control-type). A Field Option's `Name` controls the display name of the Field Option in the UI and the Field Option's `Value` is used to to define the value that is ultimately consumed when a Field Option is selected. To create a Field Option, right-click a Field Configuration and use the `Add Option` option. ### Consuming values captured during the wizard #### For Metadata `.installation.config` files All Field Configuration values are available as [template strings](https://docs.intentarchitect.com/docs-md/application-templates/template-string-substitution/template-string-substitution.md) for use in `.installation.config` files. Use a Field Configuration's `Value` using the `${}` format and it will be substituted with the user captured (or otherwise default) value. For example, for a [Field Configuration](#field-configurations) with a `Name` of `Custom Field` and a `Value` of `custom-field`: ![Field Configuration Name and Value](images/field-setting-name-and-value.png) You would use `${custom-field}` in `.installation.config` files to have the user captured value substituted. #### For Module Settings Application Templates can set initial Module Setting field values with values from `Field Configuration`s. The Field Configuration's value as configured in the Application Template builder will need to match the `id` of the Module Setting's field. A quick way of finding this `id` is to install the Module in an Application and then look at the Application's `.application.config` file to find the field with its `id`. An Application's `.application.config` file path can be located on the `Settings` screen and the path can be clicked on to have the file revealed in your OS's file system browser: ![Finding an Application's `.application.config` path](images/application-config-path.png) Make note of the Module Setting Field's label from Settings screen to be able to search for it in the `.application.config` file, for example if we want to set the value for `Database Provider` field, we make note of the label being that name: ![The Module Setting Field Label](images/module-setting-field-label.png) Then search for the text of the label in the `.application.config` file and on the same element is the `id` attribute with the value that we need: ![The Module Setting Field Id](images/module-setting-id-attribute.png) We then use the `id` value as the `Value` for the `Field Configuration`: ![Field Configuration with id for the Value](images/application-setting-configuration-with-module-setting-field-id-value.png) Because Module Settings can still be changed after using the Application Template wizard, it's common to simply give these fields a default value and set their `Control Type` to `Hidden`: ![Hidden and Default Value for Field Configuration](images/application-setting-configuration-with-module-setting-field-id-value-hidden-default-value.png) ## Next steps You can use the `App Templates` Designer to add/change Groups, Components, Modules and Settings Configuration, re-run the Software Factory and see the affect it has on your Application Template when used in Intent Architect. Using the [metadata installation](https://docs.intentarchitect.com/docs-md/application-templates/metadata-installation/metadata-installation.md) system, new applications created with your template can have default metadata installed, for example you could have the Visual Studio designer on the new application pre-configured with your desired solution and project layout. ## See also - [About Application Templates](https://docs.intentarchitect.com/docs-md/application-templates/about-application-templates/about-application-templates.md) - [Metadata installation](https://docs.intentarchitect.com/docs-md/application-templates/metadata-installation/metadata-installation.md) - [Application Settings](https://docs.intentarchitect.com/docs-md/module-building/application-settings/application-settings.md) --- # How to set Module Versions To set or update the version of a Module that is installed by an Application Template, specify it in `Version` property of the `Module Settings` Stereotype in the `Application Templates` Designer: ![The 'Module Version' Property](images/module-version-property.png) --- # Installation files ## Overview Application Templates can specify files to be copied to a target location as part of application creation. This is done using **File Installation Rules** configured in the Application Template designer under the `[installation settings]` node. ## How it works Each File Installation Rule has the following properties: | Property | Description | |-|-| | **Name** | A display name for the rule, typically the filename being installed. | | **Match Files** | The filename (or glob pattern) used to locate the source file in the `resources` folder of the Application Template package. | | **Target** | The destination directory — either `Output Directory` or `Application Config Directory`. | | **Relative Output Folder** | *(Optional)* A subfolder path within the target directory where the file will be placed. | For a file to be installed, a file whose name matches the **Match Files** pattern must be present in the `resources` folder of the Application Template. ## Target options | Target | Description | |-|-| | `Output Directory` | The root output directory of the created Application, where generated code is placed. | | `Application Config Directory` | The Intent Architect configuration directory for the Application. | ## Template string substitution The content of installation files supports [template string substitution](https://docs.intentarchitect.com/docs-md/application-templates/template-string-substitution/template-string-substitution.md), allowing dynamic values such as the application name to be embedded in the file at installation time. --- # Metadata installation ## Overview Intent Architect can automatically install metadata for Designers as an Intent Architect Application is created from an [Application Template](https://docs.intentarchitect.com/docs-md/application-templates/about-application-templates/about-application-templates.md) or alternatively as a Module is installed. > [!NOTE] > Metadata installation for a Module does not occur if it is being upgraded or re-installed, this allows users to delete installed metadata from Designers without it "coming back" again each time a Module is updated or re-installed. During packaging of both Application Templates and Modules, the Intent Architect packager will look for any `.installation.config` files present in the `content` sub-folder. We recommend following a convention of naming `.installation.config` files after the designer for which they have metadata, for example `visual-studio.installation.config` for the Visual Studio designer or `domain.installation.config` for the Domain Designer. These files are expected to contain an XML representation of the metadata to install as well as specifying which designer it should be installed into. The easiest way to create this XML in a valid format is to use the "Export" button on the toolbar when a designer is open: ![The export button on the toolbar](images/export-button.png) Pressing this button will copy an XML representation of all (even unsaved) content within the designer onto your clipboard which you can then paste into the `.installation.config` file of the Module or Application Template. ## Template string substitution All content of `.installation.config` files support [template string substitution](https://docs.intentarchitect.com/docs-md/application-templates/template-string-substitution/template-string-substitution.md), allowing values such as the solution or application name to be resolved at installation time. ## Metadata merging To avoid duplicate elements from being created, Intent Architect merges the metadata to install with any metadata already existing in the designer. For each element it needs to install, it searches in all folders by its `id` in the `.installation.config` file with the `externalReference` value in the Designer's existing metadata. If no match is found by `id` and `externalReference` it will then try match by name and folder path within the metadata. When there is a match it will merge child elements recursively following the same logic. Because Intent Architect fundamentally requires that all elements have a unique `id`, during metadata installation it will always create new unique `id`s for them, but it will also store the source element's original `id` value in the `externalReference` attribute so that the above correlation is possible. ## Avoid exporting metadata from "production" Applications for installation We recommend that in your Intent Architect "Modules" Solution, you create a new Application with a name such as `.Metadata`. In this Application install any designers which you want to export metadata from and then model the desired metadata in them as you normally would. This allows you to put [template string](https://docs.intentarchitect.com/docs-md/application-templates/template-string-substitution/template-string-substitution.md) values in element names so that the exported metadata requires no "correction" after export. Exporting from working Applications also has other disadvantages in that you may export additional data that you didn't intend to (for example since your original export other data was captured as necessary for the Application), or that shouldn't ideally be installed (for example it's better NOT to export and install `Template Output` elements of the Visual Studio designer as these should be managed as per [output targetting](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-template-output-targeting/about-template-output-targeting.md)). ## Tutorial ### Tutorial overview This tutorial will guide you through setting up metadata to be installed for an Application Template and then walk you through setting up metadata to be installed for a Module. ### Assumptions This tutorial assumes we already have an existing Intent Architect solution called `MyOrganisation.Modules` with the following Applications: ![Initial solution view](images/original-solution-view.png) By default the Application Template will install the `Domain` and `Persistence` Modules with the `UnitTesting` Module being optional. The software factory has been executed before for these Applications with a working Visual Studio solution with projects having been generated. ### Create the metadata Application for the Application Template - Click `Create new application`. - Click the icon so we can change it: ![Clickable icon](images/create-application-icon.png) - Click the `Font Awesome` tab, then enter `export` in the filter box, select the `File Export` item and finally click `SELECT`: ![Clickable icon](images/create-application-icon-selection.png) - Change the Name to `MyOrganisation.ApplicationTemplates.Metadata`. - Press `CREATE EMPTY` at the bottom of the dialogue. ![Choose Application name](images/create-application-name.png) - Press `YES` when asked to confirm that you want to continue creating an empty Application. - Once the application is created, right-click on the application and select `Manage Modules`. On the Modules tab search for `Visual Studio`, select the `Intent.VisualStudio.Projects` Module and finally click the install button on the right: ![Search for and install the Visual Studio Module](images/application-template-metadata-add-visual-studio-module.png) - Select the `Visual Studio` designer on the left. - Press the `CREATE NEW PACKAGE` button. - Use `${application.name}` for the "Name" of the package and press `DONE`: ![Choose package name](images/application-template-metadata-choose-package-name.png) - Right-click on the `${application.name}` element and select `C# Project (.NET)`. - Set its name to `${application.name}.Domain`. - Right-click the `${application.name}.Domain` element and select `Add Role`. - Set its name to `Domain`. - Right-click on the `${application.name}` element and select `C# Project (.NET)`. - Set its name to `${application.name}.Persistence`. - Right-click the `${application.name}.Persistence` element and select `Add Role`. - Set its name to `Persistence`. - Press the `Export` button in the toolbar and `Copied to clipboard...` will appear at the bottom right of the window. ![Metadata exported to clipboard](images/application-template-metadata-export-metadata.png) ### Add the exported metadata to the Application Template - Open the `.sln` file for your Modules solution. > [!TIP] > To quickly locate the `.sln` file, click `Settings` on the left and then on the link below `Relative Output Location` which will open the folder which contains your `.sln` file. > > [!NOTE] > This tutorial assumes you have already run the software factory for the other Modules and that the `.sln` file and its projects would already have been generated. - Inside the `MyOrganisation.ApplicationTemplates` project, expand the folder containing your Application Template's `metadata.iatspec` file (for example `MyOrganisation.ApplicationTemplate`) and inside it create a `resources` folder. - Inside the `resources` folder create a file called `visual-studio.installation.config`. - Paste the contents from your clipboard into the file. - Build the project. - Note the line with `1>Added resources/visual-studio.installation.config.` in the build log which confirms that the file was included in the packaged `.iat` file. ![Built in Visual Studio](images/application-template-metadata-vs-ide.png) ### Application Template metadata summary If you create a new Application with the Application Template, you will see it now installs the metadata into the Visual Studio designer. ### Create the metadata Application for the Module > [!NOTE] > Creating the metadata Application for the Module is almost the same as [creating the metadata Application for the Application Template](#create-the-metadata-application-for-the-application-template), so for brevity, nearly identical screenshots have not been included below. - Click `Create new application`. - Change the icon for the new Application to the `Font Awesome` `File Export` icon. - Change the Name to `MyOrganisation.Modules.UnitTesting.Metadata`. - Press `CREATE EMPTY` at the bottom of the dialogue. - Press `YES` when asked to confirm that you want to continue creating an empty Application. - Once the Application is created, click on `Modules` in the left pane, search for `Visual Studio`, select the `Intent.VisualStudio.Projects` Module and finally click the install button on the right: - Select the `Visual Studio` designer on the left. - Press the `CREATE NEW PACKAGE` button. - Use `${application.name}` for the "Name" of the package and press `DONE`. > [!NOTE] > By having given the Solution element the same name as for the Application Template, when this Module is installed the [metadata merging](#metadata-merging) logic prevents a duplicate being created. - Right-click on the `${application.name}` element and select `C# Project (.NET)`. - Set its name to `${application.name}.UnitTesting`. - Right-click the `${application.name}.UnitTesting` element and select `Add Role`. - Set its name to `UnitTesting`. - Press the `Export` button in the toolbar and `Copied to clipboard...` will appear at the bottom right of the window. ![Metadata exported to clipboard](images/module-metadata-export-metadata.png) ### Add the exported metadata to the Module - Open the `.sln` file for your Modules solution. - Inside the `MyOrganisation.Modules.UnitTesting` project create a `content` folder. - Inside the `content` folder create a file called `visual-studio.installation.config`. - Paste the contents from your clipboard into the file. - Build the project. - Note the line with `1>Added content/visual-studio.installation.config.` in the build log which confirms that the file was included in the packaged `.imod` file. ![Built in Visual Studio](images/module-metadata-vs-ide.png) ### Module metadata summary If you install (or re-install) the Module, you will see it now adds a project called `${application.name}.UnitTesting` to the `Visual Studio` designer. --- # Template string substitution Template strings of the form `${...}` are substituted with dynamic values during installation of Application Templates. They can be used in: - Metadata `.installation.config` files - [Installation file](https://docs.intentarchitect.com/docs-md/application-templates/installation-files/installation-files.md) contents. ## Built-in values The following template strings are always available: | Template String | Description | |-|-| | `${solution.name}` | The Intent Architect Solution name. | | `${application.name}` | The Intent Architect Application name. | | `${mcpServer.executable}` | The path to the Intent Architect MCP server executable. | | `${mcpServer.executableArguments}` | The arguments to pass to the Intent Architect MCP server executable. | ## Field Configuration values All [Field Configuration](https://docs.intentarchitect.com/docs-md/application-templates/how-to-create-application-templates/how-to-create-application-templates.md#field-configurations) values are available as template strings. Use a Field Configuration's `Value` with the `${}` format and it will be substituted with the user-captured (or default) value. For example, for a Field Configuration with a `Value` of `custom-field`, use `${custom-field}` in installation files to have the captured value substituted. ## Case conversion functions The following functions can be applied to any substitutable value by wrapping it in a function call: | Function | Description | Example | |-|-|-| | `camelCase(...)` | Converts the value to camelCase. | `${camelCase(application.name)}` | | `pascalCase(...)` | Converts the value to PascalCase. | `${pascalCase(application.name)}` | | `kebabCase(...)` | Converts the value to kebab-case. | `${kebabCase(application.name)}` | ## Common usage Template strings are commonly used in Visual Studio designer metadata where the root element has a name of `${solution.name}` and projects have names like `${application.name}.Data.Entities`. --- # Version Range Notation You might have come across a version specification looking like this: `[3.0.0,4.0.0)`. It is very similar to the [mathematical interval notation](https://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals) where the `[`, `]` (square bracket) characters indicate inclusivity, while the `(`, `)` (parenthesis) indicate exclusivity. So starting with a `[` character means that the number that follows will be included in the range check and a number that exists right before the last `)` will indicate the end of the range without the number itself. This is how Intent Architect denotes how to interpret version numbers when determining whether a given component is compatible with another component. For instance, a Module might be built for a given version of Intent Architect which is guaranteed to be compatible. A future version may have a different API exposed which might not be compatible with this Module and therefore the Module might instruct Intent Architect that if the version of Intent Architect is greater than or equal to 3.0.0 **AND** less than 4.0.0, **then** it will be deemed compatible. --- # Get Intent Architect Intent Architect runs locally on your machine, no cloud dependencies, no lock-in. Follow these simple steps to create your account, download, and install the app. --- > [!NOTE] > To use Intent Architect, you’ll need a valid license. If your organization already subscribes, your license will be assigned automatically. If you would like to get a license, please [get in touch](https://intentarchitect.com/#/contact-us) with our team or send an email to [info@intentarchitect.com](mailto:info@intentarchitect.com?subject=Intent%20Architect%20-%20License%20Request). ### 1️⃣ Create an account If you haven't yet done so, [create an account](https://intentarchitect.com/signup). ### 2️⃣ Download Download the app from our [website](https://intentarchitect.com/#/downloads). ### 3️⃣ Install Install the app. 🚀 You’re all set! --- ## Troubleshooting Having issues installing the app? Check out our [Detailed Installation & Troubleshooting guide](https://docs.intentarchitect.com/docs-md/introducing/installation-guide.md). ## Next Steps - Continue to **[Quick start](https://docs.intentarchitect.com/docs-md/introducing/quickstart.md)** to build your first application in under 15 minutes - Jump to **[Intent Architect fundamentals](https://docs.intentarchitect.com/docs-md/tutorials/fundamentals/fundamentals-landing-page.md)** --- # Enable Long Path Support (Windows only) Depending on the number of parent folders for the root path of your Intent Application, it can be quite easy to hit Windows' [maximum file path length limitation](https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation#enable-long-paths-in-windows-10-version-1607-and-later), this can cause various problems to manifest, such as: - Errors when extracting/compressing files into / out from .zip files. - Errors checking files in or out of source control management (such as Git). As of Windows 10 1607 it is possible to disable this limitation for all applications. ## Configuring Windows to support long paths # [PowerShell](#tab/powershell) ```powershell New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force ``` # [Registry (.reg) file](#tab/regedit) ```text Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem] "LongPathsEnabled"=dword:00000001 ``` --- ## Configuring Git to support long paths ```bash git config --system core.longpaths true ``` ## Troubleshooting ### Github Desktop If even after performing the above steps, you still get a `Filename too long` error when cloning a repository using the **Github Desktop Client**, perform the following steps: - Browse to the location of your GitHub Desktop installation (default location is `%USERPROFILE%\AppData\Local\GitHubDesktop`) - Browse to the subfolder: `app-x.x.x >> resources >> app >> git >> etc`, so the full path would be `%USERPROFILE%\AppData\Local\GitHubDesktop\app-x.x.x\resources\app\git\etc` - Edit the `gitconfig` file (in your text editor of choice) - Add `longpath=true` under the `core` section ``` [core] symlinks = false autocrlf = true fscache = true longpaths = true ``` - Restart the Github Client and try the clone again. --- # Take a tour ## Introduction This tour covers the key concepts in Intent Architect at a high-level. It aims to introduce each concept without going into too much detail. Let's get started. Once [installed and logged in with your account](https://docs.intentarchitect.com/docs-md/getting-started/get-the-application/get-the-application.md), you will be presented with the home view of Intent Architect. From here you have options to `Create a new application`, `Create a new module`, or `Open an existing solution workspace`. ![Getting Started](images/home-getting-started.png) Previously opened solutions will be listed under the **Open recent** header for quick access later on. ## Applications Applications in Intent Architect represent a _scope of code files_ that we want to automate. It could be a full-stack monolithic application, a microservice, or even just a single folder in which we wish to generate and manage files. Applications are fundamentally composed of installed Modules, Designers, and some high-level configuration settings. These concepts will be discussed later in the tour. Creating a new application in Intent Architect can be done by clicking on the `Create a new application` button in the home view to launch the _Create application_ wizard. ![Create Application Start](images/create-application-start.png) The first page of the wizard lists the available [Application Templates](#application-templates) in the selected repository (by default this will be Intent Architect's website [https://intentarchitect.com/](https://intentarchitect.com/) but could be a local directory or network location. [Learn how to change your default repositories here](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-change-user-settings/how-to-change-user-settings.md)). Here you can search and choose which application template you want and directly configure key settings like the Application's name, location, and the [solution](#solutions)'s name. > [!NOTE] > It is possible to create an empty application by clicking on the `CREATE EMPTY` button instead of `NEXT`. This would create a new application without any Modules or metadata installed. ### Application Templates Application Templates allow specifying a pre-defined set of options of which Modules and Metadata (for [Designers](#designers)) to automatically install into the new Application during its creation. To illustrate this, let's create a sample .NET Core Application using the `Clean Architecture .NET` Application Template. Following the steps we took above, select the template and fill out the Application's name, location, and the Solution name, then click `NEXT`. The wizard then moves to the next page which displays the high-level _Components_ that make up the template. ![Application Template Components](images/application-template-components.png) Each block is a _Component_ and each represents one or more Modules that will be installed into the new Application. Each Component may be included or excluded as required. To see exactly which Modules will be installed (which is based on our selection of Components), we can expand the Component list on the right side of the page. ![Application Template Modules](images/application-template-modules.png) _This screenshot of the component list on the right of the screen shows exactly which modules would be installed from the selected Components._ To create the Application, we then click on the `CREATE` button. Intent Architect will download and install the Modules correlating to the selected Components, as well as create any default metadata that is required. > [!NOTE] > Although this example is targeting .NET, this process would work in the same way for other Application Templates that are designed for other languages. ## Application Settings Once Intent Architect has finished creating the application and installing the selected components, it will open the _Settings_ view automatically. ![Application Settings](images/application-settings.png) Here you will be able to rename the Application, change its icon, add a description, and change the _Relative Output Location_. Below the _Relative Output Location_, Intent Architect shows the full path into where code will be created. To open this location in the default file explorer, you can simply click on the full path _link_. > [!NOTE] > If the path doesn't exist yet, it won't be able to open. Settings can be accessed through the applications's context menu. ## Modules Modules are distributable artifacts which are the _building blocks_ of pattern reuse in Intent Architect. Typically, the purpose of a Module is to generate and manage a set of code files in a codebase, usually around a particular architectural pattern. This could for example be the entities in our domain, simple bootstrapping files, ORM mappings, controllers in our API, etc. Modules have similarities with package systems such as NuGet, NPM, and Maven. However, where the primary objective of these systems is to facilitate code-reuse, the primary objective of Modules is to facilitate _pattern-reuse_. Modules have versions and dependencies and don't directly introduce any runtime dependencies. They can, however, be configured to introduce package dependencies if the designer of the Module so chooses. To see your applications installed modules right-click on the application and select the `Manage Modules` menu option. ![Application Context Menu](images/application-context-menu.png) ![Application Modules](images/application-modules-installed.png) _This example shows the list of Installed Modules in our sample application. The `Intent.Application.MediatR` Module has been selected, with its details displayed in the pane on the right._ > [!NOTE] > When you select an installed Module, Intent Architect gives a view into the internals of that module (i.e. the Templates, Decorators, and Factory Extensions that it is made up of). ### Module Settings It is worth noting that some Modules provide additional settings that a developer can configure. These sections can be found on the [Application Settings](#application-settings) page. ![Module Settings](images/application-settings-module-settings.png) _This example shows `Database Settings` which have been introduced by the installed modules, as an example the `Key Type` selector, which allows a developer to choose which Datatype would best represent a Primary Key for that application. In this case, it will be represented as a `guid`._ ## Designers Designers in Intent Architect allow you to describe your application's design as visual models and hierarchical concepts. For example, Designers could be used to describe things such as entities in a domain, services that make up the applications API, events that are published and subscribed, etc. Designers are added to the Application when a Module that has a designer configuration is installed. You can therefore choose which Designers you would like to use in your Application. > [!NOTE] > Designers can be created and configured by using the Intent Module Builder. Existing Designers can also be extended with new concepts, configurations and options. ![Domain Designer](images/designers-domain.png) _An example from our sample application showing a Domain model inside of the Domain Designer._ Designers serve as a **blueprint** of your system. They can be used to describe any aspect of your Application. Typically, they are used to capture the following: - **Codebase Structure** - Visual Studio Projects in .NET, Folder Structures in other languages, etc. - **Entities** - Entities and their relationships to one another, Domain Driven Design (DDD) concepts (Aggregate Roots, Entities, and Value Objects), etc. - **Database Schemas** - Tables, Documents, Foreign keys, Indices, etc. - **Services** - RESTful web services, SOAP services, security settings, transactional settings, etc. - **Client Proxies** - Web client proxies, Synchronous Query Proxies, etc. - **Eventing** - Messages, Topics, Queues, Subscriptions, etc. (often used to support Microservice architectures) - **Workflows** - Workflow Diagrams, Process Diagrams, etc. - **Front-End Structure** - Components, Routing, Modules, View Models, Views, etc. ## Software Factory Execution The Software Factory Execution is the process that executes the installed Modules with the metadata from the Designers within an Application. This results in changes to the codebase which are **staged** before being accepted or rejected.

Intent Architect will not make changes to your codebase without your consent. The changes that are listed can be clicked on which will launch a Diff tool for you to view the changes between the files - like a _pull request_ from your robot developer friend. ![Diff Example](images/diff-example.png) _An example diff of changes made to a C# interface that's managed by Intent Architect._ The Software Factory Execution can also be minimized while it's busy processing the changes it needs to apply. This is useful when processing times might be long or for executing additional Software Factory Executions in parallel. You can end a Software Factory Execution prematurely by clicking on the Red button with the cross.

The Software Factory Execution is initiated from within an Application by clicking on the _Play_ button in the top tool bar labelled `Run Software Factory`. The execution is typically kicked off after completing some design changes or installing/updating Modules. ![Play Button](images/software-factory-execution-play-button.png) > [!NOTE] > When you are developing Modules, it is possible to **Debug** them. Intent Architect supports this by prompting you to attach a debugger when you click the down arrow next to the _Play_ button and then selecting `Run with Debugging`. > ![Debug Button](images/software-factory-execution-debug-button.png) ## Solutions Solutions in Intent Architect serve as a _view_ on one or more Applications. They have some basic settings and can configure which Repositories are available for the Applications. An Application can only be opened in the context on a Solution, which is represented by an `.isln` (Intent Solution) file on the disk drive. ![Solution Explorer](images/solution-explorer.png) _Solution Explorer with a single Application from this tour._ Additional Applications can be created from the Solution Explorer through the by context menu clicking on the `Create new application ...` menu option or the `+` icon in the toolbar. ## What's Next ### [The "Hello World" tutorial](https://docs.intentarchitect.com/docs-md/tutorials/hello-world/hello-world-tutorial.md) Tutorial on how to create a new Application from scratch. --- # Welcome ## Overview Intent Architect is the first architecture-centric code automation platform for professional .NET developers. It’s the platform leading .NET teams use to deliver up to 10X faster, standardize flawlessly at scale, and maintain systems with ease – using their preferred tech stack, IDE and LLM. It combines cutting-edge code generation systems, such as Pattern Reuse and your favorite LLM, to continuously generate clean, production-ready code driven by your architectural intention and approved design - so your code stays standardized, consistent, and easy to maintain. ## Watch a demo If you're new to Intent Architect, the best way to understand the platform is to watch a demo. Watch this recent webinar where Gareth Baars - founder of Intent Architect - walks you through how to build enterprise-grade C# / .NET applications in a fraction of the time with Intent Architect. What's covered in this demo: * What is "Pattern Reuse" actually? * A comprehensive introduction & demo of the platform, Intent Architect * Building a working, high-quality .NET application in minutes following a Clean Architecture * A sneak peek at how Intent Architect can leverage generative-AI to push productivity even further. ## How it's achieved Intent Architect combines visual modelling, code-management, and pattern reuse into a powerful and intuitive platform to offer the first truly practical approach to code-automation. These mechanisms are explained briefly below. ### Pattern Reuse Software systems are made up of patterns – each instance is different, but the patterns are the same. These patterns glue the technologies to the business logic and can easily account for over 80% of a codebase. Pattern reuse is the ability to turn these patterns into artifacts that can be reused within projects and across organizations. Intent Architect's _Modules_ serve as a collection of one or more of these artifacts and provide the mechanism to turn visual models and metadata into code. With Intent Architect, teams can choose to build and maintain their own Modules or simply reuse modules made by other developers. ![Pattern Reuse Example](images/pattern-reuse-example-dark.png) _An example of an application template for a .NET Core web application that uses Robert Martin's clean architectural principles. Each component represents Modules that automate a specific aspect of the architecture._ ### Visual Models Intent Architect allows developers to use visual models to describe their application's design. By compressing information into visual formats (e.g. entity-relationship diagrams), the human mind can quickly interpret and digest it. When instructed, Intent Architect synchronizes the visual models with the codebase according to the patterns (Modules) that the developer has installed. In this way, it ensures that the visual models are a true representation of the codebase, thereby providing teams with blueprints of their systems. ![Visual Modelling Example](images/visual-modelling-example-dark.png) _Example of an entity-relationship diagram being used to design a domain and its persistence characteristics._ ### Code Management Code-automation, as used in the software industry, has two flavours: once-off (scaffolding) and continuous. Both have their place but come with trade-offs and compromises in the form of customizability and control. Code-management is a unique approach to code-automation that completely avoids the typical compromises. It utilizes abstract syntax tree parsing and intelligent algorithms to merge user-written code with automatically generated code. Code-management allows developers to control the automation systems of Intent Architect from a high-level down to the granular level of the members within each managed code file (e.g. classes, fields, functions, methods, etc.). As an example, a developer may configure a C# or Java file such that they are managing the implementation of one method within a class, while Intent Architect will manage the remaining methods. They may then choose to change this configuration, perhaps deciding to take over management of the entire class or just one of the other methods. The configuration of each file is completely controlled by the developer, preventing the automation system from ever getting in the way. ![Code-Management Example](images/code-management-example-dark.png) _An example of how Intent Architect would change a C# `Startup.cs` file that is partially managed by the developer and partially by Intent Architect. Changes are staged before being accepted by the developer, allowing them to diff the changes before accepting or rejecting them._ ## Supported technologies Intent Architect **does not introduce any hard runtime dependencies** and is capable of generating and managing files for _any_ programming language or technology. Therefore, since Intent Architect is language-oriented (it manages code files), it completely supports any framework or technology (including custom, in-house built frameworks) that is managed by the language. For the advanced [code-management](#code-management) capabilities described above, Intent Architect currently has support for the following languages: - C# - Java - TypeScript / JavaScript - HTML - SQL - Kotlin This list will continue to grow to include other popular programming languages such as Python and Go in the future. ## Non-prescriptive Intent Architect is non-prescriptive, making it unopinionated regarding the choice of architecture, language, technologies, or even how the application's design is prescribed. While other code generation solutions tend to generate code that works and looks in a particular way; Intent Architect is instead just a platform, and the code that is managed is determined by the particular _Modules_ that the team has installed into any particular application. Since _Modules_ are typically created and managed by the architect(s) within an organization, there is no constraint on forced opt-in since the development team is completely in control of what code is managed under automation and what code they will manage by hand. Similarly, Intent Architect does not dictate how developers design their system. Where most modelling products constrain developers to use strict UML diagrams (Class Diagrams, State Diagrams, Sequence Diagrams, etc.), Intent Architect's modelling systems are completely customizable, configurable, and extensible. Teams choose how _they_ want to design their application and _which_ parts of the system they want to model visually. ## Use cases Intent Architect is ideal for automating architectural, infrastructural, and boilerplate code. Depending on the project, this type of code can make up over 80% of the codebase. Typically, the tool is used by developers to achieve the following: - **Bootstrapping** - Microservices, Monolithic Applications, Application Modules, Identity, etc. - **Persistence Infrastructure** - ORM Mappings, Repositories, etc. - **Service Infrastructure** - RESTful Web Services, Data Transfer Objects, Dispatch Patterns (e.g. Mediator, Interface Dispatch), etc. - **Eventing Infrastructure** - Events, Message Broker Configuration, Message Dispatch Infrastructure, etc. - **Business Logic Placeholders** - Domain Entities, Service Call Handlers, Command / Query Handlers, etc. - **Front-End Infrastructure** - Components, Service Proxies, Models, etc. - **Workflow Design** - Workflow Infrastructure, Flow Control Systems, etc. The use cases for Intent Architect really are endless. A general rule of thumb is **if it can be described, it can be automated**. In other words, if a developer can define a way to adequately model their _design intent_, they would be able to create a _Module_ to automate that particular pattern. ## No lock-in Intent Architect is not a framework or runtime platform. It does not inherently introduce any hard runtime dependencies. Because it generates and manages code (and the code is written in the same style as a developer - a key principle when creating Modules), there is no dependency on it and therefore no lock-in. Teams may choose to continue building their project without Intent Architect and all the code that was managed by Intent Architect will be no worse off than if it were originally written by hand. ## What's Next ### [Get the application](https://docs.intentarchitect.com/docs-md/getting-started/get-the-application/get-the-application.md) How to download and install Intent Architect, and how to create an account. ### [Take a tour](https://docs.intentarchitect.com/docs-md/getting-started/take-a-tour/take-a-tour.md) Discover the key concepts and features in Intent Architect. --- # Getting help: Reference & resources Have a question, issue, or suggestion? Here’s how to find help and get the support you need. --- ## ![In-app Help icon](images/in-app-g.png) **In-app help** Browse contextual tips and how-tos right inside the app *Find in-context answers inside the app* [Learn more about in-app help topics→](#in-app-help) ────── ## ![Docs icon](images/docs-g.png) **Website documentation** Guides, tutorials, and best practices *Explore how-to guides and tutorials to deepen your knowledge* [Go to the docs website →](https://docs.intentarchitect.com/docs-md/tutorials/fundamentals/fundamentals-landing-page.md) ────── ## ![Contact icon](images/contact-support-g.png) **Contact support** Can’t find it? Reach our team for personalized help *Reach our team with your question or feedback* [Learn how to contact our support team →](#contact-support) ────── ## ![Release Notes icon](images/release-notes-g.png) **Release notes** See what’s new, fixed, or changed by version *Check what's new in the latest module versions* [Learn more about release notes →](#release-notes) --- ## In-app help Intent Architect includes a built-in help system to provide quick access to guidance and documentation. You can open it by clicking the **blue question mark button in the top-right corner** of the window, or simply by pressing `F1`. ![In-app Help Button](images/in-app-help-button.png) ### Help dialog Clicking the help button opens the **Help dialog**, which displays relevant help topics based on your currently installed modules. This includes context-specific documentation tailored to the currently selected element and open designer. You'll also find quick links to contact support or open the main Intent Architect documentation website. You can browse through the suggested topics or use the **search box** to look up keywords. When searching, **all available documentation** is included - even topics from modules that aren’t currently installed. This makes it easy to discover additional features and functionality you might want to add to your application. > [!NOTE] > The Help dialog is **context-aware** by default, showing topics related to the currently active designer and selected elements. > Use the search box to explore beyond your current context or to discover features from other modules. ![In-app Help Dialog](images/in-app-help-dialog.png) --- ## Contact support Need help, request a feature or share feedback? Here are a few ways to get in touch with us: ### ![Email](images/email-g.png) **Email us** Have a question or issue? [Reach out to us via email →](mailto:support@intentarchitect.com) ────── ### ![Github](images/github-g.png) **GitHub issues** Log a ticket on our public GitHub support repository or browse existing issues for answers. [Log an issue on GitHub →](https://github.com/IntentArchitect/Support) > [!TIP] > You can also find this link inside the [in-app help documentation](#in-app-help) ────── ### ![In-app Feedback](images/in-app-feedback.png) **In-App feedback** Send feedback or rate the application directly within the app using the *yellow face feedback icon* in the top bar: ![In-app Feedback Icon](images/in-app-feedback-icon.png) ────── ### ![Contact icon](images/teams-g.png) **Teams / Slack** You can also contact us through your company’s dedicated **Teams** or **Slack** support channel. --- ## Release notes On the `Modules` screen, each module includes a link to its release notes. ![Release Notes link](images/release-notes-module-list.png) Clicking **Release Notes** displays a versioned list of *enhancements, bug fixes, and new features* for the module. ![Release Notes Details](images/release-notes-module-details.png) --- ## Helpful Links - **[Official site](https://intentarchitect.com)** - **[Get Intent Architect](https://docs.intentarchitect.com/docs-md/getting-started/get-the-application/get-the-application.md)** - **[Quick start](https://docs.intentarchitect.com/docs-md/introducing/quickstart.md)** - **[Intent Architect fundamentals](https://docs.intentarchitect.com/docs-md/tutorials/fundamentals/fundamentals-landing-page.md)** --- # Installation guide ## Windows Simply run the digitally signed `.exe` file which is a one-click installer that will install Intent Architect and then run it immediately afterward. > [!TIP] > To avoid issues with long paths we strongly recommend [enabling long path support](https://docs.intentarchitect.com/docs-md/getting-started/long-path-support-windows-only/long-path-support-windows-only.md). > > [!TIP] > To ensure Intent Architect can run the Software Factory as quickly as possible, we highly recommend reviewing [Environmental factors which can slow down Software Factory execution](https://docs.intentarchitect.com/docs-md/application-development/software-factory/environmental-factors-which-can-slow-down-software-factory-execution/environmental-factors-which-can-slow-down-software-factory-execution.md). ## MacOS Simply open the `.dmg` file and drag Intent Architect into the Applications folder. ## Linux For Linux, we recommend downloading the [AppImage](https://appimage.org/) package format option which is able to run on any common Linux-based operating system (Ubuntu, Debian, openSUSE, RHEL, CentOS, Fedora, etc.). Although not essential, the easiest way to use AppImages is to first install [AppImageLauncher](https://github.com/TheAssassin/AppImageLauncher) which makes using AppImages seamless and also enables integration with your desktop environment, for example, it will "install" AppImages such that they appear in your desktop environment's list of Applications. If you just want to run Intent Architect without "installing" it, you can just set the downloaded file to be executable by running `chmod a+x intent-architect*.AppImage` after which you can then run it. ### I'm getting an "The configured user limit (128) on the number of inotify instances has been reached" error On some Linux distributions, the Software Factory may show the following error: ```text System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached, or the per-process limit on the number of open file descriptors has been reached. at System.IO.FileSystemWatcher.StartRaisingEvents() ``` You can get your current inotify file watch limit by executing: ```bash cat /proc/sys/fs/inotify/max_user_instances cat /proc/sys/fs/inotify/max_user_watches ``` You can set a temporary new limit with: ```bash sudo sysctl -w fs.inotify.max_user_instances=16384 sudo sysctl -w fs.inotify.max_user_watches=16384 ``` To make your limit permanent use: ```bash echo fs.inotify.max_user_instances=16384 | sudo tee -a /etc/sysctl.conf echo fs.inotify.max_user_watches=16384 | sudo tee -a /etc/sysctl.conf sudo sysctl -p ``` --- # Quick start: Build your first application Learn how to create, generate, and run your first application in **Intent Architect**, all in under 15 minutes. **Goal:** Build a working .NET Clean Architecture application. --- [Watch video on YouTube](https://www.youtube.com/watch?v=kFa_3ZHFusk) --- ## 1️⃣ Create a new solution Start **Intent Architect** and click **Create New Solution**. Choose the **Clean Architecture template**, name your app (e.g., *MyShop*), and keep the default modules selected. ## 2️⃣ Generate your codebase Run the **Software Factory** to stage and apply your first code generation. Intent Architect will create a full, working solution, no runtime dependencies, 100% standard .NET code. ## 3️⃣ Run and explore Open the generated `.sln` in Visual Studio or Rider. Run the API project, open **Swagger**, and try out the customer endpoints. You’ve just built a complete, architecture-aligned application with clean, deterministic code you can evolve. --- ## Next steps - Continue to **[Fundamentals #2: Introduction to Domain Modeling](https://docs.intentarchitect.com/docs-md/tutorials/fundamentals/fundamentals-2-intro-to-domain-modeling.md)** - Learn more about **[How It Works](https://docs.intentarchitect.com/docs-md/index.md#key-concepts)** --- # AI Agents / Tools Leverage your existing context engineering setup and preferred AI-coding harness and service provider via the Intent MCP, or drive agents directly in the platform – and add the control you need to scale agentic development safely and reliably. Intent Architect allows teams to go from requirements to visual designs to working, production-ready code, with full traceability. Developers focus on engineering decisions and AI agents handle implementation. Intent Architect's own skills are bridged into each agent's native skill discovery – so your existing setup is respected rather than replaced. And the platform pre-engineers relevant context automatically, ensuring agents execute within the guardrails and in full conformance with your design and architecture, without overly complex context engineering or excessive validation. Spec-Driven Development features drive requirements through design to implemented code, with traceability maintained end to end. Reviewers can always establish what a change is for and where it came from. Teams ultimately choose how much they hand over – fully agentic, developer-augmented, or even manually driven. --- ## Key benefits - **🎯 Agents that conform to your design and architecture by default** Agents stay within the lines drawn by your system and architectural designs, executing accurately and in full conformance. Design models supply context on every turn rather than agents having to infer it from the codebase. This reduces the need for excessive context engineering, prompting and validating to keep agents aligned. - **📝 Specifications delivered as verified, traceable code** Leverage Spec-Driven Development (SDD) features and agentically drive business requirements through design specifications to production-ready code, with full traceability. Requirements are captured as precise, testable user stories, realized through an approved design expressed as changes to your model, and verified against their acceptance criteria once implemented. Traceability links flow through to Changes Review, so reviewers see the requirement behind every change. - **🧰 Any model or coding harness, without re-engineering your setup** Teams can leverage their existing harness, models and context engineering and add the governance tools needed to scale agentic coding safely and reliably. The Intent MCP Server exposes Intent Architect's tools to external agents, so a team can continue working in their existing harnesses. Alternatively, agents are driven directly in the platform, where Claude Code, Codex, Copilot and Kiro are first-class participants via the Agent Client Protocol, alongside OpenAI, Anthropic, Azure OpenAI, Gemini and any OpenAI-compatible endpoint. The context files in your existing repository (e.g. `AGENTS.md`, `CLAUDE.md`, `.cursor/rules`, Copilot instruction files, etc.) are loaded automatically, and Intent Architect's own skills are bridged into each agent's native skill discovery. The result is more control with the tooling the team already runs. --- ## The Agentic Development Workflow Intent Architect enables an end-to-end agentic development workflow, where developers can focus almost entirely on engineering and design decisions, and agents take care of the rest. For developers that choose to drive their agentic workflow from within Intent Architect, the platform presents a single AI chat interface (integrated with your preferred harness and LLM) where design and implementation are handled in one workflow. The agent helps you translate requirements into comprehensive system designs directly in the visual designers, faster and more accurately than working manually (all model changes are made in memory and never saved without your explicit approval). When implementation work is needed, it is dispatched to a coding sub-agent that handles the custom coding, while the Software Factory rolls out the architecture, infrastructure, and boilerplate deterministically to guarantee consistency at scale. In practice, the workflow looks like this: confirm your system's design visually in a single chat interface, run the Software Factory and AI coding tasks, and out the other side comes working, production-ready software. Well architected, consistent, and built to your standards, at any scale.
![Software Factory with AI coding agents](images/golden-path-v5.png) --- ## Context Engineering The accuracy of Intent Architect's AI agents comes down to context and guardrails. Intent Architect derives this context directly from your structured visual models, giving agents precise knowledge of your design intent – automatically. Behind every coding agent is a customizable and sophisticated context engineering system that determines exactly which code files, architecture descriptions, use case intentions, and Skills are relevant for each task. Agents also have full support for standard context files your team is already using – CLAUDE.md, AGENTS.md, copilot-instructions.md and others, as well as Instruction Files – so your existing conventions, standards and workflows are respected automatically. The result is AI that executes accurately and in full conformance with your design and architecture – without excessive manual context setup or prompting.
![AI Modeling Assistant with context engineering](images/modeling-screen-ai-assistant-v2.png) --- ## Custom Agents For teams that want to go further, Intent Architect supports fully custom agents. Authored as .agent.md markdown files, custom agents can be tailored to your domain, technology stack, or proprietary coding standards – and configured to appear in either the modeling or coding context. --- ## The Intent MCP Server The Intent MCP Server gives teams complete flexibility in how they configure their AI tooling. Use Intent Architect's integrated agents, your own external AI coding tools, or any combination of both – all while keeping your design and architecture managed centrally and visually in Intent Architect. This means teams can use whichever tools suit them best, without conflicts between external agents and Intent Architect-managed code. Details on how to configure the Intent MCP can be found in the AI Configuration dialog (xref:ai.configuration). --- ## Connect Your Preferred Provider Intent Architect is designed to work with the AI providers and models your team already uses. Connect to OpenAI, Azure OpenAI, Anthropic, or other compatible providers directly from the AI Configuration dialog – which also walks you through setting up the Intent MCP Server and any additional MCP servers your agents can use. AI agents are pre-configured to work well for most use cases, with the flexibility to customize context engineering and agent behavior for specialized domains or proprietary coding styles.
![AI Configuration](images/ai-configuration.png) --- ## Learn More - **[Authoritative Design Blueprints](https://docs.intentarchitect.com/docs-md/key-concepts/authoritative-design-blueprints.md)** - **[Architectural Guardrails](https://docs.intentarchitect.com/docs-md/key-concepts/architectural-guardrails.md)** - **[Codebase Governance](https://docs.intentarchitect.com/docs-md/key-concepts/codebase-governance.md)** --- # Architectural Guardrails Guaranteed architectural adherence and consistency across teams and systems. Intent Architect's guardrail system uses Modules – customizable and reusable architectural patterns – to deterministically turn your chosen architecture into code, guaranteeing consistent implementation at any scale. Although AI-assisted, this is deterministic code generation: 100% predictable, 100% consistent and developer-controlled – so guardrails scale effortlessly and AI-driven development becomes more reliable, without the need for constant validation. It's not a scaffolding system but a continuously synchronized process, where the architecture can evolve with the assistance of AI, or large-scale technology changes can be made across entire systems in one go, while adherence is automatically enforced and changes or customizations made by agents or developers are immediately flagged for review. Because any application can be configured with a completely unique set of modules, the system accommodates all the different patterns, standards, technologies, and architectures your teams may follow. --- ## Key Benefits - **✅ Guaranteed adherence that scales reliably without the validation burden** Guardrails maintained purely through context files have to be internalized by every developer on every system and project before adherence can be efficiently validated, which makes them hard to scale and adds to the validation burden. Intent Architect's guardrails are encoded in Modules and enforced by the Software Factory on every run: a given architectural design produces the same code every time, with no drift and no room for interpretation. Customizations and deviations are still fully allowed, but tracked and flagged automatically, so adherence is managed by exception rather than by review. - **🔄 System-wide architectural changes in one action** Agents can generate a framework upgrade or a convention change across a system, but the cost of doing so scales with the system: every affected service produces its own diff, every diff has to be individually validated, and consistency across all of them is never guaranteed due to AI's probabilistic nature. A single service that drifts becomes permanent inconsistency that every subsequent change has to accommodate. Because the pattern is encoded once in a Module, updating or swapping that Module propagates the change across every application of the pattern in a single pass. So, regardless of the number of services, the work and the review are the same, and the result is achieved significantly faster and guaranteed to be consistent. - **🛠️ AI-assisted module building and customization** Module development is AI-assisted, so architectural patterns can be altered and evolved with minimal investment. Aligning a Module with your team's standards, accommodating an existing convention, or adapting a pattern as your architecture evolves is an efficient and streamlined process. Guardrails therefore remain accurate to the architecture they enforce, and evolve at the pace the team does. --- ## Modules Modules are the core building blocks of Intent Architect's deterministic guardrail system. Each Module encodes one or more architectural patterns, translating your visual design intent into precise, consistent code. When a Module is applied, it produces the same output every time, without deviation. When a Module is updated, every instance of that pattern across your system is updated automatically. When you run the Software Factory, it analyzes your visual design and applies your installed Modules to generate and update code across your solution, producing precisely the changes needed to bring your codebase into alignment with your design. The process is transparent, controlled, and fully deterministic. The deterministic guardrail system is particularly well-suited to managing: - **Bootstrapping:** Microservices, Monolithic Applications, Application Modules, Identity, etc. - **Persistence Infrastructure:** ORM Mappings, Repositories, etc. - **Service Infrastructure:** RESTful Web Services, Data Transfer Objects, Dispatch Patterns (e.g. Mediator, Interface Dispatch), etc. - **Eventing Infrastructure:** Events, Message Broker Configuration, Message Dispatch Infrastructure, etc. - **Business Logic Placeholders:** Domain Entities, Service Call Handlers, Command / Query Handlers, etc. - **Front-End Infrastructure:** Components, Service Proxies, Models, etc. - **Workflow Design:** Workflow Infrastructure, Flow Control Systems, etc.
![Software Factory](images/software-factory.png)
Intent Architect offers a library of over 100 pre-built Modules covering the most popular .NET architectural patterns and technologies, giving teams immediate access to community-tested, best-practice implementations. For teams with custom standards or specialized domains, the platform offers a powerful Module-building ecosystem, where architectural patterns can quickly and easily be authored by leveraging AI, giving you complete control over your architecture and how it is implemented – and how it evolves.
![Modules](images/modules.png) --- ## Non-Prescriptive by Design Intent Architect does not impose an architecture, a framework, or a coding style. The code it manages is determined entirely by the Modules your team installs. Teams are free to design their system however suits them, automate as much or as little as they choose, and maintain full control over what is managed by the deterministic guardrail system, or what is handled by agents or by hand. --- ## No Lock-in Intent Architect is not a framework, a runtime, or a set of libraries. It introduces no dependencies into your codebase. What it does is implement the code that realizes each architectural pattern, in exactly the way your developers or AI agents would write it, in your stack, following your conventions. This is pattern reuse, not code reuse. The knowledge of how to implement a pattern is encoded in the Module, but the output is plain, independent code that belongs entirely to your project. Teams can continue without Intent Architect at any point and the codebase is completely unaffected. --- ## Learn More - **[Authoritative Design Blueprints](https://docs.intentarchitect.com/docs-md/key-concepts/authoritative-design-blueprints.md)** - **[AI Agents / Tools](https://docs.intentarchitect.com/docs-md/key-concepts/ai-agents-tools.md)** - **[Codebase Governance](https://docs.intentarchitect.com/docs-md/key-concepts/codebase-governance.md)** --- # Authoritative Design Blueprints Condense validation and comprehension to a fraction of the time to minimize risk and sustain AI-driven velocity. As developers spend less time writing code and more time validating and governing, they need better tools to stay in control of how their systems evolve as they scale agentic coding. Intent Architect provides powerful AI-driven visual designers for modeling applications, allowing you to express every layer of your system's design, from domain and services to architecture, in a way that's intuitive, precise, and always in sync with your codebase. Requirements can be captured as precise, testable user stories and converted agentically into authoritative design specifications and living blueprints, and then realized as code – so every requirement stays linked to the model elements and code files that realize it. Visuals are a highway to the brain, and by expressing complex systems in visual formats (e.g. entity-relationship diagrams), teams can quickly interpret and reason about design and architecture that would otherwise require deep codebase immersion. Unlike static diagrams or external modeling tools, these designs are living blueprints: they track code changes as they happen, and together with advanced Change Review features and end-to-end traceability, identify exactly where to focus and why each change exists, alleviating the validation and comprehension bottlenecks of agentic coding. --- ## Key Benefits - **📋 Condense validation and comprehension to alleviate delivery bottlenecks** As agents write more and more code, comprehensive code review becomes the delivery constraint, while approving output without adequate scrutiny pushes technical debt, cognitive debt, and latent quality risk downstream. Intent Architect's extensible designers give you an aggregated, model-centric view of your design specification that's easy to comprehend and always true to the underlying codebase, so you stay in control of what matters most. In addition, model-centric diffs highlight codebase-related changes as they happen, and advanced Change Review features prioritize files for review, streamlining the validation and comprehension process so teams can safely sustain AI-driven delivery speed. - **🗺️ Authoritative design blueprints bring design decisions to the forefront** The design and architecture are explicit and visible to the entire team, rather than implicit in the code. The blueprints generated in Intent Architect are living design specifications, that realize directly as code, and therefore always accurately represent what's implemented - agentically or otherwise. This means design and architectural decisions are better and made faster, changes are made with more confidence, and issues are resolved quicker. - **⚡ A precise context engine for agents and guardrails** Design decisions are difficult to infer from code, which shows what was built but not which choices were intentional or what they were meant to enforce. In Intent Architect every design decision is captured in the model and stored as structured metadata alongside your source code, forming an always-current representation of intent that both AI agents and the deterministic guardrail system operate from. Agents execute against the design as modelled rather than as interpreted, and are provided with task context up front rather than inferring it from the codebase, so implementations conform to the approved design by default. --- ## The Designers The Domain Designer lets you model your system's core entities, relationships, and data structures, the structural foundation from which your application is built. The Services Designer defines how your system behaves: use cases, contracts, and the integrations that connect your applications. The UI Designer captures user flows, screens, and data interactions from end to end. Each designer targets a different layer of your architecture. Together, they give you a complete, structured picture of your entire system at any scale. ![The Designers](images/modeling-designers-v1.png) --- ## Designing with AI One of the most powerful ways to use the visual designers is with AI. Rather than building designs from scratch, you describe your requirements in natural language (or simply upload a specification) and the AI agent proposes the full design within the visual environment, entities, relationships, services, and more. All changes are made in memory and never saved without your explicit approval, so you stay in full control of every design decision.
![AI Modeling Assistant](images/ai-modeling-assistant.png) --- ## The Context Engine When you design in Intent Architect, every element you place, an entity, a service, a relationship, is saved as structured metadata alongside your source code. Collectively, this metadata forms the platform's context engine, a precise, always-current representation of your system's design intent. This is what makes agentic development more reliable at scale. AI coding agents all generate code from this same structured source of truth. Rather than inferring design intent from code, which is imprecise and incomplete, every downstream system works from the exact design decisions you have made, visually, in the designers. Your intent is never lost in translation. --- ## Living Documentation Because designs are stored as structured metadata alongside your source code, they are always synchronized with your codebase, reflecting the current state of your system's design and architecture. New team members can explore the full system architecture visually rather than reverse-engineering it from thousands of lines of code, significantly accelerating the time it takes to validate/comprehend the current state of the system and contribute meaningfully. --- ## Learn More - **[Architectural Guardrails](https://docs.intentarchitect.com/docs-md/key-concepts/architectural-guardrails.md)** - **[AI Agents / Tools](https://docs.intentarchitect.com/docs-md/key-concepts/ai-agents-tools.md)** - **[Codebase Governance](https://docs.intentarchitect.com/docs-md/key-concepts/codebase-governance.md)** --- # Codebase Governance No matter how you incorporate Intent Architect into your agentic development workflow, you stay in full control with complete visibility and direct access to the entire codebase. Teams always work in an integrated way with their existing coding tools and have full control over the boundaries of the architectural guardrails or what gets generated deterministically. And manage adherence by exception rather than through constant review. In addition, Change Review features help teams quickly understand what changed – whether it was deterministically generated or agent/developer written – and flag exactly what needs your attention. Inline diffs show what changed in the code, model-centric diffs show what it means for the design, and each change is attributed to whoever made it. Where a change traces back to a requirement, that link is surfaced too, so reviewers can see why it exists. --- ## Key benefits - **🔍 Streamline code reviews and stay in control as review volume grows** Code reviews stay effective and manageable as the volume of agent-written code grows. Changes Review flags what Needs Attention, prioritizes files for review, and presents inline diffs alongside model-centric diffs, so developers see code changes in conjunction with their effect on the design. Teams sustain review discipline and remain accountable as they scale agentic coding. - **🎚️ Precise control over the architectural boundaries for agentic development** Architectural adherence remains easy to enforce as teams scale up agentic coding. Code Management systems give developers full control over the boundaries between the guardrails and agent- or developer-managed code, from entire files down to individual methods. Agents always stay within these boundaries so teams can scale agentic development without giving up structure, consistency and quality. - **🛡️ Architectural governance by exception, not constant review** Guardrails scale better as they are managed by exception rather than constant review. Architectural deviations or customizations are flagged for approval directly in Changes Review. Customization Tracking captures what was changed, by whom, and how it diverges from the guardrails, giving teams a clear record of where and why the codebase departs from the standard as the system scales. --- ## Changes Review Changes Review is a code review system built for agentic development, where the volume of change is amplified beyond what conventional review processes can practically handle. It is a single place to review everything that has landed in the codebase, or is about to land before it is committed. A Needs Attention section surfaces the files that actually require review, and a nested, drill-in change tree categorizes every changed file as fully Module generated, Module generated with customizations, or entirely custom code written by agents (or developers). Each file expands to reveal its diff inline, complete with collapsible summaries and line-level add and remove statistics. Reviewers can also click through to the models affected by a change or commit, where the platform highlights the model diffs. This shows how code changes affected the design across the domain and service models, rather than only what changed in the files. Deviations are approved directly in the review, so the customization is recorded as intentional and the Software Factory stops flagging it. Linked requirements are surfaced on a change, so reviewers can establish what it is for without leaving the diff.
![Changes Review](images/changes-review.png) _An example of the Changes Review for the working tree against the Git HEAD._ --- ## The Software Factory Every change, whether Module or agent driven, passes through the Software Factory before it lands in your codebase. Changes are surfaced as clear diffs, giving developers the opportunity to review, adjust, or reject any modification before it is applied. Teams choose how the output lands. By default it is staged for review and applied on approval. Write-through mode applies it immediately for a tighter generate-and-go loop, and every run is checkpointed against the change baseline, so changes remain tracked, reviewable and revertible after the fact. Developers can also enable "Bypass all permissions" mode to let AI agents run the Software Factory unattended. The choice is always yours – configure the level of control that suits your team and task.
![Software Factory](images/software-factory-ai-chat.png) --- ## Code Management Intent Architect uses abstract syntax tree parsing and intelligent merge algorithms to combine agent- or developer-written code with Module generated code, without conflict. Developers can configure what code is managed by Modules at any granularity, from a single method implementation up to an entire file. Configuration is always in the developer's hands and can be changed at any time. This is part of what makes the architectural guardrails work at scale: developers are in full control of the architectural boundaries and what code is managed by the Modules, agents or themselves.
![Code Management](images/customization-tracking.png) For more information, read [About Code-Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md). --- ## Customization Tracking When developers or agents intentionally deviate from a generated pattern, i.e., the architectural guardrails or Module managed code, those changes are captured and surfaced across the system. Customization Tracking shows what was changed and how it diverges from the reference pattern or Module, creating an audit trail of architectural decisions that remain valuable as the system and team scale.
![Customization Tracking](images/customizations.png) For more information, read [The Software Factory Customizations screen](https://docs.intentarchitect.com/docs-md/application-development/software-factory/customizations-screen/customizations-screen.md). --- ## Learn More - **[Authoritative Design Blueprints](https://docs.intentarchitect.com/docs-md/key-concepts/authoritative-design-blueprints.md)** - **[Architectural Guardrails](https://docs.intentarchitect.com/docs-md/key-concepts/architectural-guardrails.md)** - **[AI Agents / Tools](https://docs.intentarchitect.com/docs-md/key-concepts/ai-agents-tools.md)** --- # Extensible Platform ## Modules ## Designers ## Templates ## Factory Extensions ## Migrations ## Accelerators | **Module-driven** | Every behavior is modular and extensible. Most modules are open source, allowing teams to customize and evolve patterns transparently. | - **Extensible platform for code automation** - extend Intent Architect with your own modules or templates, or customize existing ones - most are open source. --- # About the Module Builder The `Intent.ModuleBuilder` [Module](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) provides a designer for modelling Intent Architect Modules along with their components such as: - [Templates](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/about-csharp-templates/about-csharp-templates.md) - [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) - [Designer Extensions](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/about-designer-extensions/about-designer-extensions.md) - [Decorators](https://docs.intentarchitect.com/docs-md/module-building/decorators/about-decorators/about-decorators.md) - [Factory Extensions](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-software-factory-execution/about-software-factory-execution.md) - [Stereotypes](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-stereotypes/about-stereotypes.md) ![A screenshot of the module builder](images/module-builder-basic.png) See the following to learn more about Module Building: - [Tutorial: Create a Template](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/01-create-a-template-introduction/create-a-template-introduction.md) - [Tutorial: Create an Event as a Designer Extension](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/tutorial-create-an-event-as-a-designer-extension/tutorial-create-an-event-as-a-designer-extension.md) - [Designer Modelling](https://docs.intentarchitect.com/docs-md/module-building/designers/designer-modelling/designer-modelling.md) - [Configuring a Template's default output location](https://docs.intentarchitect.com/docs-md/module-building/templates-general/configuring-a-templates-default-output-location/configuring-a-templates-default-output-location.md) - [Distributing your Modules to others](https://docs.intentarchitect.com/docs-md/module-building/module-distribution/module-distribution.md) --- # Backwards compatibility for module building APIs Ensuring backwards compatibility in our module building related APIs is a fundamental principle that guides our development process. We take a meticulous approach to prevent breaking changes, minimizing disruption for our users and ensuring a stable ecosystem. This article outlines our approach to handling breaking changes, our internal processes to mitigate risks, and the policies we have in place to communicate these changes effectively. ## Types of breaking changes We categorize breaking changes into two primary areas: ### Changes in Intent Architect If breaking changes occur in Intent Architect, they are only introduced during major version upgrades. Our approach includes: - **Highlighting breaking changes in release notes** - Each major release includes detailed notes explaining the changes and their impact. - **Providing migration guidance** - Our documentation outlines clear steps for upgrading and adapting to the changes. - **Using the [Obsolete attribute](https://learn.microsoft.com/dotnet/api/system.obsoleteattribute) and XML documentation** - Previous versions of the SDK include these markers to help developers transition smoothly. - **Creating transition SDK versions** - If necessary, we release a minor or patch SDK version on the previous major version that maintains compatibility while offering migration documentation. - **Hands-on assistance** - Our support team makes itself available to assist with module updates required for transitioning from obsolete and/or removed APIs. - **Allowing side-by-side installation of major versions** - Each major version of Intent Architect is installed as an independent application meaning that major versions can be run side-by-side with each other on the same computer. This prevents users from accidentally upgrading to an incompatible version and for custom module upgrades to happen within your own timelines. - **Version compatibility enforcement** - Modules and application templates define their compatible Intent Architect versions, ensuring they are hidden by default from incompatible versions. ### Changes in Modules Modules may depend on each other, so breaking changes are managed with caution. Our approach includes: - **Major version increments** - Any breaking change in a module triggers a major version bump. - **Release notes and migration documentation** - We highlight necessary upgrade steps and provide thorough documentation in module release notes. - **Hands-on assistance** - Our support team makes itself available to assist with module updates required for transitioning from obsolete and/or removed APIs. - **Interoperability dependency management** - Modules can use an "interoperability" module dependency type which can be used to detect and upgrade other installed modules which have become incompatible with a module change. As automatic of upgrading of other modules may not always be appropriate, this option should be carefully considered before being employed. ## Frequency of creaking changes ### Intent Architect - Breaking changes only occur in major version updates. - The last breaking changes occurred in versions 2 and 3, with version 4 remaining fully backwards compatible. - While the Intent Architect tool itself does not strictly follow semantic versioning, our general approach is: - **Patch updates** focus on bug fixes and minor improvements. - **Minor version updates** introduction of note-worthy features. - **Major version updates** occur when breaking changes are necessary or significant new features are introduced. ### Modules - Breaking changes result in major version updates. - It has been rare that module major version bumps have occurred for compatibility reasons in the past and they have been mostly due to major versions bumps of Intent Architect itself. ## Risk mitigation and change control measures To ensure a stable ecosystem, we implement rigorous internal processes: - **Breaking changes in non-major versions are prohibited**. - **Major version updates require internal discussion and justification** - We carefully evaluate whether a breaking change is truly necessary and that associated improvements justify the potential inconvenience to module authors. - **Comprehensive testing in CI environments**: - We create test cases for new and updated modules. - Our CI server runs integration tests using the [Software Factory CLI](https://docs.intentarchitect.com/docs-md/tools/software-factory-cli/software-factory-cli.md). - For existing modules our CI server downloads the original immutable module artifacts to detect binary compatibility issues between the .NET assemblies inside of them. - **Major Intent Architect updates require module version alignment**: - If Intent Architect undergoes a major version update, all dependent modules receive version bumps with updated compatibility ranges. ## `Intent.SoftwareFactory.SDK` compatibility reference The table below shows the minimum version of Intent Architect required when using a particular particular version of the [Intent.SoftwareFactory.SDK](https://www.nuget.org/packages/Intent.SoftwareFactory.SDK) NuGet package: |SDK version |Minimum required Intent Architect version| |--------------|-----------------------------------------| |3.14.1 |5.0.2-a| |3.14.0 |5.0.0-a| |3.13.1 |4.6.2-a| |3.13.0 |4.6.0-a| |3.12.1 |4.5.25-a| |3.12.0 |4.5.21-a| |3.11.0 |4.5.18-a| |3.10.0 |4.5.15-a| |3.9.0 |4.5.0-a| |3.8.0-pre.2 |4.4.0-beta.2| |3.8.0-pre.1 |4.4.0-alpha.0| |3.8.0-pre.0 |4.4.0-alpha.0| |3.7.0 |4.3.0| |3.6.0-pre.6 |4.2.4-pre.1| |3.6.0-pre.5 |4.2.4-beta.5| |3.6.0-pre.4 |4.2.4-beta.2| |3.6.0-pre.3 |4.2.4-beta.2| |3.6.0-pre.2 |4.2.0-beta.0| |3.6.0-pre.1 |4.2.0-beta.0| |3.6.0-pre.0 |4.1.3-pre.3| |3.5.0-pre.3 |4.1.2-pre.1| |3.5.0-pre.2 |4.1.0-alpha.27| |3.5.0-pre.1 |4.1.0-alpha.13| |3.5.0-pre.0 |4.1.0-alpha.13| |3.4.2 |4.0.1-pre.2| |3.4.2-pre.0 |4.0.0-beta.10| |3.4.1 |4.0.0-beta.5| > [!NOTE] > SDK versions prior to `3.4.1` have not been captured. ## `Intent.Persistence.SDK` compatibility reference |SDK version |Minimum required Intent Architect version| |--------------|-----------------------------------------| |1.0.3 |4.6.0-a| |1.0.2 |4.5.18-a| |1.0.1 |4.5.0-a| > [!NOTE] > SDK versions prior to `1.0.1` have not been captured. [](#intentpersistencesdk-compatibility-reference) --- # Application Settings Application Settings allow Modules to provide configuration options that end-users can customize within Intent Architect. These settings help enable flexible, modular behavior without requiring code changes. Settings can be accessed through the Application's context menu option `Settings`. Modules can create new `Settings Groups` (collections of related settings) or extend existing ones from other modules. ![Application Settings Example](images/application-module-settings.png) ## Creating Application Settings for your Module To create a new settings group for your module, open the Module Builder and follow these steps: 1. Right-click on the **Package** element at the top of the tree view and select `New Settings Group`. 2. Enter a name for the settings group that will appear on the Application Settings screen (for example, `My Module Settings`). 3. Right-click on the new `Settings Group` and select `Add Field`. 4. Enter a field name (for example, `Enable My Setting`) and choose a field type (for example, `Switch`). ![My Modules Setting Example](images/my-module-settings-example.png) Once the Module is installed in your target Application, the new `Settings Group` will appear on the Application's Settings screen. ![My Modules Settings Screen Example](images/my-module-settings-screen-example.png) > [!TIP] > To change the icon displayed for your settings group, open the Settings page for your Module Builder application and change the module's icon. > > ![Change Settings Icon](images/change-settings-icon.png) ## Consuming Application Settings inside your Module The generated Setting code is available in your Module's Visual Studio solution under the `Settings` folder. To use a setting in your Template code: 1. Access `ExecutionContext.Settings` in your template 2. Use the generated extension method (for example, `GetMyModuleSettings()`) 3. Call the property accessor method (for example, `EnableMySetting()`) ```csharp private bool IsMySettingEnabled() { return ExecutionContext.Settings.GetMyModuleSettings().EnableMySetting(); } ``` To read the same setting inside a Factory Extension, access it through the supplied `application` parameter: ```csharp protected override void OnBeforeTemplateExecution(IApplication application) { var isMySettingEnabled = application.Settings.GetMyModuleSettings().EnableMySetting(); } ``` ## Extending an existing Module Settings Group You can extend settings groups created by other modules. First, install the target module with the settings group you want to extend (ensure only the `Install Designer Metadata` option is checked). For example, install `Intent.Modelers.Domain`. Then, in your Module Builder, follow these steps: 1. Right-click on the **Package** element and select `New Settings Extension`. 2. Enter a distinct name for the `Settings Extension`. 3. In the **Type** dropdown, select the `Settings Group` you want to extend (for example, `Domain Settings`). 4. Right-click on the new `Settings Extension` and select `Add Field`. 5. Enter a field name (for example, `Custom Settings Field`) and choose a field type (for example, `Switch`). 6. Optionally, fill in the **Hint** property to explain what this setting does. Once your module is installed in an Application, your new field will be added to the selected `Settings Group`. ![My Modules Setting Extension Example](images/my-module-settings-extension-example.png) ## User Settings User Settings are similar to Application Settings, but they are scoped to individual users rather than the entire application. These settings persist across multiple applications and allow users to configure module behavior according to their personal preferences. ### Creating User Settings for your Module To create a new user settings group for your module, open the Module Builder and follow these steps: 1. Right-click on the **Package** element at the top of the tree view and select `New Settings Group`. 2. Enter a name for the settings group (for example, `Custom User Settings`). 3. Right-click on the new `Settings Group` and select `Add Field`. 4. Enter a field name (for example, `Custom User Setting`) and choose a field type `Text Box`. The difference from Application Settings is in how these are accessed and where they appear in the user interface. User Settings appear in the **User Settings** dialog, which is accessible by left-clicking on the profile icon at the top-right corner of the Intent Architect window. ![Module Builder User Settings Example](images/module-builder-user-settings-example.png) Once your module is installed, these settings will appear in the **User Settings** dialog: ![User Settings Dialog Example](images/user-settings-dialog-example.png) ### Consuming User Settings inside your Module User Settings are accessed similarly to Application Settings. In your Template code, you can read user settings by: 1. Accessing `ExecutionContext.Settings` in your template 2. Using the generated extension method (for example, `GetCustomUserSettings()`) 3. Calling the property accessor method (for example, `CustomSettingsField()`) ```csharp private string GetUserPreference() { return ExecutionContext.Settings.GetCustomUserSettings().CustomUserSetting(); } ``` To read user settings inside a Factory Extension, access them through the supplied `application` parameter: ```csharp protected override void OnBeforeTemplateExecution(IApplication application) { var customUserSetting = application.Settings.GetCustomUserSettings().CustomUserSetting(); } ``` ## Related Topics - [Application Settings in Application Templates](https://docs.intentarchitect.com/docs-md/application-templates/how-to-create-application-templates/how-to-create-application-templates.md#for-module-settings) --- # Best Practice Guide ## Philosophical principles of pattern-reuse When applying pattern-reuse to software development, there are a set of philosophical principles that, if followed, yield a highly effective implementation. These philosophical principles are listed below: ### 1. Developers stay in control Changes to the codebase by the automation system should never be done without the direct consent of the developer. In addition, the developer should have a clear view on what the changes in question are. ### 2. Automate best coding standards. The code which is automated should be clear, concise and follow best practice. It should be as if it was written by your best developer, on his best day. ### 3. Don't convolute the codebase. Just because an automation system can make it easy to solve problems in verbose ways, this should be avoided. Code structures and implementations should ideally follow the same approach that the team would have taken if they weren't using a code-automation platform but had the time to do the ideal implementation. ### 4. The automation system should never get in the way The code-automation system should never get in the way, impede or prevent the developers from making a particular change or delivering on time. Where managed code needs to changed, the developer must have an easy way to tell the automation system to stop managing that code. --- # Debugging Modules When developing Modules, it's very useful to be able to debug code inside the Module. Intent Architect supports this by pausing the Software Factory Execution process in order to allow you to connect a debugger to the running process. For example, to debug using Visual Studio: 1. Run the Software Factory Execution in _DEBUG_ mode by clicking on the down arrow next to the _Run Software Factory_ button and then selecting _Run with Debugging_. The Software Factory Execution will launch and pause with a `Attach Debugger` dialog awaiting for confirmation to proceed. ![Attach Debugger](images/attach-debugger.png) 2. Use the process ID or Name to attach the debugger in your IDE. ![Attach Debugger in VS](images/visual-studio-attach-debugger.png) 3. Click the `OK` button on the `Attach Debugger` dialog. The breakpoints in your module will now be hit:

--- # About Decorators Decorators are a mechanism which can be used by Templates in order to inject content at certain _hook points_ within a Template. Templates expose a `Decorator contract` which can be implemented by Decorators in different modules. In this way Templates can be extended by modules on which they have no dependency and might have been created by completely different authors or even companies. ## See also - [How to decorate a Template](https://docs.intentarchitect.com/docs-md/module-building/decorators/how-to-decorate-a-template/how-to-decorate-a-template.md) --- # How to decorate a Template Even though Templates are simple for a developer to change, Intent Architect does offer another mechanism for extending the content that gets generated by a Template without you having to actually modify the template itself. This mechanism is referred to as a Decorator. If you have followed through the [Create Module](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/04-create-a-files-per-model-template/create-a-files-per-model-template.md) tutorial, it will be useful to note that we will be extending the Template in order to allow Decorators to be applied to the Entity template at runtime and to have a Decorator that adds validation logic to our Entity class. ## Creating a Decorator Contract In Intent Architect, open the `MyModule` solution (as was created in the tutorial) and select the `MyModule.Entities` Project that represents the Module that will generate the code for a C# Entity. Right click on the Entity Template and select `Add Decorator Contract`. Give this contract a name of `FieldAttributeDecoratorContract`. ![Create Decorator Contract](images/add-decorator-contract.png) ## Creating a Decorator Right click on the package and select `New Template Decorator`. Give it the name of `DataAnnotationValidationDecorator` and select (as the type) `FieldAttributeDecoratorContract`. ![Create Decorator](images/add-decorator.png) > [!NOTE] > Make sure to Run the Software Factory Execution as this will generate the Decorator as well as the Contract for the Template. > > ![Software Factory Run](images/software-factory-execution-staging.png) ## Update the Module Open your `MyModules.Entities` Visual Studio project and locate the `FieldAttributeDecoratorContract.cs` file and open it. Update this class to look like the one below: ```cs using Intent.Modelers.Domain.Api; using Intent.RoslynWeaver.Attributes; using Intent.Templates; using System.Collections.Generic; [assembly: DefaultIntentManaged(Mode.Ignore)] [assembly: IntentTemplate("Intent.ModuleBuilder.Templates.TemplateDecoratorContract", Version = "1.0")] namespace MyModules.Templates.Entity { [IntentManaged(Mode.Merge, Signature = Mode.Fully)] public abstract class FieldAttributeDecoratorContract : ITemplateDecorator { public int Priority { get; protected set; } = 0; public abstract IEnumerable GetEntityFieldAttributes(AttributeModel attributeModel); } } ``` Next open the `EntityTemplate.tt` file and modify it so that it looks like the one below: ```cs <#@ template language="C#" inherits="CSharpTemplateBase" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Linq" #> <#@ import namespace="Intent.Modules.Common" #> <#@ import namespace="Intent.Modules.Common.Templates" #> <#@ import namespace="Intent.Modules.Common.CSharp.Templates" #> <#@ import namespace="Intent.Templates" #> <#@ import namespace="Intent.Metadata.Models" #> using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; <# /* Add this namespace */ #> [assembly: DefaultIntentManaged(Mode.Fully)] namespace <#= Namespace #> { public class <#= ClassName #> { <# foreach(var attribute in Model.Attributes) { #> <#= GetAttributesForClassAttribute(attribute) /* Add this method expression */ #> public <#= GetTypeName(attribute) #> <#= attribute.Name.ToPascalCase() #> { get; set; } <# } #> ... ``` Next open the `EntityTemplatePartial.cs` file and add the following method implementation: ```cs public string GetAttributesForClassAttribute(AttributeModel attributeModel) { return GetDecoratorsOutput(decorator => string.Join( separator: $"{System.Environment.NewLine}{base.CurrentIndent}", values: decorator.GetEntityFieldAttributes(attributeModel))); } ``` Finally open the `DataAnnotationValidationDecorator.cs` file and add the following method implementation: ```cs public override IEnumerable GetEntityFieldAttributes(AttributeModel attributeModel) { if (!attributeModel.Type.IsNullable) { yield return "[Required]"; } } ``` > [!IMPORTANT] > Make sure to compile your module project before continuing. ## Applying the Decorator Install the `MyModule.Entities` to to your `TestApp` in Intent Architect. Follow these [steps](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/03-install-and-run-the-module/install-and-run-the-module.md#install-the-module) if you are not sure how. Remember to click on Re-install for your updated module. Open your Domain designer and select the `User` Entity. Let's make the `firsName` and `lastName` fields `nullable` to test that our decorator works. ![Nullable Fields](images/field-nullable.png) Now run the Software Factory to see that it adds the `[Required]` attribute only on the fields that are not-nullable. ![Diff Result](images/decorator-diff-result.png) > [!TIP] > You can use Stereotypes to add more complex validation logic to a Class Attribute. To learn how to work with Stereotypes, read [this article](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/how-to-use-stereotypes/how-to-use-stereotypes.md) --- # About Designer Extensions Designer Extensions extend an existing [Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md). This is useful for situations where you want add something to an existing Designer without having to make a full copy of it. The other benefit of Designer Extensions is that if the Designer being extended is updated, then the Designer Extension gets that update too, without necessarily requiring you to integrate or re-copy the changes. ![ValueObject Designer Extension Example](images/designers-extensions-valueobject.png) _Example of a Designer Extension where a new Element (Value Object) is introduced in an existing designer `Domain`._ The image above is illustrating how the `Intent.Modeller.Domain.ValueObjects` module, has extended the `Domain Designer` by adding `Value Object` element and it's associated modelling behaviours to the designer. ## How to see which Designer Extensions are currently applied You can use the settings icon in a designer to see what extensions are currently applied: ![Designer Settings Icon](images/designer-settings-icon.png) ![Designer Settings Dialogue](images/designer-settings-dialogue.png) ## See also - [Designer Modelling](https://docs.intentarchitect.com/docs-md/module-building/designers/designer-modelling/designer-modelling.md) --- # Tutorial: Create an Event as a Designer Extension This tutorial guide will walk you through creating a [Designer Extension](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/about-designer-extensions/about-designer-extensions.md) which extends the [Domain Designer](https://github.com/IntentSoftware/Intent.Modules/tree/master/Modules/Intent.Modules.Modelers.Domain) such that in addition to everything it already supports, it will also allow: - Adding a new element of type `Domain Event` which can be added through a context menu option on folders. - Ability to apply a new Stereotype to `Class` elements. - Have a property on the above Stereotype which allows selection of `Domain Event` element types created in that Designer's Package. ## Create a new Application Create a new `Module Builder` application and give it the name `DomainDesignerExtension`: ![Domain Design Extension Creation](images/design-extension-creation.png) Click `NEXT` and on the following screen ensure that the following extra options are ticked: - `Module Builder - C#`. - `Auto-Build Module`: ![Domain Design Extension Customize](images/design-extension-customize.png) Click `CREATE`: Once the `Application Installation` is complete, you will see a `Ready` message in the `Taskbar`.(bottom left of Intent Architect). ## Install the Domain designer - Right-Click on the `DomainDesignerExtension` Application and select the `Manage Modules` option. - Search for `Intent.Modelers.Domain`. - Expand `Options` on the right. - Check `Install metadata only`. - Click `Install`:

## Configure the package to reference the domain designer - Click on the `Module Builder` Designer on the left. - Select the top-level Package node and in the Properties pane on the right: - Check `Include in Module`. - Add `Domain` to `Reference in Designer`.

## Create and configure the `Domain Event` element - Right-click the root Package element and click the `Add Designers Folder` option. - Right-click the `designers` element and click the `New Designer Settings` option. - Give the new element a name of `DomainEventSettings`. - Select the `DomainEventSettings` element and in the Properties pane on the right: - For `Extend Designers` add `Domain`. - Right-click the `DomainEventSettings` element and click the `New Element Type` option. - Give the new element a name of `Domain Event`. - Right-click the `Domain Event` element and click the `Add Element Settings` option. - Give the new element a name of `Property` - Select the `Property` element and in the Properties pane on the right: - Change `Mode` to `Required`. - For `Target Types` add `Class`, `Enum` and `Type-Definition`. - Right-click the `[context-menu]` element (the one directly within `Domain Event`) and click the `Add Element Creation` option. - Give the new element a name of `Add Property` and select the `Property` type for it: ![Design Domain Event](images/design-domain-event.png) ## Create an `Add Event` context menu option for `Folder` elements - Right-click the `DomainEventSettings` and click the `New Element Extension` option. - Give the new element a name of `Folder Extension` and select the `Folder` type for it. - Right-click the `Folder Extension` element and click the `Add Menu Options` option. - Right-click the `[context-menu]` element (the one directly within `Folder Extension`) and click the `Add Element Creation` option. - Give the new element a name of `Add Event` and select the `Domain Event` type for it: ![Add Folder Extension](images/add-folder-extension.png) ## Create an `Add Event` context menu option for `Package` elements - Right-click the `DomainEventSettings` and click the `New Package Extension` option. - Give the new element a name of `Package Settings` and select the `Domain Package` type for it. - Right-click the `[context-menu]` element (the one directly within `Package Settings`) and click the `Add Element Creation` option. - Give the new element a name of `Add Event` and select the `Domain Event` type for it: ![Add Package Extension](images/add-package-extension.png) ## Create the Stereotype - Right-click the root Package element and click the `New Stereotype-Definition` option. - Give the new Stereotype Definition a name of `Has Domain Event`. - Select the `Has Domain Event` element and in the Properties pane on the right: - For `Target Types` add `Class`. - Right-click the `Has Domain Event` element and click the `Add Property` option. - Give the Property a name of `Event`. - Select the `Event` element and in the Properties pane on the right: - Change `Control Type` to `Select`. - Change `Option Source` to `Lookup Element`. - For `Lookup Types` add `Domain Event`:

## Run the Software Factory to generate the module Run the Software Factory and optionally review the proposed changes: ![Staging files](images/software-factory-run.png) Click `APPLY CHANGES`. After the Software Factory has applied the changes, you'll see that it runs `dotnet build` on the generated `Visual Studio` solution: ![Software Factory: dotnet build](images/software-factory-dotnet-build.png) Take note of the following line in the output for the following step: ```text Successfully created module C:\Code\MySolution\Intent.Modules\DomainDesignerExtension.1.0.0.imod ``` Click `CLOSE`. ## Test your Designer Extension Module Ensure you have added the output path as noted in the above step as a repository in the [Repository Manager](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/how-to-manage-repositories/how-to-manage-repositories.md). For the above output, the full path of the module is `C:\Code\MySolution\Intent.Modules\DomainDesignerExtension.1.0.0.imod`, so the repository would point to its folder, which is `C:\Dev\MySolution\Intent.Modules`: ![Manage Repositories](images/repo-manager-module-folder.png) - Open or create the Intent Architect application where you want to install your newly created Module. - Right-Click on the application and select `Manage Modules`. - Ensure the application has the `Intent.Modelers.Domain` module installed. - Select your repository from the drop-down on the right and locate your Module to install: ![Install Module](images/test-module-install.png) You will now have the option of creating the new `Domain Event` element and using it in the `Has Domain Event` Stereotype that was added by the Designer Extension we created above:

## See also - [About Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) --- # Designer Modelling The [Module Builder](https://docs.intentarchitect.com/docs-md/module-building/about-the-module-builder/about-the-module-builder.md) is a designer for authoring [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) and [Designer Extensions](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/about-designer-extensions/about-designer-extensions.md). ## Structure ### The `Designers Folder` element type ![Designers Folder](images/designers-folder.png) All Designer and Designer Extensions must be modelled inside this folder, as the folder is not created by default you may need to create it first. Only a single `Designers Folder` element can exist and its name, `designers`, cannot be changed. ### The `Designer` element ![Designer](images/designer.png) This Designer is seen as a component by Intent Architect, instructing it when the module is installed that a new Designer tab needs to be setup (located on the left hand side of the screen) which will contain your custom made designer interface. | Property Name | Description | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------| | Display Order | Capture an integer number that will be used to determine the order in which your designer gets displayed along with all the other installed designers. 🔍| ### The `Designer Settings` element ![Designer Settings](images/designer-settings.png) Designer Settings contain various elements that will describe how a given Designer is to be extended. | Property Name | Description | |------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| | Is Reference | This option is used internally by the Module Builder system. | | Extend Designers | Optional. If you are not intending to create your own designer but extend an existing one, select that specific designer (or designers) here. | ### The `Element Settings` element ![Element Settings](images/element-settings.png) Element Settings contains metadata that will introduce a new designer Element into your new or, an existing designer. They are composite in nature so that Elements are made up of other Elements. So conceptually an Element can have a Parent Element and nested (or child) Elements. Element Settings can only be created inside `Designer Settings`. ![Parent / Children example](images/element-settings-parent-children-example.png) In this example a `Workflow Step` is the parent Element that contains two nested Elements: `StepAttribute` and `Interaction`. `Interaction` also contains a nested Element: `Interaction Parameter`. This configuration will produce the following for the end-user (example): ![Parent / Children example result](images/element-settings-parent-children-example-result.png) _The example contains the StepAttribute visible in the top highlighted area and the Interaction (with the parameter) in the bottom highlighted area._ | Property Name | Description | |-----------------------|-| | Save Mode | How the Element's metadata gets saved to disk is controlled by this dropdown. [See here](#save-options) for information on the available options. | | Icon | The default icon used to represent the Element in a tree view in the designer. This is also the collapsed icon. 🔍 | | Expanded Icon | Inside a tree view designer, specify a distinct icon for when the Element is expanded. 🔍 | | Display Text Function | By default the Element's name gets displayed on the visual designer 🔍. This can be overridden by coding a display function in JavaScript that will return a string that gets treated as the visual display text. 🔍 | | Validate Function | Optional. A JavaScript function to validate the state of this Element. Return `null` (or leave the validation function blank) to say that validation is successful. Return a string (with an error message) to say that validation failed and display the string as the error message why it failed. 🔍 | | Allow Rename | If checked, the Element's name can be modified by the user. This will add the option in the context menu to rename. | | Allow Abstract | If checked, allows the end-user to set the `Is Abstract` checkbox on an Element. | | Allow Set Value | If checked, a text field will be provided for the Element to have a specific value captured by the user. | | Allow Generic Types | If checked, the Element will allow Generic type parameters to be specified on itself which can be overridden with specific Types by other Elements that reference them. 🔍 | | Allow Sorting | If checked, nested Elements can be sorted according to a certain criteria. | | Sort Children | Select which criteria should be used when sorting nested Elements if `Allow Sorting` is checked. [See here](#sort-children-options) for information on the available options. | | Allow Find in View | If checked, adds a `Find in View` context menu to the element which when used will center the visual diagram on the element and select it. | #### Save Options | Save Option | Description | |-------------|---------------------------------------------------------------------------------------------------------------------------------------| | Default | Intent Architect automatically chooses the most appropriate save option depending on the Element's position in the Element-hierarchy. | | Own File | The Element will have its own `.xml` file stored to disk. | | As Child | Nested Elements will be stored inside their parent Element's `.xml` file. | #### Sort Children Options | Sort Children Option | Description | |-----------------------|---------------------------------------------------------------------------------------| | By type then manually | Elements are ordered by Type yet allows the user to manually order them within. | | By type then name | Elements are ordered by Type, then Name. The user cannot manually order the Elements. | | By name | Elements are only ordered by Name. The user cannot manually order the Elements. | #### Type Reference Settings Enabling Type Reference Settings to your Element gives the end-user the capability to select and assign a given Type to your Element. You can filter which Types you are allowed to select and what the relationship between the Element and the Type looks like. This is not the same as an [Association](#association-types). | Property Name | Description | |------------------|-| | Mode | Disabled - No Type Reference. Optional - Select a Type Reference or keep it unassigned (void). Required - Mandatory to select a Type Reference. | | Display Name | The Property Name for selecting a `Type` is by default called "Type" 🔍. This can be renamed if "Type" is not appropriate for your use case. 🔍 | | Target Types | Select one or more Elements that become selectable types (🔍) when your Element is used in the designer. 🔍 | | Represents | The default value of `Reference` is sufficient for general use. The other option of `Inheritance` is mostly used internally by the Module Building system. | | Default Type Id | If there is an existing instance of an Element created somewhere and you would like that one to become a default selectable Type, copy that Id into this field. 🔍 | | Allow Nullable | Controls whether or not the `Is Nullable` checkbox is visible for the Element in the designer. 🔍 | | Allow Collection | Controls whether or not the `Is Collection` checkbox is visible for the Element in the designer. 🔍 | #### Context Menu ![Context Menu Example](images/element-settings-context-menu.png) Each Element has their own Context Menu (when you right click on the Element) which can be defined in these sections. This is needed to allow the creation of other nested Elements. #### Diagram Settings ![Diagram Settings](images/element-settings-diagram-settings.png) This will instruct Intent Architect to identify this Element to have a Diagram (eg: [Domain designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md)). When you double click on that Element it will open up the Diagram for that Element. It is possible to have other Element instances have their own Diagrams. Diagrams also have their own [Context Menus](#context-menu). #### Mapping Settings ![Mapping Settings](images/element-settings-mapping-settings.png) This setting will enable end-users to map the Element to another Element (potentially from a different Designer). It is possible to map Elements recursively, in other words you can map a high-level Element with another high-level Element, then proceed to map their nested Elements one-by-one. Each level can be configured in the designer to allow or limit the end-user over how deep they may map Elements 🔍. The following table documents what the overall mapping looks like but won't describe how to map the granular elements with each other. | Property Name | Description | |---------------------------|-------------| | Default Designer | Upon creating a mapping, the mapping dialog will be presented with a dropdown featuring all the available Designers. Set the default designer here. 🔍 | | Option Source | Determine what the list of Elements are going to be when presented on the mapping dialog. Option `Elements of Type` will present the `Lookup Types` field, while `Lookup Element` will present the `Lookup Element Function` field. | | Lookup Types | Select the Elements that can be selectable on the mapping dialog. | | Lookup Element Function | Write a JavaScript function that will populate what kind of Elements can be selectable on the mapping dialog. | | Map From | Selecting `Root Element` will present the mapping dialog in a tree-view style where the top-level Element and its nested Elements can be selected 🔍. Selecting `Child Elements` will only present the nested Elements on the mapping dialog and allow you to choose only one option 🔍. | | Auto-sync Type References | Each time the designer is loaded, Elements with mappings and with this setting checked auto-synchronize their types with the mapping source in case they have been changed. | | Symbol | On the tree-view display the symbol that denotes the mapping is by default a left pointing arrow. This symbol can be changed by selecting a different symbol from the font-awesome icons 🔍. | ##### Element Mapping ![Element Mapping - Granular](images/element-settings-mapping-settings-granular.png) This mapping can only be created once there is a `Mapping Setting`. This table documents how each granular element mapping will look like in a hierarchy form. | Property Name | Description | |----------------------|-------------| | From Type | Select the Element type to map from. This is the type from the external designer. | | Has Type-Reference | Yes: Mapped Element must have a Type-Reference. No: Mapped Element must not have a Type-Reference. Not Applicable: Ignore this criteria. | | Has Children | Yes: Mapped Element must have nested Elements. No: Mapped Element must not have nested Elements. Not Applicable: Ignore this criteria. | | Is Collection | Yes: Mapped Element must be a Collection. No: Mapped Element must not be a Collection. Not Applicable: Ignore this criteria. | | Auto-select Children | When checked, then when selecting a parent Element, all its nested Elements will be selected by default. | | Child Mapping Mode | `Map To Type`: Mapping to this Element can only be done on a Type level and cannot drill down into its nested Elements. `Traverse`: Mapping to this Element will allow to drill down into its nested Elements. | | To Type | Select the Element type to map to. This is the type from your current designer. | | Use Mapping Settings | If `Child Mapping Mode` is `Traverse`, set the Mapping Setting to inherit during the traversal. | ### Element Extensions ![Element Extension](images/element-extension.png) With the Type set to an instance of an Element outside of the current designer (ensuring that the relevant package is referenced), this Element allows you to extend it by adding nested Elements, mappings, diagrams, etc. [See Element Settings](#the-element-settings-element) for more info. ### Association Types ![Association Types](images/association-types.png) Associations bring a relationship aspect to Elements which can be used by end-users to model how Elements are related or bound to one another. Examples would be having an [aggregate relationship between two Class Elements](https://docs.intentarchitect.com/docs-md/application-development/modelling/domain-designer/modeling-the-domain/modeling-the-domain.md#modeling-entity-associations). Each Association have a Source end and a Target end. The Source end is where the end-user started creating the association and the Target end is where the end-user finished creating the association. ![Source / Target](images/association-type-source-target.png) #### Association End Settings The properties are the same for Source and Target association ends and is denoted as "Association End". | Property Name | Description | |-----------------------|-------------| | Target Types | Select the Element types where the Association End can be connected to. | | Display Text Function | By default the Associations's name gets displayed on the visual designer 🔍. This can be overridden by coding a display function in JavaScript that will return a string that gets treated as the visual display text. 🔍 | | Api Property Name | The Module Builder will generate API code for this Element and you can set what the Association End's name can be here. 🔍 | | Is Navigable Enabled | If checked, the end-user may set whether an association end can be navigated to. | | Is Nullable Enabled | If checked, the end-user may set whether an association end can be set to a nullable type or not. | | Is Collection Enabled | If checked, the end-user may set whether an association end can be set to expect zero or more instances of the Element its associated with. | | Is Navigable Default | Set the default state of Navigable. | | Is Nullable Default | Set the default state of Nullable. | | Is Collection Default | Set the default state of Collection. | | Allow Multiple | If checked, this Association can be created multiple times, otherwise it can only be created once. | ### Association Extensions With the Type set to an instance of an Association outside of the current designer (ensuring that the relevant package is referenced), this association allows you to extend it by adding association end settings, visuals, etc. See [Association Types](#association-types) for more info. ### Package Types ![Package Type](images/package-type.png) ![Package Type Example](images/package-type-example.png) Every designer needs packages to store Elements and associations in and this type allows you to create a unique package for your designer. It is required to set the `Context Menu` in order for your designer to enable the end-user to create Elements within your designer. | Property Name | Description | |---------------|-------------| | Default Name | When your designer is installed and the end-user opens it up for the first time, a package creation prompt is presented. Set the default name for your package here or leave it blank for the end-user to be obliged to complete. | | Sorting | With a package presented in a tree-view, set the mode how the Elements inside it is ordered. | | Package Sorting Option | Description | |------------------------|---------------------------------------------------------------------------------------| | Manually | Elements are not ordered automatically, the user determines the order. | | By type then manually | Elements are ordered by Type yet allows the user to manually order them within. | | By type then name | Elements are ordered by Type, then Name. The user cannot manually order the Elements. | | By name | Elements are only ordered by Name. The user cannot manually order the Elements. | --- # Designer Scripting: Advanced Mappings Learn how to automate advanced mapping creation using scripts. This guide is for module builders and developers who want to programmatically create mappings between designer elements. ## What You'll Learn This guide covers: - Creating basic field-to-field mappings - Mapping collections and nested objects - Querying and validating existing mappings - Common patterns and troubleshooting ## What Are Advanced Mappings? Advanced Mappings are **blueprints for code generation**. When you map a `CreateOrder` command to an `Order` entity, you're telling templates: - Which constructor or method to call (**Invocation Mapping**) - Which fields map to which properties (**Data Mapping**) - How to filter or query data (**Filter Mapping**) Templates read these mappings and can generate code like this: ```csharp // Your mapping creates this blueprint: // Invocation: new Order() // Data: RefNo → RefNo, CreatedDate → CreatedDate // Template generates: var order = new Order { RefNo = command.RefNo, CreatedDate = command.CreatedDate }; ``` ## Learn through examples ### Complete setup script > [!IMPORTANT] > > Before running any examples, execute this comprehensive setup script. It creates all the model elements needed for every example in this guide. #### Step 1: Domain designer setup Open your **Domain Designer** and run this script: ```javascript // Get the first package let packageId = getPackages()[0]?.id; if (!packageId) { await dialogService.error("Create a Domain package first"); return; } const GuidTypeId = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; // Create Order entity with attributes let order = createElement("Class", "Order", packageId); createElement("Attribute", "RefNo", order.id); createElement("Attribute", "CreatedDate", order.id); createElement("Attribute", "TotalAmount", order.id); // Create OrderLine entity for collection example let orderLine = createElement("Class", "OrderLine", packageId); createElement("Attribute", "Description", orderLine.id); // Order -> OrderLines (1:many) let orderLinesAssoc = createAssociation("Association", order.id, orderLine.id, "OrderLines"); orderLinesAssoc.typeReference.setIsCollection(true); orderLinesAssoc.getOtherEnd().typeReference.setIsCollection(false); // Create ShippingInfo for nested object example let shippingInfo = createElement("Class", "ShippingInfo", packageId); createElement("Attribute", "Street", shippingInfo.id); createElement("Attribute", "City", shippingInfo.id); // Order -> ShippingInfo (1:1) let shippingAssoc = createAssociation("Association", order.id, shippingInfo.id, "ShippingInfo"); shippingAssoc.typeReference.setIsCollection(false); shippingAssoc.getOtherEnd().typeReference.setIsCollection(false); const diagram = getCurrentDiagram(); let space = diagram.findEmptySpace(diagram.getViewPort().getCenter(), { width: 500, height: 550 }); diagram.layoutVisuals(getPackages()[0], space, true); await dialogService.info("Domain entities created!"); ``` #### Step 2: Services designer setup Switch to your **Services Designer** and run this script: ```javascript // Get the first package let packageId = getPackages()[0]?.id; if (!packageId) { await dialogService.error("Create a Services package first"); return; } const GuidTypeId = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; // Create CreateOrder command let createOrder = createElement("Command", "CreateOrder", packageId); createElement("DTO-Field", "RefNo", createOrder.id); createElement("DTO-Field", "CreatedDate", createOrder.id); createElement("DTO-Field", "TotalAmount", createOrder.id); // Create OrderLineDto for collection example let orderLineDto = createElement("DTO", "OrderLineDto", packageId); createElement("DTO-Field", "Description", orderLineDto.id); // Add OrderLines collection to CreateOrder let orderLinesField = createElement("DTO-Field", "OrderLines", createOrder.id); orderLinesField.typeReference.setType(orderLineDto.id); orderLinesField.typeReference.setIsCollection(true); // Create ShippingDetailsDto for nested example let shippingDetailsDto = createElement("DTO", "ShippingDetailsDto", packageId); createElement("DTO-Field", "Street", shippingDetailsDto.id); createElement("DTO-Field", "City", shippingDetailsDto.id); // Add ShippingDetails to CreateOrder let shippingField = createElement("DTO-Field", "ShippingDetails", createOrder.id); shippingField.typeReference.setType(shippingDetailsDto.id); // Create GetOrderById query let getOrderById = createElement("Query", "GetOrderById", packageId); let idField = createElement("DTO-Field", "Id", getOrderById.id); idField.typeReference.setType(GuidTypeId); // Create OrderDto for query return let orderDto = createElement("DTO", "OrderDto", packageId); idField = createElement("DTO-Field", "Id", orderDto.id); idField.typeReference.setType(GuidTypeId); createElement("DTO-Field", "RefNo", orderDto.id); createElement("DTO-Field", "CreatedDate", orderDto.id); createElement("DTO-Field", "TotalAmount", orderDto.id); getOrderById.typeReference.setType(orderDto.id); await dialogService.info("Services elements created!"); ``` ![Created Domain Elements](images/created-domain-elements.png) ![Created Service Elements](images/created-service-elements.png) **What was created:** - `Order`, `OrderLine`, `ShippingInfo` entities with associations (Domain) - `CreateOrder` command with fields, collections, and nested objects (Services) - `GetOrderById` query and `OrderDto` for query example (Services) --- All examples run in the **Services Designer**. Make sure you've completed the [setup scripts](#complete-setup-script) first. ### Example 1: Map command fields to Entity Map `CreateOrder` command fields to `Order` entity attributes using the three-step process. ```javascript // Find the command and entity let command = lookupTypesOf("Command").find(x => x.getName() === "CreateOrder"); let entity = lookupTypesOf("Class").find(x => x.getName() === "Order"); // Step 1: Create association (declares intent) let action = createAssociation("Create Entity Action", command.id, entity.id); // Step 2: Create the mapping let mapping = action.createAdvancedMapping(command.id, entity.id); // Step 3a: Add invocation (tells template to call constructor) mapping.addMappedEnd("Invocation Mapping", [command.id], [entity.id]); // Step 3b: Map simple fields let fields = ["RefNo", "CreatedDate", "TotalAmount"]; fields.forEach(name => { let field = command.getChildren("DTO-Field").find(x => x.getName() === name); let attr = entity.getChildren("Attribute").find(x => x.getName() === name); if (field && attr) { mapping.addMappedEnd( "Data Mapping", [command.id, field.id], // Source: command.Field [entity.id, attr.id] // Target: entity.Attribute ); } }); // Step 3c: Map shipping info fields (nested object with multi-level paths) let shippingField = command.getChildren("DTO-Field").find(x => x.getName() === "ShippingDetails"); let shippingAssoc = entity.getAssociations("Association").find(x => x.getName() === "ShippingInfo"); if (shippingField && shippingAssoc) { let shippingDto = shippingField.typeReference.getType(); let shippingEntity = shippingAssoc.typeReference.getType(); // Map each nested field ["Street", "City"].forEach(fieldName => { let dtoField = shippingDto.getChildren("DTO-Field").find(x => x.getName() === fieldName); let entityAttr = shippingEntity.getChildren("Attribute").find(x => x.getName() === fieldName); if (dtoField && entityAttr) { mapping.addMappedEnd( "Data Mapping", [command.id, shippingField.id, dtoField.id], // command.ShippingDetails.Street [entity.id, shippingAssoc.id, entityAttr.id] // entity.ShippingInfo.Street ); } }); } await dialogService.info("Mapping created!"); ``` **Understanding the code:** - `lookupTypesOf("Command")` - Finds all Commands in the Services Designer - `createAssociation()` - Creates "Create Entity Action" connecting command to entity - `createAdvancedMapping()` - Creates the mapping blueprint on the association - `addMappedEnd()` - Adds each transformation rule (Invocation + Data mappings) - **Path arrays** `[command.id, field.id]` - Represents "command.RefNo" traversal - **Nested fields** - Use 3-level paths like `[command.id, shippingField.id, streetField.id]` to map individual nested properties ![Advanced mapping created](images/advanced-mapping-example1.png) **Possible generated code:** ```csharp var order = new Order // From Invocation Mapping { RefNo = command.RefNo, // From Data Mapping CreatedDate = command.CreatedDate, // From Data Mapping TotalAmount = command.TotalAmount, // From Data Mapping ShippingInfo = new ShippingInfo { Street = command.ShippingDetails.Street, // From 3-level Data Mapping City = command.ShippingDetails.City // From 3-level Data Mapping } }; ``` ### Example 2: Filter mapping for queries Create a query mapping that filters by ID. ```javascript let query = lookupTypesOf("Query").find(x => x.getName() === "GetOrderById"); let entity = lookupTypesOf("Class").find(x => x.getName() === "Order"); // Create query action and mapping let action = createAssociation("Query Entity Action", query.id, entity.id); let mapping = action.createAdvancedMapping(query.id, entity.id); // Map Id field to Id attribute (filter condition) let idField = query.getChildren("DTO-Field").find(x => x.getName() === "Id"); let idAttr = entity.getChildren("Attribute").find(x => x.getName() === "Id"); if (idField && idAttr) { mapping.addMappedEnd( "Filter Mapping", // Tells template this is a WHERE clause [query.id, idField.id], [entity.id, idAttr.id] ); } await dialogService.info("Filter mapping created!"); ``` **Understanding the code:** - `"Query Entity Action"` - Association type for query operations - `"Filter Mapping"` - Tells templates to generate a WHERE clause - Same path array pattern: `[parent.id, child.id]` ![Advanced mapping query](images/advanced-mapping-example2.png) **Possible generated code:** ```csharp var order = dbContext.Orders .Where(x => x.Id == query.Id) // From Filter Mapping .FirstOrDefault(); ``` ### Example 3: Map collection fields Map a collection property from command to entity. ```javascript let command = lookupTypesOf("Command").find(x => x.getName() === "CreateOrder"); let entity = lookupTypesOf("Class").find(x => x.getName() === "Order"); // Get or create mapping (reuse from Example 1 if it exists) let action = command.getAssociations("Create Entity Action") .find(x => x.typeReference?.typeId === entity.id); if (!action) { action = createAssociation("Create Entity Action", command.id, entity.id); } let mapping = action.getAdvancedMappings()[0]; if (!mapping) { mapping = action.createAdvancedMapping(command.id, entity.id); } // Find collection field and association let orderLinesField = command.getChildren("DTO-Field").find(x => x.getName() === "OrderLines"); let orderLinesAssoc = entity.getAssociations("Association").find(x => x.getName() === "OrderLines"); if (orderLinesField && orderLinesAssoc) { // Map the collection mapping.addMappedEnd( "Data Mapping", [command.id, orderLinesField.id], [entity.id, orderLinesAssoc.id] ); // Map fields within the collection let lineDto = orderLinesField.typeReference.getType(); let lineEntity = orderLinesAssoc.typeReference.getType(); let descField = lineDto.getChildren("DTO-Field").find(x => x.getName() === "Description"); let descAttr = lineEntity.getChildren("Attribute").find(x => x.getName() === "Description"); if (descField && descAttr) { mapping.addMappedEnd( "Data Mapping", [command.id, orderLinesField.id, descField.id], // 3-level path [entity.id, orderLinesAssoc.id, descAttr.id] // command.OrderLines[].Description ); } } await dialogService.info("Collection mapping created!"); ``` **Understanding the code:** - **3-level paths** `[command.id, orderLinesField.id, descField.id]` - Represents "command.OrderLines[].Description" - First `addMappedEnd()` maps the collection itself - Second `addMappedEnd()` maps fields within each collection item ![Collection mapping with nested field mappings](images/advanced-mapping-example3.png) **Possible generated code:** ```csharp OrderLines = command.OrderLines.Select(line => new OrderLine { Description = line.Description // From 3-level Data Mapping }).ToList() ``` --- ### Example 4: Query and Validate Mappings Check existing mappings and find unmapped fields. ```javascript let command = lookupTypesOf("Command").find(x => x.getName() === "CreateOrder"); let entity = lookupTypesOf("Class").find(x => x.getName() === "Order"); // Find the association let action = command.getAssociations("Create Entity Action")     .find(x => x.typeReference?.typeId === entity.id); if (!action) {     console.log("No mapping found");     return; } // Get the mapping let mapping = action.getAdvancedMappings()[0]; if (!mapping) {     console.log("No advanced mapping found");     return; } // Show what's mapped console.log("\n=== Mapped Fields ==="); mapping.getMappedEnds().forEach(end => {     let sourcePath = end.sourcePath.map(p => p.name).join(".");     let targetPath = end.targetPath.map(p => p.name).join(".");     console.log(`${end.mappingType}: ${sourcePath} → ${targetPath}`); }); // Find unmapped attributes let targetAttrs = entity.getChildren("Attribute"); let mappedAttrIds = new Set(); mapping.getMappedEnds().forEach(end => {     if (end.targetPath.length > 0) {         let lastElement = end.targetPath[end.targetPath.length - 1];         mappedAttrIds.add(lastElement.id);     } }); let unmapped = targetAttrs.filter(attr => !mappedAttrIds.has(attr.id)); let results = "=== Validation ===\n"; if (unmapped.length > 0) {     results += `⚠ Unmapped attributes: ${unmapped.map(a => a.getName()).join(", ")}`; } else {     results += "✓ All attributes mapped"; } await dialogService.info(results); ``` **Understanding the code:** - `getAdvancedMappings()` - Returns all advanced mappings on the association - `getMappedEnds()` - Returns all mapped ends (Invocation + Data mappings) - `sourcePath.map(p => p.name).join(".")` - Converts path array to readable string - Uses `Set` to track which attributes are mapped - Finds unmapped attributes by comparing all attributes vs mapped ones ![Query results](images/query-results.png) --- // Querying mappings action.getAdvancedMappings()                  // Get all mappings action.getAdvancedMapping(typeId)             // Get specific mapping type mapping.getMappedEnds()                       // Get all mapped ends ## Common Issues ### Duplicate mappings **Cause:** Running the same script multiple times. **Fix:** Check if mapping exists before creating: ```javascript let action = element.getAssociations("Create Entity Action") .find(x => x.typeReference?.typeId === targetElement.id); if (!action) { action = createAssociation("Create Entity Action", element.id, targetElement.id); } ``` ### Wrong generated code **Cause:** Incorrect path arrays. Must be in traversal order: `[parent.id, child.id]`. **Fix:** Build paths from root to leaf: ```javascript // ✅ Correct: command → ShippingDetails → Street [command.id, shippingDetailsField.id, streetField.id] // ❌ Wrong: Missing intermediate element [command.id, streetField.id] ``` ## Quick Reference ### Common Mapping Type IDs ```javascript // Use these constants at the top of your scripts const CreateEntityMappingId = "5f172141-fdba-426b-980e-163e782ff53e"; const UpdateEntityMappingId = "01721b1a-a85d-4320-a5cd-8bd39247196a"; const QueryEntityMappingId = "25f25af9-c38b-4053-9474-b0fabe9d7ea7"; ``` ### Key Methods ```javascript // Finding elements lookupTypesOf("Command") // Get all commands element.getChildren("DTO-Field") // Get child elements element.getAssociations("Create Entity Action") // Get associations by type // Creating mappings createAssociation(type, sourceId, targetId) // Step 1: Create association action.createAdvancedMapping(srcId, tgtId) // Step 2: Create mapping mapping.addMappedEnd(type, sourcePath, targetPath) // Step 3: Add mapped ends ``` ## Next Steps - **[Designer Scripting Guide](https://docs.intentarchitect.com/docs-md/module-building/designers/designer-scripting/designer-scripting.md)** - Learn the full Designer Scripting API. - **[Advanced Mapping Tutorial](https://docs.intentarchitect.com/docs-md/module-building/tutorial-advanced-mapping/tutorial-advanced-mapping.md)** - Configure mapping-enabled designers in the Module Builder. - **[API Documentation](https://github.com/IntentArchitect/Intent.Modules/tree/development/DesignerMacros/typings)** - Review Full API Documentation in the Designer Scripting reference for detailed method signatures. --- # Designer Scripting Designer scripting allows you to automate modeling tasks in Intent Architect's designers through JavaScript-based scripts. Whether you need to create dozens of entities at once, enforce modeling conventions, or generate boilerplate structures, designer scripting can save significant time and ensure consistency across your models. There are two primary ways to use designer scripting: - **Execute Script Dialog**: Run ad-hoc scripts to perform one-time bulk operations or automate repetitive modeling tasks - **Event Handlers**: Create automated responses to modeling events (e.g., automatically configure new entities or associations as they're created) Both approaches share the same powerful API that gives you full programmatic access to create, modify, and configure elements in your designers. IntelliSense and inline documentation are available throughout the scripting environment to help you discover available functions and their usage. ## Getting Started with the Execute Script Dialog Intent Architect includes a scripting editor which can be launched by clicking the Execute Script Dialog button (``) located in the designer toolbar. This editor allows you to write and execute JavaScript scripts directly within your designer environment. ![Code Docs in Code Completion Screenshot](images/code-complete-code-docs.png) You can access detailed documentation by clicking on the small arrow beside each code construct in the IntelliSense dropdown to expand the respective documentation and learn more about the available functions. ### Your First Script Here's a simple example that creates a class with five attributes: ```javascript let mainPackage = getPackages()[0]; let newClass = createElement("Class", "Customer", mainPackage.id); const stringTypeId = "d384db9c-a279-45e1-801e-e4e8099625f2"; for (let i = 1; i <= 5; i++) { let attr = createElement("Attribute", `Attribute${i}`, newClass.id); attr.typeReference.setType(stringTypeId); } ``` This script demonstrates the core concepts: - `getPackages()` retrieves available packages in your designer - `createElement(type, name, parentId)` creates new elements - Type IDs (like the string type ID) are used to set data types - Elements are created hierarchically (attributes belong to a class) ## Understanding the Basics Before diving into more complex examples, it's helpful to understand the key concepts and building blocks available in designer scripting. ### Element Types by Designer Different designers support different element types. Here are the most common ones: **Domain Designer:** - Class - Attribute - Association - Constructor - Operation **Services Designer:** - Service - DTO - DTO-Field - Command - Query - Operation **Common (available in multiple designers):** - Operation - Parameter - Package (folder) ### Common Type IDs When setting type references for attributes, parameters, or return types, you'll need to use type IDs. Here are the most commonly used ones: | Type | ID | |------|-----| | string | `d384db9c-a279-45e1-801e-e4e8099625f2` | | int | `fb0a362d-e9e2-40de-b6ff-5ce8167cbe74` | | long | `33013006-E404-48C2-AC46-24EF5A5774FD` | | bool | `e6f92b09-b2c5-4536-8270-a4d9e5bbd930` | | guid | `6b649125-18ea-48fd-a6ba-0bfff0d8f488` | | datetime | `a4107c29-7851-4121-9416-cf1236908f1e` | | decimal | `675c7b84-997a-44e0-82b9-cd724c07c9e6` | | double | `24A77F70-5B97-40DD-8F9A-4208AD5F9219` | **Best practice:** Define these as constants at the top of your script rather than using magic strings: ```javascript // Define type constants for better maintainability const stringType = "d384db9c-a279-45e1-801e-e4e8099625f2"; const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74"; const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; ``` ### Working with Packages Packages are the containers (folders) that organize elements in your designer. You can find existing packages or create new ones: ```javascript // Get all packages let allPackages = getPackages(); // Find a specific package by name let domainPackage = getPackages().find(p => p.name === "Domain"); // Use the first package as default if target not found let targetPackage = getPackages().find(p => p.name === "MyPackage") || getPackages()[0]; ``` ### Printing to the Console When running scripts in the Execute Script dialog you can write messages and inspect objects using `console.log`. The output is shown in the [Task Output Console](https://docs.intentarchitect.com/docs-md/release-notes/intent-architect-v4.3.md#task-output-console) in Intent Architect. For complex objects use `JSON.stringify` to get a readable JSON representation. ```javascript // Get all packages and print them let allPackages = getPackages(); console.log(`allPackages = ${JSON.stringify(allPackages)}`); // Find a named package and print it (may be undefined if not present) let domainPackage = getPackages().find(p => p.name === "Domain"); console.log(`domainPackage = ${JSON.stringify(domainPackage)}`); // Use the first package as default if target not found let targetPackage = getPackages().find(p => p.name === "MyPackage") || getPackages()[0]; console.log(`targetPackage = ${JSON.stringify(targetPackage)}`); ``` > [!TIP] > > `JSON.stringify` provides compact output; if you prefer pretty output use `JSON.stringify(obj, null, 2)`. Example of what the results pane might show (trimmed/simplified): ```text [12:24:56] Executed Script: Executing script (0ms) allPackages = [{"specializationId":"1a824508-4623-45d9-accc-f572091ade5a","specialization":"Domain Package","id":"fad9fe56-bb78-4b1d-8945-0e40ca9d77d3","name":"NewApplication.Domain",...}] domainPackage = undefined targetPackage = {"specializationId":"1a824508-4623-45d9-accc-f572091ade5a","specialization":"Domain Package","id":"fad9fe56-bb78-4b1d-8945-0e40ca9d77d3","name":"NewApplication.Domain",...} ``` This mirrors the example screenshots in the editor: objects printed as JSON, `undefined` printed when a find returns nothing, and the selected fallback package printed when the named package is not found. ### Creating Elements and Setting Types The basic pattern for creating elements is: ```javascript // Get arbitrary package ID let packageId = getPackages()[0].id; // Create an element: createElement(elementType, name, parentId) let myClass = createElement("Class", "Customer", packageId); // Create a child element let myAttribute = createElement("Attribute", "Name", myClass.id); // Set the attribute's type const stringTypeId = "d384db9c-a279-45e1-801e-e4e8099625f2"; myAttribute.typeReference.setType(stringTypeId); ``` ### Working with Stereotypes Stereotypes provide a way to extend elements with additional metadata and properties. The scripting API gives you full access to check, retrieve, and modify stereotype information on elements. #### Checking if a stereotype is applied ```javascript // Check if an element has a specific stereotype by name if (element.hasStereotype("Audit")) { console.log("Element has the Audit stereotype"); } // You can also check using the stereotype ID const auditStereotypeId = "9a8c3e2f-4b1d-4e2c-8f3a-2c1e4d5f6g7h"; if (element.hasStereotype(auditStereotypeId)) { console.log("Element has the Audit stereotype"); } ``` > [!TIP] > > You can use either the stereotype name or its ID when calling `hasStereotype()` or `getStereotype()`. Using IDs is more reliable when working with stereotypes that might have been renamed. Define stereotype IDs as constants to avoid magic strings throughout your code. #### Getting and modifying stereotype properties ```javascript // Get a stereotype by name let stereotype = element.getStereotype("Audit"); // Get a property within the stereotype and set its value stereotype.getProperty("CreatedBy").setValue("System"); stereotype.getProperty("CreatedDate").setValue(new Date().toISOString()); // Access nested properties let auditStereotype = element.getStereotype("Audit"); if (auditStereotype) { let createdByProp = auditStereotype.getProperty("CreatedBy"); if (createdByProp) { createdByProp.setValue("AdminUser"); } } // Set value on multi-select properties with static options // JSON.stringify is used to convert the array to a JSON string let permissionStereotype = element.getStereotype("Permissions"); if (permissionStereotype) { let scopesProp = permissionStereotype.getProperty("Scopes"); if (scopesProp) { scopesProp.setValue(JSON.stringify(["Read", "Write"])); } } // Set value on multi-select properties with lookup element options // JSON.stringify is used to convert the array to a JSON string if (permissionStereotype) { // commandsProp has a "Lookup Types" value of "Command" let commandsProp = permissionStereotype.getProperty("Accessible Commands"); if (commandsProp) { // set the property value to the Id's of two Command elements set prior in the script commandsProp.setValue(JSON.stringify([createCommand.id, deleteCommand.id])); } } ``` #### Applying stereotypes programmatically ```javascript // Apply a stereotype to an element by name element.applyStereotype("Soft Delete Entity"); // Remove a stereotype element.removeStereotype("Soft Delete Entity"); ``` #### Practical example: Stereotype-driven configuration ```javascript // Check for specific stereotypes and configure element accordingly if (element.hasStereotype("Aggregate Root")) { // Add specific attributes for aggregate roots let versionAttr = createElement("Attribute", "Version", element.id); versionAttr.typeReference.setType("33013006-E404-48C2-AC46-24EF5A5774FD"); // long type } if (element.hasStereotype("Value Object")) { // Value Objects are typically immutable element.getChildren("Attribute").forEach(attr => { attr.setMetadata("is-readonly", true); }); } ``` ## Practical Examples: Ad-hoc Scripts The following examples demonstrate common scenarios where ad-hoc scripts can save significant time. You can copy these examples and adapt them to your needs. ### Bulk Domain Model Creation This example creates a complete e-commerce domain model with multiple entities and different relationship types. This is useful when you need to quickly scaffold a domain model based on existing documentation or requirements. ```javascript // Define type constants const stringType = "d384db9c-a279-45e1-801e-e4e8099625f2"; const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74"; const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e"; const decimalType = "675c7b84-997a-44e0-82b9-cd724c07c9e6"; // Helper function to add attributes to entities function addAttributes(entity, attributes) { // Note: Id attributes are auto-generated when Intent.Metadata.RDBMS module is installed attributes.forEach(attr => { let attribute = createElement("Attribute", attr.name, entity.id); attribute.typeReference.setType(attr.type); }); } // Find target package let domainPackage = getPackages().find(p => p.name === "Domain") || getPackages()[0]; // Create entities let customer = createElement("Class", "Customer", domainPackage.id); let order = createElement("Class", "Order", domainPackage.id); let orderItem = createElement("Class", "OrderItem", domainPackage.id); let product = createElement("Class", "Product", domainPackage.id); // Add attributes to each entity // Use (or create) a DTO package to hold generated DTOs // (fall back to the first package if a dedicated DTOs package doesn't exist) let dtosPackage = getPackages().find(p => p.name === "DTOs") || getPackages()[0]; addAttributes(customer, [ { name: "Name", type: stringType }, { name: "Email", type: stringType } ]); addAttributes(order, [ { name: "OrderDate", type: datetimeType }, { name: "TotalAmount", type: decimalType } ]); addAttributes(orderItem, [ { name: "Quantity", type: intType }, { name: "UnitPrice", type: decimalType } ]); addAttributes(product, [ { name: "Name", type: stringType } ]); // Create relationships with different patterns: // 1. Aggregate: Customer -> Orders (defaults to 1-to-many aggregate relationship) createAssociation("Association", customer.id, order.id); // 2. Composition: Order -> OrderItems (1-to-many composite - OrderItem can't exist without Order) // Key: Use getOtherEnd() to configure both sides for composite relationships let orderItemAssoc = createAssociation("Association", order.id, orderItem.id); orderItemAssoc.getOtherEnd().typeReference.setIsCollection(false); // Order side (one) orderItemAssoc.typeReference.setIsCollection(true); // OrderItem side (many) // 3. Reference: OrderItem -> Product (many-to-1 reference - Product exists independently) createAssociation("Association", orderItem.id, product.id); await dialogService.info("Created e-commerce domain model with proper relationship types!"); ``` Once created, you can drag them onto the diagram to resemble this: ![Bulk domain element creation](images/bulk-domain-element-creation.png) ### Gathering User Input with Dynamic Forms When you need to make your scripts more flexible and reusable, you can prompt users for input using dynamic forms. This example shows how to create a configurable entity generator that asks users what entities to create and what common fields to add. ```javascript // Define type constants const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; const intType = "fb0a362d-e9e2-40de-b6ff-5ce8167cbe74"; const longType = "33013006-E404-48C2-AC46-24EF5A5774FD"; const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e"; // Configure a form to collect entity generation parameters let formConfig = { title: "Bulk Entity Generator", submitButtonText: "Generate Entities", minWidth: "500px", fields: [ { id: "entityNames", fieldType: "textarea", label: "Entity Names (one per line)", isRequired: true, placeholder: "Customer\nOrder\nProduct\nCategory", hint: "Enter each entity name on a separate line" }, { id: "addIdAttribute", fieldType: "checkbox", label: "Add Id attribute to each entity", value: true }, { id: "idType", fieldType: "select", label: "Id Type", selectOptions: [ { id: guidType, description: "Guid" }, { id: intType, description: "Int" }, { id: longType, description: "Long" } ], value: guidType }, { id: "addAuditFields", fieldType: "checkbox", label: "Add audit fields (CreatedDate, UpdatedDate)", value: true } ] }; // Show the form and get user input let result = await dialogService.openForm(formConfig); let entityNames = result.entityNames.split('\n').filter(name => name.trim()); // Create entities based on form input let targetPackage = getPackages()[0]; entityNames.forEach(name => { let entity = createElement("Class", name.trim(), targetPackage.id); // Add Id attribute if requested if (result.addIdAttribute) { let idAttr = createElement("Attribute", "Id", entity.id); idAttr.typeReference.setType(result.idType); } // Add audit fields if requested if (result.addAuditFields) { let createdDate = createElement("Attribute", "CreatedDate", entity.id); createdDate.typeReference.setType(datetimeType); let updatedDate = createElement("Attribute", "UpdatedDate", entity.id); updatedDate.typeReference.setType(datetimeType); } }); await dialogService.info(`Successfully created ${entityNames.length} entities!`); ``` ### Service Layer Generation This example generates CRUD (Create, Read, Update, Delete) operations for existing domain entities. This is particularly useful when you've modeled your domain and need to quickly create a corresponding service layer. > [!NOTE] > > You need to have domain entities modeled before running this script. ```javascript // Define type constants const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; // Generate CRUD operations for all domain classes let domainClasses = lookupTypesOf("Class"); let servicesPackage = getPackages().find(p => p.name === "Services") || getPackages()[0]; // Use (or create) a DTO package to hold generated DTOs let dtosPackage = getPackages().find(p => p.name === "DTOs") || servicesPackage; domainClasses.forEach(domainClass => { // Create or reuse a DTO that represents the domain entity in service layer APIs const dtoName = `${domainClass.getName()}Dto`; let dto = lookupTypesOf("DTO").find(d => d.getName() === dtoName); if (!dto) { dto = createElement("DTO", dtoName, dtosPackage.id); } // Copy attributes from the domain class into DTO fields where available // (This keeps the service layer decoupled from domain element types) domainClass.getChildren("Attribute").forEach(attr => { let field = createElement("DTO-Field", attr.getName(), dto.id); if (attr.typeReference) { // Use the attribute's type for the DTO field when possible field.typeReference.setType(attr.typeReference.getTypeId()); field.typeReference.setIsNullable(attr.typeReference.getIsNullable()); field.typeReference.setIsCollection(attr.typeReference.getIsCollection()); } }); let service = createElement("Service", `${domainClass.getName()}Service`, servicesPackage.id); // Create CRUD operations let createOp = createElement("Operation", `Create${domainClass.getName()}`, service.id); let getOp = createElement("Operation", `Get${domainClass.getName()}`, service.id); let updateOp = createElement("Operation", `Update${domainClass.getName()}`, service.id); let deleteOp = createElement("Operation", `Delete${domainClass.getName()}`, service.id); // Set return/parameter types to the DTO (not the domain element) // Get should return the DTO representation getOp.typeReference.setType(dto.id); // Create returns an id (guid) createOp.typeReference.setType(guidType); // Add parameters to operations - use DTO for payloads let createParam = createElement("Parameter", `create${domainClass.getName()}Request`, createOp.id); createParam.typeReference.setType(dto.id); let idParam = createElement("Parameter", "id", getOp.id); idParam.typeReference.setType(guidType); }); ``` ### Command/Query Pattern Generator If you're following the CQRS (Command Query Responsibility Segregation) pattern, this script can automatically generate Commands and Queries from existing Service Operations. It analyzes operation names to determine whether to create a Command or Query and copies parameters as DTO fields. > [!TIP] > > Run the script in [Service Layer Generation](https://docs.intentarchitect.com/docs-md/module-building/designers/designer-scripting/designer-scripting.md#service-layer-generation) before executing this one. ```javascript // Generate Commands and Queries for selected service operations let services = lookupTypesOf("Service"); let commandsPackage = getPackages()[0]; let queriesPackage = getPackages()[0]; services.forEach(service => { service.getChildren("Operation").forEach(operation => { let operationName = operation.getName(); if (operationName.startsWith("Get") || operationName.startsWith("Find") || operationName.startsWith("Search")) { // Create Query let query = createElement("Query", `${operationName}Query`, queriesPackage.id); // Copy parameters as DTO fields operation.getChildren("Parameter").forEach(param => { let field = createElement("DTO-Field", param.getName(), query.id); if (param.typeReference) { field.typeReference.setType(param.typeReference.getTypeId()); field.typeReference.setIsNullable(param.typeReference.getIsNullable()); field.typeReference.setIsCollection(param.typeReference.getIsCollection()); } }); // Set return type to match operation if (operation.typeReference && operation.typeReference.getTypeId()) { query.typeReference.setType(operation.typeReference.getTypeId()); query.typeReference.setIsCollection(operation.typeReference.getIsCollection()); } } else { // Create Command let command = createElement("Command", `${operationName}Command`, commandsPackage.id); // Copy parameters as DTO fields operation.getChildren("Parameter").forEach(param => { let field = createElement("DTO-Field", param.getName(), command.id); if (param.typeReference) { field.typeReference.setType(param.typeReference.getTypeId()); field.typeReference.setIsNullable(param.typeReference.getIsNullable()); field.typeReference.setIsCollection(param.typeReference.getIsCollection()); } }); // Commands typically return void or an ID if (operation.typeReference && operation.typeReference.getTypeId()) { command.typeReference.setType(operation.typeReference.getTypeId()); } } }); }); await dialogService.info("Commands and Queries generated successfully!"); ``` ## Working with Advanced Mappings For scenarios beyond basic element creation and modification, Intent Architect also supports programmatically creating **Advanced Mappings** - declarative configurations that define data transformation paths between model elements. This is particularly useful when generating CRUD operations that need sophisticated mapping logic. See [Scripting Advanced Mappings](https://docs.intentarchitect.com/docs-md/module-building/designers/designer-scripting-advanced-mappings/designer-scripting-advanced-mappings.md) for detailed examples of creating Invocation Mappings, Data Mappings, and Filter Mappings programmatically. ## Configuring Scripts in the Module Builder While the Execute Script Dialog is great for one-off scripting, creating reusable scripts as part of a module requires configuration in the [Module Builder](https://docs.intentarchitect.com/docs-md/module-building/about-the-module-builder/about-the-module-builder.md). This section covers how to set up scripts that persist as part of your module and are automatically available to users. ### Designer Settings for Script Configuration When configuring context menu scripts and event handlers in the Module Builder, you need to ensure the designer is properly configured to support scripting: 1. **Create or extend a Designer** in the Module Builder 2. **Add Element or Association Extensions** to define where scripts can be configured 3. **Configure event handlers** on specific events (e.g., `On Created`, `On Changed`, etc.) ### Example: Setting up a Designer Extension with Script Support In the Module Builder designer, you can extend an existing designer element to add script support: ![Designers Folder](images/designer-extension-example.png) Key steps: - Select the element type you want to extend (e.g., `Class`) - Add Element Event Handlers for the events where your script should execute - Write your JavaScript logic in the event handler script editor The scripts you configure here become part of your module and will automatically execute when users interact with the designer in their Intent Architect applications. > [!NOTE] > > Scripts configured in the Module Builder are deployed as part of your module and execute automatically for all users who have the module installed. This is different from ad-hoc scripts in the Execute Script Dialog, which are one-time operations. ## Context Menu Scripts Context menu scripts allow users to manually trigger JavaScript operations directly from the designer UI. These scripts are invoked through right-click context menus on elements, associations, or packages, providing a way to automate modeling tasks on demand. ### Adding a Context Menu Script Option To add a context menu script option in the Module Builder: 1. Create a **Designer Extension** targeting the element type (e.g., `Class`, `Association`, or `Package`) 2. Add a **Run Script Option** to the extension 3. Configure the script that should execute when the user selects that option from the context menu #### Example: Adding a context-menu option to Class elements Here's how you might add a context menu option to the `Class` element in the Domain designer: ![Designers Folder](images/manual-script-example-add-script-option.png) When a user right-clicks a Class element, they'll see your custom script option in the context menu. Clicking it will execute your JavaScript. ### Accessing Elements in Context Menu Scripts In a context menu script, you have access to the element that was right-clicked through the `element` variable: ```javascript // Access the element that triggered the context menu console.log(`Element name: ${element.getName()}`); // Modify element properties element.getStereotype("Stereotype Name").getProperty("Property Name").setValue(""); // Get the element's type let elementType = element.specialization; console.log(`Element type: ${elementType}`); // Access child elements element.getChildren("Attribute").forEach(attr => { console.log(`Attribute: ${attr.getName()}`); }); ``` ### Example: Stereotype-based context menu script Here's a practical example that configures caching for a service by applying the `Cacheable` stereotype and setting cache-related properties: ```javascript const cacheableStereotype = "Cacheable"; // Check if the stereotype is already applied if (element.hasStereotype(cacheableStereotype)) { // Element is already marked as cacheable return; } // Apply the stereotype element.applyStereotype(cacheableStereotype); // Configure cache properties let stereotype = element.getStereotype(cacheableStereotype); if (stereotype) { // Set cache duration in seconds stereotype.getProperty("DurationSeconds").setValue("300"); // Set cache strategy stereotype.getProperty("Strategy").setValue("Distributed"); // Set cache key pattern stereotype.getProperty("KeyPattern").setValue("${ClassName}:${MethodName}"); } await dialogService.info(`Applied ${cacheableStereotype} to ${element.getName()} with 5-minute cache duration`); ``` ## Event-Driven Automation While ad-hoc scripts are great for one-time operations, event handlers allow you to automate responses to modeling actions. Event handlers run automatically when specific events occur, such as when an element is created, modified, or when an association is drawn. Event handlers are particularly useful for: - Enforcing modeling conventions automatically - Auto-configuring new elements with common attributes - Maintaining relationships between elements - Applying stereotypes and metadata consistently > [!NOTE] > Event handlers are created in the Module Builder designer and become part of a module. This section assumes you're familiar with basic module building concepts. See [Module Builder overview](https://docs.intentarchitect.com/docs-md/module-building/about-the-module-builder/about-the-module-builder.md) for conceptual background. ### Element Event Handlers Element event handlers execute when an element is created or modified. This example shows how to automatically manage a "soft delete" pattern by adding or removing an `IsDeleted` attribute based on whether a stereotype is applied. ![Event Triggered Script for Elements Screenshot](images/event-triggered-script-element.png) Inside the Module Builder designer, you can add Element Event Handlers for an Element you've created or extend an existing Element from a Designer. Installed as: Element Event Handler on the `Class` element's `On Changed` event. Requires a Stereotype `Soft Delete Entity`. ```javascript const softDeleteStereotype = "Soft Delete Entity"; const boolTypeId = "e6f92b09-b2c5-4536-8270-a4d9e5bbd930"; if (element.hasStereotype(softDeleteStereotype)) { let isDeleteAttr = element.getChildren("Attribute").filter(x => x.hasMetadata("soft-delete"))[0] || createElement("Attribute", "IsDeleted", element.id); isDeleteAttr.typeReference.setType(boolTypeId); isDeleteAttr.setMetadata("soft-delete", true); return; } let isDeleteAttr = element.getChildren("Attribute").filter(x => x.hasMetadata("soft-delete"))[0]; if (isDeleteAttr) { isDeleteAttr.delete(); } ``` In this JavaScript example, the script activates when a Class element is modified. It performs the following actions: - When a Class has a Soft Delete stereotype applied, it adds an `IsDeleted` attribute of boolean type, marked with soft-delete metadata - When the Soft Delete stereotype is removed, it searches for any attribute with soft-delete metadata and deletes it from the Class ### Auto-Configuring New Entities This example shows how to automatically add common attributes (like Id, CreatedDate, UpdatedDate) whenever a new entity is created. This ensures consistency across your domain model without manual repetition. Installed as: Element Event Handler on the `Class` element's `On Created` event. ```javascript // Define type constants const guidType = "6b649125-18ea-48fd-a6ba-0bfff0d8f488"; const datetimeType = "a4107c29-7851-4121-9416-cf1236908f1e"; // When a new Class is created, auto-add common attributes if (element.specialization === "Class") { // Add Id attribute if it doesn't exist let hasId = element.getChildren("Attribute").some(attr => attr.getName().toLowerCase() === "id"); if (!hasId) { let idAttr = createElement("Attribute", "Id", element.id); idAttr.typeReference.setType(guidType); } // Add CreatedDate and UpdatedDate for audit trail let createdDate = createElement("Attribute", "CreatedDate", element.id); createdDate.typeReference.setType(datetimeType); let updatedDate = createElement("Attribute", "UpdatedDate", element.id); updatedDate.typeReference.setType(datetimeType); } ``` ### Association Event Handlers Association event handlers execute when associations (relationships) are created between elements. This is useful for automatically configuring relationship properties based on conventions or element types. ![Event Triggered Script for Associations Screenshot](images/event-triggered-script-association.png) Inside the Module Builder designer, you can add Association Event Handlers for an Association you've created or extend an existing Association from a Designer. Installed as: Association Event Handler on the `Association` element's `On Created` event. Here's a simple example that automatically configures new associations so that the source is the composite owner of the relationship (no matter which association type you use): ```javascript if (!association) { return; } let sourceEnd = association.getOtherEnd().typeReference; sourceEnd.setIsCollection(false); sourceEnd.setIsNullable(false); ``` This script gets executed when an association is created and turns it into a 1-to-1 composite relationship by disabling `Is Collection` and `Is Nullable` on the source end of the association. ### Auto-configuring association properties by aggregate semantics Rather than relying on literal type names, this example shows a more robust approach: determine whether an element is an aggregate root (no other entity composes it) or a value object (specialization `Value Object`), and apply relationship semantics accordingly. Installed as: Association Event Handler on the `Association` element's `On Created` event. ```javascript // Auto-configure association properties using aggregate-root inference if (!association) { return; } function isAggregateRoot(element) { return !element.getAssociations("Association") .some(x => x.isSourceEnd() && !x.typeReference.isCollection && !x.typeReference.isNullable); } const sourceElement = association.getOtherEnd().typeReference.getType(); const targetElement = association.typeReference.getType(); // 1) If target is explicitly a Value Object -> make it a composition (parent one -> child many) if (targetElement && targetElement.specialization === 'Value Object') { association.getOtherEnd().typeReference.setIsCollection(false); // Parent: only one composite owner association.typeReference.setIsCollection(true); // Child: collection of value objects association.typeReference.setIsNullable(false); // Child instances cannot be null return; } // 2) If target is an aggregate root (no composite owners) -> make relationship a required reference (many-to-one) if (isAggregateRoot(targetElement)) { association.getOtherEnd().typeReference.setIsCollection(false); // Source end: single reference association.typeReference.setIsCollection(false); // Target end: single reference association.typeReference.setIsNullable(false); // Target must be present (required) return; } // 3) Fallback: optional reference (one-to-one optional) association.getOtherEnd().typeReference.setIsCollection(false); association.typeReference.setIsCollection(false); association.typeReference.setIsNullable(true); ``` ## Full API Documentation Complete API documentation with IntelliSense is available in the built-in script editor. For the full TypeScript definitions, see the [GitHub repository](https://github.com/IntentArchitect/Intent.Modules/tree/development/DesignerMacros/typings). --- # Document Modules with Help Topics You can make your documentation easily discoverable through the application's built-in Help feature. This system allows users to search for relevant topics directly within the app, which then directs them to your module's documentation website. The Help feature is context-aware, meaning it intelligently surfaces your documentation topics based on the specific designer page or module elements the user is currently working with, ensuring your carefully crafted explanations reach users precisely when they need them most. Example of topics introduced by installed modules: ![Sample Help Documentation](images/sample-help-documentation.png) ## Creating Help Topics To create help topics for your module, you will need to ensure you have the module `Intent.ModuleBuilder` installed starting from version `3.12.0`. To add documentation to your module, in the `Module Builder` designer, follow these steps: - Right click on the package at the top of the tree-view page and select `Add Documentation`. - A Documentation element will be created. Right click on it and select `Add Topic`. - In the `Description` property, supply a description that will be visible on the tile representing the Help Topic. - In the `Icon` property, provide an icon that will give a distinct visual appearance for your Help Topic. - In the `Href` property, specify a URI location to direct the user when they click on your Help Topic. - In the `Designers` property _(optional)_, select the designers where the Help Topic should appear. If no designers are selected, you can access the Help Topic irrespective of the designer page that is open. - In the `Elements` property _(optional)_, select the elements related to the Help Topic. Right click on those elements in their respective designers will feature a `Help` option which will display your Help Topics. - In the `Tags` property _(optional)_, define keywords (separated by spaces) which will help make your Help Topic more discoverable when a search phrase is entered in the Help screen in Intent Architect. - Save and run the Software Factory to apply those changes to your module before building it. Example of Help Topics in the Entity Framework module: ![Sample Topics: Entity Framework](images/sample-entity-framework-topics.png) --- # Distributing your Modules to others Modules built using Intent Architect can be distributed to other team members using a [self-hosted Module Server](#distributing-modules-using-a-self-hosted-module-server) instance or a [file system based repository location](#distributing-modules-using-file-system-based-repositories). ## Distributing modules using a self-hosted Module Server Custom modules can be uploaded to a self-hosted Module Server which makes them accessible at an https URL. For more information on setting up the Module Server, refer to [this](https://docs.intentarchitect.com/docs-md/tools/module-server/module-server.md) article. For more information on uploading modules to your self-hosted Module Server, refer to [this](https://docs.intentarchitect.com/docs-md/tools/module-server-client-cli/module-server-client-cli.md) article. Once the self-hosted Module Server has been set up, [configure Intent Architect to use its address as repository](#configure-intent-architect-to-use-your-repository-location) ## Distributing modules using file system based repositories At a high level: * Decide on a common file system accessible location which everyone within your team has read access to. * Each Intent Architect user in your team adds this common location to their repositories under their user settings. * Modules already used by others should be treated as immutable, so if you've made changes within your module, then be sure to increment its version. * Build the module to get the `.imod` artifact. * Copy the `.imod` to the common file system accessible location. ### Decide on a location for the file system based repository #### Network based file sharing option If all members are connected via a local network (or via VPN for remote working), one can make use of network file sharing hosting solutions. For example, most operating systems can access a Windows hosted network file location that resembles `\\server\intent-modules`. This is a simple way to distribute modules so long as everyone has at least readonly access (except for the publisher who needs write access) to that server location. #### Cloud based file sharing option If your team is geographically distributed (but this option can still be leveraged for members on a local network too), then we recommend internet based file sharing with automatic synchronization to users machines, for example, Google Drive, Dropbox, OneDrive (SharePoint with OneDrive), etc. One of the benefits of this approach is that you have an offline cache available during network disruptions and your cloud storage provider also makes backups on your behalf. ##### Example: Using Sharepoint and OneDrive For example, if your organisation has SharePoint and OneDrive, you could use the following process to sync `.imod` files to developers computers: * If you don't already have a document library for this purpose, [create one in SharePoint](https://support.microsoft.com/office/create-a-document-library-in-sharepoint-306728fe-0325-4b28-b60d-f902e1d75939). * If desired, [create a folder](https://support.microsoft.com/office/create-a-document-library-in-sharepoint-306728fe-0325-4b28-b60d-f902e1d75939) within the document library for the `.imod` files. * [Set up syncing](https://support.microsoft.com/office/sync-sharepoint-and-teams-files-with-your-computer-6de9ede8-5b6e-4503-80b2-6190f3354a88) of the SharePoint folder to your computer's file system. * Make note of the location of the folder on your computer's file system to where OneDrive is syncing the folder with the `.imod` files and then [configure Intent Architect to use this location as a repository](#configure-intent-architect-to-use-your-repository-location). ## Configure Intent Architect to use your repository location This [article](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/how-to-manage-repositories/how-to-manage-repositories.md) explains how to setup your known Intent Architect module repositories which let Intent know from where it can install and update modules. ## Module versioning concerns When distributing modules to other members of the team, always ensure that you update the version number. Unless the version number is changed, Intent Architect will assume that the contents of the module are unchanged and continue to using a cached version of it. Additionally, if you're keeping your Intent Architect application in SCM (Source Code Management), Intent Architect keeps track of installed module versions inside of `modules.config` files which are alongside the Intent Architect Application and so are also committed into SCM. This means you can safely check out an earlier version of the code base and Intent Architect will restore and run the correct version of the module such that running the Software Factory will produce the same output as it did at the time of the SCM check-in. For these reasons, a version of a module which has been distributed to anyone else _must_ be treated as immutable. Intent Architect supports [Semantic Versioning 2.0.0](https://semver.org/) and it is our recommendation that you follow Semantic Versioning practices. Without having to understand Semantic Version practices in depth, you could simply increment the major version (the first version component) each time, so `1.0.0` -> `2.0.0` -> `3.0.0`, etc. By increasing version numbers, users of Intent Architect will be notified that new version of those modules are available and can choose to install them. ## Copying your `.imod` file Locate where your newly built `.imod` file is placed after it's built in your IDE (such as Visual Studio). The console output generally displays the location (where it says "Successfully created module"): Example of build output log: ```text Build started... 1>------ Build started: Project: MyModule, Configuration: Debug Any CPU ------ 1>MyModule -> C:\Dev\MyModule\MyModule\bin\Debug\net5.0\MyModule.dll 1>Intent Architect Packager PackAll Task (C:\Users\User\.nuget\packages\intent.packager\3.2.0\lib\netstandard2.0\Intent.Packager.BuildTasks.dll) 1>Packaging module for C:\Dev\MyModule\MyModule\MyModule.imodspec 1>Added lib/MyModule.dll. 1>Added lib/MyModule.pdb. 1>Added MyModule.1.0.0.imodspec. 1>Successfully created module 'C:\Dev\MyModule\Intent.Modules\MyModule.1.0.0.imod'. ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== ``` Copy the `.imod` file from the `Intent.Modules` folder to your file sharing location. --- # Module Installation ## Introduction Installing modules in the context of module building within Intent Architect is a crucial step for referencing existing metadata from other modules that would act as dependencies. This allows you to add script hooks, extend designer elements, and make use of other components like Stereotypes. This document guides you through the process of installing modules from a module builder perspective, ensuring that only the necessary metadata is referenced without inadvertently installing components that are not needed. ## Installing a Dependency Module Installing dependent modules follows the same steps in general: 1. **Open "Manage Modules..."**: - Open your Intent Architect solution. - Right-click on the application. - Select **Manage Modules** from the context menu. 2. **Selecting a Module to Install**: - In the Manage Modules window, search for the module you wish to install. - Click on the module to view its details. 3. **Expand Options Section**: - By default, Intent Architect will install all components of the module, including designers, templates, etc. - To avoid this (especially for building custom modules), expand the **Options** section. 4. **Install Metadata Only**: - Within the Options section, you will find a checkbox labeled **Install metadata only**. - Check this box to ensure that only the metadata is installed. - Proceed to click **Install**. 5. **Dependencies and References**: - Installing the module will introduce dependencies in your `imodspec` file. - It may also add NuGet references in your Visual Studio `.csproj` file related to your module project. ## Dependency Version Override Behavior There is a setting in the **Settings** page under **Module Builder Settings** called **Dependency version override behavior**. This setting controls how dependency versions are introduced in the `imodspec` and `.csproj` files. The available options are: - **Always**: Always override the dependency versions. - **If newer**: Only override if the new version is more recent than the current version. - **Never**: Do not override dependency versions (doesn't prevent dependencies being introduced). ### Configuring Dependency Override Behavior 1. Navigate to the **Settings** page in Intent Architect. 2. Under **Module Builder Settings**, locate **Dependency version override behavior**. 3. Choose the appropriate option based on your project requirements. --- # Module Manifest (.imodspec) Every Module requires a Manifest `.imodspec` file that contains module metadata needed for it to be consumed by Intent Architect. This file is automatically generated and managed (for the most part) by the Module Builder. However, developers may wish to make changes in this file (e.g. add additional `dependencies`, `files`, etc.). ## Manifest Metadata The following table provides high level information on each of the tags in the `.imodspec` file: | Tag | Description | | ------------------------ | ----------- | | id | The module identifier. This must be a unique string. | | version | The version of this module. If this value is changed, the Module Builder will only update it if the value specified in its metadata is higher. | | supportedClientVersions | The range of versions of Intent Architect that support this module. | | summary | A brief summary of what this module does. This is displayed in the Modules manager. | | description | A more detailed description of the module. | | authors | The authors of this module. | | iconUrl | The icon URL or base64 encoded image for the Module. | | projectUrl | The project URL for this module. | | tags | Tags that can be used for searching for this module. Note that this is currently not respected but will be in a later release of Intent Architect. | | releaseNotes | The inline release-notes or relative path to a file (e.g. `release-notes.md`). Markdown is supported. The release notes can be viewed within Intent Architect in the Module details panel in the Modules manager. | | templates | The manifest of [Templates](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/about-csharp-templates/about-csharp-templates.md) contained in this module. | | decorators | The manifest of [Decorators](https://docs.intentarchitect.com/docs-md/module-building/decorators/about-decorators/about-decorators.md) contained in this module. | | factoryExtensions | The manifest of [Software Factory Extensions](https://docs.intentarchitect.com/docs-md/module-building/software-factory-extensions/software-factory-extensions.md) contained in this module. | | dependencies | The module dependencies and their minimum versions. The Module Builder will ignore any added dependencies or version updates that are higher than what the Module Builder is applying. See [Dependency Installation Attributes](#dependency-installation-attributes) for optional dependency installation behavior. | | files | The executable `.dll` files that must be included in this module. If using an external library of framework, those executables will need to be registered here. An optional `inspect` attribute can be set to `false` to prevent Intent Architect from inspecting the DLL for Intent-related content and components. | | interoperability | Defines integration rules that automatically install complementary modules when specific modules are detected. When a module with a matching `detect` ID is installed, the specified packages are automatically installed to provide interoperability between independent modules. | | metadata | Metadata (e.g. packages and designer settings) that must be included in this module. | | moduleSettings | Module settings that must be installed into the Settings page for the application. | | moduleSettingsExtensions | Extensions to existing Module settings that must be installed. | ## Dependency Installation Attributes The `dependencies` element supports optional attributes to control how dependent modules are installed: - `metadataOnly="true"`: Installs the referenced module and its dependencies with only module metadata being installed. Dependencies already installed without this option set will not be changed. - `includeAssets="..."`: Installs the referenced module with a semicolon-separated list of installation assets. Valid `includeAssets` values are: - `factoryExtensions` - `applicationSettings` - `designerMetadata` - `designers` - `templateOutputs` These values align with the Installation Settings options described in [About Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md). ## Example Manifest `.imodspec` file An example `.imodspec` file (tags with `...` indicate that their metadata has been excluded for brevity): ```xml Intent.AspNetCore.Controllers 3.3.6 [3.3.0-pre.0, 4.0.0) A generic controller pattern implementation using ASP.NET Core. A generic controller pattern implementation using ASP.NET Core. Intent Architect ... https://intentarchitect.com/ AspNetCore Controllers release-notes.md ... ... ... ``` --- # Overview For an introduction to module building watch the recording of our [Introduction to Module Building Webinar](https://intentarchitect.com/#/insights-intro-module-building). ## The Two Development Roles In the same way that traditional software development teams have members who fulfil different roles (such as developing infrastructure, architecture, backends, front-end applications, API integration and consumer services, etc.) Intent Architect is no different in that developers who design and implement Applications may be different to the developers who supply the [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) that support the ability for the Application developers to function (more efficiently) and deliver value. ![Overview](images/overview-two-development-roles.png) > [!NOTE] > Not all software development teams may have the need to develop and maintain their own Modules, since Intent Architect offers a suite of Modules out of the box to development teams. However, Intent Architect offers power and flexibility for those who do. ### Module Building This can be likened in general to developers who create their own library packages for Nuget, NPM, Yarn, PIP, etc. It allows for an installable package to supply code that can plug in to the existing code-base to provide additional functionality to help achieve the goal of delivering business value. Developers who build Modules in Intent Architect are providing different value in that their focus is on finding ways to automate architecture, patterns and any other predictable aspects of software development and provide installable Module packages to the development teams who need them. This can form part of the same software development development-cycle of the team who is developing applications using Intent Architect, or it can even be an entirely different team operating on a separate development-cycle. ### Application Development These are the consumers of the said Module packages who install and benefit from the automation features that it provides. This can include new forms of code being generated which saves a lot of time and effort for development teams or it can be that new and/or enhanced designers are provided which offer richer designing experiences and can potentially offer opportunities for other parts of the code-base to be automated that previously had to be maintained by hand. ## Module Building Ecosystem ### Ecosystem ![Module Building Ecosystem](images/overview-module-builder-ecosystem.png) The module building ecosystem comprises of one or more of the following aspects: ### Metadata Metadata is the term used to describe all information that can be captured with Intent Architect designers. The Software Factory during its execution makes metadata available to modules which then use it to determine what code to generate. #### Designers [Designers](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) are the visual modellers in an Intent Architect application which allow users to capture and compose metadata. Designers are highly configurable so that they can be tailored to capture information in a way that is intuitive and natural for the particular project or application. #### Designer Extensions Intent Architect supports extending designers. [Designer extensions](https://docs.intentarchitect.com/docs-md/module-building/designer-extensions/about-designer-extensions/about-designer-extensions.md) allow adding additional element types and behaviour to an existing designer, providing the following powerful benefits: - No need to copy an entire existing designer when you want to make a single small addition to it. - Multiple designer extensions extending a single designer can be installed at the same time and they all continue working as expected. - One can generally upgrade the module with the base designer to take advantage of new designer features with the designer extensions not needing to be updated to continue working. #### Stereotypes [Stereotypes](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-stereotypes/about-stereotypes.md) allow "decoration" of elements and are analogous to decoration features in programming languages, such as [`C# Attributes`](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/) or [`Java Annotations`](https://en.wikipedia.org/wiki/Java_annotation). Common use of Stereotypes is specifying technology or business domain specific metadata for Elements in the Intent Architect designer, such as a Class having a business requirement of being audited, or a Service Operation's HTTP method. >[!NOTE] >In comparison to designers and designer extensions, stereotypes allow adding additional "fields" or values to a particular element, while designers and designer extensions are for defining additional element types and element behaviours (such as context menu options, where they can be used, etc). #### Designer APIs Not only do users of Intent Architect have an interface with which to model what the metadata should look like, Module developers will also get an easy to use generated API to query the modelled metadata for Template development, [see for example](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/how-to-use-stereotypes/how-to-use-stereotypes.md#query-stereotypes-from-templates). ### Code Generation Code generation is the process of running the Software Factory which provides metadata from the designers to installed modules which use it to generate output (normally code). #### Templates [Templates](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/about-csharp-templates/about-csharp-templates.md) are responsible for generating the actual content of output files, the Software Factory runs each template for each installed module in turn to ultimately generate the output of all the different files. The vast majority of module building is the authoring of templates. For more information on authoring templates, see the [tutorial](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/01-create-a-template-introduction/create-a-template-introduction.md). #### Decorators [Decorators](https://docs.intentarchitect.com/docs-md/module-building/decorators/about-decorators/about-decorators.md) are a mechanism which can be used by Templates in order to inject content at certain _hook points_ within a Template. Templates expose a Decorator contract which can be implemented by Decorators in different modules. In this way Templates can be extended by modules on which they have no dependency and might have been created by completely different authors or even companies. ### Additional Tools More module building features for advanced use cases or requirements. #### Module Settings Adds a new section on the Application Settings view to allow end-users to configure how your module should work by adjusting settings that your module exposed. For example a module has a setting that allows the user to choose which primitive type to use when generating Primary Key columns for SQL tables that can either be `int`, `bigint` or `uniqueidentifier`. #### Eventing A fully decoupled mechanism for modules to interact with each other through use of messages and the event dispatcher within the Software Factory, commonly used for fulfilling architectural infrastructural concerns. For example a template may need a registration to be performed with a system's dependency injection framework, without the template being aware of what dependency injection framework will ultimately be used. The template would simply publish a message saying what it needs, then multiple different modules (for example, AutoFac or Ninject modules) would be configured to listen to these messages and fulfil them making it so that regardless of which module is installed, everything still works. #### Factory Extensions [Factory Extensions](https://docs.intentarchitect.com/docs-md/module-building/software-factory-extensions/software-factory-extensions.md) are used to hook into and extend any of the pre-defined phases of the Software Factory process, use cases where this is useful include: - Loading Metadata from outside Intent Architect. - Alter the output produced from Templates. - Execute external processes which developers might have needed to execute manually after a Software Factory Execution. ## See also - [Tutorial: Create a Template](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/01-create-a-template-introduction/create-a-template-introduction.md) --- # Software factory events Software factory events allow modules to interact with each other in a decoupled manner, a Template or Software Factory Extension can publish an event without having to be directly aware of any subscribers of that event. They enable use cases like different modules being able to service cross-cutting architectural concerns. For example, a template could publish an event requesting its type be registered in a dependency injection container. The user of the module could then choose to install one of a variety of available dependency injection modules which are all listening for that event and will register the type in a technology specific way, the particular installed dependency injection module (perhaps Ninject), could be uninstalled and an alternative module (perhaps AutoFac) could be installed instead and because they both listen for the same event, the system still works with the original event publishing template not needing to be aware of which module is installed. ## Subscribing and consuming an event Create an event type (typically in a "third", common project), for example: ```csharp public class RegistrationRequest { public RegistrationRequest(IClassProvider template) { Template = template; } public IClassProvider Template { get; set; } } ``` In your template's constructor, subscribe to the event: ```csharp ExecutionContext.EventDispatcher.Subscribe(Handle); ``` `Handler` above refers to a method in a class to handle the event, the implementation of which could be like so: ```c# private void Handle(RegistrationRequest request) { AddTemplateDependency(TemplateDependency.OnTemplate(request.Template)); // Will add project dependencies if needed _typesToRegister.Add($"{request.Template.Namespace}.{request.Template.ClassName}"); } ``` Add a field to the template to hold the types: ```csharp private readonly List _typesToRegister = new(); ``` The `.tt` file can then be updated to make use of these types: ```csharp namespace <#= Namespace #> { public class <#= ClassName #> { public void DoRegistrations() { <# foreach (var type in _typesToRegister) { #> DoRegistration(typeof(<#= UseType(type) #>)); <# } #> } public void DoRegistration(Type typeToRegister) { // Custom registration logic here... } } } ``` ## Publishing an event Publishing of events must be done in the overridden `BeforeTemplateExecution()` method. Publishing from a template constructor will not work reliably because the software factory instantiates templates in an undefined order meaning that event subscribers may not have been instantiated yet and thus will not receive the published event. If not done already, `override` the `BeforeTemplateExecution()` method and publish from inside of it: ```csharp public override void BeforeTemplateExecution() { base.BeforeTemplateExecution(); ExecutionContext.EventDispatcher.Publish(new RegistrationRequest(this)); } ``` ## Conclusion When the above event is published, the subscriber adds its type to a list and a template's dependencies which later during the software factory execution is used when generating the template. --- # Software Factory Extensions Software Factory Extensions allow hooking into any of the various Software Execution phases to perform additional arbitrary actions which wouldn't make sense to be performed by a particular template. Examples of use cases for Software Factory extensions include (but are not limited to) post processing which needs to be performed after all templates have been executed or perhaps manipulating templates from other modules. Factory Extensions are powerful tools in Intent Architect that allow you to: - Manipulate code generated by other templates - Add cross-cutting concerns across multiple modules - Execute external processes during code generation - Register metadata providers and services - Coordinate complex multi-template scenarios This article assumes that you have a Module Builder Intent Architect application already set up, please refer to our [Create Module](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/01-create-a-template-introduction/create-a-template-introduction.md) article for details on how to make a Module Building Intent Architect application. ## Creating a Software Factory Extension Creating a Software Factory extension is done through the Module Builder by using the _New Factory Extension_ context menu option: ![New Software Factory context menu option](images/context-menu-option.png) Choose a name for the Factory Extension which completes the work needed inside the Module Builder designer as there is nothing else which can be configured: ![Named Factory Extension in the designer](images/named-factory-extension-in-designer.png) Run the Software Factory, apply changes and then open the generated file in Visual Studio. Factory Extensions always have the same boilerplate content: ```csharp [IntentManaged(Mode.Fully, Body = Mode.Merge)] public class MyFactoryExtension : FactoryExtensionBase { public override string Id => "ExtensionExample.MyFactoryExtension"; [IntentManaged(Mode.Ignore)] public override int Order => 0; /// /// This is an example override which would extend the /// phase of the Software Factory execution. /// See for all available overrides. /// /// /// It is safe to update or delete this method. /// protected override void OnAfterTemplateRegistrations(IApplication application) { // Your custom logic here. } /// /// This is an example override which would extend the /// phase of the Software Factory execution. /// See for all available overrides. /// /// /// It is safe to update or delete this method. /// protected override void OnBeforeTemplateExecution(IApplication application) { // Your custom logic here. } } ``` As noted in the comments above each of the methods, they are merely examples of overriding the relevant base methods. If you don't need one or both of these overrides, it is safe to delete the methods. ## Understanding the Execution Lifecycle Factory Extensions can hook into various phases of the Software Factory execution. Each overridden method is called during a particular phase of the Software Factory execution, the following is a list of the all phases which can be hooked into in their execution order: | Method Name | Description | |-------------------------------|-------------| | OnStart | Called once the Software Factory start up is complete. | | OnBeforeMetadataLoad | Called before metadata loading commences. Typically used to register custom metadata providers. | | OnAfterMetadataLoad | Called after metadata loading is complete. | | OnBeforeTemplateRegistrations | Called before template registration and instantiation (construction) is performed. | | OnAfterTemplateRegistrations | Called after template registration and instantiation (construction) has been completed. This is the most commonly overridden method for Factory Extensions. | | OnBeforeTemplateExecution | Called before the `RunTemplate` is called on all template. | | OnAfterTemplateExecution | Called after the `RunTemplate` called has been completed on all templates. | | OnBeforeCommitChanges | Called immediately before "Changes" view is presented in the Software Factory window. | | OnAfterCommitChanges | Called after the user has pressed "Apply" on the Software Factory window and all confirmed changes have been committed to the file system. | Here's a comprehensive example showing all available lifecycle hooks: ```csharp public class MyFactoryExtension : FactoryExtensionBase { public override string Id => "MyModule.MyFactoryExtension"; public override int Order => 0; // Controls execution order protected override void OnStart(IApplication application) { // Called once Software Factory startup is complete } protected override void OnBeforeMetadataLoad(IApplication application) { // Called before metadata loading - register custom providers here } protected override void OnAfterMetadataLoad(IApplication application) { // Called after all metadata is loaded } protected override void OnBeforeTemplateRegistrations(IApplication application) { // Called before templates are registered and instantiated } protected override void OnAfterTemplateRegistrations(IApplication application) { // Called after all templates are instantiated // MOST COMMONLY USED - templates are available for manipulation } protected override void OnBeforeTemplateExecution(IApplication application) { // Called before templates start executing // COMMONLY USED - apply distributed events changes here } protected override void OnAfterTemplateExecution(IApplication application) { // Called after all templates have executed } protected override void OnBeforeCommitChanges(IApplication application) { // Called before the Changes view is presented } protected override void OnAfterCommitChanges(IApplication application) { // Called after user applies changes to the file system } } ``` ## Template Discovery Patterns ### Understanding Template Discovery Methods Factory Extensions can discover templates using two primary approaches: 1. **TemplateId-based discovery** - Specific and precise 2. **Role-based discovery** - Flexible and decoupled ### TemplateId-Based Discovery Use specific Template IDs when you need to target exact templates: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { // Find specific template by exact ID var entityTemplate = application.FindTemplateInstance("Intent.Entities.DomainEntity"); // Find all instances of a specific template ID var commandTemplates = application.FindTemplateInstances("Intent.Application.MediatR.CommandModels"); // Find template for specific model instance if (application.TryGetTemplate("Intent.Entities.DomainEntity", someEntityModel, out var specificEntityTemplate)) { // Work with the specific template instance } } ``` ### Role-Based Discovery Use roles for flexible, decoupled template discovery: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { // Find templates by role - more flexible than TemplateId var allEntities = application.FindTemplateInstances("Domain.Entity"); var allRepositories = application.FindTemplateInstances("Domain.Repository"); var allControllers = application.FindTemplateInstances("Application.Controller"); // Find templates with hierarchical roles var auditableEntities = application.FindTemplateInstances("Domain.Entity.Auditable"); var securedControllers = application.FindTemplateInstances("Application.Controller.Secured"); // Broad role matching - finds all templates starting with "Domain" var allDomainTemplates = application.FindTemplateInstances("Domain"); } ``` > [!TIP] > > To work out the Template Id of a template you want to be able to find, either refer to the `TemplateId` field in the source code of the template or alternatively if it's a C# file you can open a file generated by the template and copy the value of the `IntentTemplate` assembly attribute at the top of the file. ### Advanced Template Filtering Combine discovery methods with sophisticated filtering: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { // Find templates using multiple criteria var aggregateRootRepositories = application.FindTemplateInstances("Domain.Repository") .OfType() .Where(t => t.TryGetModel(out var named) && named.Name.EndsWith("AggregateRoot")); // Filter by model stereotypes var cacheableServices = application.FindTemplateInstances("Application.Service") .Where(template => template.TryGetModel(out var model) && model.HasStereotype("Cacheable")); // Filter by template configuration var publicControllers = application.FindTemplateInstances("Application.Controller") .Where(template => !template.TryGetModel(out var model) || !model.HasStereotype("RequiresAuth")); } ``` ## Examples ### Creating a Factory Extension to manipulate files generated from other modules For cases where you want your module to be able to manipulate content generated by templates in other modules, most Intent Architect authored templates use the C# File Builder which allows for easy manipulation of the file without requiring direct dependencies on the template's .NET assembly. These template instances can be found and manipulated from within Software Factory extensions. For this example we will create a Factory Extension which finds all template instances which generate CQRS commands (from the `Intent.Application.MediatR` module), read a stereotype value off their model and if present then add an additional attribute to the generated class. Begin by [creating a new Factory Extension](#creating-a-software-factory-extension), giving it a name, applying the Software Changes and then opening it inside of your IDE. In our Factory Extension we will need to ensure we have overridden the `OnAfterTemplateRegistrations` method and change its content to the following: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var templates = application.FindTemplateInstances("Intent.Application.MediatR.CommandModels"); foreach (var template in templates) { if (template.TryGetModel(out var hasStereotypes)) { throw new Exception("TryGetModel returned false"); } if (hasStereotypes.HasStereotype("StereotypeNameOrId")) { var stereotypeValue = hasStereotypes.GetStereotypeProperty("StereotypeNameOrId", "StereotypePropertyName"); template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault() ?? throw new Exception("Could not find class on file"); @class.AddAttribute($"MyCustomAttribute(\"{stereotypeValue}\")"); }); } } } ``` In the above example we start with the following line: ```csharp var templates = application.FindTemplateInstances("Intent.Application.MediatR.CommandModels"); ``` This retrieves all the template instances which generate `Command`s, the `"Intent.Application.MediatR.CommandModels"` argument allows specifying that we only want template instances for that Template ID. We use the generic type argument of `ICSharpFileBuilderTemplate` which will cast all templates instances to this type. When iterating over each template we: - Use the `TryGetModel` method with a generic type argument of `IHasStereotypes` to try get the model for the template and we cast it to an `IHasStereotypes` which will allow us to read the stereotypes off the model without us needing to reference a NuGet package with the specific model type. - Check if the appropriate stereotype is applied. - Read a property off the stereotype using the generic type argument of `string` to convert it to this type. - The `CSharpFile.OnBuild` method is called and in which we can then do anything on the file in the same way as when we're authoring the template in its own constructor. - Find the class on the template. - Add a custom attribute to the class. If you build and install the module, it will now update commands to add this attribute. ### Creating a Factory Extension to run an external program For this example we will create a Factory Extension which runs the following command: ```cmd npm install ``` Begin by [creating a new Factory Extension](#creating-a-software-factory-extension), giving it a name, applying the Software Changes and then opening it inside of your IDE. In our Factory Extension we will need to ensure we have overridden the `OnAfterCommitChanges` method and change its content to the following: ```csharp protected override void OnAfterCommitChanges(IApplication application) { try { var cmd = new Process { StartInfo = { FileName = "cmd.exe", RedirectStandardInput = true, RedirectStandardOutput = true, CreateNoWindow = false, UseShellExecute = false, WorkingDirectory = Path.GetFullPath(application.RootLocation) } }; cmd.Start(); cmd.StandardInput.WriteLine("npm install"); cmd.StandardInput.Flush(); cmd.StandardInput.Close(); var output = cmd.StandardOutput.ReadToEnd(); Logging.Log.Info(output); } catch (Exception e) { Logging.Log.Failure($@"Failed to execute: ""npm install"", Reason: {e.Message}"); } } ``` Install your Module to your Test Application in Intent Architect. Follow these [steps](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/03-install-and-run-the-module/install-and-run-the-module.md#install-the-module) if you are not sure how. Run the Software Factory, click on the Apply button and then observe the following at the end of the process in the console output: ![Complete](images/software-factory-execution-complete.png) ## Advanced Template Manipulation ### Working with C# File Builder Templates The most common use case for Factory Extensions is manipulating templates that use the [C# File Builder System](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/csharp-file-builder-system/csharp-file-builder-system.md): ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { // Find all entity templates using role-based discovery var entityTemplates = application.FindTemplateInstances("Domain.Entity"); foreach (var template in entityTemplates) { // Use OnBuild to modify the generated code structure template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class == null) return; // Add auditing interface if (!@class.Interfaces.Any(i => i.Contains("IAuditable"))) { @class.AddInterface("IAuditable"); } // Add auditing properties if they don't exist if (!@class.Properties.Any(p => p.Name == "CreatedAt")) { @class.AddProperty("DateTime", "CreatedAt"); @class.AddProperty("string", "CreatedBy"); @class.AddProperty("DateTime?", "UpdatedAt"); @class.AddProperty("string", "UpdatedBy"); } // Add auditing using directive file.AddUsing("System"); }); } } ``` ### Working with Template Models Factory Extensions can access and query template models without direct dependencies: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var commandTemplates = application.FindTemplateInstances("Application.Command"); foreach (var template in commandTemplates) { // Try to get the model as a specific interface if (template.TryGetModel(out var stereotypedModel)) { // Check for custom stereotypes if (stereotypedModel.HasStereotype("Cacheable")) { var cacheKey = stereotypedModel.GetStereotypeProperty("Cacheable", "CacheKey"); var expiration = stereotypedModel.GetStereotypeProperty("Cacheable", "ExpirationMinutes"); template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); @class?.AddAttribute($"[Cacheable(\"{cacheKey}\", {expiration})]"); }); } } } } ``` ## Cross-Module Coordination Patterns Factory Extensions excel at coordinating activities across multiple templates and modules. Common coordination patterns include: - **Entity Framework Configuration Coordination** - Automatically apply query filters, configure entity mappings, and set up audit properties based on entity stereotypes - **Service Registration Coordination** - Collect services from multiple modules (repositories, application services, domain services) and register them in dependency injection containers - **MediatR Handler Registration** - Automatically register command and query handlers with MediatR using assembly scanning - **API Configuration** - Coordinate OpenAPI documentation, versioning, and security policies across controllers - **Database Migration Coordination** - Generate migration scripts based on entity changes across multiple domain modules - **Event Bus Configuration** - Register domain event handlers and configure distributed event publishing - **Authentication & Authorization** - Apply security policies consistently across controllers based on model stereotypes - **Caching Strategy Coordination** - Configure caching policies and cache invalidation across service layers - **Validation Pipeline Setup** - Register FluentValidation validators and configure validation behaviors - **Logging and Monitoring** - Apply structured logging and telemetry across all service layers ## Advanced Patterns and Real-World Use Cases ### 1. Cross-Cutting Security Concerns Add security attributes to all controllers based on model configuration: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var controllerTemplates = application.FindTemplateInstances("Application.Controller"); foreach (var template in controllerTemplates) { if (template.TryGetModel(out var model)) { template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class == null) return; // Add authorization based on stereotypes if (model.HasStereotype("RequiresAuth")) { @class.AddAttribute("[Authorize]"); file.AddUsing("Microsoft.AspNetCore.Authorization"); } if (model.HasStereotype("AdminOnly")) { @class.AddAttribute("[Authorize(Policy = \"AdminOnly\")]"); } if (model.HasStereotype("RateLimit")) { var limit = model.GetStereotypeProperty("RateLimit", "RequestsPerMinute"); @class.AddAttribute($"[RateLimit({limit})]"); } }); } } } ``` ### 2. Automatic Validation Integration Add FluentValidation support to all commands and queries: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var commandTemplates = application.FindTemplateInstances("Application.Command"); var queryTemplates = application.FindTemplateInstances("Application.Query"); var validatorTemplates = new List(); foreach (var template in commandTemplates.Concat(queryTemplates)) { if (template.TryGetModel(out var named)) { // Create validator template for each command/query var validatorTemplate = CreateValidatorTemplate(named.Name, template); validatorTemplates.Add(validatorTemplate); // Add validation behavior to the original template template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); @class?.AddAttribute("[ValidateRequest]"); }); } } // Register validators in DI RegisterValidators(application, validatorTemplates); } ``` ### 3. Distributed Events Integration Automatically publish domain events from aggregate roots: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var entityTemplates = application.FindTemplateInstances("Domain.Entity") .Where(t => t.TryGetModel(out var model) && model.HasStereotype("AggregateRoot")); foreach (var template in entityTemplates) { template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class == null) return; // Add domain events property @class.AddProperty("List", "_domainEvents", prop => { prop.Private().WithInitialValue("new List()"); }); @class.AddProperty("IReadOnlyCollection", "DomainEvents", prop => { prop.Getter.WithExpressionBody("_domainEvents.AsReadOnly()"); }); // Add methods for domain event handling @class.AddMethod("void", "AddDomainEvent", method => { method.Protected() .AddParameter("DomainEvent", "domainEvent"); method.AddStatement("_domainEvents.Add(domainEvent);"); }); @class.AddMethod("void", "ClearDomainEvents", method => { method.Protected(); method.AddStatement("_domainEvents.Clear();"); }); file.AddUsing("System.Collections.Generic"); }); } } ``` ## Integration with File Builder System ### Complex Code Manipulation Factory Extensions can perform sophisticated manipulations of the [C# File Builder](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/csharp-file-builder-system/csharp-file-builder-system.md) objects: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var entityTemplates = application.FindTemplateInstances("Domain.Entity"); foreach (var template in entityTemplates) { AddValidationLogic(template); AddEqualsAndHashCode(template); AddToStringMethod(template); } } private void AddValidationLogic(ICSharpFileBuilderTemplate template) { template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class == null) return; // Add validation method @class.AddMethod("ValidationResult", "Validate", method => { method.AddStatement("var result = new ValidationResult();"); // Add validation for each property foreach (var property in @class.Properties.Where(p => p.HasGetter && p.HasSetter)) { if (property.Type == "string") { method.AddStatement($"if (string.IsNullOrWhiteSpace({property.Name}))"); method.AddStatement($" result.AddError(\"{property.Name} is required\");"); } } method.AddStatement("return result;"); }); }); } private void AddEqualsAndHashCode(ICSharpFileBuilderTemplate template) { template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class == null) return; var keyProperties = @class.Properties.Where(p => p.Name.EndsWith("Id")).ToList(); if (!keyProperties.Any()) return; // Add Equals method @class.AddMethod("bool", "Equals", method => { method.Override(); method.AddParameter("object", "obj"); method.AddStatement($"return obj is {@class.Name} other && {string.Join(" && ", keyProperties.Select(p => $"{p.Name} == other.{p.Name}"))};"); }); // Add GetHashCode method @class.AddMethod("int", "GetHashCode", method => { method.Override(); var hashCode = string.Join(" ^ ", keyProperties.Select(p => $"{p.Name}.GetHashCode()")); method.AddStatement($"return {hashCode};"); }); }); } ``` ## External Process Integration ### Running Build Tools After Generation Execute external tools as part of the generation process: ```csharp protected override void OnAfterCommitChanges(IApplication application) { // Run TypeScript compilation RunTypeScriptCompilation(application); // Generate OpenAPI documentation GenerateOpenApiDocs(application); // Run code formatting FormatGeneratedCode(application); } private void RunTypeScriptCompilation(IApplication application) { try { var clientPath = Path.Combine(application.RootLocation, "ClientApp"); if (!Directory.Exists(clientPath)) return; var process = new Process { StartInfo = new ProcessStartInfo { FileName = "npm", Arguments = "run build", WorkingDirectory = clientPath, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; process.Start(); var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); process.WaitForExit(); if (process.ExitCode != 0) { Logging.Log.Warning($"TypeScript compilation warnings/errors: {error}"); } else { Logging.Log.Info("TypeScript compilation completed successfully"); } } catch (Exception ex) { Logging.Log.Warning($"Failed to run TypeScript compilation: {ex.Message}"); } } ``` ## Best Practices and Patterns ### 1. Order-Dependent Operations Control execution sequence using the `Order` property: ```csharp public class SecurityExtension : FactoryExtensionBase { public override int Order => -50; // Execute early for security setup } public class ValidationExtension : FactoryExtensionBase { public override int Order => 0; // Default order } public class DocumentationExtension : FactoryExtensionBase { public override int Order => 50; // Execute late for final documentation } ``` ### 2. Safe Template Access Always verify template availability and types: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var templates = application.FindTemplateInstances("Domain.Entity"); foreach (var template in templates) { // Verify the template is what we expect if (template?.CSharpFile?.Classes?.Any() != true) { Logging.Log.Warning($"Skipping invalid template: {template?.Id}"); continue; } // Safe manipulation with error handling try { template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); @class?.AddProperty("DateTime", "LastModified"); }); } catch (Exception ex) { Logging.Log.Error($"Failed to modify template {template.Id}: {ex.Message}"); } } } ``` ### 3. Configuration-Driven Behavior Use application settings to control Factory Extension behavior: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var settings = application.Settings.GetMyModuleSettings(); if (settings.EnableAuditing()) { AddAuditingSupport(application); } if (settings.EnableCaching()) { AddCachingSupport(application); } if (settings.EnableSecurity()) { AddSecuritySupport(application); } } ``` ### 4. Conditional Template Manipulation Only modify templates that meet specific criteria: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var entityTemplates = application.FindTemplateInstances("Domain.Entity"); foreach (var template in entityTemplates) { // Only modify templates that have specific stereotypes if (!template.TryGetModel(out var model) || !model.HasStereotype("Auditable")) { continue; } // Check if template already has auditing properties var hasAuditingAlready = false; template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); hasAuditingAlready = @class?.Properties.Any(p => p.Name == "CreatedAt") == true; }); if (!hasAuditingAlready) { AddAuditingProperties(template); } } } ``` ## Error Handling and Debugging ### Common Issues - **Templates Not Found**: Ensure you're using the correct template IDs or roles and that templates are registered before your extension runs. - **Null Reference Exceptions**: Always check for null values when accessing template properties and File Builder objects. - **Order Dependencies**: Use the `Order` property to ensure your extension runs at the right time. - **Template Model Access**: Use `TryGetModel()` to safely access template models without causing exceptions. ### Debugging Tips - **Review generated output**: Always check the actual generated C# code to understand what your Factory Extension is producing. - **Use the debugger**: You can debug your Factory Extensions and inspect the real-time state of templates and File Builder objects using the [.NET Debugger](https://docs.intentarchitect.com/docs-md/module-building/debugging-modules/debugging-modules.md). - **Log template discovery**: Use `Logging.Log.Info()` to track which templates are found and processed by your extension. - **Verify execution order**: Log entry and exit points of your extension methods to understand the execution flow. Factory Extensions provide a powerful mechanism for implementing cross-cutting concerns and coordinating complex code generation scenarios. Use them strategically to build sophisticated, maintainable module ecosystems that work together seamlessly. --- # About Stereotype Definitions Stereotypes can only be created and applied in Intent Architect Designers if their `Stereotype Definitions` are accessible. These Definitions need to be created first and can often be found when a [Module](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) is installed in your Application. They offer a blueprint that Intent Architect can recognize when trying to apply them. ![How the base properties of Stereotype Definitions look like](images/stereotype-definition-base-properties.png) | Property | Value | Description | |--------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Target Mode | Elements of Type | Only allows the Stereotype to be applied on Elements found in the `Target` property. | | | Elements that reference | Only allows the Stereotype to be applied on Elements' attributes/fields or associations that reference the Elements found in the `Target` property. | | | All elements | No constraints on where the Stereotype can be applied. | | Target | Elements found in Designer | The Element types upon which the Stereotype Definition may be applied. | | Apply mode | Manually | The user has to explicitly apply a Stereotype on a `Target` Element. | | | On element created | When a `Target` Element is created, the Stereotype is automatically applied but it can be removed. | | | Always | The Stereotype exists permanently on any `Target` Element and cannot be removed (as long as the Stereotype Definition exists). | | Icon | Formatted Text | [Change the icon](https://docs.intentarchitect.com/docs-md/application-development/user-interface/how-to-use-the-change-icon-dialogue/how-to-use-the-change-icon-dialogue.md) for the Stereotype by pressing the button with an ellipses. | | Display Icon | Check box | When selected, the Stereotype's icon will appear in the tree node and visual modellers for elements on which it is applied. | | Display Text | Function | Using the Javascript language, write a formatted text string that will be used by the Designer to add a Display Text where available. Example: `` return `[${properties["Router Link"].value}]` ``. | | Order | Number | Each Stereotype can be ordered according to this numerical value (in ascending order) in the Properties panel located on the right hand side in Intent Architect within a designer. | ## Stereotype Properties Stereotypes Definitions allow any number of Properties to be added to them. Property names must be unique per Stereotype Definition. By default Stereotype Definition Properties have their `Control Type` set to `Text Box` which allows capturing of free text, but there are many other `Control Types` which can be selected. ![How the properties of Stereotype Definition Properties look like](images/stereotype-definition-property-properties.png) | Property | Type | Description | |----------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Name | Text | Specify a friendly name for the Property, spaces and punctuation are allowed. | | Control Type | Type | Allows the Property to capture a single line of free text that represents a string. See [here](#control-types) for more detail. | | Default Value | Control Type Value | Specify a default value in for this Property depending on the Control Type specified. | | Placeholder | Text | Specify a description for this Property's input field which a user of the Stereotype can see which describes what is expected. | | Is Active Function | Javascript | Write a script that returns `true`/`false` in order to determine if the Property will be visible/hidden or not. By default this will be Active. | | Is Required Function | Javascript | Write a script that returns `true`/`false` in order to determine if the a value is required for this Property which is validated by Intent Architect. By default this will not be required. | ### Control Types #### Text Box Allows the Property to capture a single line of free text that represents a string. ![Property of type Text Box featured on a Stereotype](images/control-type-textbox-example.png) #### Number Allows only the capture of a Numerical integer value for this Property. ![Property of type Number featured on a Stereotype](images/control-type-number-example.png) #### Checkbox Presents a checkbox to capture a boolean value for the Property. ![Property of type Checkbox featured on a Stereotype](images/control-type-checkbox-example.png) #### Text Area Allows the Property to capture multiple-lines of free text that represents a string. ![Property of type Text Area featured on a Stereotype](images/control-type-textarea-example.png) #### Select/Multi-select ##### Options Provides an additional `Options` field where the Developer can specify explicit options which can be selected for that Stereotype Property. ![Example of the Option Select Property type](images/control-type-select-options-example-definition.png) ![Property of type Select Options featured on a Stereotype](images/control-type-select-options-example.png) ##### Lookup element Provides an additional `Lookup types` field where the Developer can select Element types in order to make the Property a dropdown control that features instances of those Element types. ![Example of the Element Select Property type](images/control-type-select-lookup-element-example-definition.png) ![Property of type Select Element Lookup featured on a Stereotype](images/control-type-select-lookup-element-example.png) ##### Lookup Children Provides additional `Root Type Function` and `Lookup types` fields where a Developer can determine which Element type's "child elements" can be selected. Use the `Lookup types` to select the Child-Element to be selected and specify the function to evaluate how to determine the Parent-Element in the `Root Type Function` field. Example of selecting `Enum Literals` from a previously selected `Enum`: ![Example of the Element Select Property type](images/control-type-select-child-element-example-definition-1.png) ![Example of the Child Element Select Property type](images/control-type-select-child-element-example-definition-2.png) Root Type Function expression: ```js return properties['Enum Selection'].value; ``` ![Property of type Select Child Element Lookup featured on a Stereotype](images/control-type-select-child-element-example.png) #### Javascript Function Exposes the Property as a Javascript type field where the Developer can use the built in Javascript editor to define a script which can be leveraged by other Stereotype Properties or Designer elements. ![Property of type Javascript featured on a Stereotype](images/control-type-javascript-example.png) #### Icon Exposes the Property as a standard Icon selector which acts in the same manner as the `Icon` property found on the Stereotype Definition itself. ![Property of type Icon featured on a Stereotype](images/control-type-icon-example.png) ## See also - [How to use Stereotypes](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/how-to-use-stereotypes/how-to-use-stereotypes.md) --- # How to use Stereotypes This how-to will explain how to define [Stereotypes](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-stereotypes/about-stereotypes.md) that are contained in your [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) and how to apply them to your applications. Stereotypes defined in Modules are useable in the following ways: - During use of the Designers in Intent Architect: Stereotypes can be manually or automatically applied to Elements by everyday users. - During module authoring in the IDE: Intent Architect generates "APIs" for Stereotypes for easily retrieving them and their properties for use in Templates and other aspects of module building. ## Create a Stereotype Definition [Stereotype Definitions](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/about-stereotype-definitions/about-stereotype-definitions.md) are used to creates "types" of Stereotypes which can then be applied to Element(s) (such as a Class in a Domain). > [!NOTE] > This how-to requires that the Domain Module's metadata is [installed](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/04-create-a-files-per-model-template/create-a-files-per-model-template.md#install-the-domain-metadata). Although this how-to uses the Domain Designer, the same principles can be applied to the use of any Designer. Create or open a Module Builder application (such as the one created in [Create a Module and Template](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/02-create-the-module-and-a-template/create-the-module-and-a-template.md)) in Intent Architect, open the Module Builder Designer, right-click on the package in the designer and select `New Stereotype-Definition`. In the Properties pane on the right of the screen, ensure that: - `Name` has a value (eg: `Entity`). - `Target Mode` is set to `Elements of Type`. - `Targets` has `Class`. - `Apply Mode` is set to `Manually`. - `Icon` is specified to be a Boxed Character "E" on a Black background `E|#0`. - `Display Icon` is should be `return true;`.

> [!NOTE] > During the Software Factory Execution, it generated a `ClassModelExtensions.cs` which contains an "API" for being able to more easily use the Stereotype during module building, such as for Templates. Use of this will be discussed in more detail [further below](#query-stereotypes-from-templates). ## Configure Module packaging for Stereotype Definitions Open the Module application in Intent Architect where your Stereotype Definition is located, then open the `Module Builder` Designer and click on the package. In the Properties pane on the right of the screen: - Check `Include in Module`. - For `Reference in Designer` ensure that `Domain` is included.

## Add properties to a Stereotype Properties on a Stereotype are additional "fields" (of metadata) which can be configured on an applied Stereotype. Open the Module application in Intent Architect containing the Stereotype Definition you created above and then open the `Module Builder` Designer. Right-click on the `Entity` Stereotype Definition. - Select `Add Property`. - Set the Name to `Change Detection`. - Select the Type `Checkbox`.

## Query Stereotypes from Templates Open the Module application in Intent Architect where your [Stereotype Definition](#add-properties-to-a-stereotype) is located, then open the `Module Builder` Designer. Add a `C# Template` called `EntityClass` with the following properties: - `Type` set to `File Per Model` - `Templating Method` set to `T4 Templating` - `Designer` set to `Domain` - `Model Type` set to `Class` Now execute the Software Factory, add the following content to the generated `EntityClassTemplate.tt` file: ```cs <#@ template language="C#" inherits="CSharpTemplateBase" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Linq" #> <#@ import namespace="Intent.Modules.Common" #> <#@ import namespace="Intent.Modules.Common.Templates" #> <#@ import namespace="Intent.Modules.Common.CSharp.Templates" #> <#@ import namespace="Intent.Templates" #> <#@ import namespace="Intent.Metadata.Models" #> <#@ import namespace="NewModule.Api" #> using System; using System.Collections.Generic; [assembly: DefaultIntentManaged(Mode.Fully)] namespace <#= Namespace #> { public class <#= ClassName #> { <# foreach(var attribute in Model.Attributes) { #> public <#= GetTypeName(attribute) #> <#= attribute.Name.ToPascalCase() #> { get; set; } <# } #> <# foreach(var associationEnd in Model.AssociatedClasses.Where(x => x.IsNavigable)) { #> public <#= GetTypeName(associationEnd) #> <#= associationEnd.Name.ToPascalCase() #> { get; set; } <# } #> } } ``` > [!NOTE] > Your code Solution (the project generated by the Software Factory from the Module application) should contain an `API` folder with a file called `ClassModelExtensions.cs`. > > It contains a Class with Extension Methods for performing queries against a Class Element with regards to the `Entity` Stereotype. The Module Builder generates these extension methods for each of thee entries specified in the `Targets` property of the Stereotype Definition. A Class Element can be queried for the presence of the `Entity` Stereotype. This can be invoked within your Template code: ```cs bool hasEntityStereotype = Model.HasEntity(); ``` The `Change Detection` Property of the `Entity` Stereotype can also be queried by invoking it within your Template code: ```cs bool hasChangeDetection = Model.GetEntity().ChangeDetection(); ``` The example below uses these extension methods in the `EntityClass` Template to determine whether or not it should generate additional properties: ```cs <# if (Model.HasEntity() && Model.GetEntity().ChangeDetection()) { #> public DateTime CreatedDate { get; set; } public DateTime LastUpdatedDate { get; set; } public string UserName { get; set; } <# } #> ``` > [!IMPORTANT] > In the code example above there is an `import` directive to reference the namespace of the `ClassModelExtensions` class. > > In this example it would be `<#@ import namespace="NewModule.Api" #>` After applying the example above, the complete Template should be as follows: ```cs <#@ template language="C#" inherits="CSharpTemplateBase" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Linq" #> <#@ import namespace="Intent.Modules.Common" #> <#@ import namespace="Intent.Modules.Common.Templates" #> <#@ import namespace="Intent.Modules.Common.CSharp.Templates" #> <#@ import namespace="Intent.Templates" #> <#@ import namespace="Intent.Metadata.Models" #> <#@ import namespace="NewModule.Api" #> using System; using System.Collections.Generic; [assembly: DefaultIntentManaged(Mode.Fully)] namespace <#= Namespace #> { public class <#= ClassName #> { <# if (Model.HasEntity() && Model.GetEntity().ChangeDetection()) { #> public DateTime CreatedDate { get; set; } public DateTime LastUpdatedDate { get; set; } public string UserName { get; set; } <# } #> <# foreach(var attribute in Model.Attributes) { #> public <#= GetTypeName(attribute) #> <#= attribute.Name.ToPascalCase() #> { get; set; } <# } #> <# foreach(var associationEnd in Model.AssociatedClasses.Where(x => x.IsNavigable)) { #> public <#= GetTypeName(associationEnd) #> <#= associationEnd.Name.ToPascalCase() #> { get; set; } <# } #> } } ``` ## Apply a Stereotype (manually) Create or open an `Standard ASP.NET Core Web Application` application in Intent Architect. [Install the module](https://docs.intentarchitect.com/docs-md/module-building/tutorial-create-a-template/03-install-and-run-the-module/install-and-run-the-module.md#install-the-module) that contains the [newly created Stereotype Definition](#create-a-stereotype-definition). Open the Domain Designer and create or select a Class Element. Right-click on the Element and select Add Stereotype. A popup dialog appears with a list of Stereotypes Definitions whose `Targets` property contains this `Element` type. Select your `Entity` Stereotype. Locate the Stereotype in the Properties pane on the right and check the checkbox next to "Change Detection".

Save the Designer and run the Software Factory. See that with the `Change Detection` checkbox selected, it has generated the additional properties on the C# class. If you un-selected the checkbox and ran the Software Factory again, you would see that it would no longer generate those additional properties. ![The Diff when the "Change Detection" is checked](images/change-detection-diff-result.png) ## Summary We have learnt how to create a Stereotype Definition in a module and make that Stereotype available for use in an Application as well as read the Stereotype Definition's value from within a template. --- # About C# Templates Intent Architect has first class support for generation and code management of C# files. Out of the box it has [many features](#convenience-and-utility-features-for-c-file-generation) to enable painless authoring of C# templates, for example managing things like namespaces, class names, using clauses, automatic resolution of type names, etc. > [!NOTE] > At a fundamental level all Templates in Intent Architect simply just produce text. If you look at the code-behind file for a `.tt` file, you will see that it's using a `StringBuilder` on which `.ToString()` is ultimately called. The easiest way to create a C# Template is by using the [Module Builder](https://docs.intentarchitect.com/docs-md/module-building/about-the-module-builder/about-the-module-builder.md) and ensuring you have the `Intent.ModuleBuilder.CSharp` Module installed. If you selected the `Module Builder - C#` component during the `Create new Module` wizard it will already have been installed, otherwise it can be added at any time through the [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md) screen for your [Application](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-applications/about-applications.md). When the Module is installed, inside the Module Builder [Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/about-designers/about-designers.md) you will then have the option to create a `New C# Template`. ![New C# template option](images/new-csharp-template-option.png) ## C# Template type overviews The Module Builder has three available options for C# Template types, they are briefly described in the following table and more detail on each is covered later in this article. | Type | Brief description | | --------------------------------- | -------------------------------------------------- | | [Single File](#single-file) | Single file is generated for the application. | | [File Per Model](#file-per-model) | A file is generated per model in your application. | | [Custom](#custom) | For advanced use cases. | ### Template Methods Intent Architect supports several templating technologies, the default (and recommended) approach is our `C# File Builder` method. ![Template Method Options](images/template-methods.png) > [!TIP] > For comprehensive coverage of the C# File Builder System, including advanced patterns, best practices, and integration techniques, see the [C# File Builder System Guide](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/csharp-file-builder-system/csharp-file-builder-system.md). ## Files generated during the Software Factory Execution of the Module Builder Regardless of the type selected, during the [Software Factory Execution](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-software-factory-execution/about-software-factory-execution.md) the Module Builder will always generate the following 2 files for each C# Template created in the Module Builder designer. ### 1. Template partial file | Generated file name | | -------------------------- | | `TemplatePartial.cs` | This file is the logic to render your c# file(s). On initial generation it contains the class constructor and the `DefineFileConfig` method. In this class you can control the output of the template or adjust its config. In the case of the `C# File Builder` the template, you would use the `CSharpFile` builder class to construct you output, using the various Apis it provides for constructing a C# file. Some Template Methods produce a 3rd file, which is the `View` aspect of the template, for example T4, would produce a `Template.tt` file. ### 2. Template registration file | Generated file name | | ------------------------------- | | `TemplateRegistration.cs` | Registration classes act as Factories for the Template types that are _registered_ using them. They will create one or more _instances_ of a given Template during the Software Factory Execution. > [!NOTE] > A key concept to understand about Intent Architect is that the Software Factory Execution will output a single file per registered template _instance_. A template may have none, a single or multiple _instances_ of it registered during Software Factory Execution. For example the [Single File](#single-file) C# Template Type will register a single instance of its template, while the [File Per Model](#file-per-model) C# Template Type will register an instance of its template for each occurrence of a model type in a Designer. ## Details of the C# Template types ### Single File There are two common use cases for a Single File C# Template: #### 1. Generation of files that act as an aggregation of models from a certain Designer For example, a Dependency Injection configuration class that registers up all the Services defined in the Services Designer. The generated [Template registration file](#2-template-registration-file) derives from `SingleFileListModelTemplateRegistration` and the code will register a single instance of the template. The registration class derives from `SingleFileListModelTemplateRegistration` which allows the amount of code to be kept simple and to a minimum. The generated [Template partial file](#1-template-partial-file)'s class will have a List of `TModel` as the generic type used to define the `model`. For both the template registration and partial files, the generated value for `TModel` is determined by the selected `Model Type` in the `Template Settings`: ![Template settings](images/template-settings.png) The `model` for this type of C# Template is essential as each registered template instance will have a separate output file generated with its name and content dependent upon details on the incoming `model`. #### 2. Generation of infrastructural files For example, `Startup.cs` in an ASP.NET Core project. The generated [Template registration file](#2-template-registration-file) derives from `SingleFileTemplateRegistration` and the code will register a single instance of the template. The registration class derives from `SingleFileTemplateRegistration` which allows the amount of code to be kept simple and to a minimum. The generated [Template partial file](#1-template-partial-file)'s class uses `object` for its generic type parameter with the assumption that there is no `model` to be used. ### File Per Model This C# Template type is for when you want a separate `.cs` file for each instance of a model in a Designer of a type, EG: domain entities, WebApi endpoint, DTO, etc. The generated [Template registration file](#2-template-registration-file) derives from `FilePerModelTemplateRegistration` and the code will register an instance of the template per model. The generated [Template partial file](#1-template-partial-file)'s class uses `TModel` for its generic type parameter. For both the template registration and partial files, the generated value for `TModel` is determined by the selected `Model Type` in the `Template Settings`: ![Template settings](images/template-settings.png) The `model` for this type of C# Template is essential as each registered template instance will have a separate output file generated with its name and content dependent upon details on the incoming `model`. ### Custom This C# Template type is used far less than the other types and is when the other types' template instance registration doesn't doesn't quite work for a particular use case. The Custom type allows the Developer to take control of the _registration_ process by invoking `RegisterTemplate()` (off of the `ITemplateInstanceRegistry registry` parameter) and creating _instances_ of the Template in question (which does not impose a Model type restriction like the other types). This Registration class derives from `ITemplateRegistration`. ## Template Configuration The Template configuration is specified in the [Template partial file](#1-template-partial-file), inside the `DefineFileConfig` method. In this method, the configuration of each instance can be set as literal values or determined by functions and string interpolation. The required values set in the configuration determine the Template's `ClassName` and `Namespace` properties. ![DefineFileConfig method example](images/csharp-template-config-method.png) ### The `ClassName` Property When working with C# templates, it is recommended that you use `<#= ClassName #>` for a class's name in the `.tt` file: ```csharp public class <#= ClassName #> { ... } ``` Intent Architect will then ensure it handles all the rules and edge cases for the naming of your class, including: - Applying PascalCase naming convention. - Removing invalid characters, like spaces or punctuation. ### The `Namespace` Property When working with C# templates, it is recommended that you use `<#= Namespace #>` to declare the file's namespace in the `.tt` file: ```csharp namespace <#= Namespace #> { ... } ``` The default Template configuration that uses `this.GetNamespace()` will automatically determine the `Namespace` of the class based on where the file output is created. See below for how to configure the Output Location. ### Configure the Output Location Ultimately, the output location of a Template instance is determined by two factors: #### 1. The `Template Output` location That is to say, under which folder or project the Template's `Template Output` is placed within the application. With C# Templates this is typically determined using the `Visual Studio` Designer. In the example below we can see that the `Template Output` for `Intent.AspNetCore.Startup` will be placed in the `MyApplication.Api` project: ![Template Output in Visual Studio](images/visual-studio-template-output.png) > [!TIP] > We can easily change where a Template's output will be created by dragging the `Template Output` element into a different folder or project. #### 2. The Template's `Default Location` configuration The `Default Location` is set relative to the `Template Output`'s location as described above. So for example use the `Default Location` of `Controllers` to place the Template inside the `Controllers` folder located in the `MyApplication.Api` project. > [!TIP] > The `Default Location`'s default of `this.GetFolderPath()` will respect any folders in the Designer that the `Model` element instance was created within. ## Convenience and utility features for C# file generation Through extensive experience of building templates for generating C# files, we have added many features which we consider essential to making the C# template authoring process a painless experience. ### Code Management A core feature of Intent Architect is [Code Management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md) where parts of a file are managed fully by Intent Architect while other parts in the same file are hand crafted and Intent Architect will leave those particular parts alone. For C# files, you can use [C# attributes](https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/attributes/) anywhere in the file to opt-out a particular element of code within a file from being overwritten during Software Factory Execution. For example you could place `[IntentManaged(Mode.Ignore)]` on a particular method and when Intent Architect sees this during Software Factory execution it will make sure to never change anything for it. ### Types in other Templates (dependencies) Intent Architect will automatically add required [using directives](https://docs.microsoft.com/dotnet/csharp/language-reference/keywords/using-directive) to generated C# files based on specified template dependencies when you use [`GetTypeName(...)`](https://docs.intentarchitect.com/docs-md/module-building/templates-general/resolving-type-names/resolving-type-names.md) (this can involve primitive types too like `string`). During Software Factory Execution, Intent Architect will [determine the namespaces](#the-namespace-property) of those other Template instances and add them as using statements. ### NuGet package dependencies Intent Architect will automatically install NuGet packages for projects based on specified NuGet package requirements in Templates. To specify a NuGet package dependency, use the `AddNugetDependency(...)` method in the constructor of the template in the [Template File](#1-template-partial-file): ```csharp partial class EntityBaseTemplate : CSharpTemplateBase { public EntityBaseTemplate(IOutputTarget outputTarget, object model = null) : base(TemplateId, outputTarget, model) { AddNugetDependency(NuGetPackages.MediatR); } } ``` And then at the root location of the Module's `.csproj`, create a class as follows: ```csharp public class NuGetPackages { public static INugetPackageInfo MediatR = new NugetPackageInfo("MediatR", "9.0.0"); } ``` > [!TIP] > While it's not necessary to create a separate static class like the `NuGetPackages` class above, it's highly recommended as a Module will often have multiple Templates using the same NuGet package. This will centralize the version management aspect of nuget packages and enable one to find all usages easily. > [!NOTE] > Using managed nuget dependencies, one can more easily install and upgrade the package version globally. Intent Architect will not automatically downgrade any managed nuget dependencies. ### GAC Assembly dependencies Intent Architect will automatically add [GAC](https://docs.microsoft.com/dotnet/framework/app-domains/gac) Assembly references to `.csproj` files based on specified requirements in Templates. To specify a GAC Assembly reference requirement, use the `AddAssemblyReference(...)` method in the constructor of the template in the [Template File](#1-template-partial-file): ```csharp partial class EntityBaseTemplate : CSharpTemplateBase { public EntityBaseTemplate(IOutputTarget outputTarget, object model = null) : base(TemplateId, outputTarget, model) { AddAssemblyReference(new GacAssemblyReference("System.Runtime.Serialization")); } } ``` --- # C# File Builder System The C# File Builder System is Intent Architect's primary method for generating and manipulating C# source code through templates. It provides a fluent, builder-pattern API that allows you to construct C# files programmatically using semantically meaningful methods that align with C# language constructs. ## What is the C# File Builder System? The C# File Builder System replaces traditional text-based templating approaches (like T4 templates) with a code-first builder pattern. Instead of writing string-based templates, you use strongly-typed C# code to construct your output files. This approach is similar to working with an [Abstract Syntax Tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree), where you build up the structure of your code programmatically rather than manipulating text strings. This provides a more maintainable, type-safe way to generate C# code that integrates seamlessly with other Intent Architect components like Factory Extensions. ### Why Use the File Builder System? - **Type Safety**: Unlike text-based templates, the builder system provides compile-time checking of your template logic. - **IntelliSense Support**: Full IDE support with auto-completion for all available methods and properties. - **Refactoring Safety**: Changes to your template logic are caught by the compiler rather than failing at runtime. - **Code Interrogation**: Other templates and Factory Extensions can inspect and modify the code being generated through the builder objects. - **Better Maintainability**: Complex generation logic is easier to understand and maintain when written as structured C# code. ## Core Concepts ### The `ICSharpFileBuilderTemplate` Interface When creating a C# Template, it is set to use the File Builder System by default. If not you would need to: 1. Set the **Templating Method** to `C# File Builder` in the Module Builder Designer 2. Your template class will automatically implement the `ICSharpFileBuilderTemplate` interface ![Template Method Selection](images/template-method-selection.png) The initial output will be as follows: ```csharp public partial class MyTemplate : CSharpTemplateBase, ICSharpFileBuilderTemplate { public const string TemplateId = "MyModule.MyTemplate"; public MyTemplate(IOutputTarget outputTarget, MyModel model) : base(TemplateId, outputTarget, model) { CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddClass($"{Model.Name}", @class => { // Configure the class using builder methods }); } [IntentManaged(Mode.Fully)] public CSharpFile CSharpFile { get; } [IntentManaged(Mode.Fully)] protected override CSharpFileConfig DefineFileConfig() { return CSharpFile.GetConfig(); } [IntentManaged(Mode.Fully)] public override string TransformText() { return CSharpFile.ToString(); } } ``` Or for templates whose names are suffixed with `Interface` it will be as follows: ```csharp public partial class MyTemplateInterface : CSharpTemplateBase, ICSharpFileBuilderTemplate { public const string TemplateId = "MyModule.MyTemplateInterface"; public MyTemplateInterface(IOutputTarget outputTarget, MyModel model) : base(TemplateId, outputTarget, model) { CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddInterface($"I{Model.Name}", @class => { // Configure the interface using builder methods }); } [IntentManaged(Mode.Fully)] public CSharpFile CSharpFile { get; } [IntentManaged(Mode.Fully)] protected override CSharpFileConfig DefineFileConfig() { return CSharpFile.GetConfig(); } [IntentManaged(Mode.Fully)] public override string TransformText() { return CSharpFile.ToString(); } } ``` ### The `CSharpFile` Object The `CSharpFile` object is the root of the builder hierarchy. It represents an entire C# source file and provides methods to add top-level constructs: ```csharp CSharpFile = new CSharpFile(namespace: "MyApp.Domain", relativeLocation: "Entities") .AddUsing("System") .AddUsing("System.Collections.Generic") .AddClass("Customer", @class => { // Class configuration }) .ImplementsInterface("ICustomerService", @interface => { // Interface configuration }); ``` ## Building Classes Classes are the most common construct you'll build when using the File Builder System. The builder provides extensive methods for configuring class members: ### Basic Class Structure Sample Builder Code: ```csharp .AddClass("Customer", @class => { @class .WithBaseType("EntityBase") .ImplementsInterface("ICustomer") .AddProperty("string", "FirstName") .AddProperty("string", "LastName") .AddProperty("DateTime", "CreatedDate"); }) ``` Example Output: ```csharp public class Customer : EntityBase, ICustomer { public string FirstName { get; set; } public string LastName { get; set; } public DateTime CreatedDate { get; set; } } ``` ### Adding Constructors Sample Builder Code: ```csharp .AddClass("Customer", @class => { @class.AddConstructor(ctor => { ctor.AddParameter("string", "firstName", param => { param.IntroduceReadonlyField(); // Creates private readonly field }); ctor.AddParameter("string", "lastName", param => { param.IntroduceProperty(); // Creates property and assigns it }); }); }) ``` Example Output: ```csharp public class Customer { private readonly string _firstName; public Customer(string firstName, string lastName) { _firstName = firstName; LastName = lastName; } public string LastName { get; set; } } ``` ### Adding Methods Sample Builder Code: ```csharp .AddClass("CustomerService", @class => { @class.AddMethod("Customer", "GetCustomerById", method => { method .AddParameter("int", "customerId") .AddStatement("var customer = _repository.FindById(customerId);") .AddReturn("customer ?? throw new CustomerNotFoundException(customerId)"); }); }) ``` Example Output: ```csharp public class CustomerService { public Customer GetCustomerById(int customerId) { var customer = _repository.FindById(customerId); return customer ?? throw new CustomerNotFoundException(customerId); } } ``` ### Adding Properties with Different Configurations Sample Builder Code: ```csharp .AddClass("Customer", @class => { // Simple auto-property @class.AddProperty("string", "FirstName"); // Property with private setter @class.AddProperty("DateTime", "CreatedDate", property => { property.PrivateSetter(); }); // Property with initial value @class.AddProperty("bool", "IsActive", property => { property.WithInitialValue("true"); }); // Property with custom getter logic @class.AddProperty("string", "FullName", property => { property.WithoutSetter(); property.Getter.WithBodyImplementation(@"return $""{FirstName} {LastName}"";"); }); // Property with expression implementation @class.AddProperty("string", "FullNameExpression", property => { property.WithoutSetter(); property.Getter.WithExpressionImplementation(@"$""{FirstName} {LastName}"""); }); }) ``` Example Output: ```csharp public class Customer { public string FirstName { get; set; } public DateTime CreatedDate { get; private set; } public bool IsActive { get; set; } = true; public string FullName { get { return $"{FirstName} {LastName}"; } } public string FullNameExpression => $"{FirstName} {LastName}"; } ``` ### Controlling Accessibility and Modifiers You can control the accessibility and modifiers of classes, methods, and properties: ```csharp .AddClass("CustomerService", @class => { // Public static class @class.Static(); // Private method @class.AddMethod("void", "ValidateCustomer", method => { method.Private(); method.AddParameter("Customer", "customer"); }); // Protected virtual method @class.AddMethod("bool", "CanProcess", method => { method.Protected().Virtual(); method.AddReturn("true"); }); // Static method with XML documentation @class.AddMethod("Customer", "CreateDefault", method => { method .Static() .WithComments(""" /// /// Creates a default customer instance. /// /// A new customer with default values. """); method.AddReturn("new Customer()"); }); }) ``` Example Output: ```csharp public static class CustomerService { private void ValidateCustomer(Customer customer) { } protected virtual bool CanProcess() { return true; } /// /// Creates a default customer instance. /// /// A new customer with default values. public static Customer CreateDefault() { return new Customer(); } } ``` ### Working with Async Methods The File Builder System supports async methods with proper return type handling: ```csharp .AddClass("CustomerService", @class => { // Async method returning Task @class.AddMethod("Customer", "GetCustomerAsync", method => { method .Async() // Will set the return type to Task .AddParameter("int", "customerId"); method.AddReturn("await _repository.FindByIdAsync(customerId)"); }); // Async method returning ValueTask @class.AddMethod("bool", "ExistsAsync", method => { method .Async(true) // Will set the return type to ValueTask .AddParameter("int", "customerId"); method.AddReturn("await _repository.ExistsAsync(customerId)"); }); // Async void method (for event handlers) @class.AddMethod("void", "OnCustomerChanged", method => { method .Async() // Will set the return type to Task .AddParameter("object", "sender") .AddParameter("CustomerChangedEventArgs", "e"); method.AddStatement("await ProcessCustomerChangeAsync(e.Customer);"); }); // Using standard Task type without async / await keywords @class.AddMethod("Task", "CompletedAsync", method => { method.AddReturn("Task.CompletedTask"); }); }) ``` Example Output: ```csharp public class CustomerService { public async System.Threading.Tasks.Task GetCustomerAsync(int customerId) { return await _repository.FindByIdAsync(customerId); } public async System.Threading.Tasks.ValueTask ExistsAsync(int customerId) { return await _repository.ExistsAsync(customerId); } public async System.Threading.Tasks.Task OnCustomerChanged(object sender, CustomerChangedEventArgs e) { await ProcessCustomerChangeAsync(e.Customer); } public Task CompletedAsync() { return Task.CompletedTask; } } ``` ## Common Builder Patterns ### Conditional Code Generation Sample Builder Code: ```csharp .AddClass($"{Model.Name}", @class => { // Add properties for each attribute in the model foreach (var attribute in Model.Attributes) { @class.AddProperty(GetTypeName(attribute), attribute.Name.ToPascalCase()); } // Conditionally add validation logic if (Model.HasStereotype("Validated")) { @class.AddMethod("bool", "IsValid", method => { method.AddStatement("// Validation logic here"); method.AddReturn("true"); }); } }) ``` Example Output: ```csharp public class Customer { public string FirstName { get; set; } public bool IsValid() { // Validation logic here return true; } } public class Address { public string Line1 { get; set; } public string Line2 { get; set; } public string City { get; set; } public string PostalCode { get; set; } } ``` ### C# Attributes Sample Builder Code: ```csharp .AddClass("ApiController", @class => { @class .WithBaseType("ControllerBase") .AddAttribute("[ApiController]") // C# Attribute with square brackets .AddAttribute("Route", attr => attr.AddArgument(@"""api/[controller]""")); // C# Attribute with mutable arguments foreach (var operation in Model.Operations) { @class.AddMethod("IActionResult", operation.Name, method => { method .AddAttribute($"[Http{operation.Verb}]") .AddParameter(GetTypeName(operation.RequestType), "request") .AddStatement("// Do Processing Here...") .AddReturn("Ok()"); }); } }) ``` Example Output: ```csharp [ApiController] [Route("api/[controller]")] public class ApiController : ControllerBase { [HttpPost] public IActionResult Post(RequestDto request) { // Do Processing Here... return Ok(); } } ``` ### Working with CSharp Statements The File Builder System provides a rich set of statement types and control over their formatting: ```csharp .AddMethod("void", "ProcessCustomer", method => { // Basic statements method.AddStatement("var isValid = ValidateCustomer(customer);"); // Statements with spacing control method .AddStatement("// First validation step") .AddStatement("var basicValidation = customer.Name != null;") .AddStatement("var advancedValidation = customer.Email?.Contains(\"@\") == true;") .AddStatement("// Processing logic", stmt => stmt.SeparatedFromPrevious()) // Adds extra spacing before this statement .AddIfStatement("isValid", ifStmt => { ifStmt.AddStatement("ProcessValidCustomer(customer);"); ifStmt.AddStatement("LogSuccess(customer.Id);"); }) .AddElseStatement(elseStmt => { elseStmt.AddStatement("LogError($\"Invalid customer: {customer.Id}\");"); elseStmt.AddStatement("throw new InvalidOperationException(\"Customer validation failed\");"); }); }) ``` Example Output: ```csharp public void ProcessCustomer() { var isValid = ValidateCustomer(customer); // First validation step var basicValidation = customer.Name != null; var advancedValidation = customer.Email?.Contains("@") == true; // Processing logic if (isValid) { ProcessValidCustomer(customer); LogSuccess(customer.Id); } else { LogError($"Invalid customer: {customer.Id}"); throw new InvalidOperationException("Customer validation failed"); } } ``` ### Method Invocations with Lambda Expressions You can create method calls that accept lambda expressions as arguments: ```csharp .AddMethod("void", "ConfigureServices", method => { // Method invocation with lambda argument method.AddInvocationStatement("services.Configure", invocation => { invocation.AddArgument(new CSharpLambdaBlock("options"), lambda => { lambda.AddStatement(@"options.ConnectionString = configuration.GetConnectionString(""Default"");"); lambda.AddStatement("options.EnableRetry = true;"); }); }); // Multiple lambda arguments method.AddInvocationStatement("app.UseWhen", invocation => { invocation.AddArgument(new CSharpLambdaBlock("context"), lambda => lambda.WithExpressionBody(@"context.Request.Path.StartsWithSegments(""/api"")")); invocation.AddArgument(new CSharpLambdaBlock("appBuilder"), lambda => { lambda.AddStatement("appBuilder.UseAuthentication();"); lambda.AddStatement("appBuilder.UseAuthorization();"); }); }); }) ``` Example Output: ```csharp public class ApiController { public void ConfigureServices() { services.Configure(options => { options.ConnectionString = configuration.GetConnectionString("Default"); options.EnableRetry = true; }); app.UseWhen(context => context.Request.Path.StartsWithSegments("/api"), appBuilder => { appBuilder.UseAuthentication(); appBuilder.UseAuthorization(); }); } } ``` ### Chained method invocations ```csharp method.AddStatement(new CSharpAssignmentStatement( new CSharpVariableDeclaration("activeItems"), new CSharpStatement("Items") .AddInvocation("Where", i => i.AddArgument(new CSharpLambdaBlock("x"), a => a.WithExpressionBody("x.IsActive")).OnNewLine()) .AddInvocation("Select", i => i.OnNewLine()) )); ``` Example Output: ```csharp public void Method() { var activeItems = Items .Where(x => x.IsActive) .ToArray(); } ``` ### OnBuild vs AfterBuild Callbacks The File Builder System provides two types of callbacks for modifying generated code. These callbacks are essential because they execute at the correct time during the software factory execution process, allowing you to access existing template information that wouldn't be available under normal circumstances (since templates aren't ready yet). Both callbacks execute right before templates generate their final output. ```csharp // In your template constructor CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddClass("Customer", @class => { @class.AddProperty("string", "Name"); }); // OnBuild: Executes during the file building process // Use this when you need to modify the structure before other templates can see it // Optional priority parameter for ordering (lower numbers execute first) CSharpFile.OnBuild(file => { var customerClass = file.Classes.First(c => c.Name == "Customer"); customerClass.AddProperty("DateTime", "CreatedAt"); // This modification is visible to other templates and Factory Extensions }, priority: 100); // AfterBuild: Executes after all OnBuild callbacks are complete // Generally discouraged - use priorities with OnBuild instead CSharpFile.AfterBuild(file => { var customerClass = file.Classes.First(c => c.Name == "Customer"); // Add final validation or cleanup if (!customerClass.Properties.Any(p => p.Name == "Id")) { customerClass.AddProperty("int", "Id", prop => prop.WithInitialValue("0")); } }, priority: 200); ``` > [!WARNING] > > Always use OnBuild or AfterBuild callbacks when interacting with a template instance's CSharpFile. Direct access outside these callbacks will fail because templates and their information aren't ready during normal template construction. > [!TIP] > > Use OnBuild with priority ordering instead of AfterBuild whenever possible. AfterBuild is rarely needed and should generally be avoided - priority-based OnBuild callbacks can handle most ordering requirements. ## Working with Using Directives You can manually add using directives by explicitly adding them: ```csharp CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddUsing("System.Collections.Generic") // Explicit using .AddClass("MyClass", @class => { // When you add usings explicitly, the type is used as-is @class.AddProperty("IEnumerable", "Items"); }); ``` Alternatively, you can use `UseType()` to automatically manage using directives: ```csharp CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddClass("MyClass", @class => { // This will automatically add "using System.Collections.Generic;" if not already present @class.AddProperty($"{UseType("System.Collections.Generic.IEnumerable")}", "Items"); }); ``` ### Resolving Type Names Use the template's `GetTypeName()` methods to resolve types correctly: ```csharp .AddClass("CustomerService", @class => { // GetTypeName will automatically apply the correct using directive @class.AddMethod(GetTypeName("Domain.Customer", Model), "GetCustomer", method => { method.AddParameter("int", "id"); }); }) ``` ### Template Type Resolution Methods You can also use generated template extension methods for type resolution. When you create templates in the Module Builder, extension methods are automatically generated for resolving types from other templates: ```csharp .AddClass($"{Model.Name}Repository", @class => { @class.ImplementsInterface(this.GetRepositoryInterfaceName(Model)); // ... } ``` ## Best Practices ### Use semantic method names Instead of building complex strings, use the builder's semantic methods: ```csharp // Good method.AddIfStatement("mode == 1", stmt => { stmt.AddReturn("true"); }); // Avoid - string-based approach method.AddStatements( """ if (mode == 1) { return true; } """ ); ``` > [!NOTE] > String-based code generation may work for simple cases, but you'll lose correct indentation and the ability to mutate statements at runtime through Factory Extensions. ### Leverage lambda configuration All C# File Builder semantic methods use a consistent signature pattern: - **Return Type** - The type the method/property returns - **Object Name** - The name of the method/property/class - **Configuration Lambda** - A lambda expression for configuring the object ```csharp .AddMethod("void", "ConfigureServices", method => { method.AddParameter("IServiceCollection", "services"); foreach (var service in GetServices()) { method.AddStatement($"services.AddScoped<{service.Interface}, {service.Implementation}>();"); } }); ``` ### Keep builder logic focused Don't mix business logic with building logic: ```csharp // Good - separate concerns var properties = CalculateRequiredProperties(Model); .AddClass(Model.Name, @class => { foreach (var prop in properties) { @class.AddProperty(prop.Type, prop.Name); } }); // Avoid - mixed concerns .AddClass(Model.Name, @class => { // Complex business logic mixed with building if (Model.HasComplexBusinessRule() && SomeOtherCondition()) { // ... complex logic MethodThatObscuresTheCreationOfProperties(); } }); ``` ## Manipulate Templates with the C# File Builder from Factory Extensions Factory Extensions can manipulate File Builder templates using the same `OnBuild` and `AfterBuild` callbacks, making them incredibly powerful for cross-cutting concerns: ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var templates = application.FindTemplateInstances("MyModule.Entity"); foreach (var template in templates) { // Use OnBuild to add cross-cutting concerns template.CSharpFile.OnBuild(file => { var @class = file.Classes.FirstOrDefault(); if (@class != null) { // Add auditing properties to all entities @class.AddProperty("DateTime", "CreatedAt"); @class.AddProperty("string", "CreatedBy"); // Add interface implementation @class.ImplementsInterface("IAuditable"); } }); // Use AfterBuild for final validation or cleanup template.CSharpFile.AfterBuild(file => { var @class = file.Classes.FirstOrDefault(); // Ensure all entities have required using statements if (@class?.Interfaces.Any(i => i.Contains("IAuditable")) == true) { file.AddUsing("MyApp.Core.Interfaces"); } }); } } ``` > [!NOTE] > Factory Extensions have access to the same builder APIs as templates, allowing them to perform sophisticated code modifications across multiple modules. ### Working with Statement Spacing and Organization You can control the visual organization of your generated code using spacing methods: ```csharp .AddMethod("void", "ProcessOrder", method => { // Group related statements method.AddStatement("// Validation phase"); method.AddStatement("ValidateOrder(order);"); method.AddStatement("CheckInventory(order);"); // Add visual separation before processing method .AddStatement("// Processing phase", stmt => stmt.SeparatedFromPrevious()) .AddStatement("var result = ProcessPayment(order);") .AddIfStatement("result.IsSuccess", ifStmt => { ifStmt.AddStatement("CompleteOrder(order);"); ifStmt.AddStatement("SendConfirmation(order.CustomerEmail);"); }); // Final cleanup section method .AddStatement("// Cleanup", stmt => stmt.SeparatedFromPrevious()) .AddStatement("LogOrderProcessing(order.Id, result);"); }) ``` Example: ```csharp public void ProcessOrder() { // Validation phase ValidateOrder(order); CheckInventory(order); // Processing phase var result = ProcessPayment(order); if (result.IsSuccess) { CompleteOrder(order); SendConfirmation(order.CustomerEmail); } // Cleanup LogOrderProcessing(order.Id, result); } ``` ## Examples ### Repository Pattern Generation Sample Builder Code: ```csharp .AddClass($"{Model.Name}Repository", @class => { @class .ImplementsInterface($"I{Model.Name}Repository") .AddConstructor(ctor => { ctor.AddParameter("DbContext", "context", param => param.IntroduceReadonlyField()); }) .AddMethod($"{Model.Name}", "GetById", method => { method .AddParameter("int", "id") .AddReturn($"_context.{Model.Name.Pluralize()}.FirstOrDefault(x => x.Id == id)"); }) .AddMethod("void", "Add", method => { method .AddParameter($"{Model.Name}", "entity") .AddStatement("_context.Add(entity);"); }); }) ``` Example: ```csharp public class CustomerRepository : ICustomerRepository { private readonly DbContext _context; public CustomerRepository(DbContext context) { _context = context; } public Customer GetById(int id) { return _context.Customers.FirstOrDefault(x => x.Id == id); } public void Add(Customer entity) { _context.Add(entity); } } ``` ### DTO Generation Sample Builder Code: ```csharp .AddClass($"{Model.Name}Dto", @class => { foreach (var attribute in Model.Attributes.Where(a => a.IsPublic)) { @class.AddProperty(GetTypeName(attribute), attribute.Name.ToPascalCase()); } // Add conversion methods @class.AddMethod(@class.Name, "FromDomain", method => { method .Static() .AddParameter(GetTypeName(Model), "entity") .AddObjectInitializerBlock($"return new {@class.Name}", block => { foreach (var attr in Model.Attributes.Where(a => a.IsPublic)) { block.AddInitStatement(attr.Name.ToPascalCase(), $"entity.{attr.Name.ToPascalCase()}"); } }); }); }) ``` Example: ```csharp public class CustomerDto { public string FirstName { get; set; } public string LastName { get; set; } public static CustomerDto FromDomain(Customer entity) { return new CustomerDto { FirstName = entity.FirstName, LastName = entity.LastName }; } } ``` ## Error Handling and Debugging ### Common issues - **Missing using directives**: If your generated code has compilation errors due to missing using statements, ensure you're using `GetTypeName()` methods or explicitly adding using directives with `AddUsing()`. - **Incorrect type resolution**: Always use the template's type resolution methods rather than hardcoded type names. - **Builder method order**: Some builder methods must be called in a specific order. Consult the IntelliSense documentation for guidance. ### Debugging tips - **Review generated output**: Always check the actual generated C# code to understand what the builder is producing. - **Use the debugger**: You can debug your templates and inspect the real-time state of C# File Builder objects using the [.NET Debugger](https://docs.intentarchitect.com/docs-md/module-building/debugging-modules/debugging-modules.md). ## Migration from T4 Templates If you're migrating from T4 templates to the File Builder System: ### Before (T4) ```csharp namespace <#= Namespace #> { public class <#= ClassName #> { <# foreach(var prop in Model.Properties) { #> public <#= GetTypeName(prop) #> <#= prop.Name #> { get; set; } <# } #> } } ``` ### After (File Builder) ```csharp CSharpFile = new CSharpFile(this.GetNamespace(), this.GetFolderPath()) .AddClass(ClassName, @class => { foreach (var prop in Model.Properties) { @class.AddProperty(GetTypeName(prop), prop.Name); } }); ``` The File Builder approach provides better maintainability, type safety, and integration capabilities. ## Next Steps - [Factory Extensions Integration](https://docs.intentarchitect.com/docs-md/module-building/software-factory-extensions/software-factory-extensions.md) --- # How to add custom configuration to C# projects There may be times when a C# project needs to be customized beyond what Intent Architect generates. This section details how this can be achieved. A typical use case is adding a post build event to the csproj file to execute a script after the project has been built. ## Post Build Event Example In the below example, we are going to add a post build event to the Infrastructure project, which will execute a `dotnet --info` command. 1. Create a Factory Extension in your module to handle the updating of the csproj file. 2. Add a NuGet Reference to `Intent.Modules.VisualStudio.Projects`, to gain access to the required code/templates. 3. Inject `ISoftwareFactoryEventDispatcher` into the constructor of the Factory Extension: ``` csharp private readonly ISoftwareFactoryEventDispatcher _sfEventDispatcher; public PostBuildEventExtension(ISoftwareFactoryEventDispatcher sfEventDispatcher) { _sfEventDispatcher = sfEventDispatcher; } ``` 4. In the overridden `OnAfterTemplateExecution` method in the Factory Extension, add some code to look-up the correct csproj file: ``` csharp protected override void OnAfterTemplateExecution(IApplication application) { // Get all C# project templates IEnumerable projects = application.FindTemplateInstances("Intent.VisualStudio.Projects.CSharpProject"); // Get the infrastructure project CSharpProjectTemplate infraProject = projects.FirstOrDefault(x => x.OutputTarget.Name.EndsWith("Infrastructure", StringComparison.OrdinalIgnoreCase)); } ``` 5. Next the content of the template (the project file XML) is loaded and parsed: ``` csharp // Load the content string projectContent = infraProject.LoadContent(); XDocument document = XDocument.Parse(projectContent); XElement projectElement = document.Root; if (projectElement is null) { return; } ``` 6. Construct the new content, and add it to the initial content, and then update the template with the new content ``` csharp XNamespace ns = projectElement.Name.Namespace; const string targetName = "SamplePostBuild"; XElement existingTarget = projectElement.Elements(ns + "Target") .FirstOrDefault(e => string.Equals((string)e.Attribute("Name"), targetName, StringComparison.Ordinal)); // Only add if the element doesn't already exist if (existingTarget is null) { // Build the element XElement postBuildTarget = new XElement(ns + "Target", new XAttribute("Name", targetName), new XAttribute("AfterTargets", "Build"), new XElement(ns + "Message", new XAttribute("Importance", "high"), new XAttribute("Text", "Sample post-build event running...")), new XElement(ns + "Exec", new XAttribute("Command", "dotnet --info")) ); // Add to the XML projectElement.Add(postBuildTarget); // Update the template with the new content infraProject.UpdateContent(document.ToString(), _sfEventDispatcher); } ``` The following block should now be added to the Infrastructure project when running the Software Factory: ``` xml ``` ### Full code sample Below is the full code snippet of the contents of the `PostBuildEventExtension` Factory Extension: ``` csharp private readonly ISoftwareFactoryEventDispatcher _sfEventDispatcher; public PostBuildEventExtension(ISoftwareFactoryEventDispatcher sfEventDispatcher) { _sfEventDispatcher = sfEventDispatcher; } protected override void OnAfterTemplateExecution(IApplication application) { IEnumerable projects = application.FindTemplateInstances("Intent.VisualStudio.Projects.CSharpProject"); CSharpProjectTemplate infraProject = projects.FirstOrDefault(x => x.OutputTarget.Name.EndsWith("Infrastructure", StringComparison.OrdinalIgnoreCase)); if (infraProject is not null) { string projectContent = infraProject.LoadContent(); XDocument document = XDocument.Parse(projectContent); XElement projectElement = document.Root; if (projectElement is null) { return; } XNamespace ns = projectElement.Name.Namespace; const string targetName = "SamplePostBuild"; XElement existingTarget = projectElement.Elements(ns + "Target") .FirstOrDefault(e => string.Equals((string)e.Attribute("Name"), targetName, StringComparison.Ordinal)); if (existingTarget is null) { XElement postBuildTarget = new XElement(ns + "Target", new XAttribute("Name", targetName), new XAttribute("AfterTargets", "Build"), new XElement(ns + "Message", new XAttribute("Importance", "high"), new XAttribute("Text", "Sample post-build event running...")), new XElement(ns + "Exec", new XAttribute("Command", "dotnet --info")) ); projectElement.Add(postBuildTarget); infraProject.UpdateContent(document.ToString(), _sfEventDispatcher); } } } ``` --- # How to add NuGet dependencies (C#) Often source code generated by Templates will require that certain NuGet packages are installed. Templates allow you to define such dependencies by using the `AddNugetDependency` method. First, create a static class (if not already created) that will contain the NuGet package references. By having the NuGet package references defined in a central class, multiple Templates can make use of the references and the versions can later be updated in a central location. ```csharp using Intent.Modules.Common.VisualStudio; namespace Intent.Modules.Application.AutoMapper; public static class NugetPackages { public static INugetPackageInfo AutoMapper = new NugetPackageInfo("AutoMapper", "12.0.0"); } ``` Then in your Template you can use the `AddNugetDependency` method: ```csharp [IntentManaged(Mode.Merge, Signature = Mode.Fully)] partial class MappingProfileTemplate : CSharpTemplateBase { [IntentManaged(Mode.Fully)] public const string TemplateId = "Intent.Application.AutoMapper.MappingProfile"; [IntentManaged(Mode.Merge, Signature = Mode.Fully)] public MappingProfileTemplate(IOutputTarget outputTarget, object model = null) : base(TemplateId, outputTarget, model) { AddNugetDependency(NugetPackages.AutoMapper); } ... ``` > [!IMPORTANT] > The `AddNugetDependency` method call must be invoked in either the `Constructor` or in the overridden `BeforeTemplateExecution` method as the updates to the Visual Studio Project files may not reflect the when the Software Factory execution occurs. Once the Template executes in the Software Factory, it will introduce a `PackageReference` in the corresponding Visual Studio Project file: ```xml ... ``` > [!NOTE] > Actual package restoration is still handled by your IDE (`Visual Studio`, `Rider`, etc.) / `dotnet` commands for package restoration. Refer to the NuGet documentation on how to manage package sources. --- # How to add project dependencies (C#) ## Add a reference as needed to access a referenced type When a template uses [`GetTypeName(...)`](https://docs.intentarchitect.com/docs-md/module-building/templates-general/resolving-type-names/resolving-type-names.md) and the type is from another template which is output to a different `.csproj`, then Intent Architect will automatically add a `` element to the `.csproj`. ## Add a reference manually to a project containing a particular role You can specify a project dependency to another project in the Visual Studio designer based on it containing a particular _Role_. Consider the following layout in the Visual Studio designer: ![Example solution layout](images/sample-solution.png) From the above example, you can specify `MyApplication.Api` to have a reference to `MyApplication.Infrastructure`. In the constructor for a template within the project where the reference should be created, for example `Intent.AspNetCore.Startup`, add the following: ```csharp AddProjectDependency("Infrastructure"); ``` `Infrastructure` in this case is the name of a role which can be seen in the above screenshot as being within the `MyApplication.Infrastructure` project. With this line added, the software factory when running the `Intent.AspNetCore.Startup` template will ensure that a project reference exists to the project containing the role, `MyApplication.Infrastructure`. ## Add a reference manually to a project which is not modelled in the Visual Studio designer The following will add a `` with its `Include` set to any arbitrary relative path that you specify: ```csharp AddAssemblyReference(new ProjectReference("..\\Project\\Project.csproj")); ``` > [!NOTE] > > Even on operating systems using `/` as their path separator, a `` should have its path separated using a `\`. > > [!NOTE] > > `ProjectReference` is available from the `Intent.Modules.Common.VisualStudio` namespace and requires that at least version `3.8.2` of the `Intent.Common.CSharp` module is installed. --- # How to control file properties in C# projects If you need to control aspects of a file's entry within its `.csproj` file, this can be done by updating the `GetTemplateFileConfig()` method in the template and using various `ITemplateFileConfig` extension methods prefixed with `With` available when you have a using directive for the `Intent.Modules.Common.CSharp.VisualStudio` namespace. ## Available methods Below is a list of all available methods, additionally, each method has been documented such that supporting IDEs (such as Visual Studio or Rider) will show additional details with IntelliSense or mousing-over method names. | Method | Description | | ---------------------------------------- | ----------- | | `.AsEmbeddedResource(…)` | Indicate that the file entry should be an `EmbeddedResource`. This is a convenience method which calls `.WithFileItemGenerationBehaviour(MsBuildFileItemGenerationBehaviour.Always)`, `.WithItemType("EmbeddedResource")` and `.WithRemoveItemType(removeItemType)` where `removeItemType` is `Compile` for `.cs` files and `None` for all other file types, the `removeItemType` parameter can be used to override this. | | `.WithAttribute(…)` | For setting or clearing an attribute with any name and value. Setting an attribute's value to `null` will cause it to be removed. | | `.WithAutoFormatting(…)` | Controls whether or not auto formatting of the file is applied after code merging. | | `.WithAutoGen()` | Adds an `True` child element. | | `.WithCopyToOutputDirectory(…)` | Controls the value of the `` child element. | | `.WithDependsOn(…)` | Adds a `` child element. | | `.WithDesignTime()` | Adds a `True` child element. | | `.WithFileItemGenerationBehaviour(…)` | Controls the generation behaviour of the file item element, this can be used to force the element to be/not be generated. | | `.WithItemType(…)` | Indicate that the file entry should have the specified _ItemType_, ie, `< Update=""/>` should be added to the `.csproj` file. If the `.WithRemoveItemType(…)` method has also been used, then `< Include=""/>` will be generated instead. | | `.WithNestedProjectElement(…)` | For creating a nested element with any name and value. | | `.WithRemoveItemType(…)` | Indicate that the file entry should have a `Remove` entry added for it, ie, that `< Remove="" />` should be added to the `.csproj` file. | | `.WithTextTemplatingFilePreprocessor(…)` | Adds the necessary elements to the file item for a pre-processed `.tt` file. | ## Examples ### Make a file be copied to the output directory The example below will make a file's `ItemType` be `None` and have it always be copied to the output directory. ```csharp public override ITemplateFileConfig GetTemplateFileConfig() { return new TemplateFileConfig( fileName: $"script", fileExtension: "sql") .WithItemType("None") .WithCopyToOutputDirectory(CopyToOutputDirectory.CopyAlways); } ``` ### Make a file an embedded resource The example below will make a file an embedded resource. ```csharp public override ITemplateFileConfig GetTemplateFileConfig() { return new TemplateFileConfig( fileName: $"File", fileExtension: "cs") .AsEmbeddedResource(); } ``` --- # How to disable auto-formatting for C# templates By default the Software Factory will automatically format files under [code management](https://docs.intentarchitect.com/docs-md/application-development/code-management/about-code-management/about-code-management.md). If this is undesired you can disable this behaviour by setting the `AutoFormat` property to `false` in the `DefineFileConfig` method of your template: ```csharp protected override CSharpFileConfig DefineFileConfig() { return new CSharpFileConfig( className: "MyClass", @namespace: OutputTarget.GetNamespace()) { AutoFormat = false }; } ``` --- # How to Model NuGet Dependencies (C#) Often when building modules, the generated patterns require that NuGet packages be installed. These NuGet dependencies can be modeled and managed within the module building ecosystem. > [!NOTE] > This functionality is available from `Intent.ModuleBuilder.CSharp` v3.6.*. In this example, we will model our module to have a dependency on the `Microsoft.EntityFrameworkCore` NuGet package. - Right-click on the `Module Builder` root and select `Add NuGet Packages`. ![Add `NuGet Packages`](./images/add-nuget-packages.png) - Right-click on the `NuGet Packages` element and select `Add NuGet Package`. ![Add A `NuGet Package`](./images/add-nuget-package.png) - Type in the name of the NuGet package you have a dependency on, e.g., `Microsoft.EntityFrameworkCore`, `MediatR`, etc. ![Fill in the package name](./images/capture-package-name.png) - Right-click on the package you added and select `Get latest from NuGet.org`. This will retrieve the latest version information and configure the latest versions to be installed per .NET Framework version. This process is additive and will update existing entries (it will not remove any existing entries). ![Update version info from NuGet](./images/get-latest-versions.png) > [!NOTE] > You can get the latest for all packages by performing `Get latest from NuGet.org` on the `NuGet Packages` element. You will see the relevant versioning information under the package. ![Versions Captured](./images/versions.png) > [!NOTE] > The versions are `>=`. In this example, version `7.0.20` will be used for `.net6` and `.net7`, while `8.0.8` will be used for `.net8`. At this point, if you run the Software Factory, it will add a `NugetPackages` class to your module. This class is responsible for registering your module's `NuGet` requirements and has accessor methods for your templates to declare their specific `NuGet` dependencies. ```csharp public class NugetPackages : INugetPackages { public const string MicrosoftEntityFrameworkCorePackageName = "Microsoft.EntityFrameworkCore"; public void RegisterPackages() { NugetRegistry.Register(MicrosoftEntityFrameworkCorePackageName, (framework) => framework switch { ( >= 8, 0) => new PackageVersion("8.0.8"), ( >= 6, 0) => new PackageVersion("7.0.20"), _ => throw new Exception($"Unsupported Framework `{framework.Major}` for NuGet package '{MicrosoftEntityFrameworkCorePackageName}'"), } ); } public static NugetPackageInfo MicrosoftEntityFrameworkCore(IOutputTarget outputTarget) => NugetRegistry.GetVersion(MicrosoftEntityFrameworkCorePackageName, outputTarget.GetMaxNetAppVersion()); } ``` Then in your template, you can use the `AddNugetDependency` method: ```csharp [IntentManaged(Mode.Merge, Signature = Mode.Fully)] partial class MyTemplateTemplate : CSharpTemplateBase { ... [IntentManaged(Mode.Merge, Signature = Mode.Fully)] public MyTemplateTemplate(IOutputTarget outputTarget, object model = null) : base(TemplateId, outputTarget, model) { AddNugetDependency(NugetPackages.MicrosoftEntityFrameworkCore(outputTarget)); } ... ``` > [!IMPORTANT] > The `AddNugetDependency` method call must be invoked in either the `Constructor` or in the overridden `BeforeTemplateExecution` method, as the updates to the Visual Studio Project files may not reflect when the Software Factory execution occurs. Once the template executes in the Software Factory, it will introduce a `PackageReference` in the corresponding Visual Studio Project file: ```xml ... ``` > [!NOTE] > Actual package restoration is still handled by your IDE (`Visual Studio`, `Rider`, etc.) or `dotnet` commands for package restoration. Refer to the NuGet documentation on how to manage package sources. ## Modules Vote on Versions If you have multiple modules installed, all requesting the same `NuGet` package, the `Software Factory` will select the highest version of the package specified by the modules. For example, if: - `Module1` wants `Microsoft.EntityFrameworkCore` `8.0.7`, and - `Module2` wants `Microsoft.EntityFrameworkCore` `8.0.8`, then `Microsoft.EntityFrameworkCore` `8.0.8` will be installed. This is true provided none of the modules are `locking` the versions. If there are locked versions, the lowest locked version will be used. ## Locking NuGet Package Versions In the Module Designer, you can `lock` your NuGet versions, either at the package level or for a specific .NET Framework version. This will do two things: - The `Get latest from NuGet.org` function will not change locked versions. - When deciding which version of the NuGet package to install, the `Software Factory` will favor locked versions (installing the lowest locked version if there are multiple). In the example below, we can see that the `Microsoft.EntityFrameworkCore` NuGet package is configured such that: - For `.net6` and `.net7`, the version will be locked at `7.0.20` and will not go above this version. - For `.net8`, it is locked at `8.0.8` but can be bumped up by another module requesting a newer version, say `8.0.9`. ![Locked versions](./images/locked-version.png) --- # How to Update `appsettings.json` Files The `Intent.VisualStudio.Projects` module automatically generates the `appsettings.json` file for supported .NET project types. ## How to Update from a Template To apply additional configuration to an `appsettings.json` file, use the `ApplyAppSetting` extension method within your Template. For example: ```csharp public override void BeforeTemplateExecution() { this.ApplyAppSetting( field: "CustomSectionName:SomeStringField", value: "Some Value"); this.ApplyAppSetting( field: "OtherCustomSectionName", value: new { IntValue = 10, StrValue = "My String" }); } ``` > [!NOTE] > This extension method requires `Intent.Modules.Common.CSharp` NuGet package to be installed in your module project. > > [!IMPORTANT] > This can only be invoked after construction of the template, otherwise the template which responds to this event might not yet have been constructed. > > Placing this method in the override of either the `AfterTemplateRegistration` or `BeforeTemplateExecution` methods will ensure it is called only after all other templates have been constructed. In the example above, the `appsettings.json` file will include the following: ```json { // ... other configuration... "CustomSectionName": { "SomeStringField": "Some Value" }, "OtherCustomSectionName": { "IntValue": 10, "StrValue": "My String" } } ``` ## How to update from a Factory Extension It is also possible to apply additional configuration to an `appsettings.json` file using a `Factory Extension` with the `EventDispatcher`. For example: ``` csharp protected override void OnAfterTemplateRegistrations(IApplication application) { base.OnAfterTemplateRegistrations(application); application.EventDispatcher.Publish(new AppSettingRegistrationRequest( key: "CustomSectionName:SomeStringField", value: "Some Value")); application.EventDispatcher.Publish(new AppSettingRegistrationRequest( key: "OtherCustomSectionName", value: new { IntValue = 10, StrValue = "My String" })); } ``` > [!NOTE] > When providing a complex value, you should pass in an anonymous C# object as demonstrated above. Passing a JSON formatted string is not supported and will result in the string being escaped and written as a single value in the `appsettings.json` file, rather than being parsed as a JSON object. The resulting `appsettings.json` file will be the same as in the previous `Template` example: ```json { // ... other configuration... "CustomSectionName": { "SomeStringField": "Some Value" }, "OtherCustomSectionName": { "IntValue": 10, "StrValue": "My String" } } ``` ## Variable field names To create `appsettings.json` entries with variable field names, or to update an existing section, you can use the following syntax: ``` csharp this.ApplyAppSetting( field: "OtherCustomSectionName:AdditionalStringValue", value: "AdditionalStrValue"); ``` Executing this method after the previous above example, your `appsettings.json` file will look like the following: ```json { // ... other configuration... "CustomSectionName": { "SomeStringField": "Some Value" }, "OtherCustomSectionName": { "IntValue": 10, "StrValue": "My String", "AdditionalStringValue" : "AdditionalStrValue" } } ``` ## Environment specific appsettings.json files To enable Intent Architect to generate separate `appsettings.json` files for each environment, you must configure the `runtime environments` in the Codebase Structure / Visual Studio modeling experience. See [Codebase Structure Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/codebase-structure-designer/codebase-structure-designer.md) for where to add `Runtime Environment` elements and how they affect generated files. ![Runtime environments](images/runtime-environments.png) This configuration will generate three additional appsettings files, in addition to the base appsettings.json file. ![Appsettings generation](images/appsettings-generation.png) When the `ApplySetting` method is called, the `runtime environment` can be supplied, which will result in the setting only being applied to the relevant appsettings file. ``` csharp this.ApplyAppSetting( field: "ProductionOnlySetting", value: "ProductionValue", runtimeEnvironment: "prod"); ``` --- # How to update `launchSettings.json` files The `launchSettings.json` file is managed by the `LaunchSettingsJsonTemplate`. Template authors can influence its output by publishing events/requests via the `IApplicationEventDispatcher`. This document describes each available mechanism. If you are looking for the Codebase Structure / Visual Studio modeling setup for environment-specific `appsettings` files, see [Codebase Structure Designer](https://docs.intentarchitect.com/docs-md/application-development/modelling/codebase-structure-designer/codebase-structure-designer.md). --- ## 1. Add a Launch Profile - `LaunchProfileRegistrationRequest` Adds a new named profile to `launchSettings.json`. **Namespace:** `Intent.Modules.Common.CSharp.Configuration` ### Properties | Property | JSON field | Description | |---|---|---| | `Name` | _(profile key)_ | The profile name (required). | | `CommandName` | `commandName` | e.g. `"Project"`, `"IISExpress"`, `"Executable"`. | | `CommandLineArgs` | `commandLineArgs` | Arguments passed to the process. | | `LaunchBrowser` | `launchBrowser` | Whether to open a browser on launch. | | `LaunchUrl` | `launchUrl` | URL opened in the browser. Supports `{HttpPort}` and `{HttpsPort}` tokens. | | `ApplicationUrl` | `applicationUrl` | Listening URL(s), semicolon-separated. Supports `{HttpPort}` and `{HttpsPort}` tokens. | | `PublishAllPorts` | `publishAllPorts` | Defaults to `true` (omitted from JSON). | | `UseSsl` | `useSSL` | Defaults to `true` (omitted from JSON). | | `DotnetRunMessages` | `dotnetRunMessages` | Display build messages on run. | | `InspectUri` | `inspectUri` | Debugging URI for Blazor WebAssembly. | | `ExecutablePath` | `executablePath` | Path to executable (for `Executable` command). | | `WorkingDirectory` | `workingDirectory` | Working directory for the process. | | `EnvironmentVariables` | `environmentVariables` | Key/value pairs added to this profile's environment variables. | | `ForProjectWithRole` | _(routing)_ | See [Targeting a Specific Project](#targeting-a-specific-project). | ### Notes - If a profile with the same `Name` already exists in the file it is **not** overwritten (existing file entries take precedence). - The `{HttpPort}` and `{HttpsPort}` tokens in `LaunchUrl` and `ApplicationUrl` are replaced with the actual randomly-assigned or persisted port numbers. ### Example ```csharp public override void BeforeTemplateExecution() { ExecutionContext.EventDispatcher.Publish(new LaunchProfileRegistrationRequest { Name = "Docker", CommandName = "Docker", LaunchBrowser = true, LaunchUrl = "https://localhost:{HttpsPort}/swagger", ApplicationUrl = "https://localhost:{HttpsPort};http://localhost:{HttpPort}", EnvironmentVariables = new Dictionary { ["ASPNETCORE_ENVIRONMENT"] = "Development" } }); } ``` --- ## 2. Add an Environment Variable - `EnvironmentVariableRegistrationRequest` Adds a key/value environment variable to one or more launch profiles. **Namespace:** `Intent.Modules.Common.CSharp.Configuration` ### Constructor ```csharp new EnvironmentVariableRegistrationRequest( key: "MY_SETTING", value: "my-value", targetProfiles: new[] { "MyApp", "IIS Express" }, // null = all profiles forProjectWithRole: null) ``` | Parameter | Description | |---|---| | `key` | The environment variable name. | | `value` | The environment variable value. | | `targetProfiles` | Profile names to target. Pass `null` to apply to every profile. | | `forProjectWithRole` | See [Targeting a Specific Project](#targeting-a-specific-project). | ### Notes - A variable is only added if the profile doesn't already contain an entry for that key (first-write wins). - The default `ASPNETCORE_ENVIRONMENT=Development` (or `DOTNET_ENVIRONMENT=Development` for Worker SDK projects) is injected automatically and will be skipped if you have already set it. ### Example ```csharp ExecutionContext.EventDispatcher.Publish( new EnvironmentVariableRegistrationRequest( key: "ConnectionStrings__Default", value: "Server=localhost;Database=MyDb;", targetProfiles: new[] { "MyApp" })); ``` --- ## 3. Set the Default Launch URL Path - `DefaultLaunchUrlPathRequest` Sets the `launchUrl` path for the default project profiles (profiles whose name ends with `.Api`). **Namespace:** `Intent.Modules.Common.CSharp.Configuration` `DefaultLaunchUrlPathRequest` has an `internal` constructor; publish it via the provided extension methods. ### Extension methods | Method | When to use | |---|---| | `template.PublishDefaultLaunchUrlPathRequest(urlPath, forProjectWithRole?)` | Use inside `BeforeTemplateExecution()`. Broadcasts globally and logs a warning if unhandled. | | `outputTarget.EmitDefaultLaunchUrlPathRequest(urlPath)` | Use when you want the request automatically scoped to projects that reference your output target. | ### Notes - Only one call may succeed per `launchSettings.json`; a second attempt throws `InvalidOperationException`. - If the file already exists the path is **not** updated (only applied on first generation). - The leading `/` is stripped automatically. ### Example ```csharp public override void BeforeTemplateExecution() { this.PublishDefaultLaunchUrlPathRequest("/swagger/index.html"); } ``` --- ## 4. Require an HTTP Port in Application URLs - `LaunchProfileHttpPortRequired` Publishing this event ensures every HTTPS-only profile gets an additional `http://localhost:{port}` entry appended to its `applicationUrl`. **Namespace:** `Intent.Modules.Constants` ### Usage ```csharp ExecutionContext.EventDispatcher.Publish(LaunchProfileHttpPortRequired.EventId); ``` This is useful for tooling (e.g. Docker, Aspire) that requires an explicit HTTP endpoint alongside HTTPS. --- ## 5. Suppress Generation - `AddProjectPropertyEvent` with `NoDefaultLaunchSettingsFile` Setting the `NoDefaultLaunchSettingsFile` MSBuild property to `true` via `AddProjectPropertyEvent` causes the template to skip generation entirely for that project. ```csharp ExecutionContext.EventDispatcher.Publish(new AddProjectPropertyEvent( target: outputTarget.GetProject(), propertyName: "NoDefaultLaunchSettingsFile", propertyValue: "true")); ``` --- ## Targeting a Specific Project When a solution contains multiple ASP.NET projects, each with its own `launchSettings.json`, you can route a request to the correct one by setting `ForProjectWithRole` to the name of a **Role** element defined under the target project in the Intent Architect Visual Studio designer. ```csharp new LaunchProfileRegistrationRequest { ForProjectWithRole = "API", Name = "MyProfile", // ... } ``` If `ForProjectWithRole` is `null` or empty, the request is accepted by every `launchSettings.json` template that processes it. --- # Synchronize code to design (C#) This article explains how to add support for [code to design synchronization](https://docs.intentarchitect.com/docs-md/application-development/software-factory/synchronize-code-to-design/synchronize-code-to-design.md) to C# templates. Code to design synchronization allows templates in Intent Architect to detect user edits in C# files and translate those edits back into *model changes* in Intent Architect Designers. Minimum required NuGet packages: - Intent.SoftwareFactory.SDK 3.13.0 - Intent.Modules.Common.CSharp 3.10.0 Additionally, for applications using your module, they must also use at least version `4.10.0` of the `Intent.OutputManager.RoslynWeaver` module for the template's `ISynchronizeCSharpCodeToModel.Accept(...)` method to be called. ## Overview of required implementation 1. Add the `ISynchronizeCSharpCodeToModel` interface to your template's class. 2. Implement the `void Accept(ICSharpSemanticComparisonNode rootComparisonNode)` method to store the provided `ICSharpSemanticComparisonNode` for later. This method is called by the `Intent.OutputManager.RoslynWeaver` module if it finds semantic differences in the file it generated compared to the existing file on the file system. 3. Implement the `IReadOnlyCollection GetCodeToOperationModels()` method. This is called by the Software Factory and returns any "code to model operations" (or "instructions") to be run in Intent Architect Designers to apply changes. ## The `void Accept(ICSharpSemanticComparisonNode rootComparisonNode)` method This method simply needs to store the provided `ICSharpSemanticComparisonNode` for use later, for example you could add the following to your template: ```csharp private ICSharpSemanticComparisonNode _rootComparisonNode; public void Accept(ICSharpSemanticComparisonNode rootComparisonNode) { _rootComparisonNode = rootComparisonNode; } ``` This method is called by the `Intent.OutputManager.RoslynWeaver` module if it finds any semantic differences between the generated and existing file. > [!NOTE] > > Only versions `4.10.0` and greater of the `Intent.OutputManager.RoslynWeaver` module have the logic present which calls this method. ## The `IReadOnlyCollection GetCodeToOperationModels()` method This method needs to return a list of "code of model operations" for the designers to apply when the user presses uses [synchronize code to design](https://docs.intentarchitect.com/docs-md/application-development/software-factory/synchronize-code-to-design/synchronize-code-to-design.md#using-the-feature) option on the Software Factory. The operations to return varies by each template as it depends on how a template's generated content relates to metadata in Intent Architect Designers, but in essence you will need to inspect the `ICSharpSemanticComparisonNode` and its `ChildNodes` (which are also `ICSharpSemanticComparisonNode`s), check if each node is an Addition, Removal or Update, possibly correlate with existing members of your class and then return operations as appropriate. ### Creating code to model operations To create operations, use the `CodeToModelOperationFactory` static class from the `Intent.CodeToModelOperations` namespace, this class has an `Instance` property with various factory methods available on it for various operation types, there are also extension methods available for this factory class from the `Intent.Modules.Common` namespace providing higher level convenient overloads for most operations. #### Resolving type references from type names For model operations to create or update elements with type references in Intent Architect Designers they require an `ITypeReference`. `ICSharpSemanticComparisonNode` has `Current` and `Generated` properties each with `string TypeName { get; }` members. To convert from these strings to required `ITypeReference`s for operations, the `TryGetTypeReference` on `IntentTemplateBase` (which `CSharpTemplateBase` derives from) can be used. This method was created for this purpose and (amongst other logic) uses the type resolution infrastructure already available in templates to resolve actual types, be they built in type definitions (`string`, `int`, etc) or types from other template instances which have been referred to using the `AddTypeSource` method on the current template. ### Example: DTO Here is the (probably) simplest possible example of reverse synchronizing properties from an existing `.cs` file to `Field` elements in the `Services` designer in Intent Architect. The code below was taken from [`DtoModelTemplatePartial.cs`](https://github.com/IntentArchitect/Intent.Modules.NET/blob/master/Modules/Intent.Modules.Application.Dtos/Templates/DtoModel/DtoModelTemplatePartial.cs) ([permalink](https://github.com/IntentArchitect/Intent.Modules.NET/blob/400733db176e7b0ab5a5763a9f98753e8cb989c0/Modules/Intent.Modules.Application.Dtos/Templates/DtoModel/DtoModelTemplatePartial.cs#L447)) available as open source. ```csharp public IReadOnlyCollection GetCodeToOperationModels() { var properties = _rootComparisonNode? .ChildNodes.FirstOrDefault(x => x.SyntaxKind == CSharpSyntaxKind.NamespaceDeclaration)? .ChildNodes.FirstOrDefault(x => x.SyntaxKind == CSharpSyntaxKind.ClassDeclaration)? .ChildNodes.Where(x => x.SyntaxKind == CSharpSyntaxKind.PropertyDeclaration) .ToArray(); if (properties == null || properties.Length == 0) { return []; } var changes = new List(); foreach (var property in properties) { var typeReference = TryGetTypeReference((property.Current ?? property.Generated)!.TypeName!, Model.InternalElement.Package, out var reference) ? CodeToModelOperationFactory.Instance.TypeReference(reference) : null; switch (property.DifferenceType) { case CSharpDifferenceType.Added: changes.Add(CodeToModelOperationFactory.Instance.CreateChildElement( parent: Model.InternalElement, newElementId: Guid.NewGuid().ToString(), name: property.Current!.Identifier!, specialization: DTOFieldModel.SpecializationType, specializationId: DTOFieldModel.SpecializationTypeId, typeReference: typeReference)); break; case CSharpDifferenceType.Changed: var fieldToUpdate = Model.Fields.FirstOrDefault(x => string.Equals(x.Name, property.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (fieldToUpdate != null) { changes.Add(CodeToModelOperationFactory.Instance.UpdateElement( element: fieldToUpdate.InternalElement, name: property.Current!.Identifier.ToPascalCase(), typeReference: typeReference)); } break; case CSharpDifferenceType.Removed: var fieldToRemove = Model.Fields.FirstOrDefault(x => string.Equals(x.Name, property.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (fieldToRemove != null) { changes.Add(CodeToModelOperationFactory.Instance.DeleteElement(fieldToRemove.InternalElement)); } break; } } return changes; } ``` ### Example: Domain Entity As a more complex example, the below shows synchronizing a domain entity back to a `Class` in the `Domain` Designer in Intent Architect and covering synchronization of associations, attributes, methods and parameters. The code below was taken from [`DomainEntityTemplatePartial.cs`](https://github.com/IntentArchitect/Intent.Modules.NET/blob/master/Modules/Intent.Modules.Entities/Templates/DomainEntity/DomainEntityTemplatePartial.cs) ([permalink](https://github.com/IntentArchitect/Intent.Modules.NET/blob/400733db176e7b0ab5a5763a9f98753e8cb989c0/Modules/Intent.Modules.Entities/Templates/DomainEntity/DomainEntityTemplatePartial.cs#L533)) available as open source. ```csharp public IReadOnlyCollection GetCodeToOperationModels() { var @class = _rootComparisonNode? .ChildNodes.FirstOrDefault(x => x.SyntaxKind == CSharpSyntaxKind.NamespaceDeclaration)? .ChildNodes.FirstOrDefault(x => x.SyntaxKind == CSharpSyntaxKind.ClassDeclaration); if (@class == null) { return []; } var changes = new List(); foreach (var member in @class.ChildNodes) { var typeReference = TryGetTypeReference((member.Current ?? member.Generated).TypeName, Model.InternalElement.Package, out var typeNameReference) ? CodeToModelOperationFactory.Instance.TypeReference(typeNameReference) : null; switch (member.SyntaxKind) { case CSharpSyntaxKind.MethodDeclaration: switch (member.DifferenceType) { case CSharpDifferenceType.Added: { var method = CodeToModelOperationFactory.Instance.CreateChildElement( parent: Model.InternalElement, newElementId: Guid.NewGuid().ToString(), name: member.Current.Identifier!, specialization: OperationModel.SpecializationType, specializationId: OperationModel.SpecializationTypeId, typeReference: typeReference); changes.Add(method); foreach (var parameter in member.ChildNodes.Where(x => x.SyntaxKind == CSharpSyntaxKind.Parameter)) { changes.Add(CodeToModelOperationFactory.Instance.CreateChildElement( parent: method, newElementId: Guid.NewGuid().ToString(), name: parameter.Current.Identifier!, specialization: ParameterModel.SpecializationType, specializationId: ParameterModel.SpecializationTypeId, typeReference: TryGetTypeReference((parameter.Current ?? parameter.Generated).TypeName!, Model.InternalElement.Package, out var parameterReference) ? CodeToModelOperationFactory.Instance.TypeReference(parameterReference) : null)); } break; } case CSharpDifferenceType.Removed: { var operation = Model.Operations.SingleOrDefault(x => string.Equals(x.Name, member.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (operation != null) { changes.Add(CodeToModelOperationFactory.Instance.DeleteElement(operation.InternalElement)); } break; } case CSharpDifferenceType.Changed: { var operation = Model.Operations.SingleOrDefault(x => string.Equals(x.Name, member.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (operation != null) { changes.Add(CodeToModelOperationFactory.Instance.UpdateElement( element: operation.InternalElement, name: member.Current.Identifier.ToPascalCase(), typeReference: typeReference)); } break; } } break; case CSharpSyntaxKind.PropertyDeclaration: // Attributes if (typeNameReference == null || typeNameReference.Element.SpecializationTypeId is TypeDefinitionModel.SpecializationTypeId or EnumModel.SpecializationTypeId) { switch (member.DifferenceType) { case CSharpDifferenceType.Added: changes.Add(CodeToModelOperationFactory.Instance.CreateChildElement( parent: Model.InternalElement, name: member.Current.Identifier!, specialization: AttributeModel.SpecializationType, specializationId: AttributeModel.SpecializationTypeId, typeReference: typeReference)); break; case CSharpDifferenceType.Removed: { var existing = Model.Attributes.FirstOrDefault(x => string.Equals(x.Name, member.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (existing == null) { break; } changes.Add(CodeToModelOperationFactory.Instance.DeleteElement(existing.InternalElement)); break; } case CSharpDifferenceType.Changed: { var existing = Model.Attributes.FirstOrDefault(x => string.Equals(x.Name, member.Generated!.Identifier, StringComparison.OrdinalIgnoreCase)); if (existing == null) { break; } changes.Add(CodeToModelOperationFactory.Instance.UpdateElement( element: existing.InternalElement, name: member.Current.Identifier.ToPascalCase(), typeReference: typeReference)); break; } } break; } // Associations switch (member.DifferenceType) { case CSharpDifferenceType.Added: changes.Add(CodeToModelOperationFactory.Instance.CreateAssociation( specialization: AssociationModel.SpecializationType, specializationId: AssociationModel.SpecializationTypeId, targetEndElement: (IElement)typeNameReference.Element, targetEndName: member.Current.Identifier, targetEndIsNullable: typeNameReference.IsNullable, targetEndIsCollection: typeNameReference.IsCollection, ownerEndElement: Model.InternalElement)); break; case CSharpDifferenceType.Changed: changes.Add(CodeToModelOperationFactory.Instance.CreateAssociation( specialization: AssociationModel.SpecializationType, specializationId: AssociationModel.SpecializationTypeId, targetEndElement: (IElement)typeNameReference.Element, targetEndName: member.Current.Identifier, targetEndIsNullable: typeNameReference.IsNullable, targetEndIsCollection: typeNameReference.IsCollection, ownerEndElement: Model.InternalElement)); break; } break; } } return changes; } ``` ## Summary This article has explained how to implement code to design synchronization for templates with working real world examples. --- # About T4 Templates This article serves the purpose of informing the reader about the essentials of T4 which is an option for creating Templates in your [Modules](https://docs.intentarchitect.com/docs-md/application-development/applications-and-solutions/about-modules/about-modules.md). ## What T4 Templates are > ...a T4 text template is a mixture of text blocks and control logic that can generate a text file. The control logic is written as fragments of program code in Visual C# ... The generated file can be text, such as a web page, or a resource file, or program source code in any language. > > *- [Microsoft Documentation: Code Generation and T4 Text Templates](https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022)* Similar concepts in other languages and frameworks: * [Python: Jinja](https://jinja.palletsprojects.com/en/3.0.x/templates/) * [React.js: JSX](https://reactjs.org/docs/introducing-jsx.html) * [ASP.NET Razor](https://www.w3schools.com/asp/razor_syntax.asp) * [Java Server Pages](https://www.baeldung.com/spring-template-engines#java-server-pages) > [!NOTE] > Though your template will be aimed at generating code for a given file or programming language, the control logic of the template itself will be written in C#. > [!NOTE] > If you're using Visual Studio (for Windows), we recommend installing an extension to add syntax highlighting support for `.tt` files, such as [tangible T4 Editor](https://t4-editor.tangible-engineering.com/T4-Editor-Visual-T4-Editing.html), [devart T4 editor](https://www.devart.com/t4-editor/) or [T4Editor by Tim Maes](https://marketplace.visualstudio.com/items?itemName=TimMaes.t4editor). ## T4 Text Blocks and Control Blocks Assume the following template as an example (headers still to follow): ```csharp using System; using System.Collections.Generic; [assembly: DefaultIntentManaged(Mode.Fully)] namespace <#= Namespace #> { public class <#= ClassName #> : <#= GetBaseType() #> { <# foreach(var attribute in Model.Attributes) { #> public <#= GetTypeName(attribute) #> <#= attribute.Name.ToPascalCase() #> { get; set; } <# } #> <# foreach(var associationEnd in Model.AssociatedClasses.Where(x => x.IsNavigable)) { #> public <#= GetTypeName(associationEnd) #> <#= associationEnd.Name.ToPascalCase() #> { get; set; } <# } #> <# foreach (var method in Model.Operations) { #> public <#= GetTypeName(method) #> <#= method.Name.ToPascalCase() #>(<#= string.Join(", ", method.Parameters.Select(s => $"{GetTypeName(s)} {s.Name.ToCamelCase()}")) #>) { throw new NotImplementedException(); } <# } #> } } ``` ### Code Blocks #### Standard Code Blocks Notice the use of these symbols: `<#`, `#>`. ```csharp <# foreach(var attribute in Model.Attributes) { #> public <#= GetTypeName(attribute) #> <#= attribute.Name.ToPascalCase() #> { get; set; } <# } #> ``` Based on the example above, notice that the markers `<#` and `#>` help the T4 engine differentiate when it is in a text block and when it is in a code block. Anything that sits between `<#` and `#>` indicates a code block. This is where your C# control logic can be written which will manipulate the template content that gets generated. In the example above there is `<# foreach(var attribute in Model.Attributes) { #>` and `<# } #>` as separate blocks. This is broken up to allow for a text block between them to be generated based on each `attribute` found in `Model.Attributes`. So if there were two attributes in `Model.Attributes`: * Type: `string`, Name: `name` * Type: `int`, Name: `age` It will generate this (including the whitespace found on that line): ```csharp public string Name { get; set; } public int Age { get; set; } ``` #### Expression Code Blocks Notice the use of these symbols: `<#=`, `#>`. Those are markers that tell the T4 engine that the content between them are (expression) control blocks and not text blocks or directives. ```csharp <#= attribute.Name.ToPascalCase() #> ``` This piece of code will instruct the T4 engine to execute the code expression inside it, expect a result and write out the result to the template at runtime. So if `attribute.Name` was `firstName` at runtime, it will first evaluate the expression `attribute.Name`, evoke `ToPascalCase()` on that result which will produce `FirstName` and write out `FirstName` in the position where this Control Block is located in the template. You will find an example in the Standard Control Block section. ### Text Blocks This is text content that will be directly written to the output generated file. These blocks (as mentioned before) may be situated between (standard) code blocks which would mean that based on the control logic, it will be manipulated or it may have (expression) code blocks which will substitute values or expressions inside them. ## T4 Template Header Directives The next section to cover are the Header Directives. This is at least 1 line of code on top of your T4 file that typically look like this: ```csharp <#@ template language="C#" inherits="CSharpTemplateBase" #> <#@ output extension=".cs" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Linq" #> <#@ import namespace="Intent.Modules.Common" #> <#@ import namespace="Intent.Modules.Common.Templates" #> <#@ import namespace="Intent.Modules.Common.CSharp.Templates" #> <#@ import namespace="Intent.Templates" #> <#@ import namespace="Intent.Metadata.Models" #> ``` Notice the use of these symbols: `<#@` and `#>`. Those are markers that tell the T4 engine that the content between them are directives and not text blocks or control blocks. Directives gives the T4 engine clear instructions on how to generate the content that follows after them. ### Template Directive ```csharp <#@ template language="C#" inherits="CSharpTemplateBase" #> ``` Instructs the T4 template that the control logic should be interpreted as C# code and that the template control logic should inherit a .NET class `CSharpTemplateBase`. This will expose any methods, properties, etc. to the template which the developer can leverage when developing templates for their own Modules. ### Output Extension Directive ```csharp <#@ output extension=".cs" #> ``` This directive is typically not needed for designing templates for your Modules, but it sometimes is handy to tell your IDE what kind of output file extension you're busy with so that you *might* get some syntax color highlighting for the actual template content and not just for the control logic blocks. ### Namespace Directive ```csharp <#@ import namespace="System.Collections.Generic" #> ``` Instructs the T4 template to also include Types found at the namespace `System.Collections.Generic` to be included in this template for use. ## Learn more * [Code Generation and T4 Text Templates](https://docs.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022) --- # Configuring a Template's default output location ## Template Outputs `Template Output`s in the designers are used by the Software Factory to know where on the file system that template output should be written, in particular under which sub-folder. As modules are installed, for each template within them, a `Template Output` is automatically created with its name being the value of the `TemplateId` property of the template as specified during module building. To see how Template Outputs affect the Application Development side [read this](https://docs.intentarchitect.com/docs-md/application-development/software-factory/about-template-output-targeting/about-template-output-targeting.md). ## Roles and Default Locations Templates in the module builder can have their `Role` and `Default Location` configured. These are used by Intent Architect during module installation to determine where the template's `Template Output` should be placed. ![Module Builder Template Role Specification](images/module-builder-template-role-specification.png) As part of the module installation Intent Architect will search "Output Configuration" designers for a `Role` element with a name matching the `Role` value captured in the Module Builder. If a `Role` with a matching name is found, then Intent Architect will then add any additional sub-folders specified in the `Default Location` as needed and finally place the new `Template Output` for the template within the correct sub-folder as specified by the template's `Default Location`. If `Default Location` was blank, then the `Template Output` is placed in the same folder as the `Role`. If no `Role` with a matching name was found then the `Template Output` is placed in the "root" folder of the designer. The following example depicts a Template created in the Module Builder and installed in a target Application. ![Module Builder Template Settings Relative Location Example](images/module-builder-template-settings-example-relative-location.png) The `Role` is set to `Distribution` and `Default Location` is set to `Controllers`. ![Output Configuration Relative Location Example](images/output-config-example-relative-location.png) In the example above the `Distribution` `Role` can be identified by the blue badge icon and at the same tree depth is the `Controllers` Folder. Once that Module is installed, it will create the `Template Output` named `Intent.AspNetCore.Controllers.Controller` where the `Role` named `Distribution` is located but in a folder location relative to the `Role` which is `Controllers`. > [!NOTE] > Re-installing a Module may cause unassigned Template Outputs to be relocated, however assigned Template Outputs will remain untouched. This will allow you as a user to customize the output layout as you need it to be. ## Frequently asked questions ### What does the "More than one template registration is trying to output to the same path" error mean? This occurs when there is more than one Template attempting to output to the same path on your file system. Possible causes for this are: | Description | Resolution | |-----------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Multiple modules installed with Templates that target the same file (the modules aren't intended to be installed together). | Uninstall the conflicting module. | | Multiple models with conflicting names, e.g. two services that have the same name. | Rename or delete conflicting model (in this case, one of the services). | | You have written a module that is outputting a file which is already generated by another module, e.g. `appsettings.json`, `Startup.cs`, etc. | Templates for files such as these tend to have extensibility points provided by the module authors, consider consulting the original module's documentation, e.g. see [this how-to](https://docs.intentarchitect.com/docs-md/module-building/templates-csharp/how-to-update-appsettings-json-files/how-to-update-appsettings-json-files.md) for example. | --- # How to generate Markdown files Intent Architect has first class support for generation of Markdown files. These are particularly useful for creating things like AI skill and instruction files. To create a Markdown template, open the `Module Builder` designer, right-click the package and select `New File Template`: ![Select File Template](./images/select-file-template.png) > [!NOTE] > You require Intent.ModuleBuilder v 3.18.6+ Module for this option to appear. In the properties panel, set the `Templating Method` to `Markdown File Builder`: ![Select MarkDown File Builder](./images/select-mark-down-builder.png) Run the Software Factory Execution. The following template class will be generated: ```csharp public class MyMarkdownTemplate : MarkdownBaseTemplate, IMarkdownFileBuilderTemplate { ... [IntentManaged(Mode.Fully, Body = Mode.Ignore)] public MyMarkdownTemplate(IOutputTarget outputTarget, object model = null) : base(TemplateId, outputTarget, model) { WithContentHashing = true; MarkdownFile = new MarkdownFile($"MyMarkdown") .FromMarkdown(""" --- name: my-markdown-output description: what its about. --- # My Markdown File Your Content Here """); } ... } ``` > [!NOTE] > You require the Intent.Common v 3.11.1+ NuGet Package. ## MarkdownFile Builder Usage --- ## Creating a file from scratch ```csharp MarkdownFile = new MarkdownFile("SKILL", relativeLocation: ".agents/skills/my-skill") .WithFrontMatter(fm => { fm.Set("name", "my-skill"); fm.Set("description", "Does something useful."); }) .WithSection("Overview", section => { section.WithText("This skill does something useful."); }) .WithSection("Rules", section => { section.WithListItems(""" - Always prefer existing patterns. - Never modify unrelated code. """); }); ``` --- ## Seeding from an existing Markdown string `FromMarkdown` parses the string immediately, so any `OnBuild` augmentations registered afterwards will see the fully-parsed sections and front matter. ```csharp MarkdownFile = new MarkdownFile("SKILL", relativeLocation: ".agents/skills/my-skill") .FromMarkdown(existingMarkdownString); ``` --- ## Front matter ```csharp file.WithFrontMatter(fm => { fm.Set("name", "my-skill"); // add or update fm.Set("version", "1.0"); fm.Remove("contentHash"); // remove a key }); ``` --- ## Adding and configuring sections Section titles are matched case-insensitively. You can pass a plain title (`"Core rules"`) or a heading-prefixed title (`"## Core rules"`) - both resolve to the same section. ```csharp // Get-or-create at the end of the file file.WithSection("Core rules", section => { section.WithListItem("Always do X."); section.WithListItem("Never do Y."); }); // Insert before a named section (inherits that section's heading level) file.BeforeSection("Output expectations", "Entity Framework guidance", section => { section.WithListItems(""" - Do not call repository.Update(...) when using EF repositories. - EF tracks loaded entities automatically. """); }); // Insert after a named section file.AfterSection("Core rules", "Extended rules", section => { section.WithListItem("An additional rule."); }); ``` `BeforeSection` and `AfterSection` both: - Match the anchor section at any heading depth (`#`, `##`, `###`, etc.) - Create the new section at the **same heading level** as the anchor (pass an explicit `headingLevel` to override) - Fall back to appending at the end when the anchor section is not found --- ## Augmenting an existing section ```csharp file.ConfigureSection("Core rules", section => { // Append a new rule section.WithListItem("One more rule."); // Insert immediately after a specific item section.AfterListItem( item => item.Content.Contains("Never do Y"), "Also never do Z."); // Remove matching items section.RemoveListItem(item => item.Content.Contains("obsolete rule")); }); ``` --- ## Removing a section ```csharp file.RemoveSection("Deprecated section"); ``` --- ## Checking for a section ```csharp if (!file.HasSection("Entity Framework guidance")) { file.WithSection("Entity Framework guidance", section => { ... }); } // Or find it and work with it directly var section = file.FindSection("Core rules"); ``` --- ## Sub-list items Indent sub-items with two spaces per level. Both `WithListItems` (bulk string) and `FromMarkdown` (parsed files) preserve indentation depth. ```csharp section.WithListItems(""" - Top-level rule. - Sub-item detail. - Another sub-item detail. - Another top-level rule. - Allowed exception (rare): - Only when AutoMapper is not reasonable. - Must include an inline comment explaining why. """); ``` The indent level of a new item added via `AfterListItem` is inherited from the matched sibling automatically. --- ## Adding text and code blocks to a section ```csharp file.WithSection("Examples", section => { section.WithText("The following snippet shows the pattern:"); // Without a title section.WithCodeBlock(""" var result = await _repository.GetAsync(id, cancellationToken); result.Apply(command); """, language: "csharp"); // With a title - rendered as a bold label above the fence section.WithCodeBlock(""" public class CustomerDtoProfile : Profile { public CustomerDtoProfile() { CreateMap(); } } """, language: "csharp", title: "AutoMapper profile example"); }); ``` A titled code block renders as: ```text **AutoMapper profile example** ``` ```csharp public class CustomerDtoProfile : Profile ... ``` --- ## Ordered lists Ordered list items are numbered automatically. A numbered sequence interrupted by a text block (e.g. indented sub-bullets) continues its counter rather than resetting. ```csharp file.WithSection("Workflow", section => { section.WithOrderedListItem("Read the existing handler."); section.WithOrderedListItem("Identify missing capabilities."); section.WithOrderedListItem("Implement and verify."); }); // Or bulk-parse a raw ordered list string section.WithListItems(""" 1. Read the existing handler. 2. Identify missing capabilities. 3. Implement and verify. """); ``` --- ## Extending Markdown Templates You can easily intercept and modify / compose Markdown files using the MarkdownBuilder, below is an example of a **Software Factory Extension** which can find and modify and existing Markdown Template, in this scenario adding `AutoMapper guidance` before the `Output expectations` section of the file. ```csharp protected override void OnAfterTemplateRegistrations(IApplication application) { var mdTemplate = application.FindTemplateInstance("MyMarkdownTemplateId"); mdTemplate?.MarkdownFile.OnBuild(file => { file.BeforeSection("Output expectations", "AutoMapper guidance", section => { section.WithListItems(""" - Any read/query method, including application services, that returns Application-layer DTOs (*Dto) derived from Domain entities must use AutoMapper. - Do not manually construct DTOs (`new XxxDto { ... }`) on read/query paths. - If the required mapping does not exist, create it: - Add an AutoMapper Profile. - Include mapping extension methods in the same file, matching existing conventions. - Before using repository `ProjectTo` operations, verify that the required AutoMapper mappings exist. - Manual DTO construction is allowed only when the DTO is a non-entity-shaped view model/aggregation and AutoMapper is not reasonable. - This must include an inline code comment explaining why AutoMapper is not reasonable. - “Mapping doesn’t exist yet” is not a valid exception. """); section.WithCodeBlock(""" public class CustomerDtoProfile : Profile { public CustomerDtoProfile() { CreateMap(); } } public static class CustomerDtoMappingExtensions { public static CustomerDto MapToCustomerDto(this Customer projectFrom, IMapper mapper) => mapper.Map(projectFrom); public static List MapToCustomerDtoList(this IEnumerable projectFrom, IMapper mapper) => projectFrom.Select(x => x.MapToCustomerDto(mapper)).ToList(); } """, "csharp", "Example:"); }); }); } ``` ## What is ContentHashing ? If you enable `ContentHashing` the template will include a content hash in the Markdowns Frontmatter, which can be used to detect if the file content has changed. If the file content has been changed, say by the developer, the template will automatically stop changing the file and preserve the developers changes. When ever the template wants to change the MD file, it will only do so if the current content is unmodified. > [!NOTE] > If you have a modified file and you want to get back to the generated version simply remove the `contenthash` from the Frontmatter. --- # How to Access Designer Models in Templates? When building modules using Intent Architect, it is very common to want to use the metadata you have modeled in the designers as part of the code generation. For example, if you've built a Domain model, you might want to generate a set of C# classes as the code realization of that domain model. This article covers how to achieve this. ## Overview of Designer Models and Their Usage Within Templates When using Intent Architect, you build up metadata models in various designers. For example, in the `Domain Designer`, you create a domain model reflecting your business domain. This designer describes concepts like `Class`es, `Attribute`s, `Associations`, etc. All of these concepts are models in their own right and collectively make up your domain model. ![Domain Model Example](./images/domain-model-example.png) If you select individual items in the Domain Designer, you can see what type it is by looking at the Properties pane. For example, if you select `Customer`, you can see this is of type `Class`. In code, this will be represented by a `ClassModel`. ![Class Model](./images/properties-class.png) Now, if you select an attribute on the `Customer`, you can see this is of type `Attribute`. In code, this will be represented by an `AttributeModel`. ![Attribute Model](./images/properties-attribute.png) These models can then be used as data for your templates to work with. Here is a simple example of reading this data from within a template: ```csharp // Get the Domain Designer for my currently running Application var domainDesigner = ExecutionContext.MetadataManager.Domain(ExecutionContext.GetApplicationConfig().Id); var classModels = domainDesigner.GetClassModels(); ``` Based on what we can visually see in the Domain Designer above, this would be a list of 5 `ClassModel`s, namely: - Preferences - Customer - Address - Order - OrderItem This is a simplified view of what the ClassModel looks like: ```csharp public class ClassModel : ... { public const string SpecializationType = "Class"; public const string SpecializationTypeId = "04e12b51-ed12-42a3-9667-a6aa81bb6d10"; public string Name { get; } public bool IsAbstract { get; } public IEnumerable GenericTypes { get; } public IEnumerable Stereotypes { get; } public FolderModel Folder { get; } public IList Attributes { get; } ... } ``` There are two common scenarios for accessing this data: - Binding Models to the template within the `Module Designer`. - Fetching the models using `MetadataManager` from within a template. > [!NOTE] > `ClassModel` and the `Domain` extension method off of `ExecutionContext` will not be present in your own module by default. As Intent Architect is a modular extensible platform, these concepts are bundled and packaged with the `Domain Designer`. The next section covers how to set this up. ## Accessing the Designer Data in Your Own Module Assuming you are building your own Module and you would like to access the metadata from the Domain Designer, you can simply install the `Intent.Modelers.Domain` with the `Install Metadata only` option checked. The option can be made visible by clicking on the Options arrow: ![Install Domain Designer](./images/install-designer.png) Next, you may need to add the Designer's `NuGet` package to your solution. Simply add the `Intent.Modules.Modelers.Domain` package to your module in your IDE (e.g., Visual Studio). > [!NOTE] > Certain actions in the `Module Builder` will cause the designer package to be automatically added. The NuGet package name is convention-based off the Module name, i.e., the same as the module name with a `.Modules` after the `Intent` part. For example, `Intent.Modelers.Domain`'s NuGet package is `Intent.Modules.Modelers.Domain`. The version numbers of NuGet packages correlate with the version of the Modules, e.g., a version 3.9 module will have a version 3.9 NuGet package. You should now have access to all the APIs you need to access the Designer data, which will be in the `Intent.Modelers.Domain.Api` namespace. This includes: - Designer's Model classes - Extension methods to `MetadataManager` for accessing the designer's Model classes - Stereotype extension methods > [!NOTE] > This article uses the `Domain Designer` as an example, but the same principles apply to any of the `Designer Modules`. ## Designer Extensions It is worth noting that Designers can be extended, i.e., new functionality added through additional modules. For example, the `Intent.Modelers.Services.CQRS` module extends the `Intent.Modelers.Services` designer, introducing `CQRS` paradigm models to the `Services Designer`. This works exactly the same as mentioned above. However, note that you would need to add these Modules and NuGet packages if you want to access the extension data. ## Common Intent Architect Designers ### Domain Designer (Intent.Modelers.Domain) This Designer is centered around describing your Domain / Persistence model. Common Extensions: - **Modelers.Domain.ValueObjects** - **Modelers.Domain.Services** - **Modelers.Domain.Events** ### Services Designer (Intent.Modelers.Services) This Designer is used to model your Services and, more broadly, your application layer. Common Extensions: - **Modelers.Services.CQRS** - **Modelers.Services.DomainInteractions** - **Modelers.Services.EventInteractions** - **Modelers.Services.GraphQL** ### Visual Studio Designer (Intent.Modelers.Services) Model how Intent Architect integrates with Visual Studio. ## Accessing the Models in Template Code As mentioned previously, these models can be accessed through extension methods off the `IMetadataManager` interface. For example: ```csharp // Get the Domain Designer for my currently running Application var domainDesigner = ExecutionContext.MetadataManager.Domain(ExecutionContext.GetApplicationConfig().Id); // Get all the Class models from the Domain Designer var classModels = domainDesigner.GetClassModels(); ``` The `IMetadataManager` interface can be accessed in the following ways: ### Within a Template Which Inherits from `IntentTemplateBase` `IMetadataManager` is available on the template instance through the `ExecutionContext.MetadataManager` property. ```csharp var metadataManager = ExecutionContext.MetadataManager; var metadataManager = this.ExecutionContext.MetadataManager; ``` ### Within a `FactoryExtension` `IMetadataManager` is available on the `IApplication` interface. ```csharp public class MyFactoryExtension : FactoryExtensionBase { ... protected override void OnAfterTemplateRegistrations(IApplication application) { var metadataManager = application.MetadataManager; } } ``` > [!NOTE] > This section covers fetching the Designer models directly within your template. Another way to get access to Designer Models in your templates is to data bind them to the template. In this scenario, the Model(s) will be injected into your template's constructor. [Data-binding Designer Models to Templates](https://docs.intentarchitect.com/docs-md/module-building/templates-general/how-do-i-bind-designer-models-to-templates/how-do-i-bind-designer-models-to-templates.md) ## See More - [Data-binding Designer Models to Templates](https://docs.intentarchitect.com/docs-md/module-building/templates-general/how-do-i-bind-designer-models-to-templates/how-do-i-bind-designer-models-to-templates.md) --- # Data-binding Designer Models to Templates When adding `Template`s to your own module, several template types support data-binding to designer models. Commonly used templates that support this feature: - C# Template - File Template These templates allow you to select how the data models affect the template by specifying a `Type`: - **Single File**: The template will generate a single file regardless of how many models are bound. - **File Per Model**: The template will generate multiple files, one for each model bound. - **Custom**: This option allows you to completely customize this process and is beyond the scope of this article. ![Select Template Type](./images/template-type.png) Next, in the `Template Settings`, you can specify which `Designer` and which `Model` within that designer you would like to data-bind your template to. ![Bind the Model Type](./images/data-bind-template.png) > [!NOTE] > If the designer you are looking for is not in the drop-down, make sure you have added the Designer module as a reference. [How to Access Designer Models in Templates?](https://docs.intentarchitect.com/docs-md/module-building/templates-general/how-do-i-access-designer-models/how-do-i-access-designer-models.md) In the above example, we are binding our template to the `Domain Designer`'s `Class` model. This will set up your template such that the designer model(s) are injected into your template's constructor, as per your selections. Here are examples of what the code would look like: `Single File` Template Example: ```csharp public partial class MySingleTemplate : CSharpTemplateBase>, ICSharpFileBuilderTemplate { ... // Data-bound designer models public MySingleTemplate(IOutputTarget outputTarget, IList model) : base(TemplateId, outputTarget, model) { ... } ... } ``` `File Per Model` Template Example: ```csharp public partial class MyFilePerModelTemplate : CSharpTemplateBase>, ICSharpFileBuilderTemplate { ... // Data-bound designer model public MyFilePerModelTemplate(IOutputTarget outputTarget, ClassModel model) : base(TemplateId, outputTarget, model) { ... } ... } ``` > [!NOTE] > `File Per Model` templates always require a data-binding, whereas with `Single File` templates, data-binding is optional. If you do not bind a model, the constructor model parameter will be of type `object` and have a value of `null`. ## How Can I Filter Which Designer Models are Bound to My Template? Templates are instantiated through a factory, and this factory is responsible for binding the data. These factories are commonly referred to as template registrations. When you look at the source code for a template, you will notice it has a corresponding template registration file. Here are examples of the registrations for the above files. `Single File` Template Registration (with data bound): ```csharp public class MySingleTemplateRegistration : SingleFileListModelTemplateRegistration { ... // Template Factory Method public override ITemplate CreateTemplateInstance(IOutputTarget outputTarget, IList models) { return new MySingleTemplate(outputTarget, models); } // Select which data to bind public override IList GetModels(IApplication application) { return _metadataManager.Domain(application).GetClassModels().ToList(); } } ``` `File Per Model` Template Registration: ```csharp public class MyFilePerModelTemplate : FilePerModelTemplateRegistration { ... // Template Factory Method public override ITemplate CreateTemplateInstance(IOutputTarget outputTarget, ClassModel model) { return new MyFilePerModelTemplate(outputTarget, model); } // Select which data to bind public override IList GetModels(IApplication application) { return _metadataManager.Domain(application).GetClassModels(); } } ``` In either scenario, to filter the data which is bound to your template, you can simply alter the `GetModels` method to apply the desired filter to the data. Here are some examples: ### Simple Filter Sample This filter will select all `Class`es which are not abstract. ```csharp [IntentIgnore] public override IList GetModels(IApplication application) { // Return all non-abstract classes return _metadataManager.Domain(application) .GetClassModels() .Where(c => !c.IsAbstract); } ``` ### Complex Filter Sample This filter will select all `Class`es which are aggregate roots. ```csharp [IntentIgnore] public override IList GetModels(IApplication application) { // Return all aggregate roots return _metadataManager.Domain(application) .GetClassModels() .Where(c => IsAggregateRoot(c)); } public bool IsAggregateRoot(ClassModel classModel) { AssociationEndModel[] source = (from x in classModel.AssociationEnds() where x.IsSourceEnd() && !x.IsCollection && !x.IsNullable select x).ToArray(); return !source.Any(); } ``` ### Stereotype Filter Sample This filter will select all `Class`es which have the `Table` stereotype applied. ```csharp [IntentIgnore] public override IList GetModels(IApplication application) { // Return all Classes which have the `Table` stereotype return _metadataManager.Domain(application) .GetClassModels() .Where(c => c.HasTable()); } --- # How to filter Template Instances When creating Templates that are configured to create a _File per Model_, you may need to filter which Models create Template Instances. An easy place to do this is in the Template Registrations. For this How-to Guide, we will imagine a use case where we want to _only_ create Repositories for Entities that have been flagged with a [Stereotype](https://docs.intentarchitect.com/docs-md/module-building/stereotypes/how-to-use-stereotypes/how-to-use-stereotypes.md). Let's assume that we have an `Entity Settings` Stereotype with a check-box (boolean) property `Has Repository`. It is configured to automatically apply to `Domain` entities. Finally, we will assume that a C# Template called `RepositoryTemplate` has been created with the type `File Per Model` and Designer and Model Type set to `Domain` and `Class` retrospectively: ![csharp-template-repository](images/csharp-template-repository.png) _Properties of the Repository Template, specified in the Module Builder._ To apply filtering to our Template, in the Visual Studio solution: 1. Navigate to the `RepositoryTemplateRegistration.cs` file. 2. Alter the `GetModels` method to filter based on the Stereotype, as follows: ```csharp public override IEnumerable GetModels(IApplication application) { return _metadataManager.Domain(application).GetClassModels() .Where(x => x.GetEntitySettings().HasRepository()) .ToList(); } ``` 3. Recompile the Project (`.csproj`). 4. Reinstall the Module and rerun the Software Factory Execution. > [!TIP] > Any metadata can be used to filter the creation of Template Instances. You may decide to use Stereotypes to be explicit or base the filtering on a convention like the name of the entity. ## See more - [How to query models from different designers](https://docs.intentarchitect.com/docs-md/module-building/templates-general/how-to-query-models-from-different-designers/how-to-query-models-from-different-designers.md) --- # How to generate static files This article will explain a more effective way to generate many files that are static in nature during a Software Factory Execution without having to create an individual Template for each one. ## Create a Module Inside Intent Architect create a new Module for example `MyStaticContent` and click on `NEXT`. There is no need to specify an additional programming language for this Module so you may click on `CREATE` when done. Open the `Module Builder` designer and create a `New Static Content Template` in the package provided for this Module. Give the template a name for example `MyHtmlFiles`. In the properties panel specify the following: * Content Subfolder. Give it the path for example `htmlFiles`. * Binary File Globbing Patterns. Supply file globbing patterns, to identify "binary" files in your content, this would include any file which should not be processed as text based files . * Role. Give it the name of `Static.Html` for example. >[!NOTE] >The `Content Subfolder` can be left blank if you're only expecting to make one Static Content Template. Save and run the Software Factory Execution. ## Setting up the content folder In your Module project where your Visual Studio project is located for your Module, copy the content that you wish to distribute with your Module, inside the `content/htmlFiles` (example) folder. ## Setting up a relative output location prefix In situations like having static content files be placed in a Solution Folder of the Visual Studio designer, it may be useful to have the relative output paths prefixed with one or more sub folders so that the output will be placed in these sub-folders on the file system, but these "prefixed" folders will not cause generation of Solution Folders within the `.sln` file. Simply override the `RelativeOutputPathPrefix` getter property to do this: ```csharp public override string RelativeOutputPathPrefix => "InRoot/SubFolderOfInRoot"; ``` ## Content custom keyword substitution Though the content being processed will be static, there is a **basic keyword substitution** feature which can be set inside the Registration class by populating the `Replacements` property. For example: ```cs public override IReadOnlyDictionary Replacements => new Dictionary { {"Today", DateTime.Today.ToString("yyyy-MM-dd")} }; ``` So any file content that features the following phrase `<#= Today #>` will be replaced by the Date for the current day when the content was generated. > [!IMPORTANT] > Please ensure that a single space is preserved between the `<#=`, `keyword` and `#>` symbols. ## Binary File Globbing Patterns >[!NOTE] >Binary File support was introduced Intent Architect v4.1, `Intent.Common` v3.5.0 and `Intent.ModuleBuilder` v3.7.0 modules, ensure you have at least this versions installed for this to work. These patterns are used to identify binary or non text based files, so that they don't get processed as text based templates. This patterns are standard `File globbing` patterns, for more information [see](https://learn.microsoft.com/dotnet/core/extensions/file-globbing). The standard exclusions are images (jpg, png, ico), excel files and pdfs. This list can be adapted to your specific requirements. ```text *.jpg *.png *.ico *.xlsx *.pdf `````` --- # How to query models from different designers When a Template is configured to be a _File per Model_ a `Designer` and `Model Type` need to be specified under `Template Settings`. For additional Designers along with their Model Types to be available for selection, their corresponding Module needs installed with the `Install Metadata only` option checked. For the purposes of demonstration, this how-to will explain using the `Domain` Designer and the `Class` Model Type from it. ## Install the module Install the `Intent.Modelers.Domain` with the `Install Metadata only` option checked, the option can be made visible by clicking on the `Options` arrow: ![Install Metadata Only](images/install-module-metadata-only.png) ## Select the `Designer` and `Model Type` Create or select a Template and in the properties panel and for `Designer` select `Domain` and for `Model Type` select `Class`: ![Select Designer and Model Type](images/template-select-designer.png) ## Run the Software Factory Run the Software Factory and open the Template's corresponding Registration class. It has generated a `GetModels` method that will look in the `Domain` for `Class` models using the `GetClassModels()` query method. When the module is run in an Application an instance of the Template will be instantiated for each `Class` which was modelled in the Domain Designer. ```csharp public override IEnumerable GetModels(IApplication application) { return _metadataManager.Domain(application).GetClassModels(); } ``` ## Other designers For any other Designer the query will be different depending on the Designer and the Elements that's provided by the Designer, for example using the `Services` Designer and selecting `Service` as the Model Type will generate the following: ```csharp public override IEnumerable GetModels(IApplication application) { return _metadataManager.Services(application).GetServiceModels(); } ``` ## See more - [How to filter Template Instances](https://docs.intentarchitect.com/docs-md/module-building/templates-general/how-to-filter-template-instances/how-to-filter-template-instances.md) --- # Resolving type names Intent Architect has mechanisms and infrastructure to make it easy to resolve type names and generate any additional code to be able to use that type (EG: project references, using directives, imports, etc). > [!NOTE] > > We strongly recommend _against_ using strategies like conventions to work out type names of classes which are generated by templates. There are many edge cases you will have to solve for yourself including handling collections or ensuring that appropriate `using` or `import` directives are also generated. Managing such complexities yourself invariably leads to more complicated templates which might not be able to handle all scenarios correctly. ## The `GetTypeName(...)` method The purpose of the `GetTypeName` system is to resolve dependencies. As part of that it also gets class names, configures any required project relationships (such as project references in `.csproj` files) and adds any required using directives/import statements as appropriate for the particular language being used. By routing all these concerns through a single method, it makes templates simpler which vastly improves maintainability of them and your modules. To demonstrate, the `.tt` file to generate a POCO is as simple as: ```csharp <#@ template language="C#" inherits="CSharpTemplateBase" #> <#@ import namespace="Intent.Modules.Common.Templates" #> <#@ import namespace="Intent.Modules.Common.CSharp.Templates" #> [assembly: DefaultIntentManaged(Mode.Fully)] namespace <#= Namespace #> { public class <#= ClassName #> { <# foreach (var field in Model.Fields) { #> <#= GetTypeName(field) #> <#= field.Name.ToPascalCase() #> { get; set; } <# } #> } } ``` Depending on the `GetTypeName` overload used and the type trying to be resolved, it may internally resolve an appropriate template instance. From this resolved template instance, information such as namespaces, package names, output locations and dependency requirements can be determined. ### TypeResolvers It is normal for some overloads of `GetTypeName` or if it is used with certain types, that there is no template instance to be resolved, for example for elements which reference `Type Definition`s which are for "basic" types such as a `string` or an `int`. In such cases language specific infrastructure, known as TypeResolvers, are used to determine how the requested type should be resolved. For example, when resolving an `int` in `C#` it should be resolved as `int`, but in `TypeScript` it should be resolved as a `number`. Base template types (such as `CSharpTemplateBase` or `TypeScriptTemplateBase`) have been pre-configured with language appropriate TypeResolvers. These language specific TypeResolvers have been configured to look at Stereotypes applied on `Type Definition` which can be used to specify language specific information about that type. For example, the TypeResolver used on `CSharpTemplateBase` looks for a `C#` Stereotype, and if found uses the properties on it, such as `Namespace`, when resolving the type. ## The `AddTypeSource(...)` method The `GetTypeName` system requires some additional information to know which template instances to match referenced types against, otherwise it falls back to outputting only the "name" of the element of the referenced type and doesn't know of any references/imports/using directives which may have been needed. For any of the referenced types which are generated by templates (even the same template type), you will need to use `AddTypeSource(...)` in the partial to let the `GetTypeName` system know which template instances to check against. For the above POCO example, in the template partial's constructor you would need to use `AddTypeSource` for each of the `TemplateId`s which are used, for example: ```csharp AddTypeSource(TemplateId); AddTypeSource(EnumTemplate.TemplateId); ``` Making the entire file look something like: ```csharp using Intent.Engine; using Intent.Modelers.Services.Api; using Intent.Modules.Common; using Intent.Modules.Common.CSharp.Templates; using Intent.RoslynWeaver.Attributes; [assembly: DefaultIntentManaged(Mode.Fully)] [assembly: IntentTemplate("Intent.ModuleBuilder.CSharp.Templates.CSharpTemplatePartial", Version = "1.0")] namespace MyModule.Templates.Dto { [IntentManaged(Mode.Fully, Body = Mode.Merge)] partial class DtoTemplate : CSharpTemplateBase { public const string TemplateId = "NewModule.Dto"; [IntentManaged(Mode.Fully, Body = Mode.Ignore)] public DtoTemplate(IOutputTarget outputTarget, DTOModel model) : base(TemplateId, outputTarget, model) { AddTypeSource(TemplateId); AddTypeSource(EnumTemplate.TemplateId); } [IntentManaged(Mode.Fully, Body = Mode.Ignore)] protected override CSharpFileConfig DefineFileConfig() { return new CSharpFileConfig( className: $"{Model.Name}", @namespace: $"{this.GetNamespace()}", relativeLocation: $"{this.GetFolderPath()}"); } } } ``` ## Resolving type names for templates fulfilling a role One of the mechanisms which can be used to decouple modules from each other is to have templates which fulfill roles and then other templates which need to refer to that template's type refer to it by the template's role name. By using a template role name instead of a particular `TemplateId`, it allows any module made at any time to fulfill that role without the "referencing" module being aware of it. An example of where this is used is with the `Intent.EntityFrameworkCore` module. Due to the non-trivial effort of making templates to generate [entity type configurations](https://docs.microsoft.com/ef/core/modeling/#grouping-configuration), many Intent Architect module builders choose to use Intent Architect's highly mature `Intent.EntityFrameworkCore` module, but in many cases they also want to have their own custom template for generating entities. With Intent Architect's support for template fulfilling roles, this is possible. The builder of the module simply needs to configure that their entity template fulfills the same template role that Intent Architect's `Intent.EntityFrameworkCore` module's `EntityTypeConfigurationTemplate` is looking for. The `EntityTypeConfigurationTemplate` has the following method to resolve the entity type: ```csharp private string GetEntityName() { return GetTypeName("Domain.Entity", Model); } ``` And then a template fulfills a role by having it configured with the same value for `Role` in its `Template Settings` in the Module Builder: ![Template settings](images/template-role.png) ## Collection formatting The final key concept to discuss is how the `GetTypeName` system knows that `stringField` should use `List` as its collection type, for this you can configure it with collection format strings. ### Setting the default collection format The `SetDefaultTypeCollectionFormat(...)` method can be called in the partial's constructor to set the default collection format for types being resolved by `GetTypeName` in that template, for example: ```csharp SetDefaultTypeCollectionFormat("System.Collections.Generic.List<{0}>"); ``` For these formats, `{0}` is what is substituted with the actual type. ### Setting the collection format for types resolved from a particular template The `AddTypeSource(...)` method has an overload with a `collectionFormat` parameter which takes in a formatted string as above, for example: ```csharp AddTypeSource(EnumTemplate.TemplateId, "{0}[]"); ``` ### Setting the collection format for a particular usage of `GetTypeName` The `GetTypeName(...)` method has an overload with a `collectionFormat` parameter which takes in a formatted string as above, for example: ```csharp return GetTypeName(field, "System.Collections.ObjectModel.Collection<{0}>"); ``` ### Forcing `GetTypeName` to resolve the non-collection version of a type Simply use `"{0}"` for the `collectionFormat` parameter, for example: ```csharp return GetTypeName(field, "{0}"); ``` ### Forcing `GetTypeName` to resolve the collection version of a type `GetTypeName` doesn't have an overload for doing this directly, but you can just manually output the collection type inline with the [non-collection version of the type](#forcing-gettypename-to-resolve-the-non-collection-version-of-a-type), for example: ```csharp return $"List<{GetTypeName(field, "{0}")}>"; ``` ## Module Builder generated `Get