Responsive Layouts, Client Profiles & Composition
One TicketWorkspace UserControl — navigation, ticket grid, ticket
detail, activity feed — composed from nested single-purpose containers (Dock shell, FlexLayoutPanel
regions, a TableLayoutPanel form, FlowLayoutPanel chips) that re-arranges itself for desktop,
tablet and phone when the active Client Profile changes (ClientProfiles.json +
Application.ResponsiveProfileChanged), and a reusable SearchBar UserControl used twice instead
of two copies of a textbox-and-button. The profile handler toggles panels that already exist; nothing is
rebuilt, so the selected ticket, the draft and the search text survive every switch.
Nothing here is deployed anywhere; it is a plain Wisej.NET 4 project that runs on your machine.
Run it
cd "LearnWisej-Samples/Production Architecture Deep Dive\Module 3\TicketOps"
dotnet run -f net10.0 --urls http://localhost:5103
Then open http://localhost:5103. (Visual Studio: open TicketOps.slnx, press F5 — the port is in
Properties/launchSettings.json.)
Requirements: .NET 10 SDK, the Wisej-4 4.1.0 NuGet package.
Unlike the other modules this screen is a Page (Application.MainPage) rather than a fixed-size
Form: the page fills the browser, so the responsive behaviour can be watched by resizing the real window.
What to try in the Ticket Workspace
| Action | What you should see |
|---|---|
| Resize the browser below 1024 px, then below 600 px, then back | Tablet: the rail shrinks to icons, Assignee disappears, Details / Activity tabs appear. Phone: header Tickets, no rail/toolbar; tap a row → ← Back Ticket #1003. The status bar shows Active profile: … |
| Select a row, edit it, Save | status ● Ticket #1003 saved.; the activity feed gains S. Patel updated #1003 |
| Clear the title, Save | orange banner Title is required. |
| Select #1002 (no hours logged), Close | orange banner Log hours before closing. |
| Click a chip (Open, High, Mine, Today, Unassigned, Escalated) | the grid filters; chips wrap when the list is narrow |
| Type in the ticket SearchBar / the activity SearchBar, press Enter | the grid / the feed filters — one UserControl, two hosts. One character only: orange banner Type at least 2 characters to search. |
Round trip: select #1003, type into Title, resize across the profiles and come back — the text, the selection, the chip and the feed are all still there.
Where things live
TicketOps/
├─ ClientProfiles.json Phone ≤ 600 · Tablet 601–1024 · Desktop ≥ 1025 (browser width; copied to /bin for both targets)
├─ Views/MainPage the Page that fills the browser: status banner + the workspace
├─ Controls/
│ ├─ TicketWorkspace.cs ApplyResponsiveProfile (the switch), ShowAllPanels / CollapseActivityToTab / ShowSingleTaskView, thin handlers
│ ├─ TicketWorkspace.Designer.cs the composition: Dock shell, flexBody, flexSide, tableDetail, flowChips, tabActivity
│ ├─ SearchBar.cs Query · Placeholder · ButtonText · SearchRequested · Clear() — the inner controls stay private
│ ├─ SearchBar.Designer.cs one TableLayoutPanel (100 % box + 84 px button)
│ └─ StatusBanner reusable "● state" + banner UserControl
├─ Services/
│ ├─ ITicketService.cs GetOpenTicketsAsync · SearchAsync(TicketFilter) · SaveAsync · CloseAsync · GetActivityAsync
│ └─ TicketService.cs validation, chip meaning, close rule, persistence orchestration (no UI types, no profile names)
├─ Domain/ Ticket (CanClose / Close), TicketDraft, TicketFilter, TicketEvent, OperationResult
├─ Data/ ITicketRepository + InMemoryTicketRepository (seeded with the tickets and feed)
├─ Infrastructure/ ILog / ActivityLog (server console), AppComposition (one object graph per session)
├─ Resources/Strings.cs safe user-facing messages
├─ docs/ ResponsiveLayoutNotes.md · ProfileNotes.md · ProductionReadinessNote.md · WorkspaceComposition.svg
├─ Default.html + viewport meta tag so phones report their CSS width
├─ Program.cs session entry point → Application.MainPage = AppComposition.CreateMainView()
└─ Startup.cs Kestrel host (app.UseWisej()); *.json is never served
In Visual Studio the same per-profile values (Visible, Width, Dock) could be entered in the
Wisej Designer's profile drop-down (Control.ResponsiveProfiles); this sample writes them in
ApplyResponsiveProfile so the decision reads as a table in one place.