Token导航 LogoToken导航

Agent Skill · 效率

dotnet-expert(点网专家)

dotnet-expert 用于辅助前端页面、组件、样式和交互逻辑开发,适合在 OpenClaw 中需要维护前端项目、生成组件或检查界面实现时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

dotnet-expert 是效率类 Agent Skill,可用宿主为 OpenClaw,通过ClawHub获取,权限为需要联网;许可证为 MIT-0,第三方扫描已通过,按安装量在效率类的 21,003 个 Skill 里排第 903。

总安装 61,182周安装 2,575GitHub Stars 1下载 21,424核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT-0
版本
1.0.0

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:dotnet-expert(点网专家)
来源仓库:https://github.com/jgarrison929/dotnet-expert
安装命令:
openclaw skills install dotnet-expert
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 OpenClaw 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

ClawHubOpenClaw
openclaw skills install dotnet-expert

这个 Skill 是做什么的

效率类 Skill 把重复的整理、汇总、转换工作交给助手完成,减少手工复制粘贴。

在构建 .NET 8/9 应用程序、ASP.NET Core API、Entity Framework Core、MediatR CQRS、模块化整体架构、FluentValidation、结果模式、JWT 身份验证或任何 C# 后端开发问题时使用。

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 OpenClaw;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 openclaw skills install dotnet-expert;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

dotnet-expert 怎么安装?

在终端执行 openclaw skills install dotnet-expert。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

dotnet-expert 支持哪些 AI 工具?

公开资料标注支持 OpenClaw。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

dotnet-expert 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

dotnet-expert 是免费的吗?

来源标注的许可证是 MIT-0。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

dotnet-expert 用的人多吗?

公开数据是累计安装 61,182 次,近一周安装 2,575 次,下载 21,424 次,GitHub 1 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

dotnet-expert 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 专注 OpenClaw 一种宿主,触发方式和使用习惯不用来回切换。
  • 许可证明确为 MIT-0,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 OpenClaw,想直接扩展它能力的人。
  • 需要处理效率类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

效率类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
github

GitHub 代码协作 · OpenClaw

7,632,918545需要联网MIT-0通过
nano-pdf

Nano PDF 文档 · OpenClaw

4,249,476231需要联网MIT-0通过
auto-updater

自动更新程序 · OpenClaw

2,964,063386操作浏览器MIT-0通过
video-frames

视频帧 · OpenClaw

2,621,304112需要联网MIT-0通过
gemini

Gemini 助手 · OpenClaw

2,547,39648需要联网MIT-0有提醒
dotnet-expert

点网专家 · 本页

61,1821需要联网MIT-0通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

name
dotnet-expert
version
1.0.0
description
Use when building .NET 8/9 applications, ASP.NET Core APIs, Entity Framework Core, MediatR CQRS, modular monolith architecture, FluentValidation, Result pattern, JWT authentication, or any C# backend development question.
triggers
role
specialist
scope
implementation
output-format
code

.NET Expert

Senior .NET 9 / ASP.NET Core specialist with expertise in clean architecture, CQRS, and modular monolith patterns.

Role Definition

You are a senior .NET engineer building production-grade APIs with ASP.NET Core, Entity Framework Core 9, MediatR, and FluentValidation. You follow clean architecture principles with a pragmatic approach.

Core Principles

  1. Result pattern over exceptions for business logic — exceptions for infrastructure only
  2. CQRS with MediatR — separate commands (writes) from queries (reads)
  3. FluentValidation for all input validation in the pipeline
  4. Modular monolith — organized by feature/domain, not by technical layer
  5. Strongly-typed IDs to prevent primitive obsession
  6. Async all the way — never .Result or .Wait()

Project Structure (Modular Monolith)

src/
├── Api/                          # ASP.NET Core host
│   ├── Program.cs
│   ├── appsettings.json
│   └── Endpoints/                # Minimal API endpoint definitions
├── Modules/
│   ├── Users/
│   │   ├── Users.Core/           # Domain entities, interfaces
│   │   ├── Users.Application/    # Commands, queries, handlers
│   │   └── Users.Infrastructure/ # EF Core, external services
│   ├── Orders/
│   │   ├── Orders.Core/
│   │   ├── Orders.Application/
│   │   └── Orders.Infrastructure/
│   └── Shared/
│       ├── Shared.Core/          # Common abstractions
│       └── Shared.Infrastructure/# Cross-cutting concerns
└── Tests/
    ├── Users.Tests/
    └── Orders.Tests/

Minimal API Patterns

Basic Endpoint Group

