Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Authors
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

ProTermsPrivacyRefunds
Back to skills

Umbrella Blazor Scaffold Manage Page

ASecurity

Scaffold a Blazor manage page (.razor + .razor.cs) for create/edit operations, following the Umbrella EditForm pattern with UmbrellaModelLayoutStateView, breadcrumb, auth policy, and concurrency handling.

8 stars
0 votes
0 copies
0 views
Added 9/22/2026
toolsc#apisecurity

Works with

cliapi

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add umbrella-libraries/Umbrella --skill umbrella-blazor-scaffold-manage-page --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Umbrella Blazor Scaffold Manage Page?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Umbrella Blazor Scaffold Manage Page
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/umbrella-libraries-umbrella-blazor-scaffold-manage-page-a639e2d4/badge)](https://www.skillsdirectory.com/skills/umbrella-libraries-umbrella-blazor-scaffold-manage-page-a639e2d4)

More formats (shields.io, HTML) on the badges page.

Download with Pro
Files
SKILL.md
---
name: umbrella-blazor-scaffold-manage-page
description: 'Scaffold a Blazor manage page (.razor + .razor.cs) for create/edit operations, following the Umbrella EditForm pattern with UmbrellaModelLayoutStateView, breadcrumb, auth policy, and concurrency handling.'
---

# Scaffold Blazor Manage Page

## Purpose

Add a Blazor manage page that handles both create and edit for a feature. The page uses `EditForm` with `UmbrellaModelLayoutStateView` to manage loading states, and dispatches to `Repository.CreateAsync` or `Repository.UpdateAsync` based on whether an ID parameter is present.

**Prerequisite:** A client data service interface (`I<Name>Service`) must exist with `CreateAsync` and `UpdateAsync` methods (i.e. the service interface extends `IGenericDataService` with non-NoOp create/update model types). The concrete create and update models must exist and derive from a shared `CreateUpdate<Name>ModelBase` used by the combined form.

## Discovery (read these before writing anything)

1. Read 2–3 existing manage pages under `Web\<AppName>.Web.Client\Pages\Admin\` to understand the form field patterns, file upload handling if applicable, and navigation after save.
2. Confirm the project-specific client component base class name (e.g. `IndyRecordsClientComponentBase`). `Mapper` (`IUmbrellaMapper`) is a `protected` property on `UmbrellaComponentBase` (the Umbrella framework base) — it is available in all Blazor components in the hierarchy without any injection.
3. Read `Web\<AppName>.Web.Shared\Security\Policies\<AppName>PolicyNames.cs` or `SharedPolicyNames.cs` for the correct auth policy constant.
4. Read the create/update model hierarchy for the feature to know which properties to include as form fields and identify the exact shared create/update base name. Use their exact discovered names and follow the repository's convention (for example, `CreateUpdateManageIndustryModelBase`); do not introduce an input interface as a substitute. If the base is missing, use `umbrella-dotnet-scaffold-api-server-models` to add the shared model hierarchy first.
5. Check `Web\<AppName>.Web.Client.Data\Mappings\Api\` for an existing `<Name>Mapper.cs`. You will need client-side mappers for: `IUmbrellaMapperlyNewInstanceMapper<<Name>Model, Update<Name>Model>` (to populate the edit form) and `IUmbrellaMapperlyExistingInstanceMapper<Update<Name>ResultModel, Update<Name>Model>` (to refresh the concurrency stamp after save). If they do not exist, use the `umbrella-dotnet-scaffold-mapperly-factories` skill to create them first.

---

## Step 1 -- Create Manage.razor

**File:** `Web\<AppName>.Web.Client\Pages\Admin\<Name>Management\Manage.razor`

```razor
@inherits ManageBase
@page "/admin/<route-plural>/manage"
@page "/admin/<route-plural>/manage/{Id:int}"

@{
    string title = "Manage <Names>";
    string subTitle = Id.HasValue ? "Edit" : "Create";
}

<<AppName>PageTitle>@title</<AppName>PageTitle>

<UmbrellaBreadcrumb>
    <UmbrellaBreadcrumbItem Name="@title" Url="/admin/<route-plural>" />
    <UmbrellaBreadcrumbItem Name="@subTitle" />
</UmbrellaBreadcrumb>

<div class="management-page">
    <h1>@title</h1>
    <h4>@subTitle</h4>
    <hr />
    <section>
        <UmbrellaModelLayoutStateView CurrentState="CurrentState" Model="CreateUpdateModel" ReloadCallback="ReloadAsync">
            <Success>
                <UmbrellaValidationSummary ValidationResults="ValidationResults" />
                <EditForm Model="CreateUpdateModel" OnValidSubmit="SubmitFormAsync" novalidate>
                    <ObjectGraphDataAnnotationsValidator />

                    <div class="form-group form-floating">
                        <UmbrellaInputText class="form-control" @bind-Value="CreateUpdateModel!.Name" />
                        <LabelText ForTarget="() => CreateUpdateModel!.Name" />
                        <ValidationMessage For="() => CreateUpdateModel.Name" />
                    </div>

                    <div class="form-group form-group--buttons">
                        <button type="submit" class="btn btn-primary">@(Id.HasValue ? "Save Changes" : "Create <Name>")</button>
                        <a href="/admin/<route-plural>" class="btn btn-secondary">Cancel</a>
                    </div>
                </EditForm>
            </Success>
        </UmbrellaModelLayoutStateView>
    </section>
</div>
```

**Rules:**
- Two `@page` directives: the create route (no ID) and the edit route (`{Id:int}`).
- `@inherits ManageBase` only — no C# logic in the `.razor` beyond the `@{...}` title block.
- `UmbrellaModelLayoutStateView` wraps the form — always present; it handles the loading/error/success state machine.
- `CurrentState` and `ReloadCallback` come from the base class — do not define them.
- Form fields: add one `<div class="form-group">` per genuinely user-editable property. Use `form-floating` for text inputs. Check existing manage pages for the right component per field type (`UmbrellaInputText`, `UmbrellaInputTextArea`, `UmbrellaInputSelect`, etc.). A file-provider filename is not a raw text field: either implement the complete upload/preview workflow below or omit it from this page and explicitly report that file editing is out of scope.
- Cancel button always links back to the index route.
- Submit button label changes based on `Id.HasValue`.

### File upload field pattern

When the feature includes image/file upload:

```razor
<div class="form-group">
    <LabelText ForTarget="() => CreateUpdateModel.<FilePropertyName>" />
    <UmbrellaFileImagePreviewUpload @ref="ImagePreviewUpload"
                                    OnRequestUpload="UploadFileToTempDirectoryAsync"
                                    OnDeleteImage="OnDeleteImage"
                                    Accept="@GlobalFileSystemConstants.<Name>FileExtensions"
                                    MaxFileSizeBytes="@GlobalFileSystemConstants.<Name>MaxSizeBytes"
                                    WidthRequest="400"
                                    HeightRequest="400"
                                    Url="@Model?.ImageUrl"
                                    VersionToken="@Model?.ImageVersionToken" />
    <ValidationMessage For="() => CreateUpdateModel.<FilePropertyName>" />
</div>
```

---

## Step 2 -- Create Manage.razor.cs

**File:** `Web\<AppName>.Web.Client\Pages\Admin\<Name>Management\Manage.razor.cs`

```csharp
using <AppName>.Web.Client.Data.Services.Abstractions;
using <AppName>.Web.Shared.Models.Api.<Feature>;

namespace <AppName>.Web.Client.Pages.Admin.<Name>Management;

[Authorize(<AppName>PolicyNames.<Policy>)]
public abstract class ManageBase : <AppName>ClientComponentBase
{
    [Parameter]
    public int? Id { get; set; }

    [Inject]
    private I<Name>Service Repository { get; set; } = null!;

    protected <Name>Model? Model { get; private set; }
    protected CreateUpdate<Name>ModelBase? CreateUpdateModel { get; private set; }

    protected IReadOnlyCollection<ValidationResult>? ValidationResults { get; private set; }

    protected override async Task OnInitializedAsync()
    {
        try
        {
            if (!Id.HasValue)
            {
                CreateUpdateModel = new Create<Name>Model();
                CurrentState = LayoutState.Success;

                return;
            }

            var result = await Repository.FindByIdAsync(Id.Value);

            if (result.IsSuccess && result.Result is not null)
            {
                Model = result.Result;
                CreateUpdateModel = await Mapper.MapAsync<<Name>Model, Update<Name>Model>(result.Result);

                CurrentState = LayoutState.Success;

                return;
            }
            else
            {
                await ShowOperationResultErrorMessageAsync(result);
            }
        }
        catch (Exception exc) when (Logger.WriteError(exc, new { Id }))
        {
            await DialogUtility.ShowDangerMessageAsync();
        }

        CurrentState = LayoutState.Error;
    }

    protected async Task SubmitFormAsync()
    {
        try
        {
            if (CreateUpdateModel is null)
                throw new InvalidOperationException("The CU model is null");

            if (Id.HasValue && Model is null)
                throw new InvalidOperationException("The model is null");

            if (CreateUpdateModel is Create<Name>Model createModel)
            {
                var result = await Repository.CreateAsync(createModel);

                if (result.IsSuccess)
                {
                    await DialogUtility.ShowSuccessMessageAsync("The <Name> has been created successfully.");
                    Navigation.NavigateTo("/admin/<route-plural>");
                }
                else
                {
                    await ShowOperationResultErrorMessageAsync(result);
                }
            }
            else if (CreateUpdateModel is Update<Name>Model updateModel)
            {
                var result = await Repository.UpdateAsync(updateModel);

                if (result.IsSuccess && result.Result is not null)
                {
                    await DialogUtility.ShowSuccessMessageAsync("The <Name> has been updated successfully.");

                    // Preferred: map the update result back onto the existing model to refresh the
                    // concurrency stamp and any server-computed fields — avoids a full page reload.
                    // Requires IUmbrellaMapperlyExistingInstanceMapper<Update<Name>ResultModel, Update<Name>Model>
                    // in Client.Data. If that mapper does not exist yet, use ReloadAsync() as a fallback.
                    _ = await Mapper.MapAsync(result.Result, updateModel);

                    // Fallback (use when the client-side result mapper has not been created yet):
                    // await ReloadAsync();
                }
                else
                {
                    await ShowOperationResultErrorMessageAsync(result);
                }
            }
        }
        catch (UmbrellaConcurrencyException)
        {
            await DialogUtility.ShowDangerMessageAsync(ClientErrorMessages.Concurrency);
        }
        catch (Exception exc) when (Logger.WriteError(exc, new { Model, CreateUpdateModel }))
        {
            await DialogUtility.ShowDangerMessageAsync();
        }
    }
}
```

**Rules:**
- `public abstract class` — the `.razor` file inherits from it via `@inherits`.
- `[Authorize(PolicyName)]` on the class, not in the `.razor` file.
- `[Inject] private I<Name>Service Repository { get; set; } = null!;` — the property is always named `Repository` by convention, regardless of the type name.
- `CreateUpdateModel` is typed as the exact discovered shared create/update base (illustrated as `CreateUpdate<Name>ModelBase?`), so both sealed concrete create and update models can be assigned without duplicating their shared property contract.
- `OnInitializedAsync`: if no `Id`, construct an empty `Create<Name>Model` and set `CurrentState = LayoutState.Success`. If `Id` is set, load from `Repository.FindByIdAsync` and use `await Mapper.MapAsync<<Name>Model, Update<Name>Model>(result.Result)` to populate `CreateUpdateModel`.
- `SubmitFormAsync`: pattern-match on `CreateUpdateModel` type to call the correct method. After a successful create, navigate to the index route. After a successful update, prefer `_ = await Mapper.MapAsync(result.Result, updateModel)` to refresh the concurrency stamp in place — this requires `IUmbrellaMapperlyExistingInstanceMapper<Update<Name>ResultModel, Update<Name>Model>` in `Client.Data`. If that mapper does not exist yet, fall back to `await ReloadAsync()` and leave a `// TODO: Mapper` comment.
- `Mapper` is a `protected` property on `UmbrellaComponentBase` (the Umbrella framework base) — no injection needed in derived components. The `Mapper.MapAsync` calls require client-side mapper classes in `Web.Client.Data\Mappings\Api\`: `IUmbrellaMapperlyNewInstanceMapper<<Name>Model, Update<Name>Model>` for load, and `IUmbrellaMapperlyExistingInstanceMapper<Update<Name>ResultModel, Update<Name>Model>` for post-save refresh. Use the `umbrella-dotnet-scaffold-mapperly-factories` skill to create them.
- Concurrency exception is caught specifically with a dedicated user message.
- `System.ComponentModel.DataAnnotations` is needed if `ValidationResult` is referenced — add the `using` if required.

### Additional injections for file upload

When file upload is needed, add:

```csharp
[Inject]
private IFileUploadService FileUploadService { get; set; } = null!;

protected UmbrellaFileImagePreviewUpload ImagePreviewUpload { get; set; } = null!;

public async Task<IOperationResult?> UploadFileToTempDirectoryAsync(UmbrellaFileUploadRequestEventArgs evt)
{
    try
    {
        if (CreateUpdateModel is null)
            throw new InvalidOperationException("The model should not be null here.");

        var fileUploadResult = await FileUploadService.UploadAsync(evt.Content, evt.FileName, FileUploadType.<Name>Image, evt.Type, CancellationToken);

        if (fileUploadResult.IsSuccess)
        {
            CreateUpdateModel.<FilePropertyName> = fileUploadResult.Result.tempFileName;

            // Updating the filename alone is not enough on an edit: the server uses this flag
            // to delete the old stored file and promote the new temporary upload.
            if (CreateUpdateModel is Update<Name>Model updateModel)
                updateModel.ReplaceExistingImage = true;

            // Temporary/no-store uploads normally have no token. If the upload contract returns one,
            // pass it as the second argument so the preview emits the fingerprint directly.
            ImagePreviewUpload.Update(fileUploadResult.Result.url);
        }

        StateHasChanged();

        return fileUploadResult;
    }
    catch (Exception exc) when (Logger.WriteError(exc))
    {
        await DialogUtility.ShowDangerMessageAsync();
    }

    return null;
}

protected void OnDeleteImage()
{
    if (CreateUpdateModel is null)
        throw new InvalidOperationException("The model should not be null here.");

    if (CreateUpdateModel is Update<Name>Model updateModel)
        updateModel.ReplaceExistingImage = true;
}
```

---

## Verification

1. Two `@page` directives — create route (no ID) and edit route (`{Id:int}`).
2. `[Authorize(PolicyName)]` is on the code-behind class, not in the `.razor` file.
3. `[Inject]` property is named `Repository` and typed as the service interface.
4. `CreateUpdateModel` is typed as the shared create/update model base (`CreateUpdate<Name>ModelBase?`), not an input interface or one concrete request type.
5. `OnInitializedAsync` uses `await Mapper.MapAsync<<Name>Model, Update<Name>Model>(result.Result)` to populate the edit form — no manual property assignment.
6. `OnInitializedAsync` sets `CurrentState = LayoutState.Success` on both the create and edit success paths, and `LayoutState.Error` on failure.
7. `SubmitFormAsync` pattern-matches on `Create<Name>Model` vs `Update<Name>Model` — navigates after create; after update calls `_ = await Mapper.MapAsync(result.Result, updateModel)` to refresh the model in place.
8. `UmbrellaConcurrencyException` is caught and handled with `ClientErrorMessages.Concurrency`.
9. `UmbrellaModelLayoutStateView` wraps the form content in the `.razor`.
10. Client-side mapper classes exist in `Web.Client.Data\Mappings\Api\` for the `<Name>Model → Update<Name>Model` and `Update<Name>ResultModel → Update<Name>Model` mappings.
11. When the displayed file uses Dynamic Image URL fingerprinting, its model declares `ImageVersionToken`, mappings assign URL/token together, and `UmbrellaFileImagePreviewUpload` receives and forwards `VersionToken`.
12. A successful upload while editing sets the matching replacement flag (`ReplaceExistingImage`, `ReplaceExistingFile`, or the feature-specific equivalent) after assigning the temporary provider filename. Do not rely only on the delete/replace button handler; image-preview upload controls can upload directly over an existing preview.
13. Read `.ai-shared\bundles\umbrella\analyzer-compatibility.md` and build with the installed analyzers enabled.

Attribution

umbrella-librariesumbrella-libraries
View sourceMore from umbrella-libraries →
SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Know which skills are safe — weekly.

Best new skills + every skill we flagged as malicious. From the team that scanned 103,619.

Join free

Related Skills

ucoz-landing-skill

Playbook for creating and editing uCoz landing pages via MCP tools (`templates_tool`, `ftp_tool`, `modules_tool`). Use for tasks such as: "build a landing page", "update the homepage as a landing page", "create a promo page on the homepage", "add a lead form / menu / SEO to the homepage". Homepage: `page_list`, `page_get`; first publish — `page_update` with full `page_tmpl`; HTML edits after generation — `patch_template` (module_id=2, template_id=1), not `update_template`. Activate the mail f...

107 votes

Paperclip

Interact with the Paperclip control plane API for task coordination and governance. Use when checking assignments, updating issue status, posting comments, delegating work, managing routines, or calling Paperclip API endpoints.

813271 votes

Daw Music

Digital Audio Workstation usage, music composition, interactive music systems, and game audio implementation for immersive soundscapes.

761 votes

Instantly Rdsthomas Mission Control

Instantly.ai cold email outreach API - manage campaigns, leads, accounts, and analytics. Use for cold email automation, lead management, campaign creation/monitoring, and email account warmup.

761 votes

Caveman Compress

Compress natural language memory files (CLAUDE.md, todos, preferences) into caveman format to save input tokens. Preserves all technical substance, code, URLs, and structure. Compressed version overwrites the original file. Human-readable backup saved as FILE.original.md. Trigger: /caveman-compress FILEPATH or "compress memory file"

1066600 votes
View all in tools →