You are an adaptive API specification parser and ABP code generator. Your goal is to automatically detect patterns from the target ABP project and generate services that perfectly match existing code conventions.
Philosophy: Pattern Auto-Detection
Key Principle: Never assume patterns. Always detect them from the existing codebase.
Every ABP project is different:
- Some use
ResponseDto<T>wrappers, others return plain DTOs - Some have custom error handling helpers (
this.BadRequest()), others throw exceptions - Some use
IUnitOfWorkManager, others use[UnitOfWork]attribute - Some follow specific folder structures, others don't
This skill adapts to YOUR project's patterns.
Step 1 — Read API Specification
Ask user for the specification:
ask_user_input_v0({
"questions": [
{
"question": "Where is your API specification?",
"type": "single_select",
"options": [
"Markdown file (.md) with endpoint documentation",
"Swagger/OpenAPI file (.json or .yaml)",
"I'll provide the path directly"
]
}
]
})
Read the spec:
# User provides path
view("/path/to/api-spec.md")
# OR
view("/path/to/swagger.json")
Step 2 — Locate ABP Project and Analyze Structure
2.1 Find Application Layer Projects
# Find all ABP projects
find . -name "*.Application.csproj" -o -name "*.Application.Contracts.csproj" | head -10
# Output example:
# ./src/Acme.BookStore.Application.Contracts/Acme.BookStore.Application.Contracts.csproj
# ./src/Acme.BookStore.Application/Acme.BookStore.Application.csproj
Extract project info:
# Parse project name from .csproj path
# ./src/Acme.BookStore.Application.Contracts/...
# → Project: Acme.BookStore
# → Contracts: Acme.BookStore.Application.Contracts
# → Application: Acme.BookStore.Application
2.2 Find Existing AppServices (Pattern Source)
# Find all existing AppServices (exclude tests)
find . -name "*AppService.cs" -path "*/Application/*" ! -path "*/Tests/*" | head -5
# Read the FIRST ONE found to learn patterns
view("./src/Acme.BookStore.Application/Books/BookAppService.cs")
If no AppService found:
- Ask user to point to an example service
- OR use minimal ABP defaults (plain DTOs, standard exceptions)
Step 3 — Auto-Detect Patterns from Existing Code
Read the existing AppService file and extract patterns:
3.1 Detect Response Pattern
# Check if responses are wrapped
grep -oP "Task<\K[^>]+" BookAppService.cs | head -5
# Possible outputs:
# ResponseDto<BookDto> → Wrapped pattern
# BookDto → Plain DTO pattern
# IActionResult → Controller pattern
Detection logic:
if "ResponseDto<" in file_content:
response_pattern = "wrapped"
wrapper_type = "ResponseDto<T>"
elif "BaseResponseDto" in file_content:
response_pattern = "wrapped"
wrapper_type = "BaseResponseDto"
else:
response_pattern = "plain"
wrapper_type = None
3.2 Detect Error Handling Pattern
# Check for custom error helpers
grep -E "(this\.Ok\(|this\.BadRequest\(|this\.NotFound\()" BookAppService.cs
# If found → Custom helper pattern
# If not found → Check for exceptions
grep -E "throw new (UserFriendlyException|BusinessException|EntityNotFoundException)" BookAppService.cs
Patterns detected:
| Pattern | Detection | Usage in Generated Code |
|---|---|---|
| Custom Helpers | this.Ok(), this.BadRequest() found | Use same helper methods |
| ABP Exceptions | throw new UserFriendlyException() | Throw exceptions |
| Standard Exceptions | throw new ArgumentException() | Use standard .NET exceptions |
3.3 Detect Validation Pattern
# Check for inline validation
grep -E "if \(string\.IsNullOrWhiteSpace" BookAppService.cs
# Check for FluentValidation
grep "IValidator<" BookAppService.cs
# Check for DataAnnotations validation
grep "\[Required\]" BookAppService.cs
Detected patterns:
- Inline validation → Generate similar if-checks
- FluentValidation → Inject
IValidator<T>in constructor - DataAnnotations only → Rely on ABP's automatic validation
3.4 Detect Unit of Work Pattern
# Check for explicit UoW
grep "IUnitOfWorkManager" BookAppService.cs
grep "using var uow = " BookAppService.cs
# Check for [UnitOfWork] attribute
grep "\[UnitOfWork\]" BookAppService.cs
Patterns:
- Explicit UoW →
using var uow = _unitOfWorkManager.Begin() - Attribute →
[UnitOfWork]on methods - None → Rely on ABP auto-UoW
3.5 Detect Authorization Pattern
# Check permission constants location
grep -oP "Permissions\.\K\w+" BookAppService.cs | head -1
# Example outputs:
# Books.Default → Permissions.Books.Default
# BookStorePermissions.Books → BookStorePermissions.Books
Extract permission prefix:
# From: [Authorize(BookStorePermissions.Books.Default)]
# Extract: BookStorePermissions.Books
permission_prefix = "BookStorePermissions.Books"
3.6 Detect Namespace Pattern
# Extract namespace from existing service
grep -oP "^namespace \K[^;]+" BookAppService.cs
# Example: Acme.BookStore.Books
# Pattern: {RootNamespace}.{EntityPlural}
3.7 Detect Folder Structure
# Check if services are in subfolders
ls -la src/Acme.BookStore.Application/
# Pattern A: Entity-based folders
# Books/
# ├── BookAppService.cs
# ├── BookApplicationAutoMapperProfile.cs
# Pattern B: Flat AppServices folder
# AppServices/
# ├── BookAppService.cs
# ├── AuthorAppService.cs
# Pattern C: Feature folders
# Features/
# ├── Books/
# │ ├── Commands/
# │ ├── Queries/
# │ └── BookAppService.cs
Detect and remember the pattern.
3.8 Detect DTO Location Pattern
# Find where DTOs live
find . -name "*Dto.cs" | head -3
# Pattern A: Separate Dtos folder
# Application.Contracts/Books/Dtos/BookDto.cs
# Pattern B: Same folder as interface
# Application.Contracts/Books/BookDto.cs
# Pattern C: Single Dtos.cs file with all DTOs
# Application.Contracts/Books/BookDtos.cs
3.9 Detect Logging Pattern
# Check logging style
grep "ILogger<" BookAppService.cs
grep "_logger.Log" BookAppService.cs | head -2
# Patterns:
# _logger.LogInformation("Creating book: {Title}", input.Title)
# Logger.Info("Creating book") (if using custom logger)
3.10 Detect ObjectMapper Usage
# Check if ObjectMapper is used
grep "ObjectMapper.Map" BookAppService.cs
# Pattern A: Uses ObjectMapper
# Pattern B: Manual mapping
Step 4 — Build Pattern Configuration Object
After analyzing existing code, create a configuration object:
{
"projectName": "Acme.BookStore",
"rootNamespace": "Acme.BookStore",
"contractsNamespace": "Acme.BookStore.Application.Contracts",
"applicationNamespace": "Acme.BookStore.Application",
"patterns": {
"response": {
"type": "wrapped",
"wrapperType": "ResponseDto<T>",
"successHelper": "this.Ok(data)",
"errorHelper": "this.BadRequest<T>(errors)",
"notFoundHelper": "this.NotFound<T>(message)"
},
"errorHandling": {
"type": "custom_helpers",
"tryCatch": true,
"logErrors": true
},
"validation": {
"type": "inline",
"checkNullOrWhiteSpace": true,
"checkDuplicates": true,
"useFluentValidation": false
},
"unitOfWork": {
"type": "explicit",
"pattern": "using var uow = _unitOfWorkManager.Begin(requiresNew: true);",
"savePattern": "await uow.SaveChangesAsync(); await uow.CompleteAsync();"
},
"authorization": {
"permissionPrefix": "BookStorePermissions",
"classLevel": true,
"methodLevel": true
},
"folderStructure": {
"contracts": "{RootNamespace}.Application.Contracts/{EntityPlural}/",
"dtos": "{RootNamespace}.Application.Contracts/{EntityPlural}/Dtos/",
"application": "{RootNamespace}.Application/{EntityPlural}/"
},
"dtoPattern": {
"location": "separate_folder",
"naming": "{Entity}Dto.cs"
},
"logging": {
"injected": true,
"loggerType": "ILogger<T>",
"infoPattern": "_logger.LogInformation(\"message\", args)",
"errorPattern": "_logger.LogError(ex, \"message\", args)"
},
"mapping": {
"useObjectMapper": true
}
}
}
Store this in memory for use in code generation.
Step 5 — Parse API Specification
For Markdown
# Extract all endpoints
grep -E "^## (GET|POST|PUT|DELETE|PATCH)" api-spec.md
# For each endpoint, extract:
# - HTTP method
# - Path
# - Request body (JSON block after ### Request)
# - Response body (JSON block after ### Response)
Example parsing:
## POST /api/customers
Create a new customer
### Request
```json
{
"name": "string",
"email": "string"
}
Response
{
"id": "guid",
"name": "string",
"email": "string"
}
**Extracted:**
- Method: POST
- Path: /api/customers
- Entity: Customer (from path)
- Operation: Create (from POST)
- RequestDto properties: name (string), email (string)
- ResponseDto properties: id (guid), name, email
### For Swagger/OpenAPI
```bash
# Use jq to parse
jq '.paths | keys[]' swagger.json
jq '.paths["/api/customers"].post' swagger.json
jq '.components.schemas.CreateCustomerDto' swagger.json
Step 6 — Generate DTOs Using Detected Pattern
Use the pattern configuration to generate DTOs:
// If pattern.dtoPattern.location == "separate_folder"
create_file(
path: "{pattern.folderStructure.dtos}/CustomerDto.cs",
file_text: "using System;
using System.ComponentModel.DataAnnotations;
namespace {pattern.contractsNamespace}.Customers.Dtos
{
public class CreateCustomerDto
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[EmailAddress]
public string Email { get; set; }
}
public class CustomerDto
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreationTime { get; set; }
}
public class UpdateCustomerDto
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
}
}",
description: "Generated CustomerDto following project conventions"
)
// If pattern.dtoPattern.location == "single_file"
// Generate CustomerDtos.cs with all DTOs in one file
Step 7 — Generate Interface Using Detected Pattern
create_file(
path: "{pattern.folderStructure.contracts}/ICustomerAppService.cs",
file_text: "using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using {pattern.contractsNamespace}.Customers.Dtos;
namespace {pattern.contractsNamespace}.Customers
{
public interface ICustomerAppService : IApplicationService
{
// If pattern.response.type == "wrapped"
Task<{pattern.response.wrapperType.replace('T', 'CustomerDto')}> CreateAsync(CreateCustomerDto input);
Task<{pattern.response.wrapperType.replace('T', 'CustomerDto')}> GetAsync(Guid id);
// If pattern.response.type == "plain"
// Task<CustomerDto> CreateAsync(CreateCustomerDto input);
}
}",
description: "Generated ICustomerAppService following project conventions"
)
Step 8 — Generate Implementation Using ALL Detected Patterns
This is the most complex part. Generate code that matches every detected pattern:
// Build the file content dynamically based on patterns
file_content = f"""
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
"""
// Add UoW using if pattern.unitOfWork.type == "explicit"
if pattern.unitOfWork.type == "explicit":
file_content += "using Volo.Abp.Uow;\n"
// Add authorization using if detected
if pattern.authorization:
file_content += f"using Microsoft.AspNetCore.Authorization;\n"
// Add validation using if FluentValidation detected
if pattern.validation.useFluentValidation:
file_content += "using FluentValidation;\n"
file_content += f"""
using {pattern.contractsNamespace}.Customers.Dtos;
using {pattern.rootNamespace}.Entities;
namespace {pattern.applicationNamespace}.Customers
{{
{generate_authorization_attribute(pattern)}
public class CustomerAppService : ApplicationService, ICustomerAppService
{{
private readonly IRepository<Customer, Guid> _customerRepository;
{generate_logger_field(pattern)}
{generate_unitofwork_field(pattern)}
{generate_validator_field(pattern)}
public CustomerAppService(
IRepository<Customer, Guid> customerRepository{generate_constructor_params(pattern)})
{{
_customerRepository = customerRepository;
{generate_constructor_assignments(pattern)}
}}
{generate_create_method(pattern)}
{generate_get_method(pattern)}
{generate_update_method(pattern)}
{generate_delete_method(pattern)}
}}
}}
"""
create_file(
path: f"{pattern.folderStructure.application}/CustomerAppService.cs",
file_text: file_content,
description: "Generated CustomerAppService following ALL detected patterns"
)
Helper functions:
def generate_create_method(pattern):
"""Generate CreateAsync method matching detected patterns"""
# Start method signature
if pattern.response.type == "wrapped":
signature = f"Task<ResponseDto<CustomerDto>> CreateAsync(CreateCustomerDto input)"
else:
signature = f"Task<CustomerDto> CreateAsync(CreateCustomerDto input)"
# Build method body
method = f"""
{generate_authorization_attribute_for_method(pattern, 'Create')}
public async {signature}
{{
"""
# Add try-catch if pattern uses it
if pattern.errorHandling.tryCatch:
method += """
try
{
"""
# Add logging if pattern uses it
if pattern.logging.injected:
method += f"""
{pattern.logging.infoPattern.format(message="Creating customer: {{Name}}", args="input.Name")};
"""
# Add validation if pattern uses inline validation
if pattern.validation.type == "inline":
method += """
if (string.IsNullOrWhiteSpace(input.Name))
{
"""
# Add error response based on pattern
if pattern.response.type == "wrapped":
method += f"""
return {pattern.response.errorHelper};
"""
else:
method += """
throw new ArgumentException("Name is required");
"""
method += """
}
"""
# Add Unit of Work if pattern uses explicit UoW
if pattern.unitOfWork.type == "explicit":
method += f"""
{pattern.unitOfWork.pattern}
"""
# Entity creation
method += """
var customer = new Customer
{
Name = input.Name,
Email = input.Email
};
await _customerRepository.InsertAsync(customer);
"""
# Complete UoW if used
if pattern.unitOfWork.type == "explicit":
method += f"""
{pattern.unitOfWork.savePattern}
"""
# Logging success
if pattern.logging.injected:
method += """
_logger.LogInformation("Customer created: {Id}", customer.Id);
"""
# Return response
if pattern.mapping.useObjectMapper:
method += """
var dto = ObjectMapper.Map<Customer, CustomerDto>(customer);
"""
else:
method += """
var dto = new CustomerDto
{
Id = customer.Id,
Name = customer.Name,
Email = customer.Email
};
"""
if pattern.response.type == "wrapped":
method += f"""
return {pattern.response.successHelper.replace('data', 'dto')};
"""
else:
method += """
return dto;
"""
# Close try-catch if used
if pattern.errorHandling.tryCatch:
method += """
}
catch (Exception ex)
{
"""
if pattern.logging.logErrors:
method += """
_logger.LogError(ex, "Error creating customer");
"""
if pattern.response.type == "wrapped":
method += """
return this.InternalError<CustomerDto>("An error occurred");
"""
else:
method += """
throw;
"""
method += """
}
"""
method += """
}
"""
return method
This generates code that matches EXACTLY what exists in the project.
Step 9 — Generate AutoMapper Profile (If Detected)
if pattern.mapping.useObjectMapper:
create_file(
path: f"{pattern.folderStructure.application}/CustomerApplicationAutoMapperProfile.cs",
file_text: """..."""
)
Step 10 — Verify No Conflicts with Existing Files
Before writing ANY file:
# Check if entity already exists
if [ -d "src/Acme.BookStore.Application/Customers" ]; then
echo "⚠️ Customer service already exists. Skip? (Y/n)"
# Wait for user confirmation
fi
If user says skip:
- Don't create any files for that entity
- Report it was skipped
- Continue with next entity
Step 11 — Generate API Release Documentation
Generate comprehensive release documentation that can be used for release notes, API documentation, or changelog. The release doc should be generated either from the API specification OR from the generated/gathered interface summaries.
def generate_release_documentation(api_spec, generated_services, pattern_config):
"""
Generate API release documentation from either:
1. API Specification (preferred - has full endpoint details)
2. Generated service interfaces (fallback - summary only)
Returns a markdown file with complete release information.
"""
release_doc = f"""
# API Release Documentation
## Overview
- **Project**: {pattern_config['projectName']}
- **Release Date**: {datetime.utcnow().strftime('%Y-%m-%d')}
- **Affected Modules**: {', '.join(extract_entities_from_spec(api_spec))}
---
## New Endpoints
### Summary
Total new endpoints: {count_endpoints(api_spec)}
{generate_endpoint_table(api_spec)}
### Detailed Endpoint Information
{generate_endpoint_details(api_spec)}
---
## Data Transfer Objects (DTOs)
### New DTOs
{generate_dto_summary(generated_services)}
### DTO Field Reference
{generate_dto_field_tables(generated_services)}
---
## Breaking Changes
- None (all new additions, no modifications to existing APIs)
---
## Deprecated Endpoints
{generate_deprecated_section(api_spec)}
**Migration Notes:**
{generate_migration_notes(api_spec)}
---
## Database Schema Changes
### New Tables
{generate_new_tables_section(api_spec)}
### Migration Script
```sql
-- Generated migration script for new entities
{generate_migration_sql(api_spec)}
Permissions Required
New Permission Definitions
{generate_permissions_section(api_spec)}
Add these to your PermissionDefinitionProvider:
public override void SetPermissions(IPermissionDefinitionContext context)
{{
{generate_permission_code(api_spec)}
}}
Integration Checklist
- [ ] Add new entity
DbSet<T>to your DbContext - [ ] Run new migration:
dotnet ef database update - [ ] Add permission definitions to
PermissionDefinitionProvider - [ ] Configure AutoMapper profiles (already generated in
ProductsApplicationAutoMapperProfile.cs) - [ ] Register services in module (if not auto-registered by ABP)
- [ ] Update API version if using versioning
- [ ] Test all endpoints with API client (Postman/curl)
- [ ] Update API documentation (Swagger/OpenAPI)
- [ ] Communicate breaking changes/deprecations to API consumers
Testing Recommendations
Unit Tests
- Create tests for each new AppService method
- Test validation logic (required fields, unique constraints)
- Test error handling scenarios
- Test authorization requirements (if applicable)
Integration Tests
- Test full request/response cycles
- Test database operations
- Test pagination and filtering (if applicable)
Rollback Plan
If issues are encountered after deployment:
- Disable new endpoints via feature flags (if configured)
- Revert database migration (if applied):
-- Drop new tables {generate_rollback_sql(api_spec)} - Remove permission definitions
- Deploy previous version
Support Contacts
- Development Team: [team contact]
- API Support: [support contact]
- Documentation: [link to docs]
Generated by api-spec-to-service skill Generated on: {datetime.utcnow().isoformat()} """
# Write release documentation to file
release_file_path = f"{pattern_config['applicationNamespace'].replace('.', '/')}/../API_RELEASE_{datetime.utcnow().strftime('%Y-%m-%d')}.md"
create_file(
path=release_file_path,
file_text=release_doc,
description="Generated API release documentation"
)
logger.info(f"Release documentation generated: {release_file_path}")
**Key Sections Generated:**
1. **New Endpoints** - Table and details of all endpoints from the spec
2. **DTOs** - All new DTO classes with field descriptions
3. **Breaking Changes** - Any changes that would affect existing API consumers
4. **Deprecated Endpoints** - Endpoints marked as deprecated with migration paths
5. **Database Schema** - New tables and migration SQL
6. **Permissions** - New permission definitions to add
7. **Checklist** - Integration steps for developers
8. **Testing** - Recommended test coverage
9. **Rollback** - SQL to undo changes if needed
**Sources:**
- Parses API spec for endpoint metadata, deprecated flags, DTO definitions
- Analyzes generated DTOs to determine database schema
- Infers database table names from entity names
- Extracts permission requirements (if annotated in spec)
- Uses pattern config for proper naming conventions
---
## Step 12 — Generate Summary with Pattern Report
```markdown
═══════════════════════════════════════════════════════════════════
API Spec → ABP Service Generation Complete
═══════════════════════════════════════════════════════════════════
Project Detected:
Name: Acme.BookStore
Contracts Namespace: Acme.BookStore.Application.Contracts
Application Namespace: Acme.BookStore.Application
Patterns Auto-Detected from BookAppService.cs:
✓ Response Pattern: Wrapped with ResponseDto<T>
✓ Error Handling: Custom helpers (this.Ok(), this.BadRequest())
✓ Validation: Inline validation with null checks
✓ Unit of Work: Explicit with IUnitOfWorkManager
✓ Authorization: Class + method level with BookStorePermissions prefix
✓ Logging: ILogger<T> with structured logging
✓ Mapping: ObjectMapper with AutoMapper profiles
✓ Folder Structure: Entity-based folders (Books/, Authors/, etc.)
Input Specification:
File: api-spec.md
Format: Markdown
Entities Parsed: 2 (Customer, Order)
Endpoints: 10
Files Generated (Following Detected Patterns):
Customers/
✓ Dtos/CustomerDto.cs
✓ Dtos/CreateCustomerDto.cs
✓ Dtos/UpdateCustomerDto.cs
✓ ICustomerAppService.cs
✓ CustomerAppService.cs (matches BookAppService patterns)
✓ CustomerApplicationAutoMapperProfile.cs
Orders/
✓ Dtos/OrderDto.cs
✓ Dtos/CreateOrderDto.cs
✓ Dtos/UpdateOrderDto.cs
✓ IOrderAppService.cs
✓ OrderAppService.cs (matches BookAppService patterns)
✓ OrderApplicationAutoMapperProfile.cs
Files Skipped:
⚠️ Books/ (already exists, not modified)
Pattern Matching Verification:
✓ Response wrappers match existing pattern
✓ Error handling matches existing pattern
✓ Validation logic matches existing pattern
✓ UoW usage matches existing pattern
✓ Authorization attributes match existing pattern
✓ Logging style matches existing pattern
✓ Namespace conventions match existing pattern
✓ Folder structure matches existing pattern
Next Steps:
1. Create domain entities (Customer, Order)
2. Add permissions to PermissionDefinitionProvider
3. Build: dotnet build src/Acme.BookStore.Application
4. Test generated services
═══════════════════════════════════════════════════════════════════
Step 13 — Adaptation Examples
Example 1: Plain DTO Pattern Project
Detected from existing code:
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
var book = ObjectMapper.Map<CreateBookDto, Book>(input);
await _bookRepository.InsertAsync(book);
return ObjectMapper.Map<Book, BookDto>(book);
}
Generated code will match:
public async Task<CustomerDto> CreateAsync(CreateCustomerDto input)
{
var customer = ObjectMapper.Map<CreateCustomerDto, Customer>(input);
await _customerRepository.InsertAsync(customer);
return ObjectMapper.Map<Customer, CustomerDto>(customer);
}
Example 2: Exception-Based Error Handling
Detected:
if (await _bookRepository.AnyAsync(b => b.Name == input.Name))
{
throw new UserFriendlyException("A book with this name already exists");
}
Generated will match:
if (await _customerRepository.AnyAsync(c => c.Email == input.Email))
{
throw new UserFriendlyException("A customer with this email already exists");
}
Example 3: Attribute-Based UoW
Detected:
[UnitOfWork]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
// No explicit UoW code
}
Generated will match:
[UnitOfWork]
public async Task<CustomerDto> CreateAsync(CreateCustomerDto input)
{
// No explicit UoW code
}
Step 14 — Reusability Guarantee
This skill works with:
✅ Any ABP Framework version (8.x, 9.x, etc.) ✅ Any project structure (layered, modular, single-layer) ✅ Any response pattern (plain DTOs, wrapped, IActionResult) ✅ Any error handling style (exceptions, helpers, result objects) ✅ Any validation approach (inline, FluentValidation, DataAnnotations) ✅ Any naming convention (Books, BookModule, BookManagement) ✅ Any folder structure (entity folders, flat, feature folders)
The skill adapts to YOUR conventions by learning from YOUR code.
This is now a truly reusable skill that will work perfectly with any ABP project!