Collabrium
A connected enterprise workspace bringing project delivery, people, learning, community, and reporting into one tenant-aware platform.
I’m Raghad Hanon. I turn complex workflows into connected, maintainable backends with C#, ASP.NET Core, and a thoughtful approach to software design.

A graduation platform, a full-stack learning project, and an internship API. Three different contexts for putting backend engineering into practice.
A connected enterprise workspace bringing project delivery, people, learning, community, and reporting into one tenant-aware platform.
An activity-based social application connecting event discovery, attendance, profiles, and live discussion.
A booking backend covering hotel discovery, room management, reservations, discounts, and PDF invoices.
Small decisions shape the whole system. Explore twelve examples of how I approach domain rules, data boundaries, and the details that connect them.
12 decisions / actual project code
The tenant middleware compares the authenticated user’s tenant with the resolved organization before assigning a scoped context. Tenant-aware queries and project policies build on that context.
if (!string.Equals(user.TenantId, tenant.Id, StringComparison.Ordinal))
{
throw new UnauthorizedAccessException("The authenticated user does not belong to this tenant.");
}
tenantContext.TenantId = tenant.Id;
tenantContext.CurrentTenant = tenant;The UserStory model checks linked work before allowing testing, release, or completion. Completing a story checks that linked tasks and bugs are done and linked test cases have passed.
if (target == WorkItemStatus.Done)
{
Guard.Against(!allTasksDone,
"Cannot complete UserStory until all linked Tasks are Done.");
Guard.Against(!noOpenBugs,
"Cannot complete UserStory while there are open Bugs.");
Guard.Against(!allTestsPassed,
"Cannot complete UserStory until all linked TestCases are Passed.");
}Actify routes a comment through its application command, then broadcasts the returned comment to the activity’s SignalR group. Existing comments load for the connecting client.
var comment = await mediator.Send(
new AddComment.Command {
CreateCommentDto = createCommentDto
});
await Clients.Group(createCommentDto.ActivityId)
.SendAsync("ReceiveComment", comment.Value);The Unit of Work runs the use case, saves its changes, and commits. If the operation throws, it rolls the database transaction back. Work-item creation uses this to coordinate its record, project counter, and history.
await using var tx = await _dbContext.Database.BeginTransactionAsync(cancellationToken);
try
{
await action(cancellationToken);
await _dbContext.SaveChangesAsync(cancellationToken);
await tx.CommitAsync(cancellationToken);
}
catch
{
await tx.RollbackAsync(cancellationToken);
throw;
}A dedicated read model filters by work item, project, and tenant, then projects only the code and title. AsNoTracking makes the read intent explicit.
return await _dbContext.WorkItems
.AsNoTracking()
.Where(workItem =>
workItem.Id == wId &&
workItem.ProjectId == pId &&
workItem.Project.TenantId == tId)
.Select(workItem =>
workItem.Code + " - " + workItem.Title)
.FirstOrDefaultAsync(cancellationToken);IdentityService checks the stored token’s active state. A successful refresh records revocation, links the replacement token, and persists the new token with an expiry.
storedToken.RevokedAtUtc = DateTime.UtcNow;
storedToken.ReplacedByToken = newRefreshToken;
var newRefreshTokenEntity = new RefreshTokenEntity
{
Token = newRefreshToken,
UserId = user.Id,
CreatedAtUtc = DateTime.UtcNow,
ExpiresAtUtc = DateTime.UtcNow.AddDays(7)
};
_dbContext.RefreshTokens.Add(newRefreshTokenEntity);
await _dbContext.SaveChangesAsync(cancellationToken);Generated work-item fields go through JSON extraction, typed deserialization, normalization, and usability checks. Invalid output feeds a repair prompt, with bounded attempts and fallback suggestions.
NormalizeSuggestion(workItemType, suggestion, summary);
if (!IsSuggestionUsable(workItemType, suggestion, out error))
return false;
return true;The history recorder compares old and new property values, skips unchanged values and ignored technical fields, and resolves related identifiers into display text before recording a change.
var oldRawValue =
property.GetValue(oldObject)?.ToString();
var newRawValue =
property.GetValue(newObject)?.ToString();
if (string.Equals(
oldRawValue,
newRawValue,
StringComparison.Ordinal))
{
continue;
}Completing a sprint moves only incomplete work back to the backlog. Completed items remain associated with that sprint, while the lifecycle update and history are committed together.
await workItemsRepository
.MoveIncompleteWorkItemsFromSprintToBacklogAsync(
tenantId,
sprint.Id,
sprint.ProjectId,
transactionCancellationToken);
sprint.Complete();PatchHelpers gives string updates three meanings: null leaves the existing value alone, whitespace clears it, and supplied text sets a trimmed value. Work-item updates use these decisions explicitly.
public static PatchStringDecision DecideStringPatch(string? input)
{
return input is null
? new PatchStringDecision(PatchStringAction.Ignore, null)
: string.IsNullOrWhiteSpace(input)
? new PatchStringDecision(PatchStringAction.Clear, null)
: new PatchStringDecision(PatchStringAction.Set, input.Trim());
}Sprint completion publishes a domain-relevant application event. A dedicated handler derives distinct recipients from the team snapshot and creates their notifications; a separate handler owns email delivery.
var recipientUserIds = notification.TeamMembers
.Select(m => m.Id)
.Where(id => !string.IsNullOrWhiteSpace(id))
.Distinct()
.ToList();The event reminder worker uses PeriodicTimer and the host’s stopping token. Each run creates a dependency-injection scope, finds due events, targets attendees, and marks processed reminders.
using var timer = new PeriodicTimer(CheckInterval);
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await ProcessAsync(stoppingToken);
}Examples from Collabrium, the four-person graduation platform I led, and Actify, a course-based implementation. Each source link opens the exact version reviewed.
More inside the Collabrium case studyC# · .NET · ASP.NET Core Web API
EF Core · SQL Server · LINQ
Clean Architecture · CQRS · MediatR
Identity · JWT · Multi-tenancy
xUnit · Git · GitHub Actions CI
Swagger · Postman · Azure deployment
React · TypeScript · JavaScript
SignalR · HTML · CSS · MUI
I’m a Computer Systems Engineering graduate from Palestine Technical University — Kadoorie, where I graduated first in the College of Engineering & Technology with an average of 93.21/100.
I’m drawn to the work behind the interface: turning requirements into domain rules, deciding where responsibilities belong, and connecting features into a coherent system.
Competitive programming taught me to reason carefully. Training and team projects taught me to translate that reasoning into software other people can use and build on.
TAP — Job Hunting Program
Selected for a sponsored place in TAP’s Job Hunting Program. Participating in career coaching, interview preparation, professional networking, and a structured job-search plan for international software engineering opportunities.
Foothill Technology Solutions, LLC
Structured backend training in C#, ASP.NET Core, EF Core, clean code, SOLID, architecture, and automated testing. Delivered the Hotel Booking API as the final project, with GitHub Actions CI and hands-on Azure deployment.
Knowledge Academy
Built and consumed REST APIs using C#, ASP.NET, and relational databases. Developed responsive React interfaces and integrated them with backend functionality.
Palestine Technical University — Kadoorie
Ranked 1st in the College of Engineering & Technology. Graduated with a 93.21/100 average and led the four-person Collabrium graduation project team.
Academic achievement, team competitions, and years of algorithm practice have shaped how I approach unfamiliar challenges.
Palestine Technical University — Kadoorie.
93.21/100 graduating average.
Regional placement in a team programming contest across Africa and the Arab region.
View recognitionNationwide placement, solving algorithmic problems through team collaboration.
View recognitionCompeted with the 3Geeks team. Our team also placed 1st in the qualification contest.
Read the event postAn early milestone in exploring practical ideas through science and technology.
Participation listed among my LinkedIn certifications.
Participation listed among my LinkedIn certifications.
An early experience turning an idea into a technology project.
Course-based full-stack development practice that informed Actify.
HTML, CSS, JavaScript, Bootstrap, and React.
Udemy course with LeetCode exercises.
Language learning and communication development.
Participation listed among my LinkedIn certifications.
Career development and preparation for international opportunities.
Real workflows, original recordings, and the documents behind Collabrium. Explore the finished product and the thinking that shaped it.
Follow project planning, work items, and the delivery workflow.
Explore the organization workspace and the people and teams inside it.
See the reporting workflow and the analytical views of project work.
Explore roadmaps, courses, assignments, and shared learning resources.
A visual overview of Collabrium’s tenant structure and role hierarchy.
Use the player’s full-screen control to inspect the interface in detail. Recordings play on request.
Categories match the folders in the original screens archive. Select a module to see its overview, then open the image for a closer look.

Requirements, system design, domain models, and the implementation walkthrough.
Read the thesis9.1 MB
The updated graduation presentation, including platform workflows and product demonstrations.
Download the presentation196.3 MBI’m open to junior .NET backend and software engineering opportunities. Have a role or a problem in mind? I’d love to hear about it.