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.

  • 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

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-indicator CSS 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.UtcNow for creation instead of DateTime.Now.
  • Non UTC dates should only be temporary display values, and not saved back into the db.
  • Use timestamptz instead of timestamp in postgres schema.