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 Dotnet Scaffold Api Data Service Controller

ASecurity

Scaffold a thin API controller backed by a controller service (GenericRepositoryDataServiceApiController pattern). Supports Blazor SSR pre-rendering when the service interface lives in the client data project. Produces 4 artifacts: service interface, server controller service, controller, and DI registration.

8 stars
0 votes
0 copies
0 views
Added 9/22/2026
toolsgoexpressapisecurity

Works with

cliapi

Security Analysis

A100/100

Scanned 9/22/2026

Install to Claude Code

$npx -y skills add umbrella-libraries/Umbrella --skill umbrella-dotnet-scaffold-api-data-service-controller --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Umbrella Dotnet Scaffold Api Data Service Controller?

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

Security grade badge for Umbrella Dotnet Scaffold Api Data Service Controller
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/umbrella-libraries-umbrella-dotnet-scaffold-api-data-service-controll-umbrella/badge)](https://www.skillsdirectory.com/skills/umbrella-libraries-umbrella-dotnet-scaffold-api-data-service-controll-umbrella)

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

Download with Pro
Files
SKILL.md
---
name: umbrella-dotnet-scaffold-api-data-service-controller
description: 'Scaffold a thin API controller backed by a controller service (GenericRepositoryDataServiceApiController pattern). Supports Blazor SSR pre-rendering when the service interface lives in the client data project. Produces 4 artifacts: service interface, server controller service, controller, and DI registration.'
---

# Scaffold API Data Service Controller

## Purpose

Add a thin API controller backed by a *controller service* that inherits `UmbrellaRepositoryDataService`. All CRUD hooks and repository access live in the controller service — the controller itself is a minimal wrapper.

This pattern decouples the service interface from the transport layer, enabling Blazor SSR pre-rendering: the same interface that the controller service implements server-side can also be implemented client-side as an HTTP data service. For pure API projects without Blazor, the interface simply lives in a server-side abstractions folder.

Use `umbrella-dotnet-scaffold-api-repo-controller` instead when no service abstraction is needed, and `umbrella-dotnet-scaffold-custom-api-controller` when the endpoint shape does not fit the generic CRUD patterns at all.

## Discovery (read these before writing anything)

1. Read 1–2 existing controller services in `Web\<AppName>.Web.Server\Services\` (fall back to the legacy `Web\<AppName>.Web.Server\Services\Api\` location in older projects) to confirm the `UmbrellaRepositoryDataService` generic parameter order (11 params) and any project-specific patterns.
2. Read 1–2 existing controllers in `Web\<AppName>.Web.Server\Controllers\Api\` to confirm the project-specific base class wrapper (e.g. `IndyRecordsGenericRepositoryDataServiceApiController`) and its 9 generic parameter order.
3. **Determine interface location**: if `Web\<AppName>.Web.Client.Data\Services\Abstractions\` exists, the interface goes there (Blazor project — enables SSR pre-rendering). Otherwise it goes in `Web\<AppName>.Web.Server\Services\Abstractions\` or equivalent.
4. Read `Web\<AppName>.Web.Server\IServiceCollectionExtensions.cs` — find the `// Controller Services` section to see whether existing registrations use `AddScoped` or `ReplaceScoped`.
5. Read `Web\<AppName>.Web.Shared\Security\Policies\<AppName>PolicyNames.cs` for auth policy constant names.

---

## Step 1 -- Create the service interface

**Blazor project:** `Web\<AppName>.Web.Client.Data\Services\Abstractions\IManage<Name>Service.cs`  
**API-only project:** `Web\<AppName>.Web.Server\Services\Abstractions\IManage<Name>Service.cs`

```csharp
using <AppName>.Web.Shared.Models.Api.Manage<Name>;
using Umbrella.Utilities.Data.Pagination;

namespace <AppName>.Web.[Client.Data|Server].Services.Abstractions;

public interface IManage<Name>Service : IGenericDataService<
    Manage<Name>Model,
    int,
    SlimManage<Name>Model,
    PaginatedResultModel<SlimManage<Name>Model>,
    CreateManage<Name>Model,
    CreateManage<Name>ResultModel,
    UpdateManage<Name>Model,
    UpdateManage<Name>ResultModel>;
```

**`IGenericDataService` generic params in order (8 total):** `TModel`, `TIdentifier`, `TSlimModel`, `TPaginatedResultModel`, `TCreateModel`, `TCreateResultModel`, `TUpdateModel`, `TUpdateResultModel`.

---

## Step 2 -- Create the server controller service

**File:** `Web\<AppName>.Web.Server\Services\Manage<Name>ControllerService.cs`

Concrete controller services live directly in the `Services` folder, alongside the `Services\Abstractions\` interfaces folder — mirroring the client data project layout. (Older projects may have them in `Services\Api\`; new controller services still go in `Services\`.)

```csharp
using <AppName>.Core.Data.Repositories.Abstractions;
using <AppName>.Core.Domain.Entities;
using <AppName>.Web.[Client.Data|Server].Services.Abstractions;
using <AppName>.Web.Shared.Models.Api.Manage<Name>;
using Umbrella.DataAccess.Abstractions;
using Umbrella.DataAccess.Abstractions.Options;
using Umbrella.Utilities.Data.Abstractions;
using Umbrella.Utilities.Data.Pagination;
using Umbrella.Utilities.Mapping.Abstractions;
using Umbrella.Utilities.Security.Abstractions;
using Umbrella.Utilities.Threading.Abstractions;

namespace <AppName>.Web.Server.Services;

public class Manage<Name>ControllerService : UmbrellaRepositoryDataService<
    Manage<Name>Model,
    SlimManage<Name>Model,
    PaginatedResultModel<SlimManage<Name>Model>,
    CreateManage<Name>Model,
    CreateManage<Name>ResultModel,
    UpdateManage<Name>Model,
    UpdateManage<Name>ResultModel,
    I<Name>Repository,
    <Name>,
    RepoOptions,
    int>, IManage<Name>Service
{
    public Manage<Name>ControllerService(
        ILogger<Manage<Name>ControllerService> logger,
        IHostEnvironment hostingEnvironment,
        UmbrellaRepositoryDataServiceOptions options,
        IUmbrellaMapper mapper,
        IUmbrellaAuthorizationService authorizationService,
        ISynchronizationManager synchronizationManager,
        Lazy<IDataAccessUnitOfWork> dataAccessUnitOfWork,
        Lazy<I<Name>Repository> repository,
        IDataExpressionFactory dataExpressionFactory)
        : base(logger, hostingEnvironment, options, mapper, authorizationService,
               synchronizationManager, dataAccessUnitOfWork, repository, dataExpressionFactory)
    {
    }

    protected override bool AuthorizationSlimReadChecksEnabled => false;
    protected override bool AuthorizationCreateChecksEnabled => false;
    protected override bool AuthorizationReadChecksEnabled => false;
    protected override bool AuthorizationUpdateChecksEnabled => false;
    protected override bool AuthorizationDeleteChecksEnabled => false;
}
```

The `UmbrellaRepositoryDataService` base enables resource authorization checks by default. Each `=> false` override above deliberately suppresses one check because the template assumes the controller's declarative policy is sufficient. When a registered resource authorization handler protects an operation, omit that operation's suppression override so the base check remains enabled.

**Rules:**
- `public class` (not `internal`) — both the server and client resolve it through the shared interface.
- Note `IHostEnvironment` in the constructor (not `IWebHostEnvironment` — that is used by the controller, not the service).
- `UmbrellaRepositoryDataService` generic params in order (11 total): `TModel`, `TSlimModel`, `TPaginatedResultModel`, `TCreateModel`, `TCreateResultModel`, `TUpdateModel`, `TUpdateResultModel`, `TRepository`, `TEntity`, `TRepositoryOptions`, `TEntityKey`. Note: `TModel` comes before `TSlimModel` here — the opposite order from the controller.
- Extra dependencies (e.g. `I<Name>FileHandler`) go after the 9 base constructor params and are stored as `private readonly` fields.
- `AfterCreateEntityAsync`, `AfterUpdateEntityAsync`, `AfterDeleteEntityAsync` are added only when needed. Follow the same guard/cancellation pattern as repositories.
- Controller services always use full concrete model types — no NoOp/`object` patterns.
- Do not override `PostAsync`, `PutAsync`, `DeleteAsync`, `GetAsync`, or `SearchSlimAsync` in the controller service. All CRUD customisation belongs in lifecycle hooks (`BeforeCreateEntityAsync`, `AfterCreateEntityAsync`, etc.) — UA019 enforces this.
- **Layer note:** the controller service lives in the Web layer and may reference Web.Models types — this is intentional, it is the translation boundary. Core.Logic services (see `umbrella-dotnet-scaffold-service`) must never reference Web.Models. Do not place complex domain logic in the controller service; if the logic belongs in Core.Logic, create a dedicated service there and inject it here.

### Lifecycle hook example (file handling)

```csharp
protected override async Task AfterCreateEntityAsync(<Name> entity, CreateManage<Name>Model model, CreateManage<Name>ResultModel result, CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();
    Guard.IsNotNull(entity);
    Guard.IsNotNull(model);
    Guard.IsNotNull(result);

    _ = await _fileHandler.CreateByGroupIdAndTempFileNameAsync(entity.Id, model.ImageProviderFileName, null, cancellationToken);
    UmbrellaVersionedUrl image = await _fileHandler
        .GetVersionedWebFilePathAsync(entity.Id, model.ImageProviderFileName, cancellationToken)
        ?? throw new InvalidOperationException("The saved image could not be resolved.");

    result.ImageUrl = image.Url;
    result.ImageVersionToken = image.VersionToken;
}
```

When Dynamic Image fingerprinting is enabled, the result model declares the matching nullable `ImageVersionToken`; always populate URL/token pairs together.

---

## Step 3 -- Create the controller

**File:** `Web\<AppName>.Web.Server\Controllers\Api\Manage<Name>Controller.cs`

```csharp
using Microsoft.AspNetCore.Authorization;
using <AppName>.Web.[Client.Data|Server].Services.Abstractions;
using <AppName>.Web.Server.Infrastructure.Mvc;
using <AppName>.Web.Shared.Models.Api.Manage<Name>;
using <AppName>.Web.Shared.Security.Policies;
using Umbrella.Utilities.Data.Pagination;

namespace <AppName>.Web.Server.Controllers.Api;

[Authorize(<AppName>PolicyNames.<Policy>)]
public class Manage<Name>Controller : <AppName>GenericRepositoryDataServiceApiController<
    SlimManage<Name>Model,
    PaginatedResultModel<SlimManage<Name>Model>,
    Manage<Name>Model,
    CreateManage<Name>Model,
    CreateManage<Name>ResultModel,
    UpdateManage<Name>Model,
    UpdateManage<Name>ResultModel,
    int,
    IManage<Name>Service>
{
    public Manage<Name>Controller(
        ILogger<Manage<Name>Controller> logger,
        IWebHostEnvironment hostingEnvironment,
        Lazy<IManage<Name>Service> repositoryDataService)
        : base(logger, hostingEnvironment, repositoryDataService)
    {
    }
}
```

**Rules:**
- The controller is always thin — no overrides, no extra dependencies. All logic lives in the controller service.
- Generic type params in order (9 total): `TSlimModel`, `TPaginatedResultModel`, `TModel`, `TCreateModel`, `TCreateResultModel`, `TUpdateModel`, `TUpdateResultModel`, `TEntityKey`, `TRepositoryDataService`.
- No DI registration for the controller — auto-discovered by ASP.NET Core.

---

## Step 4 -- Register the controller service in DI

**File:** `Web\<AppName>.Web.Server\IServiceCollectionExtensions.cs` — `// Controller Services` section, alphabetical order.

**If no Blazor client project, or the interface lives in the server project:**
```csharp
_ = services.AddScoped<IManage<Name>Service, Manage<Name>ControllerService>();
```

**If the interface lives in `Web.Client.Data.Services.Abstractions` and a Blazor client has already registered it with `AddScoped`:**
```csharp
_ = services.ReplaceScoped<IManage<Name>Service, Manage<Name>ControllerService>();
```

Check `Web\<AppName>.Web.Client.Data\IServiceCollectionExtensions.cs` to see whether the client already registers `IManage<Name>Service`. If it does, use `ReplaceScoped`; otherwise use `AddScoped`. When the `blazor-scaffold-client-data` skill runs later, it will add the client `AddScoped` and update the server registration to `ReplaceScoped` if needed.

---

## Custom action methods

Custom action methods on Pattern 2 controllers are rare — the controller should remain thin. If the custom logic is non-trivial, add it to the controller service and delegate from the controller. When most of the controller's surface is custom-shaped, use `umbrella-dotnet-scaffold-custom-api-controller` (Variant C, `UmbrellaDataServiceApiController`) instead of adding many custom actions here.

When a genuinely custom endpoint is needed, follow these conventions.

**Attribute:** Use `[UmbrellaProducesResponseType(StatusCodes.StatusXxx)]` (NOT the standard `[ProducesResponseType]`). Umbrella's attribute automatically maps status codes to `UmbrellaProblemDetails` or `UmbrellaValidationProblemDetails` with the correct `application/problem+json` content type.

**Status codes to declare per verb** (401, 403, 500 are already covered at class level):

| HTTP verb | Declare on the custom method |
|---|---|
| `[HttpGet]` | 200, 400, 404 (if not found is possible) |
| `[HttpPost]` | 201, 400, 409 (if conflict is possible), 422 |
| `[HttpPut]` | 200, 400, 404, 409 (conflict/concurrency), 422 |
| `[HttpPatch]` | 200, 400, 404, 409 (conflict/concurrency), 422 |
| `[HttpDelete]` | 204, 400, 404, 409 (conflict/concurrency) |

Note: 405 (Method Not Allowed) is returned by the base class for disabled endpoints — do not declare it on custom methods.

**Error helper methods** inherited from `UmbrellaApiController`:

```csharp
BadRequest(reason, code?)          // 400 — UmbrellaValidationProblemDetails
NotFound(reason, code?)            // 404 — UmbrellaProblemDetails
Conflict(reason, code?)            // 409 — UmbrellaProblemDetails
ConcurrencyConflict(reason)        // 409 — code: HttpProblemCodes.ConcurrencyStampMismatch
InternalServerError(reason, code?) // 500 — UmbrellaProblemDetails
OperationResult(IOperationResult)  // converts OperationResultStatus enum to the correct HTTP response
```

**Example: delegating to the controller service**

Prefer composing the inherited `ExecuteOperationAsync` envelope (from `UmbrellaDataServiceApiController`) with an expression lambda over the service — it owns cancellation, the `IOperationResult` → HTTP mapping, logging with caller info, and the `500` response. Have the service method return an `IOperationResult<T>` (e.g. `NotFound` when the summary is missing):

```csharp
[HttpGet("GetSummary")]
[UmbrellaProducesResponseType(StatusCodes.Status200OK)]
[UmbrellaProducesResponseType(StatusCodes.Status400BadRequest)]
[UmbrellaProducesResponseType(StatusCodes.Status404NotFound)]
public Task<IActionResult> GetSummaryAsync([FromQuery] int id, CancellationToken cancellationToken = default)
    => ExecuteOperationAsync<Manage<Name>SummaryModel>(
        (service, token) => service.GetSummaryAsync(id, token),
        "An error occurred while attempting to load the summary.",
        cancellationToken,
        new { id });
```

Do not use `async` lambdas with `ExecuteOperationAsync` — pass an expression lambda returning the service's `Task` directly, or the call can bind to the wrong overload and lose the typed response body.

---

## Analyzer compatibility

Before finishing, read `.ai-shared\bundles\umbrella\analyzer-compatibility.md` and build the affected projects with their installed analyzers enabled. Treat diagnostics introduced by the generated or changed code as defects in this workflow.

## Verification

1. Service interface is in the correct location — client data abstractions for Blazor projects, server abstractions for API-only.
2. The controller service file is in `Web\<AppName>.Web.Server\Services\` with namespace `<AppName>.Web.Server.Services`.
3. `IGenericDataService` has 8 type params; the first is `TModel` (full model), second is `TIdentifier` (int), third is `TSlimModel`.
4. `UmbrellaRepositoryDataService` has 11 type params; `TModel` comes before `TSlimModel` (opposite order from the controller's type list).
5. The controller service is `public class` (not `internal`).
6. The controller service constructor uses `IHostEnvironment` (not `IWebHostEnvironment`); the 9 base params are passed to `: base(...)`.
7. The controller is thin — no overrides or extra dependencies.
8. DI registration uses `AddScoped` or `ReplaceScoped` based on whether the client already registers the interface.
9. Authorization suppressions are deliberate: any operation requiring row-level authorization has no `=> false` override and has a registered resource authorization handler.
10. No standard CRUD method (`PostAsync`, `GetAsync`, `PutAsync`, `DeleteAsync`, `SearchSlimAsync`) is overridden in the controller service without a `base.XxxAsync(...)` call.
11. No `using` directive in the controller service references a `Core.Logic` namespace — domain logic belongs in a dedicated Core.Logic service, not here.

---

## Next steps

After the controller and controller service build and DI is wired, generate integration tests with `umbrella-dotnet-generate-api-data-service-controller-tests` (run `umbrella-dotnet-audit-api-controller-response-contract` first — enablement and authorization flags resolve on the controller service for this pattern).

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 →