Skip to main content
Backend & architecture

System architecture.

I'm Amir and I developed this project myself. On this page I briefly explain how the application is built behind the scenes — from the data model to delivery in the browser. All figures on this page are calculated directly from the code when the application starts, not entered by hand.

amirafshar2 / AmirPortfolio
C# · .NET 6.0 4 layers 40 entities 74 controllers 31 migrations
View on GitHub
4 Layers
40 Entities
56 Admin controllers
24 ViewComponents

Tools

Tech stack.

Everything listed here is actually in use — verified against Program.cs, the .csproj files and the Dockerfile.

Backend

C# Language for all four layers of the solution.
ASP.NET Core MVC Web framework for controllers, routing and views.
Entity Framework Core 6 ORM for all data access, versioned through migrations.

Database

SQLite Embedded database — a single file, no separate server needed.

Security

ASP.NET Identity Login, registration and password hashing.
Role-based authorisation SuperAdmin/Admin/Blogger/User via [Authorize(Roles=…)].

Frontend

Razor Server-side rendering of the views (.cshtml).
Bootstrap Grid and component base in the admin area.
JavaScript Vanilla JS for interaction, no frontend frameworks.

Integrations

GitHub API Live sync of my own repositories into the admin area.
Gemini AI text generation in the blog editor.
Cloudflare Workers AI AI-generated cover images for blog posts.

DevOps

Docker Multi-stage Dockerfile for deployment on Render.

Core

The four layers.

Each layer only knows the layer directly below it.

WebApplication3 Presentation

This is where my controllers, views and view components live. I separated the admin area (Areas/Admin) completely from the public site and secured it with role-based authorisation.

Controllers/ Views/ + ViewComponents/ Areas/Admin/ TagHelpers/PermissionTagHelper.cs

Dependencies: only → Business Logic

Controllers/CvController.cs
public class CvController : Controller
{
    private readonly CvManager _cvmanager;

    public CvController(CvManager cvmanager)
    {
        _cvmanager = cvmanager;
    }

    public IActionResult Index()
    {
        var value = _cvmanager.ReadAll()
            .Where(x => x.Status == true)
            .FirstOrDefault();

        if (value != null)
        {
            ViewBag.Name = value.Name;
            ViewBag.Path = value.Picture_Path;
        }
        return View();
    }
}

Flow

The journey of a request.

What actually happens when a page of this application is opened in the browser.

Example: GET /Cv/Index

Proof, not an example

This is what it looks like in code.

No pseudocode — real, shortened excerpts from this very project, following the same example: GET /Cv/Index.

Controllers/CvController.cs
public class CvController : Controller
{
    private readonly CvManager _cvmanager;

    public CvController(CvManager cvmanager)
    {
        _cvmanager = cvmanager;
    }

    public IActionResult Index()
    {
        var value = _cvmanager.ReadAll()
            .Where(x => x.Status == true)
            .FirstOrDefault();

        if (value != null)
        {
            ViewBag.Name = value.Name;
            ViewBag.Path = value.Picture_Path;
        }
        return View();
    }
}
public class CvManager : ICvService
{
    ICvDAL _DLL;

    public CvManager(ICvDAL dLL)
    {
        _DLL = dLL;
    }

    public List<Cv> ReadAll() => _DLL.ReadAll();
    public Cv ReadById(int t) => _DLL.ReadById(t);
    public void Create(Cv t) => _DLL.Create(t);
    public void Update(Cv t) => _DLL.Update(t);
    public void Delate(Cv t) => _DLL.Delate(t);
}
public class GenericRepository<T> : IGenericDAL<T> where T : class
{
    private readonly AddDbContext _context;

    public GenericRepository(AddDbContext context)
    {
        _context = context;
    }

    public void Create(T t)
    {
        _context.Add(t);
        _context.SaveChanges();
    }

    public List<T> ReadAll() => _context.Set<T>().ToList();

    public T ReadById(int id) => _context.Set<T>().Find(id);

    public void Update(T t)
    {
        _context.Update(t);
        _context.SaveChanges();
    }
}
public class AddDbContext : IdentityDbContext<User, Roll, int>
{
    public AddDbContext(DbContextOptions<AddDbContext> options,
                         IHttpContextAccessor? httpContextAccessor = null)
        : base(options)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // Viele-zu-viele zwischen ProjectDetail und Technicalskills
        modelBuilder.Entity<ProjectDetail>()
            .HasMany(pd => pd.TechStack)
            .WithMany(t => t.projectDetails);
    }

    public DbSet<Project> projects { get; set; }
    public DbSet<Cv> cvs { get; set; }
    // … 19 weitere DbSets, ein Set pro Entity
}
builder.Services.AddDbContext<AddDbContext>(options =>
{
    options.UseSqlite($"Data Source={dbPath}");
});

builder.Services.AddIdentity<User, Roll>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 4;
})
.AddEntityFrameworkStores<AddDbContext>()
.AddDefaultTokenProviders();

// Eine generische Registrierung deckt GenericRepository<T>
// für ALLE Entities gleichzeitig ab:
builder.Services.AddScoped(typeof(IGenericDAL<>), typeof(GenericRepository<>));

builder.Services.AddScoped<ICvDAL, EfCvDAL>();
builder.Services.AddScoped<CvManager>();
Copied to clipboard.

Project structure

This is what it looks like in practice.

The solution consists of 4 projects — a classic .NET n-tier layout. Folders can be expanded.

