Controller Actions
- Do not put business logic inside of controller actions. If an action requires more then three lines of code to return a View, move the logic into another class
Views and Partial Views
-
For simple pages with few components, controller actions may return full views (cshtml files)
-
For pages with multiple components, prioritize controller actions that return partial views.
- When using HTMX to swap HTML content within a page, the HTMX should make a request to a controller action that returns a partial view ( https://www.woodruff.dev/going-modular-using-htmx-with-partial-views-in-razor-pages/)
-
Prioritize Partial views for nested objects over for-loops within .cshtml files. Instead of:

Favor:

- Reasoning:
- HTMX: If we later decide to load or update the content dynamically, the controller action can simply return the partial again, with new values injected into the list
- Alpine.js: Easily wrap partials inside x-data components
- Increased re-usability
- Reasoning:
Entities and Domain Models
- Favor rich domain models and a minimal service layer
- Entity classes should contain the business logic governing their data
- Domain Services should contain business logic that spans multiple entities.
- Favor internal value objects of immutable types over primitive types. Value objects should encapsulate self-validation.
- Value objects are not entities. They are hydrated from SQL data which may be stored as columns in the external entity table.
- If an entity contains a list of value objects, a database table should be created for the value objects, with a foreign key to the entity table. The value object table should have a composite primary key consisting of the foreign key and the value object properties.
Tailwind
- Prioritize pre-built components from the UI library, we can re-style later
- Begin development by linking Tailwind from a CDN in _Layout.cshtml, we can add the Tailwind CLI to our buildchain after the first few sprints
- Prioritize using @apply within input.css over in-line styling when possible. As an example, ff every button uses the same 10 tailwind classes, do this:

Alpine.js
- Favor re-usability by extracting logic from the HTML and into a components.js file when appropriate:

HTMX
- We are not making a single page application. We will have page navigation and URLs.
- HTMX is for targeted updates within a page, for lite SPA-like behavior without full page refreshes or manual AJAX requests.
- Never use
hx-get: - Always pair network requests with the
.htmx-indicatorCSS class to dim elements or show spinners during data transport. This prevents double-clicks while the backend is processing logic.
Error Handling with Partial Views
- No try-catch blocks or error portals for controller actions returning partial views, instead rely on standard HTTP status codes.
- In a global JS file, hook into htmx:responseError and htmx:sendError to retrieve error codes, convert to messages, and send a show-toast browser event
- Persistent Alpine.js component in the _Layout.cshtml listens for this event, pushes the messages into a toasts array, and on a set timeout removes entries
- Render with Alpine using x-for and x-transition — no external JS toast libraries
- Use unique IDs during push and filter in case of rapid-fire failures
Working with Dates
- Dates should always be UTC unless displayed by the UI.
- Therefore, default to
DateTime.UtcNowfor creation instead ofDateTime.Now. - Non UTC dates should only be temporary display values, and not saved back into the db.
- Use
timestamptzinstead oftimestampin postgres schema.