Back to server for load speed
This commit is contained in:
@@ -0,0 +1,151 @@
|
|||||||
|
---
|
||||||
|
sessionId: session-260909-133404-1um1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
### Overview & Goals
|
||||||
|
Convert the `Web` project from a standalone Blazor WebAssembly application back to an ASP.NET Core Blazor Server application with interactive server components.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
#### In Scope
|
||||||
|
- Update `Web/Web.csproj` to use `Microsoft.NET.Sdk.Web` and remove WebAssembly client dependencies.
|
||||||
|
- Convert `Web/Program.cs` from `WebAssemblyHostBuilder` to ASP.NET Core `WebApplication.CreateBuilder` with Razor Components and interactive server render modes.
|
||||||
|
- Relocate configuration files (`appsettings.json`, `appsettings.Development.json`) from `Web/wwwroot/` to the project root `Web/`.
|
||||||
|
- Remove `Web/wwwroot/index.html` and restore `Web/Components/App.razor` as the HTML host root.
|
||||||
|
- Add `Web/Components/Routes.razor`, `Web/Components/Pages/Error.razor`, and `Web/Components/Layout/ReconnectModal.razor` (with corresponding CSS and JS).
|
||||||
|
- Update `Web/Components/_Imports.razor` with server rendering imports.
|
||||||
|
|
||||||
|
#### Out of Scope
|
||||||
|
- Modifying resume content data files (`Overview.cs`, `PersonalInfo.cs`, `Skills.cs`, `WorkExperience.cs`).
|
||||||
|
- Modifying resume UI layouts or styles in `Home.razor`, `MainLayout.razor`, or `app.css`.
|
||||||
|
|
||||||
|
### User Stories
|
||||||
|
- As a visitor, I want to load the resume application rendered quickly from the ASP.NET Core server with interactive server capabilities.
|
||||||
|
- As a developer, I want the project configured as a standard ASP.NET Core Blazor Server app so that it can leverage server-side capabilities and runtime services.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
- The application must start using ASP.NET Core `WebApplication` hosting.
|
||||||
|
- Navigation to `/` must render the resume page (`Home.razor`) inside `MainLayout.razor`.
|
||||||
|
- Navigation to unknown routes must display the custom not-found page (`NotFound.razor`) via `UseStatusCodePagesWithReExecute("/not-found")` and the Blazor router.
|
||||||
|
- Disconnected or reconnecting Blazor Server circuits must display the interactive reconnection modal dialog.
|
||||||
|
- Server-side error routing must be handled via `/Error` rendering `Error.razor`.
|
||||||
|
|
||||||
|
### Non-Functional Requirements
|
||||||
|
- Maintain compatibility with .NET 10 (`net10.0`).
|
||||||
|
- Ensure clean build with zero compile errors.
|
||||||
|
|
||||||
|
# Technical Design
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
The `Web` project is currently configured as a standalone Blazor WebAssembly client using `Microsoft.NET.Sdk.BlazorWebAssembly`, with `Program.cs` invoking `WebAssemblyHostBuilder.CreateDefault(args)`, static hosting through `Web/wwwroot/index.html`, and client configuration files in `Web/wwwroot/`.
|
||||||
|
|
||||||
|
### Key Decisions
|
||||||
|
- **Hosting Model**: Use ASP.NET Core Blazor Server with interactive server components (`AddInteractiveServerComponents()` and `.AddInteractiveServerRenderMode()`).
|
||||||
|
- **Host Document**: Use `Web/Components/App.razor` as the HTML root document containing head elements, styles, scripts, reconnect modal, and `<Routes />`.
|
||||||
|
- **Configuration Placement**: Move `appsettings.json` and `appsettings.Development.json` to the `Web/` project root, matching standard ASP.NET Core hosting conventions.
|
||||||
|
|
||||||
|
### Proposed Changes
|
||||||
|
1. **`Web/Web.csproj`**:
|
||||||
|
- Change SDK to `Microsoft.NET.Sdk.Web`.
|
||||||
|
- Remove `<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" ... />` and `<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" ... />`.
|
||||||
|
- Set `<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>`.
|
||||||
|
2. **`Web/Program.cs`**:
|
||||||
|
- Build server host via `var builder = WebApplication.CreateBuilder(args);`.
|
||||||
|
- Register `builder.Services.AddRazorComponents().AddInteractiveServerComponents();`.
|
||||||
|
- Configure HTTP request pipeline: exception handler `/Error`, HSTS, status code re-execute `/not-found`, HTTPS redirection, antiforgery, static assets mapping (`MapStaticAssets()`), and `MapRazorComponents<App>().AddInteractiveServerRenderMode()`.
|
||||||
|
3. **`Web/wwwroot/`**:
|
||||||
|
- Remove `index.html`.
|
||||||
|
- Move `appsettings.json` and `appsettings.Development.json` up to `Web/`.
|
||||||
|
4. **`Web/Components/`**:
|
||||||
|
- `App.razor`: HTML host markup with `<Routes />`, `<ReconnectModal />`, `<script src="@Assets["_framework/blazor.web.js"]"></script>`.
|
||||||
|
- `Routes.razor`: `<Router>` definition referencing `Program.Assembly` and `NotFoundPage="typeof(Pages.NotFound)"`.
|
||||||
|
- `Layout/ReconnectModal.razor` (+ `.razor.css`, `.razor.js`): Circuit reconnection overlay.
|
||||||
|
- `Pages/Error.razor`: Error display component.
|
||||||
|
- `_Imports.razor`: Add `@using static Microsoft.AspNetCore.Components.Web.RenderMode` and remove WebAssembly-only namespaces.
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
```
|
||||||
|
Web/
|
||||||
|
├── appsettings.json
|
||||||
|
├── appsettings.Development.json
|
||||||
|
├── Program.cs
|
||||||
|
├── Web.csproj
|
||||||
|
├── Properties/
|
||||||
|
│ └── launchSettings.json
|
||||||
|
├── Components/
|
||||||
|
│ ├── _Imports.razor
|
||||||
|
│ ├── App.razor
|
||||||
|
│ ├── Routes.razor
|
||||||
|
│ ├── Layout/
|
||||||
|
│ │ ├── MainLayout.razor
|
||||||
|
│ │ ├── MainLayout.razor.css
|
||||||
|
│ │ ├── ReconnectModal.razor
|
||||||
|
│ │ ├── ReconnectModal.razor.css
|
||||||
|
│ │ └── ReconnectModal.razor.js
|
||||||
|
│ └── Pages/
|
||||||
|
│ ├── Error.razor
|
||||||
|
│ ├── Home.razor
|
||||||
|
│ ├── Home.razor.css
|
||||||
|
│ └── NotFound.razor
|
||||||
|
├── Data/
|
||||||
|
│ ├── Overview.cs
|
||||||
|
│ ├── PersonalInfo.cs
|
||||||
|
│ ├── Skills.cs
|
||||||
|
│ └── WorkExperience.cs
|
||||||
|
└── wwwroot/
|
||||||
|
├── app.css
|
||||||
|
├── favicon.png
|
||||||
|
└── lib/bootstrap/...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Architecture Diagram
|
||||||
|
```mermaid
|
||||||
|
graph TD
|
||||||
|
Browser[Web Browser] -->|HTTP / WebSocket SignalR| ASPNET[ASP.NET Core Server]
|
||||||
|
ASPNET --> Pipeline[Middleware Pipeline]
|
||||||
|
Pipeline --> MapStatic[MapStaticAssets]
|
||||||
|
Pipeline --> RazorComponents[MapRazorComponents: App.razor]
|
||||||
|
RazorComponents --> Routes[Routes.razor / Router]
|
||||||
|
Routes --> MainLayout[MainLayout.razor]
|
||||||
|
MainLayout --> Home[Home.razor]
|
||||||
|
MainLayout --> NotFound[NotFound.razor]
|
||||||
|
Home --> Data[Data Layer: Overview, PersonalInfo, Skills, WorkExperience]
|
||||||
|
```
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
### Validation Approach
|
||||||
|
- Verify successful compilation with `dotnet build Web/Web.csproj`.
|
||||||
|
- Validate that no obsolete WebAssembly packages or unused `index.html` remain.
|
||||||
|
- Validate that all server components, router, reconnect modal, error handling, and static asset mappings compile and link properly.
|
||||||
|
|
||||||
|
### Key Scenarios
|
||||||
|
- **Build Verification**: Ensure `dotnet build Web/Web.csproj` completes with 0 errors.
|
||||||
|
- **Root Page Loading**: Ensure root route `/` routes to `Home.razor` within `MainLayout.razor`.
|
||||||
|
- **404 Handling**: Ensure unknown routes re-execute to `/not-found` rendering `NotFound.razor`.
|
||||||
|
- **Error Page**: Ensure `/Error` is routable to `Error.razor`.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
- Disconnected SignalR circuit: Reconnection modal displays appropriate rejoining/paused/failed statuses.
|
||||||
|
- Static asset path resolution: Verify `_framework/blazor.web.js`, Bootstrap CSS, and `app.css` resolve with `@Assets`.
|
||||||
|
|
||||||
|
# Delivery Steps
|
||||||
|
|
||||||
|
### ✓ Step 1: Convert project SDK, configuration files, and ASP.NET Core server pipeline
|
||||||
|
The Web project is configured as an ASP.NET Core Web application with server hosting services and pipeline middleware.
|
||||||
|
|
||||||
|
- Update `Web/Web.csproj` SDK to `Microsoft.NET.Sdk.Web`, remove `Microsoft.AspNetCore.Components.WebAssembly` package dependencies, and set `BlazorDisableThrowNavigationException`.
|
||||||
|
- Relocate `appsettings.json` and `appsettings.Development.json` from `Web/wwwroot/` to the project root directory `Web/`.
|
||||||
|
- Remove the standalone static `Web/wwwroot/index.html` host file.
|
||||||
|
- Rewrite `Web/Program.cs` to initialize `WebApplication.CreateBuilder(args)`, configure Razor components with interactive server support (`AddRazorComponents().AddInteractiveServerComponents()`), and set up HTTP middleware (antiforgery, static asset mapping, status code re-execution, exception handling, and `MapRazorComponents<App>().AddInteractiveServerRenderMode()`).
|
||||||
|
|
||||||
|
### ✓ Step 2: Restore Blazor Server host, routing, reconnect UX, and imports
|
||||||
|
The application renders HTML from the server with full routing, error handling, and circuit reconnection UI.
|
||||||
|
|
||||||
|
- Update `Web/Components/App.razor` to serve as the HTML root document with `<Routes />`, `<ReconnectModal />`, `<HeadOutlet />`, stylesheet assets, and `_framework/blazor.web.js`.
|
||||||
|
- Add `Web/Components/Routes.razor` defining the `<Router>` with `AppAssembly="typeof(Program).Assembly"` and `NotFoundPage="typeof(Pages.NotFound)"`.
|
||||||
|
- Add `Web/Components/Layout/ReconnectModal.razor` and its scoped CSS / JS assets (`ReconnectModal.razor.css`, `ReconnectModal.razor.js`) to handle Blazor Server circuit reconnection states.
|
||||||
|
- Add `Web/Components/Pages/Error.razor` to handle server-side request errors and show request diagnostic IDs.
|
||||||
|
- Update `Web/Components/_Imports.razor` to import static render modes and server component namespaces.
|
||||||
|
- Verify the solution builds without errors and serves the resume application over Blazor Server.
|
||||||
+23
-12
@@ -1,12 +1,23 @@
|
|||||||
<Router AppAssembly="@typeof(Program).Assembly">
|
<!DOCTYPE html>
|
||||||
<Found Context="routeData">
|
<html lang="en">
|
||||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)" />
|
|
||||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
<head>
|
||||||
</Found>
|
<meta charset="utf-8" />
|
||||||
<NotFound>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<PageTitle>Not found</PageTitle>
|
<base href="/" />
|
||||||
<LayoutView Layout="@typeof(Layout.MainLayout)">
|
<ResourcePreloader />
|
||||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
<link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" />
|
||||||
</LayoutView>
|
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||||
</NotFound>
|
<link rel="stylesheet" href="@Assets["Web.styles.css"]" />
|
||||||
</Router>
|
<ImportMap />
|
||||||
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
|
<HeadOutlet />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<Routes />
|
||||||
|
<ReconnectModal />
|
||||||
|
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||||
|
|
||||||
|
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||||
|
<div class="components-reconnect-container">
|
||||||
|
<div class="components-rejoining-animation" aria-hidden="true">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<p class="components-reconnect-first-attempt-visible">
|
||||||
|
Rejoining the server...
|
||||||
|
</p>
|
||||||
|
<p class="components-reconnect-repeated-attempt-visible">
|
||||||
|
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||||
|
</p>
|
||||||
|
<p class="components-reconnect-failed-visible">
|
||||||
|
Failed to rejoin.<br />Please retry or reload the page.
|
||||||
|
</p>
|
||||||
|
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||||
|
Retry
|
||||||
|
</button>
|
||||||
|
<p class="components-pause-visible">
|
||||||
|
The session has been paused by the server.
|
||||||
|
</p>
|
||||||
|
<p class="components-resume-failed-visible">
|
||||||
|
Failed to resume the session.<br />Please retry or reload the page.
|
||||||
|
</p>
|
||||||
|
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
|
||||||
|
Resume
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
.components-reconnect-first-attempt-visible,
|
||||||
|
.components-reconnect-repeated-attempt-visible,
|
||||||
|
.components-reconnect-failed-visible,
|
||||||
|
.components-pause-visible,
|
||||||
|
.components-resume-failed-visible,
|
||||||
|
.components-rejoining-animation {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||||
|
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||||
|
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||||
|
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||||
|
#components-reconnect-modal.components-reconnect-retrying,
|
||||||
|
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||||
|
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||||
|
#components-reconnect-modal.components-reconnect-failed,
|
||||||
|
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#components-reconnect-modal {
|
||||||
|
background-color: white;
|
||||||
|
width: 20rem;
|
||||||
|
margin: 20vh auto;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||||
|
opacity: 0;
|
||||||
|
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||||
|
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||||
|
&[open]
|
||||||
|
|
||||||
|
{
|
||||||
|
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal::backdrop {
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes components-reconnect-modal-slideUp {
|
||||||
|
0% {
|
||||||
|
transform: translateY(30px) scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-reconnect-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal p {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal button {
|
||||||
|
border: 0;
|
||||||
|
background-color: #6b9ed2;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal button:hover {
|
||||||
|
background-color: #3b6ea2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#components-reconnect-modal button:active {
|
||||||
|
background-color: #6b9ed2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-rejoining-animation {
|
||||||
|
position: relative;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-rejoining-animation div {
|
||||||
|
position: absolute;
|
||||||
|
border: 3px solid #0087ff;
|
||||||
|
opacity: 1;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.components-rejoining-animation div:nth-child(2) {
|
||||||
|
animation-delay: -0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes components-rejoining-animation {
|
||||||
|
0% {
|
||||||
|
top: 40px;
|
||||||
|
left: 40px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
4.9% {
|
||||||
|
top: 40px;
|
||||||
|
left: 40px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
5% {
|
||||||
|
top: 40px;
|
||||||
|
left: 40px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Set up event handlers
|
||||||
|
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||||
|
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||||
|
|
||||||
|
const retryButton = document.getElementById("components-reconnect-button");
|
||||||
|
retryButton.addEventListener("click", retry);
|
||||||
|
|
||||||
|
const resumeButton = document.getElementById("components-resume-button");
|
||||||
|
resumeButton.addEventListener("click", resume);
|
||||||
|
|
||||||
|
function handleReconnectStateChanged(event) {
|
||||||
|
if (event.detail.state === "show") {
|
||||||
|
reconnectModal.showModal();
|
||||||
|
} else if (event.detail.state === "hide") {
|
||||||
|
reconnectModal.close();
|
||||||
|
} else if (event.detail.state === "failed") {
|
||||||
|
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||||
|
} else if (event.detail.state === "rejected") {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retry() {
|
||||||
|
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Reconnect will asynchronously return:
|
||||||
|
// - true to mean success
|
||||||
|
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||||
|
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||||
|
const successful = await Blazor.reconnect();
|
||||||
|
if (!successful) {
|
||||||
|
// We have been able to reach the server, but the circuit is no longer available.
|
||||||
|
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||||
|
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||||
|
if (!resumeSuccessful) {
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
reconnectModal.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// We got an exception, server is currently unavailable
|
||||||
|
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resume() {
|
||||||
|
try {
|
||||||
|
const successful = await Blazor.resumeCircuit();
|
||||||
|
if (!successful) {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function retryWhenDocumentBecomesVisible() {
|
||||||
|
if (document.visibilityState === "visible") {
|
||||||
|
await retry();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
@page "/Error"
|
||||||
|
@using System.Diagnostics
|
||||||
|
|
||||||
|
<PageTitle>Error</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="text-danger">Error.</h1>
|
||||||
|
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||||
|
|
||||||
|
@if (ShowRequestId)
|
||||||
|
{
|
||||||
|
<p>
|
||||||
|
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3>Development Mode</h3>
|
||||||
|
<p>
|
||||||
|
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||||
|
It can result in displaying sensitive information from exceptions to end users.
|
||||||
|
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||||
|
and restarting the app.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@code{
|
||||||
|
[CascadingParameter]
|
||||||
|
private HttpContext? HttpContext { get; set; }
|
||||||
|
|
||||||
|
private string? RequestId { get; set; }
|
||||||
|
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||||
|
|
||||||
|
protected override void OnInitialized() =>
|
||||||
|
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
|
||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Web
|
@using Web
|
||||||
@using Web.Components
|
@using Web.Components
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public static class WorkExperience
|
|||||||
new Point
|
new Point
|
||||||
{
|
{
|
||||||
Description =
|
Description =
|
||||||
"Came to the conclusion that I wanted to focus my career on .NET, and left the company to pursue Blazor research and C# opportunities. Unfortunately, Blazor development isn't popular.",
|
"Came to the conclusion that I wanted to focus my career on .NET, and left the company to pursue Blazor research and C# opportunities. Unfortunately, Blazor development isn't popular, and such specialization makes less sense in the AI era.",
|
||||||
IsVisible = true
|
IsVisible = true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+24
-7
@@ -1,11 +1,28 @@
|
|||||||
using Microsoft.AspNetCore.Components.Web;
|
|
||||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
|
||||||
using Web.Components;
|
using Web.Components;
|
||||||
|
|
||||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
builder.RootComponents.Add<App>("#app");
|
|
||||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
|
||||||
|
|
||||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
// Add services to the container.
|
||||||
|
builder.Services.AddRazorComponents()
|
||||||
|
.AddInteractiveServerComponents();
|
||||||
|
|
||||||
await builder.Build().RunAsync();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
|
if (!app.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||||
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
|
app.UseHsts();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseStatusCodePagesWithReExecute("/not-found");
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseAntiforgery();
|
||||||
|
|
||||||
|
app.MapStaticAssets();
|
||||||
|
app.MapRazorComponents<App>()
|
||||||
|
.AddInteractiveServerRenderMode();
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
|||||||
@@ -1,25 +1,23 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"http": {
|
"http": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
"applicationUrl": "http://localhost:5202",
|
||||||
"applicationUrl": "http://localhost:5202",
|
"environmentVariables": {
|
||||||
"environmentVariables": {
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
}
|
||||||
}
|
},
|
||||||
},
|
"https": {
|
||||||
"https": {
|
"commandName": "Project",
|
||||||
"commandName": "Project",
|
"dotnetRunMessages": true,
|
||||||
"dotnetRunMessages": true,
|
"launchBrowser": true,
|
||||||
"launchBrowser": true,
|
"applicationUrl": "https://localhost:7263;http://localhost:5202",
|
||||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
"environmentVariables": {
|
||||||
"applicationUrl": "https://localhost:7263;http://localhost:5202",
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-6
@@ -1,15 +1,11 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<RootNamespace>Web</RootNamespace>
|
<RootNamespace>Web</RootNamespace>
|
||||||
|
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.6" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.6" PrivateAssets="all" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Jonathan McCaffrey - Resume</title>
|
|
||||||
<base href="/" />
|
|
||||||
<link rel="stylesheet" href="lib/bootstrap/dist/css/bootstrap.min.css" />
|
|
||||||
<link rel="stylesheet" href="app.css" />
|
|
||||||
<link rel="stylesheet" href="Web.styles.css" />
|
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
|
||||||
<HeadOutlet />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="app">
|
|
||||||
<svg class="loading-progress">
|
|
||||||
<circle r="40%" cx="50%" cy="50%" />
|
|
||||||
<circle r="40%" cx="50%" cy="50%" />
|
|
||||||
</svg>
|
|
||||||
<div class="loading-progress-text"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="blazor-error-ui">
|
|
||||||
An unhandled error has occurred.
|
|
||||||
<a href="." class="reload">Reload</a>
|
|
||||||
<span class="dismiss">🗙</span>
|
|
||||||
</div>
|
|
||||||
<script src="_framework/blazor.webassembly.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user