AmirPortfolio.slnx
    • Project.cs, Cv.cs, BlogPost.cs, Certificate.cs, Video.cs …
    • Abstract/ I*DAL interfaces
    • EntityFramework/ Ef*DAL implementations
    • Repository/GenericRepository.cs
    • Migrations/ 31 EF Core migrations
    • Abstract/ I*Service interfaces
    • Concrete/ *Manager implementations
    • Controllers/ 18 public controllers
    • Views/ + ViewComponents/ 24 reusable components
    • Areas/Admin/ role-protected CMS, 56 controllers
    • Infrastructure/ Roles.cs, SlugHelper.cs, ArchitectureStatsService.cs …
    • Services/ GeminiTextService, CloudflareImageService, GitHubApiClientService
    • TagHelpers/ PermissionTagHelper.cs
    • dockerfile Multi-stage build (with FFmpeg) for deployment on Render
    • Databases/main.db SQLite

Protection

Security.

Only mechanisms that are actually in the code — no wish list.

ASP.NET Identity

Login, registration and password hashing run on ASP.NET Core Identity, not on a home-made solution.

AddIdentity<User, Roll>()

Role-based authorisation

Four roles — SuperAdmin, Admin, Blogger, User — control access to every admin action via [Authorize(Roles=…)].

Infrastructure/Roles.cs

Claim-based UI permissions

A custom TagHelper hides or disables UI elements when the user lacks the matching permission claim.

TagHelpers/PermissionTagHelper.cs

Anti-forgery tokens

Forms that change data are protected with CSRF tokens.

[ValidateAntiForgeryToken] (7 files)

Secure file uploads

Uploads are checked against a whitelist of file extensions and content types, limited to 5 MB, and images are re-encoded on the server.

Areas/Admin/Models/PicSave.cs

What else is inside

Special features.

A few parts of the application that go beyond a plain CRUD CMS.

Admin CMS

56 separate admin controllers — I maintain every piece of content on this site (projects, CV, blog, links, skills …) myself, without touching the database by hand.

Areas/Admin/Controllers/

AI text generation (Gemini)

In the blog editor I can have drafts generated via the Gemini API and then revise them by hand.

Services/GeminiTextService.cs

AI cover images (Cloudflare)

Cover images for blog posts can be generated with Cloudflare Workers AI.

Services/CloudflareImageService.cs

Live GitHub sync

My real repositories are fetched via the GitHub API and synchronised in the admin area.

Services/GitHubApiClientService.cs

Scroll-driven hero video

On the About me page, a video scrubs in sync with the scroll progress (GSAP ScrollTrigger).

Aboutmevideopartial

Activity log with auto-cleanup

Admin actions are logged; when the application starts, entries older than 6 months are removed automatically.

Program.cs (startup) + ActivityLog

Reflection

Decisions & lessons learned.

Honest, not sugar-coated — including the parts I would build differently today.

Decisions

  • Why 4 separate projects instead of one? — Entities, DataAccess, BusinessLagiclayer and WebApplication3 are four separate .csproj files, not just folders. This enforces the direction of dependencies technically as well — WebApplication3 cannot accidentally bypass DataAccess and access the database directly.
  • Why repository + service instead of EF Core directly in the controller? — GenericRepository<T> covers CRUD for all 26 entities with a single class. The *Manager layer above it leaves room for business logic that goes beyond plain reading/writing (e.g. AddOrUpdateDetailToProject with tech-stack matching).
  • Why SQLite instead of SQL Server? — For a single-user portfolio with a manageable amount of data, an embedded file database is enough — no separate database server, easier to deploy and back up (main.db can simply be copied).
  • Where the trade-off lies — GenericRepository<T> is deliberately kept simple (Create/ReadAll/ReadById/Update/Delate). More complex queries (e.g. GetProjectWithDetails with several includes) therefore live directly in the respective Ef*DAL classes, not in the generic base class.

What I learned

  • Migrations early and often — 13 small, named migrations are easier to follow and, if in doubt, to roll back than a few huge ones.
  • Typos in names are expensive — I have since corrected EntityFramwork → EntityFramework (50 affected files) — BusinessLagiclayer still carries its typo. A single misspelled namespace quickly spreads through the whole solution.
  • Two permission systems in parallel — role-based ([Authorize(Roles=…)]) for page/action access and claim-based (PermissionTagHelper) for individual UI elements have different jobs — but maintaining both also takes a bit more discipline.
  • Public folders really are public — wwwroot is served completely without authentication — any file that ends up there by mistake (e.g. an appsettings.json) is immediately reachable on the web.

Next steps

  • Done Security review: files accidentally placed under wwwroot identified and removed
  • Planned Upgrade from .NET 6 (out of support) to .NET 8 or a newer LTS
  • Planned Unit tests (xUnit) — there is currently no test project in the solution
  • Planned CI/CD with GitHub Actions — the workflow folder already exists, but without a workflow file yet
  • Done Docker deployment — a multi-stage Dockerfile is in production use (Render)

Questions about the implementation?

I'm happy to explain every decision in detail.

Contact

Let's work together.

Do you have a project, a question or an internship offer? I look forward to hearing from you.

Open to an internship from June 2027
Email info@amirrezaafshar.de
Location Stuttgart, Germany
Spam protection without cookies or third parties – Details.

Your details are stored only to answer your enquiry and are deleted automatically after 6 months at the latest. Phone and subject are optional. More in the privacy policy.