// Api/Endpoints/UserEndpoints.cs
public static class UserEndpoints
{
    public static void MapUserEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/users")
            .WithTags("Users")
            .RequireAuthorization();

        group.MapGet("/", GetUsers);
        group.MapGet("/{id:guid}", GetUserById);
        group.MapPost("/", CreateUser);
        group.MapPut("/{id:guid}", UpdateUser);
        group.MapDelete("/{id:guid}", DeleteUser);
    }

    private static async Task<IResult> GetUsers(
        [AsParameters] GetUsersQuery query,
        ISender mediator,
        CancellationToken ct)
    {
        var result = await mediator.Send(query, ct);
        return result.Match(
            success => Results.Ok(success),
            error => Results.Problem(error.ToProblemDetails()));
    }

    private static async Task<IResult> GetUserById(
        Guid id,
        ISender mediator,
        CancellationToken ct)
    {
        var result = await mediator.Send(new GetUserByIdQuery(id), ct);
        return result.Match(
            success => Results.Ok(success),
            error => error.Type == ErrorType.NotFound
                ? Results.NotFound()
                : Results.Problem(error.ToProblemDetails()));
    }

    private static async Task<IResult> CreateUser(
        CreateUserCommand command,
        ISender mediator,
        CancellationToken ct)
    {
        var result = await mediator.Send(command, ct);
        return result.Match(
            success => Results.Created($"/api/users/{success.Id}", success),
            error => Results.Problem(error.ToProblemDetails()));
    }
}

Program.cs Setup

var builder = WebApplication.CreateBuilder(args);

// Add modules
builder.Services.AddUsersModule(builder.Configuration);
builder.Services.AddOrdersModule(builder.Configuration);

// Add shared infrastructure
builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssemblies(
        typeof(UsersModule).Assembly,
        typeof(OrdersModule).Assembly));

builder.Services.AddValidatorsFromAssemblies(new[]
{
    typeof(UsersModule).Assembly,
    typeof(OrdersModule).Assembly,
});

// Add validation pipeline behavior
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]!)),
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapUserEndpoints();
app.MapOrderEndpoints();

app.Run();

Result Pattern

Result Type

// Shared.Core/Result.cs
public sealed class Result<T>
{
    public T? Value { get; }
    public Error? Error { get; }
    public bool IsSuccess { get; }

    private Result(T value) { Value = value; IsSuccess = true; }
    private Result(Error error) { Error = error; IsSuccess = false; }

    public static Result<T> Success(T value) => new(value);
    public static Result<T> Failure(Error error) => new(error);

    public TResult Match<TResult>(
        Func<T, TResult> onSuccess,
        Func<Error, TResult> onFailure) =>
        IsSuccess ? onSuccess(Value!) : onFailure(Error!);
}

public sealed record Error(string Code, string Message, ErrorType Type = ErrorType.Failure)
{
    public static Error NotFound(string code, string message) => new(code, message, ErrorType.NotFound);
    public static Error Validation(string code, string message) => new(code, message, ErrorType.Validation);
    public static Error Conflict(string code, string message) => new(code, message, ErrorType.Conflict);
    public static Error Forbidden(string code, string message) => new(code, message, ErrorType.Forbidden);

    public ProblemDetails ToProblemDetails() => new()
    {
        Title = Code,
        Detail = Message,
        Status = Type switch
        {
            ErrorType.NotFound => StatusCodes.Status404NotFound,
            ErrorType.Validation => StatusCodes.Status400BadRequest,
            ErrorType.Conflict => StatusCodes.Status409Conflict,
            ErrorType.Forbidden => StatusCodes.Status403Forbidden,
            _ => StatusCodes.Status500InternalServerError,
        },
    };
}

public enum ErrorType { Failure, NotFound, Validation, Conflict, Forbidden }

Usage in Handlers

// No exceptions for business logic!
public sealed class CreateUserHandler : IRequestHandler<CreateUserCommand, Result<UserResponse>>
{
    private readonly AppDbContext _db;

    public CreateUserHandler(AppDbContext db) => _db = db;

    public async Task<Result<UserResponse>> Handle(
        CreateUserCommand command, CancellationToken ct)
    {
        // Business rule validation returns errors, not exceptions
        var existingUser = await _db.Users
            .AnyAsync(u => u.Email == command.Email, ct);

        if (existingUser)
            return Result<UserResponse>.Failure(
                Error.Conflict("User.DuplicateEmail", "A user with this email already exists"));

        var user = new User
        {
            Id = Guid.NewGuid(),
            Email = command.Email,
            Name = command.Name,
            CreatedAt = DateTime.UtcNow,
        };

        _db.Users.Add(user);
        await _db.SaveChangesAsync(ct);

        return Result<UserResponse>.Success(user.ToResponse());
    }
}

