Portfolio och CMS-plattform
Det här projektet började egentligen som något betydligt enklare. Tanken från början var att bygga en traditionell webbplats med ASP.NET Core Razor Pages där varje sida hade sin egen vy med innehåll direkt skrivet i koden.
Efter hand började jag dock fundera på hur innehållet skulle hanteras när webbplatsen växte. Att hårdkoda all text och allt innehåll direkt i Razor-vyerna fungerade, men kändes ganska begränsande och inte särskilt flexibelt. Nästa steg blev därför att flytta innehållet till tjänster och modeller för att separera innehåll från presentation.
Ju längre projektet utvecklades desto tydligare blev det att innehållet egentligen borde lagras i en databas. Samtidigt kändes det inte optimalt att skapa separata tabeller och kolumner för varje tänkbar sektion som kunde förekomma på en sida. Om varje ny innehållstyp krävde databasändringar, migrationer och nya modeller skulle systemet snabbt bli svårt att underhålla.
Det var ungefär där tankarna började röra sig mot ett CMS-liknande upplägg. Från att ha varit en enkel portfoliosida började projektet successivt utvecklas till en mer generell plattform där innehållet styrs av data istället för hårdkodade vyer.
Även om ett befintligt CMS hade kunnat lösa många av dessa problem är syftet med projektet inte bara att få fram en färdig webbplats. En stor del av motivationen har varit att lära sig nya tekniker, utforska olika arkitekturmönster och bygga något från grunden för att förstå hur systemen fungerar bakom kulisserna. Många delar av lösningen är därför mer avancerade än vad som egentligen krävs för en portfolio, men just den typen av tekniska utmaningar är också det som gör projektet roligt att arbeta med.
Övergripande arkitektur
Webbplatsen är byggd med ASP.NET Core Razor Pages och PostgreSQL som databas.
Istället för att varje sida har en egen hårdkodad struktur består systemet till stor del av sidor som byggs upp dynamiskt från innehållsblock som lagras i databasen. Samtidigt har det varit viktigt att inte låsa hela webbplatsen till CMS-strukturen. Eftersom Razor Pages i grunden är sidcentrerat finns det fortfarande möjlighet att bygga vissa sidor mer traditionellt när det passar bättre.
Exempel på detta är startsidan, sidan om mig eller andra vyer där innehållet är mer unikt och inte nödvändigtvis behöver representeras av återanvändbara innehållsblock. I sådana fall kan sidan byggas upp direkt i vyn eller med hjälp av befintliga sektionsmodeller utan att använda den generella renderingskomponenten.
Tanken är att CMS-funktionaliteten ska vara ett verktyg och inte ett krav. Om en specifik sida behöver specialanpassad logik eller innehåll som bara kommer att användas på ett enda ställe finns det inget som hindrar att den implementeras mer direkt. Detta ger en balans mellan flexibiliteten i det dynamiska innehållssystemet och enkelheten i traditionella Razor Pages när det är den mest lämpliga lösningen.
Målet har hela tiden varit att skapa en flexibel struktur där nya sidor och sektioner kan byggas på olika sätt beroende på behov, utan att den grundläggande arkitekturen behöver förändras.
Databasdesign
Databasen bygger på ett antal centrala entiteter som beskriver webbplatsens innehåll.
Varje sida representeras av en egen Page-entitet som innehåller grundläggande information om sidan, exempelvis titel, slug och metadata. Till varje sida finns sedan ett eller flera PageContent-objekt kopplade som beskriver det faktiska innehållet som ska visas.
För innehåll som återanvänds på flera ställen används separata entiteter i databasen. Exempel på detta är:
- Projektkort
- Taggar
- Övriga återanvändbara objekt
På så sätt undviks duplicering av data samtidigt som samma information kan användas på flera sidor.
Eftersom systemet bygger på ett blockbaserat innehållssystem där innehållssektioner lagras som JSON istället för i separata databastabeller blir databasstrukturen relativt enkel och kompakt. För den nuvarande omfattningen räcker denna modell väl, samtidigt som den ger stor flexibilitet när nya innehållstyper behöver introduceras. I takt med att CMS:et vidareutvecklas kommer sannolikt fler entiteter och relationer att tillkomma, men den grundläggande strukturen kan fortfarande hållas förhållandevis enkel. En sida kan exempelvis innehålla flera innehållssektioner samtidigt som samma taggar, projektkort eller andra återanvändbara objekt kan användas på flera platser i systemet.
Dynamiskt innehållssystem
Kärnan i systemet är det blockbaserade innehållssystemet.
Istället för att lagra varje innehållstyp i egna databaskolumner lagras innehållssektioner som JSON i databasen. När innehållet sparas serialiseras objekten automatiskt och när de hämtas deserialiseras de tillbaka till sina respektive modeller.
Detta gör att nya innehållstyper kan introduceras utan att databasen behöver ändras.
Detta gör att nya innehållstyper kan introduceras utan att databasen behöver ändras.
Exempel på innehållsblock som finns idag är:
- TextSection
- ImageSection
- ImageWithPositionSection
- ListSection
- HeroSection
- CardSection
Fördelen med denna lösning är att en sida kan byggas upp av valfria kombinationer av sektioner samtidigt som databasen förblir relativt enkel.
Här är ett exempel på hur ett sektions block är uppbyggt
En modell byggs upp för sektionen med dess olika egenskaper och ärver fler från en basklass.
ContentSection fungerar som basklass för samtliga innehållssektioner. Klassen innehåller gemensamma egenskaper samtidigt som JsonDerivedType-attributen används för att konfigurera polymorf serialisering och deserialisering. Detta gör att systemet automatiskt kan avgöra vilken sektionstyp som ska instansieras när innehåll hämtas från databasen.
I detta fallet är partialen uppdelad i olika delar för läsbarhetens skull. Här kontrolleras det vilken position som är vald för bilden.
Sedan renderas bilden på rätt plats i vyn, i detta fallet så är det en topp/bottnen placering.
Rendering av sidor
Alla projektsidor använder samma Razor Page för rendering.
Istället för att skapa en separat sida för varje projekt används en gemensam renderingsmodell där rätt innehåll hämtas baserat på sidans slug.
Själva renderingen sker genom en specialiserad View Component som fungerar som en central renderingsmotor för systemet. Komponenten går igenom innehållssektionerna och avgör vilken partialvy som ska användas för respektive sektionstyp.
För att göra systemet mer robust används även ICompositeViewEngine för att kontrollera att en partialvy faktiskt existerar innan den renderas. På så sätt upptäcks eventuella fel tidigt samtidigt som det blir enklare att utveckla nya innehållsblock.
Nu blir det enkelt att skapa nya sektionstyper utan att behöva ändra på renderingslogiken, det enda man behöver göra är att skapa:
- En modell
- En partialvy
Servicelager
För närvarande används främst en central PageService som ansvarar för att hämta sidor, innehåll och relaterade objekt.
Under utvecklingen har detta varit en enkel lösning som gjort det möjligt att snabbt bygga vidare på funktionaliteten. Tanken är dock att servicelagret framöver ska delas upp i mer specialiserade tjänster med tydligare ansvarsområden men just nu när systemet inte är så stort så fungerar det utmärkt.
Samtidigt finns planer på att införa en gemensam basklass för repositories för att minska duplicerad kod och skapa en mer enhetlig dataåtkomst.
Temasystem
Webbplatsen innehåller ett temasystem där användaren kan välja mellan flera olika visuella teman.
Bland de teman som finns idag återfinns bland annat:
- Sci-Fi
- Matrix
- Alien Tech
- Starfleet
Temana bygger på CSS-variabler och ett data-theme-attribut på rot-elementet. Genom att byta tema ändras färger, gradienter, skuggor, bakgrunder och visuella effekter utan att de enskilda komponenterna behöver känna till vilket tema som används. Att dölja sidoväggarna är också ett alternativ, där värdet sparas i en cookie.
Det gör det enkelt att introducera nya teman samtidigt som gränssnittet förblir konsekvent.
Responsiv design
Hela webbplatsen är byggd med responsiv design som utgångspunkt.
Layout och innehåll anpassar sig automatiskt efter skärmstorleken vilket gör att webbplatsen fungerar på:
- Dator
- Surfplatta
- Mobiltelefon
Målet har varit att samma innehåll ska fungera oavsett enhet utan att separata mobilversioner behöver utvecklas.
Prestanda och caching
Eftersom att innehållet är identiskt för alla besökare lämpar sig systemet väl för caching.
In-memory caching används för innehåll som sällan förändras, exempelvis:
- Sidor
- Innehållssektioner
- Projektkort
- Taggar
Detta minskar antalet databasförfrågningar och förbättrar laddningstiderna för besökaren.
Hemsidan är relativt liten och hade sannolikt inte behövt denna optimering, men caching används här främst som ett sätt att testa tekniken och fördjupa förståelsen för hur den kan användas i praktiken.
Analytics
Ett lättviktigt analysverktyg byggt från grunden för att ge insikt i webbplatsens trafik utan att kompromissa med besökarnas integritet. För att registrera nya besök så byggde jag en middleware och cookie-baserad samtyckeshantering.
Målet var att få en grundläggande bild av webbplatsens användning samtidigt som datainsamlingen hålls så begränsad och transparent som möjligt.
Framtida utveckling
Även om systemet idag används för den egna portfolion har arkitekturen utvecklats med betydligt större möjligheter i åtanke.
Ett naturligt nästa steg är att bygga en separat administrationsapplikation där innehåll kan hanteras genom ett grafiskt gränssnitt istället för att seedas in programmatiskt.
På längre sikt finns även tankar kring att vidareutveckla lösningen till ett mer generellt CMS som kan användas i andra projekt. Delar av funktionaliteten skulle exempelvis kunna paketeras som ett NuGet-paket för att göra systemet återanvändbart även utanför den här webbplatsen.
Oavsett hur långt projektet utvecklas har det redan fyllt sitt viktigaste syfte: att fungera som en plattform för att experimentera, lära sig nya tekniker och bygga erfarenhet kring arkitektur, datamodellering och innehållshantering i större webbapplikationer.