All projects

liveC# / ASP.NET Core / Azure App Service

faresm.dev

This site: ASP.NET Core 10 with server-rendered pages and a REST API over the same JSON-backed data, deployed to Azure App Service by GitHub Actions using OIDC, with no publish profile or client secret stored.

  • C#
  • ASP.NET Core
  • Azure App Service
  • GitHub Actions
  • OIDC
  • Razor Pages
  • REST APIs
  • JSON
  • xUnit
  • JavaScript
  • HTML5
  • CSS3
Deployment pipeline for faresm.dev A push to the main branch of the GitHub repository starts a GitHub Actions build job that builds the app, runs the tests and publishes an artifact. A deploy job downloads the artifact, signs in to Azure with an OIDC short-lived token instead of a stored secret, and deploys to Azure App Service, which serves faresm.dev over HTTPS. push to main → production GitHub repo push to main Build job build · test · publish Deploy job download artifact OIDC login short-lived token App Service Azure · Production faresm.dev HTTPS · HSTS no publish profile or client secret stored in GitHub

Overview

The site you are reading. It started as a static page that fetched JSON in the browser and was rebuilt so the server does the work: every page is rendered with Razor Pages from the same data service that backs a small REST API, so crawlers, link previews and visitors without JavaScript get complete HTML. It runs on Azure App Service at faresm.dev over HTTPS, and every push to main goes through an automated build, test and deploy pipeline.

Architecture

Content lives in two JSON files (profile and projects) outside the web root. A singleton data service reads them, caches the parsed result in memory and evicts it when a file changes. Razor Pages render the home page and one detail page per project from that service, while API controllers expose the same data at /api/profile and /api/projects. A media service discovers screenshot folders on disk and reads image dimensions from file headers, so every image ships with explicit width and height. Deployment is a two-job GitHub Actions workflow: the build job builds the app, runs the xUnit test suite and publishes an artifact; the deploy job signs in to Azure with OpenID Connect and deploys that artifact to App Service.

Key Features

  • Server-rendered pages: complete HTML for crawlers, link previews and visitors without JavaScript
  • REST endpoints (/api/profile, /api/projects, /api/projects/{slug}) backed by the same data service as the pages
  • Real 404 responses for unknown routes and project slugs
  • Screenshot galleries discovered from the filesystem, with image dimensions read from file headers
  • xUnit integration tests (WebApplicationFactory) that run in CI before every deploy
  • Deployment authenticated with OIDC federated credentials instead of a stored publish profile or client secret
  • Custom domain (faresm.dev) served over HTTPS with HSTS

Technical Challenges

  • Moving from client-side to server-side rendering without changing the JSON API or the data format behind it
  • Making sure the content files are only reachable through the API and the pages, not downloadable as raw static files
  • Deploying to Azure from CI without keeping long-lived credentials in the repository

Engineering Decisions

  • Rendered pages on the server with Razor Pages instead of adding a frontend framework, keeping JavaScript for progressive enhancement only
  • Kept content in JSON files rather than a database, since it is small, changes rarely and is edited by hand; an in-memory cache with file-change invalidation keeps reads cheap
  • Used OIDC federated credentials for the Azure login, so the workflow exchanges a short-lived GitHub token for Azure access instead of storing a deploy password
  • Read image dimensions from file headers instead of adding an imaging library dependency

Technologies Used

C# on ASP.NET Core 10 (Razor Pages and API controllers), vanilla JavaScript, HTML and CSS on the front end, xUnit with WebApplicationFactory for tests, GitHub Actions for CI/CD, and Azure App Service for hosting.

Lessons Learned

  • When the content already lives on the server, server-side rendering is the simplest fix for SEO and link previews
  • Federated identity removes the need to store and rotate a deploy secret in CI/CD
  • Integration tests against the real HTTP pipeline catch routing and status-code regressions that unit tests miss