MediatR CQRS

Commands (Write Operations)

// Users.Application/Commands/CreateUserCommand.cs
public sealed record CreateUserCommand(
    string Email,
    string Name,
    string Password) : IRequest<Result<UserResponse>>;

Queries (Read Operations)

// Users.Application/Queries/GetUsersQuery.cs
public sealed record GetUsersQuery(
    int Page = 1,
    int PageSize = 20,
    string? Search = null) : IRequest<Result<PagedResult<UserResponse>>>;

public sealed class GetUsersHandler : IRequestHandler<GetUsersQuery, Result<PagedResult<UserResponse>>>
{
    private readonly AppDbContext _db;

    public GetUsersHandler(AppDbContext db) => _db = db;

    public async Task<Result<PagedResult<UserResponse>>> Handle(
        GetUsersQuery query, CancellationToken ct)
    {
        var dbQuery = _db.Users.AsNoTracking();

        if (!string.IsNullOrWhiteSpace(query.Search))
            dbQuery = dbQuery.Where(u =>
                u.Name.Contains(query.Search) || u.Email.Contains(query.Search));

        var total = await dbQuery.CountAsync(ct);

        var users = await dbQuery
            .OrderBy(u => u.Name)
            .Skip((query.Page - 1) * query.PageSize)
            .Take(query.PageSize)
            .Select(u => u.ToResponse())
            .ToListAsync(ct);

        return Result<PagedResult<UserResponse>>.Success(
            new PagedResult<UserResponse>(users, total, query.Page, query.PageSize));
    }
}

Validation Pipeline Behavior

public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;

    public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
        => _validators = validators;

    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        if (!_validators.Any()) return await next();

        var context = new ValidationContext<TRequest>(request);
        var results = await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, ct)));

        var failures = results
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();

        if (failures.Count > 0)
            throw new ValidationException(failures);

        return await next();
    }
}

FluentValidation

public sealed class CreateUserValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("Email is required")
            .EmailAddress().WithMessage("Invalid email format")
            .MaximumLength(255);

        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Name is required")
            .MinimumLength(2)
            .MaximumLength(100)
            .Matches(@"^[a-zA-Z\s'-]+$").WithMessage("Name contains invalid characters");

        RuleFor(x => x.Password)
            .NotEmpty()
            .MinimumLength(8)
            .Matches("[A-Z]").WithMessage("Password must contain uppercase letter")
            .Matches("[a-z]").WithMessage("Password must contain lowercase letter")
            .Matches("[0-9]").WithMessage("Password must contain a number")
            .Matches("[^a-zA-Z0-9]").WithMessage("Password must contain a special character");
    }
}

Entity Framework Core 9

DbContext

public sealed class AppDbContext : DbContext
{
    public DbSet<User> Users => Set<User>();
    public DbSet<Order> Orders => Set<Order>();
    public DbSet<OrderItem> OrderItems => Set<OrderItem>();

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
    }

    public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
    {
        // Auto-set audit fields
        foreach (var entry in ChangeTracker.Entries<IAuditable>())
        {
            if (entry.State == EntityState.Added)
                entry.Entity.CreatedAt = DateTime.UtcNow;

            if (entry.State == EntityState.Modified)
                entry.Entity.UpdatedAt = DateTime.UtcNow;
        }

        return await base.SaveChangesAsync(ct);
    }
}

Entity Configuration

public sealed class UserConfiguration : IEntityTypeConfiguration<User>
{
    public void Configure(EntityTypeBuilder<User> builder)
    {
        builder.ToTable("users");

        builder.HasKey(u => u.Id);

        builder.Property(u => u.Email)
            .HasMaxLength(255)
            .IsRequired();

        builder.HasIndex(u => u.Email).IsUnique();

        builder.Property(u => u.Name)
            .HasMaxLength(100)
            .IsRequired();

        builder.Property(u => u.PasswordHash)
            .HasMaxLength(255)
            .IsRequired();

        builder.HasMany(u => u.Orders)
            .WithOne(o => o.User)
            .HasForeignKey(o => o.UserId)
            .OnDelete(DeleteBehavior.Cascade);

        // Query filter for soft delete
        builder.HasQueryFilter(u => u.DeletedAt == null);
    }
}

Migrations

# Create migration
dotnet ef migrations add AddUserTable -p src/Users.Infrastructure -s src/Api

# Apply migration
dotnet ef database update -p src/Users.Infrastructure -s src/Api

# Generate SQL script (for production)
dotnet ef migrations script -p src/Users.Infrastructure -s src/Api -o migrations.sql --idempotent

Query Optimization

// ❌ BAD: N+1 queries
var users = await _db.Users.ToListAsync(ct);
foreach (var user in users)
{
    var orders = await _db.Orders.Where(o => o.UserId == user.Id).ToListAsync(ct);
}

// ✅ GOOD: Eager loading
var users = await _db.Users
    .Include(u => u.Orders)
    .ToListAsync(ct);

// ✅ BEST: Projection (only load what you need)
var users = await _db.Users
    .AsNoTracking()
    .Select(u => new UserResponse
    {
        Id = u.Id,
        Name = u.Name,
        Email = u.Email,
        OrderCount = u.Orders.Count,
    })
    .ToListAsync(ct);

ASP.NET Identity + JWT Auth

Identity Setup

builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireUppercase = true;
    options.Password.RequireNonAlphanumeric = true;
    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
    options.Lockout.MaxFailedAccessAttempts = 5;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

JWT Token Generation

public sealed class TokenService : ITokenService
{
    private readonly IConfiguration _config;

    public TokenService(IConfiguration config) => _config = config;

    public string GenerateAccessToken(ApplicationUser user, IList<string> roles)
    {
        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new(ClaimTypes.Email, user.Email!),
            new(ClaimTypes.Name, user.UserName!),
        };

        claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_config["Jwt:Key"]!));

        var token = new JwtSecurityToken(
            issuer: _config["Jwt:Issuer"],
            audience: _config["Jwt:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(15),
            signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256));

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public string GenerateRefreshToken()
    {
        var randomBytes = new byte[64];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(randomBytes);
        return Convert.ToBase64String(randomBytes);
    }
}

Domain Entity Pattern

public sealed class Order : IAuditable
{
    public Guid Id { get; private set; }
    public Guid UserId { get; private set; }
    public OrderStatus Status { get; private set; }
    public decimal Total { get; private set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }

    private readonly List<OrderItem> _items = [];
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();

    private Order() { } // EF Core

    public static Order Create(Guid userId)
    {
        return new Order
        {
            Id = Guid.NewGuid(),
            UserId = userId,
            Status = OrderStatus.Pending,
            Total = 0,
        };
    }

    public Result<OrderItem> AddItem(Guid productId, int quantity, decimal unitPrice)
    {
        if (Status != OrderStatus.Pending)
            return Result<OrderItem>.Failure(
                Error.Validation("Order.NotPending", "Cannot add items to a non-pending order"));

        if (quantity <= 0)
            return Result<OrderItem>.Failure(
                Error.Validation("Order.InvalidQuantity", "Quantity must be positive"));

        var item = new OrderItem(Guid.NewGuid(), Id, productId, quantity, unitPrice);
        _items.Add(item);
        RecalculateTotal();

        return Result<OrderItem>.Success(item);
    }

    public Result<bool> Submit()
    {
        if (_items.Count == 0)
            return Result<bool>.Failure(
                Error.Validation("Order.Empty", "Cannot submit an empty order"));

        Status = OrderStatus.Submitted;
        return Result<bool>.Success(true);
    }

    private void RecalculateTotal()
    {
        Total = _items.Sum(i => i.Quantity * i.UnitPrice);
    }
}

public enum OrderStatus { Pending, Submitted, Processing, Shipped, Delivered, Cancelled }

Anti-Patterns to Avoid

  1. ❌ Throwing exceptions for validation/business logic — use Result pattern
  2. ❌ Anemic domain models (entities with only properties) — put behavior in entities
  3. ❌ Fat controllers/endpoints — delegate to MediatR handlers
  4. ❌ .Result or .Wait() on async calls — async all the way
  5. ❌ Returning IQueryable from repositories — materialize queries in the handler
  6. ❌ Using AutoMapper for simple mappings — manual mapping or extension methods
  7. ❌ Catching Exception broadly — catch specific exceptions at infrastructure boundaries
  8. ❌ Hard-coding connection strings — use IConfiguration and environment variables
  9. ❌ Missing CancellationToken — pass it through the entire call chain
  10. ❌ Using DbContext without AsNoTracking() for read queries

平台分布

OpenClaw

73.44%
按下载量换算15,734

安全审计

VirusTotal

通过

ClawScan

通过

Static analysis

未展示

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills