Indotalent Enterprise Kit Documentation
A comprehensive guide to the architecture, features, and development workflow of the Indotalent ASP.NET Core MVC enterprise starter kit.
1. Architecture Overview
Indotalent uses Vertical Slice Architecture (VSA) with ASP.NET Core Areas. Each feature lives in its own self-contained folder, including its controller, CQRS handlers, validators, API endpoints, views, and JavaScript. This eliminates the need to jump between multiple projects when working on a single feature.
Key Architectural Decisions
| Aspect | Implementation |
|---|---|
| Architecture | Vertical Slice via ASP.NET Core Areas |
| Backend API | Minimal API (not MVC controllers for data operations) |
| CQRS | Plain handlers (no MediatR dependency) |
| Database | EF Core with multi-provider (InMemory / SQL Server / PostgreSQL) |
| Primary Keys | String (GUID) — no auto-increment |
| Soft Delete | IHasIsDeleted + global query filter |
| Audit | IHasAudit + auto-populated on SaveChanges |
| Validation | FluentValidation (server) + custom JS (client) |
| Frontend | Vue 3 Composition API + DataTables (inside MVC views) |
| Auth | ASP.NET Core Identity + JWT with Refresh Token Rotation + Firebase SSO |
| Rate Limiting | System.Threading.RateLimiting — 4 policies |
| Background Jobs | Hangfire with built-in dashboard |
2. Project Structure
The project is organized into ASP.NET Core Areas. Each area groups features by access level:
| Area | Purpose | Auth Required |
|---|---|---|
Areas/Public/ | Public-facing pages (Home, Privacy, Documentation) | No |
Areas/Identity/ | ASP.NET Core Identity pages (Login, Register, Manage) | Mixed |
Areas/Admin/ | Admin-only features (User, Role, Tax, Currency, etc.) | Admin role |
Areas/Main/ | Member features (Todo, etc.) | Member role |
Areas/Components/ | Reusable partial views (Audit Trail card, etc.) | N/A |
Feature Folder Convention (VSA)
Every feature follows this convention:
├── Controllers/{EntityName}Controller.cs
├── Cqrs/
│ ├── Get{EntityName}ListHandler.cs
│ ├── Get{EntityName}ByIdHandler.cs
│ ├── Create{EntityName}Handler.cs + Validator.cs
│ ├── Update{EntityName}Handler.cs + Validator.cs
│ └── Delete{EntityName}Handler.cs
├── Endpoints/{EntityName}Endpoint.cs
└── Views/
├── Index.cshtml + Index.cshtml.js
├── Create.cshtml + Create.cshtml.js
├── Edit.cshtml + Edit.cshtml.js
└── Detail.cshtml + Detail.cshtml.js
3. Application Name
The application name — displayed in the browser title bar, top-left logo, footer, and sidebar logo —
is configured centrally through appsettings.json. This allows you to rebrand the entire
application without editing any layout files manually.
| File / Path | Description |
|---|---|
Areas/Public/Views/Shared/_Layout.cshtml | Renders the app name in the browser title, navbar logo, and footer |
Areas/_LayoutArea.cshtml | Renders the app name in the browser title and sidebar logo |
appsettings.json → AppSettings | Central application name configuration |
Configure the application name in appsettings.json under AppSettings:
"AppSettings": {
"Name": "Indotalent"
}
To rebrand the application, simply change the "Name" value. The layouts read this value
at runtime via @Configuration["AppSettings:Name"], so the title, logo, and footer update
automatically across both the public area and the authenticated area layouts.
Functional Features
Overview
Helpdesk is an internal IT support ticketing application. Guests submit and track their own support requests through a self-service portal, while an agent team (Members and Admins) triages, assigns, and resolves those tickets through a resolution-stage lifecycle. Every ticket belongs to a support team and an agent (PIC), is measured against a Service Level Agreement (SLA) that produces a target solve estimation date, and is supported by image/file attachments and a running discussion thread.
| Persona | Role | Scope |
|---|---|---|
| Guest | Guest | Self Service "My Ticket" — create, track, and discuss own tickets |
| Member | Member | Main — teams, agents, SLA policies, ticket groups, and ticket management |
| Admin | Admin | All Main features plus the existing platform admin features |
Helpdesk Team Management
Helpdesk Teams are the support groups that own tickets. A team is a simple master record (name and description) that tickets, helpdesk agents, and SLA policies reference. Routing a ticket to a team selects the team's SLA policy and makes the ticket visible to that team's agents. Typical teams include the IT Support Team, the Network Team, and the Application Support Team.
Helpdesk Agent Management
Helpdesk Agents are the support staff who resolve tickets. Each agent links a login account
(an ApplicationUser with the Member role) to a helpdesk team and acts as the
person-in-charge (PIC) for assigned tickets. Because agents reference login accounts, agent
records are created after the corresponding user exists, and a ticket can only be assigned
to an agent who belongs to the ticket's team.
SLA Policy Management
An SLA Policy defines the service commitment for a helpdesk team: the target resolution
stage and the number of resolution due hours. When a ticket is created or updated with a
team that has an SLA policy, the solve estimation date is computed automatically as
SolveEstimationDate = TicketDate + ResolutionDueHours. Tickets without a
matching policy simply have no estimation date.
| Team | Target Stage | Resolution Due Hours |
|---|---|---|
| IT Support Team | Solved | 48 |
| Network Team | Solved | 24 |
| Application Support Team | Solved | 72 |
Ticket Group & Ticket Sub Group
Ticket Groups and Ticket Sub Groups form a two-level classification taxonomy that helps agents understand the nature of a request at a glance. Groups are broad categories such as Hardware, Software, Network, and Access. Sub Groups refine a category further — for example, under Hardware you find Desktop, Laptop, and Printer; under Software you find Email, ERP, and OS; under Network you find VPN, WiFi, and Internet.
Ticket Management
Tickets are the core work item of the helpdesk. Each ticket captures a title, summary, long description, priority, tags, the owning team and agent, the requesting guest, the ticket date, and an SLA-derived solve estimation date. Tickets progress through the resolution stage lifecycle:
New → InProgress → OnHold → Solved or Cancelled
Every ticket can carry image and file attachments, and maintains a discussion thread of messages from the requesting guest and the assigned agents, keeping the full history of the request in one place.
Self-Service "My Ticket"
The Self Service area gives Guests a personal view of their own support requests. Guests
can create tickets, track their resolution stage, and participate in the discussion thread.
Ownership is enforced server-side: a guest can only see, open, and reply to tickets where
they are the recorded owner (GuestUserId), and the owner is set by the server,
never by the client.
Enterprise Features
Authentication
Full-featured authentication with ASP.NET Core Identity, JWT access tokens with refresh token rotation, and optional Firebase SSO.
| File / Path | Description |
|---|---|
Infrastructures/Authentications/Jwt/JwtService.cs | JWT token generation, refresh token creation, hashing, and validation |
Infrastructures/Authentications/Jwt/JwtAuthEndpoints.cs | Minimal API endpoints: POST /api/auth/* |
Infrastructures/Authentications/Firebase/ | Firebase token verification on server side |
Areas/Identity/Pages/Account/ | Razor Pages for Login, Register, Manage, etc. |
| Config | appsettings.json → JwtSettings |
// 1. Login → POST /api/auth/login with email+password
// 2. Response returns: { token, refreshToken, expiresAt, user }
// 3. When access token expires → POST /api/auth/refresh
// with { refreshToken } → new token pair (rotation)
// 4. Refresh token is hashed (SHA256) and stored in DB
SSO Firebase
Indotalent supports Firebase Single Sign-On (SSO) as an optional authentication method.
When enabled, users can sign in using their Google account via Firebase Authentication.
The Firebase configuration is stored in appsettings.json under the SsoFirebase section.
| File / Path | Description |
|---|---|
Infrastructures/Authentications/Firebase/ | Firebase token verification service |
appsettings.json → SsoFirebase | Firebase project configuration |
To enable Firebase SSO, configure the following in appsettings.json:
"SsoFirebase": {
"IsUsed": true,
"ProjectId": "xxx",
"ApiKey": "xxx",
"AuthDomain": "xxx.firebaseapp.com",
"StorageBucket": "xxx.firebasestorage.app",
"MessagingSenderId": "xxx",
"AppId": "xxx"
}
Set "IsUsed": true to enable Firebase SSO. Replace the placeholder values (xxx)
with your actual Firebase project credentials from the Firebase Console.
Set "IsUsed": false to disable Firebase SSO and use only the built-in Identity authentication.
AutoNumber Generation
Entities implementing IHasAutoNumber get auto-generated codes like COMP-0001.
| File / Path | Description |
|---|---|
Data/Interfaces/IHasAutoNumber.cs | Interface definition |
Infrastructures/AutoNumberGenerator/AutoNumberGeneratorService.cs | Number generation service |
| Usage | Add : BaseEntity, IHasAutoNumber to entity |
Background Jobs (Hangfire)
Hangfire with built-in dashboard at /hangfire (Admin only). Supports recurring, fire-and-forget, and delayed jobs.
| File / Path | Description |
|---|---|
Infrastructures/BackgroundJobs/DI.cs | Hangfire configuration + storage |
Infrastructures/BackgroundJobs/HangfireAuthorizationFilter.cs | Admin-only dashboard access |
Infrastructures/BackgroundJobs/Jobs/SerilogCleanupJob.cs | Sample recurring job |
Multi-Database
Switch between InMemory, SQL Server, and PostgreSQL with a single config change. The application supports three database providers — simply toggle "IsUsed" to switch between them.
| File / Path | Description |
|---|---|
Infrastructures/Databases/DatabaseSettingsModel.cs | Configuration model |
Infrastructures/Databases/DI.cs | EF Core provider registration |
appsettings.json | Set "IsUsed": true for your provider |
Configure your database provider in appsettings.json under DatabaseSettings:
"DatabaseSettings": {
// InMemory (default, no external DB needed)
"InMemory": {
"IsUsed": true,
"ConnectionString": "IndotalentDb",
"TimeoutInSeconds": 1800
},
// Microsoft SQL Server
"MsSQL": {
"IsUsed": false,
"ConnectionString": "Server=localhost\\SQLEXPRESS;Database=MyDb;Trusted_Connection=True;TrustServerCertificate=True",
"TimeoutInSeconds": 1800
},
// PostgreSQL
"PostgreSQL": {
"IsUsed": false,
"ConnectionString": "Host=localhost;Database=MyDb;Username=postgres;Password=yourpassword",
"TimeoutInSeconds": 1800
}
}
To switch providers, set the desired provider's "IsUsed" to true and the others to false.
Only one provider can be active at a time. Update the ConnectionString to match your database server credentials.
Demo Mode
Indotalent includes a Demo Mode feature that, when enabled, automatically seeds the database with dummy demo data on application startup. This is useful for testing, presentations, or evaluation purposes without needing to manually enter data.
| File / Path | Description |
|---|---|
Infrastructures/Databases/DatabaseSeeder.cs | Seeds demo data when Demo Mode is active |
appsettings.json → DemoMode | Toggle Demo Mode on/off |
Configure Demo Mode in appsettings.json:
"DemoMode": {
"IsDemo": true
}
Set "IsDemo": true to enable Demo Mode — the application will seed dummy data
(sample users, roles, and demo records) on every startup.
Set "IsDemo": false to disable it and start with a clean database.
AI Chat
Indotalent includes an AI Chat feature that can be enabled by configuring your preferred AI provider's API key. The application supports multiple AI providers including ChatGPT, Claude, Gemini, and DeepSeek.
| File / Path | Description |
|---|---|
appsettings.json → AiSettings | AI provider selection and API keys |
Configure AI Chat in appsettings.json under AiSettings:
"AiSettings": {
// Choose your provider: "ChatGPT", "Claude", "Gemini", or "DeepSeek"
"Provider": "ChatGPT",
"ChatGPT": {
"ApiKey": "sk-your-chatgpt-api-key",
"Model": "gpt-4o"
},
"Claude": {
"ApiKey": "sk-ant-your-claude-api-key",
"Model": "claude-3-opus-20240229"
},
"Gemini": {
"ApiKey": "your-gemini-api-key",
"Model": "gemini-1.5-pro"
},
"DeepSeek": {
"ApiKey": "your-deepseek-api-key",
"Model": "deepseek-v4-flash"
}
}
To enable AI Chat, set the "Provider" field to your chosen provider name and
fill in the corresponding "ApiKey" with your actual API key from that provider.
Leave the API keys empty to disable the AI Chat feature.
Email Delivery
Multi-provider email service supporting SendGrid, Mailgun, SMTP, and Mailjet. Toggle "IsUsed" to switch between providers.
| File / Path | Description |
|---|---|
Infrastructures/Email/EmailSettingsModel.cs | Provider selection + API keys |
Infrastructures/Email/EmailService.cs | Main email service with templates |
Infrastructures/Email/SendGrid/, Mailgun/, etc. | Provider implementations |
Infrastructures/Email/IdentityEmailSenderAdapter.cs | Identity integration |
Configure email delivery in appsettings.json under EmailSettings:
"EmailSettings": {
// SendGrid
"SendGrid": {
"IsUsed": false,
"ApiKey": "SG.your-sendgrid-api-key",
"FromEmail": "noreply@email.com"
},
// Mailgun
"Mailgun": {
"IsUsed": false,
"ApiKey": "key-your-mailgun-api-key",
"Domain": "mg.yourdomain.com",
"FromEmail": "noreply@email.com"
},
// Mailjet
"Mailjet": {
"IsUsed": false,
"ApiKey": "mj-your-public-key",
"ApiSecret": "mj-your-private-key",
"FromEmail": "noreply@email.com"
},
// SMTP (default)
"Smtp": {
"IsUsed": true,
"Host": "smtp.gmail.com",
"Port": 465,
"UserName": "your-email@gmail.com",
"Password": "your-app-password",
"FromAddress": "your-email@gmail.com",
"FromName": "no-reply"
}
}
To switch email providers, set the desired provider's "IsUsed" to true and the others to false.
Only one provider can be active at a time. Fill in the API keys and credentials for your chosen provider.
File Upload / Download
File storage service supporting local file system with upload, download, delete operations.
| File / Path | Description |
|---|---|
Infrastructures/File/FileStorageService.cs | Core service |
Infrastructures/File/FileStorageSettingsModel.cs | Storage path, allowed extensions, max size |
Infrastructures/File/Local/ | Local file system implementation |
Health Checks
Built-in health check endpoints with dashboard UI at /Admin/HealthCheck/Index.
| File / Path | Description |
|---|---|
Infrastructures/HealthChecks/DI.cs | Health check registration |
| Endpoints | /healthz (liveness), /ready (readiness), /health |
| Dashboard | /Admin/HealthCheck/Index |
Logging (Serilog)
Structured logging with Serilog. Writes to rolling files with automatic 3-day cleanup via Hangfire.
| File / Path | Description |
|---|---|
Infrastructures/Logging/Serilog/ | Serilog configuration |
wwwroot/data/serilog/ | Log file output directory |
Infrastructures/BackgroundJobs/Jobs/SerilogCleanupJob.cs | Auto-cleanup job (daily at midnight) |
Rate Limiting
Four rate limiting policies using System.Threading.RateLimiting, configurable via appsettings.json.
| Policy | Scope | Default |
|---|---|---|
| Global | All requests | 100 req/min |
| Authenticated | Authenticated users | 200 req/min |
| Write | POST/PUT/DELETE | 50 req/min |
| Admin | Admin role | 500 req/min |
6. CQRS Pattern (Step-by-Step)
Every feature uses a simple CQRS pattern with plain C# handlers (no MediatR).
Each CRUD operation has its own handler class with a single HandleAsync() method.
Step 1: List Handler
public class GetTaxListHandler
{
private readonly AppDbContext _context;
public GetTaxListHandler(AppDbContext context) => _context = context;
public async Taskobject>> HandleAsync(GetTaxListRequest request)
{
var query = _context.Tax.AsQueryable();
// Apply search filter
if (!string.IsNullOrWhiteSpace(request.Search))
query = query.Where(x => x.Name.Contains(request.Search) || x.Code.Contains(request.Search));
int page = request.Page ?? 1;
int pageSize = request.PageSize ?? 10;
var total = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(x => new TaxListItem { ... })
.ToListAsync();
return ApiResponse<object>.Ok(new { items, total, page, pageSize });
}
}
Step 2: Create Handler
public class CreateTaxHandler
{
public async Task> HandleAsync(CreateTaxRequest request)
{
// 1. Validate with FluentValidation
var validator = new CreateTaxValidator();
var result = await validator.ValidateAsync(request);
if (!result.IsValid)
return ApiResponse.Fail(
"Validation failed", result.ToDictionary());
// 2. Check for duplicate Code
if (await _context.Tax.AnyAsync(x => x.Code == request.Code))
return ApiResponse.Fail("Code already exists");
// 3. Save to database
var entity = new Tax
{
Code = request.Code,
Name = request.Name,
PercentageValue = request.PercentageValue,
Description = request.Description
};
_context.Tax.Add(entity);
await _context.SaveChangesAsync();
return ApiResponse.Ok(
new CreateTaxResponse { Id = entity.Id, Code = entity.Code },
"Tax has been created successfully");
}
}
Step 3: Update Handler
Similar to Create but loads existing entity, validates it exists, updates properties, and saves.
Step 4: Delete Handler
public class DeleteTaxHandler
{
public async Taskobject>> HandleAsync(string id)
{
var entity = await _context.Tax.FindAsync(id);
if (entity == null)
return ApiResponse<object>.Fail("Tax not found");
_context.Tax.Remove(entity);
await _context.SaveChangesAsync();
return ApiResponse<object>.Ok(new { id }, "Tax deleted successfully");
}
}
Standard API Response
All handlers return ApiResponse which wraps the result:
public class ApiResponse
{
public bool Success { get; set; }
public string? Message { get; set; }
public T? Data { get; set; }
public IDictionary<string, string[]>? Errors { get; set; }
}
7. Minimal API Endpoints
Data operations use ASP.NET Core Minimal API (not MVC controllers). Each feature registers its endpoints
in a single {EntityName}Endpoint.cs file.
| Method | Route | Action | Auth |
|---|---|---|---|
| GET | /api/{entity} | Paginated list with search & sort | Required |
| GET | /api/{entity}/{id} | Get by ID | Required |
| POST | /api/{entity} | Create new record | Required |
| PUT | /api/{entity} | Update existing record | Required |
| DELETE | /api/{entity}/{id} | Delete record | Required |
Endpoints are registered in Program.cs via app.Map{EntityName}Endpoints();.
8. Vue 3 Frontend Tutorial
The frontend uses Vue 3 Composition API with the global build (vue.global.prod.js).
Vue is loaded in the layout and each page mounts its own Vue app instance on a specific element.
This is not a Single Page Application — Vue enhances specific pages inside ASP.NET Core MVC views.
How Vue is Loaded
In _Layout.cshtml (line ~11), Vue is loaded via a simple script tag:
// File: _Layout.cshtml (line ~11)
<script src="~/js/vue.global.prod.js"></script>
This exposes the global Vue object. Each page then creates its own app — no build tools, no SPA routing, just lightweight page-level reactivity.
Basic Vue Setup Pattern
Every page that uses Vue follows this pattern:
// 1. Destructure Vue APIs you need
const { createApp, ref, reactive, onMounted } = Vue;
// 2. Create and mount a Vue app
createApp({
setup() {
// Reactive state (Vue will track changes)
const contentReady = ref(false);
const errorMessage = ref(null);
const submitting = ref(false);
// Initialize on mount
onMounted(async function() {
contentReady.value = true;
});
// Return makes these available in HTML template
return { contentReady, errorMessage, submitting };
}
}).mount('#app-index'); // Mounts on
Example 1: DataTable Index Page
This is the pattern used in Areas/Admin/Tax/Views/Index.cshtml.js. It combines Vue with DataTables for server-side paginated tables.
1
Vue Setup for Row Selection
Index.cshtml.js — Vue Setup
const { createApp, ref, onMounted } = Vue;
createApp({
setup() {
const contentReady = ref(false);
const selectedId = ref(null);
function selectRow(row, id) {
selectedId.value = id;
}
function clearSelection() {
selectedId.value = null;
}
// Expose to window for DataTables to call
window.vueApp = { selectRow, clearSelection };
onMounted(function() {
setTimeout(function() {
contentReady.value = true;
}, 500);
});
return { contentReady, selectedId };
}
}).mount('#app-index');
2
DataTable Initialization
Index.cshtml.js — DataTable
var table = new DataTable('#taxTable', {
processing: true,
serverSide: true,
ajax: {
url: '/api/tax',
data: function(d) {
d.search = d.search?.value || '';
d.page = (d.start / d.length) + 1;
d.pageSize = d.length;
},
dataSrc: function(json) {
if (json.success) {
json.recordsTotal = json.data.total;
json.recordsFiltered = json.data.total;
return json.data.items;
}
return [];
}
},
columns: [
{ data: 'code' },
{ data: 'name' },
{
data: 'percentageValue',
render: function(data) {
return '' + data + '%';
}
}
],
pageLength: 10
});
// Row click / draw handlers
table.on('draw', function() {
if (window.vueApp) window.vueApp.clearSelection();
});
Example 2: Create Form with Validation
This is the pattern used in Areas/Admin/Tax/Views/Create.cshtml.js.
1
Form State & Reactivity
Create.cshtml.js — Form Setup
const { createApp, ref, reactive } = Vue;
createApp({
setup() {
// Form data (reactive object)
const form = reactive({
code: '',
name: '',
percentageValue: '',
description: ''
});
// Validation errors (reactive)
const errors = reactive({});
// UI state
const submitting = ref(false);
const created = ref(false);
const errorMessage = ref('');
return { form, errors, submitting, created, errorMessage };
}
}).mount('#app-create');
2
Client-Side Validation
Create.cshtml.js — Validation
function validate() {
// Clear previous errors
Object.keys(errors).forEach(key => delete errors[key]);
errorMessage.value = '';
if (!form.code || !form.code.trim()) {
errors.code = 'Tax Code is required';
} else if (form.code.length > 50) {
errors.code = 'Tax Code must not exceed 50 characters';
}
if (!form.name || !form.name.trim()) {
errors.name = 'Tax Name is required';
}
const val = parseFloat(form.percentageValue);
if (isNaN(val) || val < 0 || val > 100) {
errors.percentageValue = 'Percentage must be between 0 and 100';
}
return Object.keys(errors).length === 0;
}
3
Submit with 500ms Smooth Delay
Create.cshtml.js — Submit
async function submitForm() {
if (!validate()) return;
submitting.value = true;
try {
// Smooth UI delay: 500ms before actual request
await new Promise(r => setTimeout(r, 500));
const response = await fetch('/api/tax', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: form.code,
name: form.name,
percentageValue: parseFloat(form.percentageValue),
description: form.description
})
});
const result = await response.json();
if (result.success) {
created.value = true;
window.showToast('success', 'Created',
'Record created successfully');
} else {
if (result.errors) {
for (const key in result.errors) {
errors[key] = result.errors[key][0];
}
}
errorMessage.value = result.message || 'Failed to create';
window.showToast('error', 'Failed', result.message);
}
} catch (err) {
errorMessage.value = 'An error occurred while submitting the form';
} finally {
submitting.value = false;
}
}
Example 3: Loading States Pattern
Every page includes these essential reactive states for a polished UX:
loading-pattern.js
// Essential reactive states
const contentReady = ref(false); // Controls v-if on main content
const loading = ref(true); // Used for spinner display
const errorMessage = ref(null); // Error notification
const successMessage = ref(null); // Success notification
// Auto-hide after a few seconds
setTimeout(() => { successMessage.value = null; }, 3000);
setTimeout(() => { errorMessage.value = null; }, 4000);
Example 4: Custom Confirmation Modal for Delete
Delete operations use a custom modal (not confirm()) with smooth UX:
confirm-delete.js
const showDeleteModal = ref(false);
const deleting = ref(false);
function closeDeleteModal() {
showDeleteModal.value = false;
}
async function confirmDelete(id) {
deleting.value = true;
await new Promise(r => setTimeout(r, 500)); // Smooth delay
try {
const res = await fetch('/api/tax/' + id, { method: 'DELETE' });
if (res.ok) {
showDeleteModal.value = false;
// Show success, reload table, redirect, etc.
}
} catch (err) {
// Handle error
} finally {
deleting.value = false;
}
}
// Toggle modal via v-bind:class / v-bind:style in HTML
//
Vue Component Checklist
When creating a new Vue-enhanced page, ensure you include:
✔
const { createApp, ref, reactive, onMounted } = Vue;
✔
contentReady, loading, errorMessage states
✔
500ms smooth delay before async operations
✔
Loading spinner v-bind:disabled="submitting"
✔
Success (3s) + Error (4s) auto-hide notifications
✔
Custom modal for delete (not confirm())
✔
Mount on #app-{action} (e.g., #app-create)
✔
onMounted for initial data fetching
9. AI-Assisted Development
Indotalent ships with an automatic AI-assisted development pipeline driven by the
.ai-assisted/ folder. The only file the developer writes is
.ai-assisted/DATA-DICTIONARY.md — the AI generates everything else: the feature
specification, the technical PRD, and the complete application.
How to Start the Development Sequence (automatic)
- Fill
.ai-assisted/DATA-DICTIONARY.md — application name, persona, and feature description.
- Start the sequence — tell your AI coding agent exactly this command:
start the development
- The AI runs the whole chain automatically: Gate 0 (identity check) → DATA-DICTIONARY review → Phase 0 (
FEATURE.md) → Phase 1 (PRD.md) → Phase 2 (build the application).
- Done! A ready-to-use application, verified with
dotnet build (0 errors) after every feature.
⚠
Important — before you start: make sure .ai-assisted/DATA-DICTIONARY.md
has been updated to match the new application you are about to build. The AI builds
exactly what that file describes — template placeholders ([ ... ]), the
## EXAMPLE app, or data from a previous project would be built as-is.
What the AI Generates Automatically
One command produces three deliverables:
✔
FEATURE.md — business source of truth (Phase 0)
✔
PRD.md — technical blueprint / build backlog (Phase 1)
✔
The full application, feature by feature (Phase 2)
Entity Types Auto-Detected by AI
Pattern in Entity Detected Type
public ICollection? Items { get; set; } Master-Detail
public string {X}Id { get; set; } + navigation propertyWith Lookup
Neither pattern above Pure Master Data
: BaseEntity, IHasAutoNumberAdds auto-numbering
Each Feature Is Generated With 18 Files
For every feature, the AI creates the full vertical slice:
✔
{Entity}Controller.cs
✔
4 CQRS Handlers + 2 Validators
✔
{Entity}Endpoint.cs
✔
4 Views (Index, Create, Edit, Detail)
✔
4 JS Files (collocated with views)
✔
Program.cs + DbContext updates
Maintenance Mode — Adding a Single Feature
Once the application is customized (AppSettings:Name is no longer
Indotalent), the pipeline is inactive. To add a single feature, work directly with
.ai-assisted/SKILL-SOFTWARE-ENGINEERING.md: create the entity class in
Data/Entities/{Entity}.cs and let the AI generate the feature following the skill.
Prompt Examples — Copy & Use (maintenance mode)
These per-feature prompts apply when the pipeline is inactive (maintenance mode). For a greenfield
project, use the single command start the development instead. Replace {Entity}
with your entity name.
PURE MASTER DATA
Generate a simple CRUD feature with no relationships:
Generate full CRUD for {Entity}. Follow the skill.
WITH LOOKUP
Generate a feature that references another entity via foreign key:
Generate full CRUD for {Entity} with lookup to {LookupEntity}. Follow the skill.
MASTER-DETAIL
Generate a header-detail feature (e.g., Sales Order with line items):
Generate full CRUD for {MasterEntity} with {DetailEntity}. Follow the skill.
WITH SEED DATA
Generate a feature with pre-populated seed data:
Generate full CRUD for {Entity} with seed data. Follow the skill.
Smart Prompt Strategies
To get the best results from your AI agent and save tokens, use these strategies:
Limit Context to One Folder
"Read Areas/Admin/Currency/ and generate a new feature following the same pattern."
This restricts the AI to just the Currency feature folder, saving thousands of tokens.
Reference an Existing Entity
"Generate full CRUD for Category. Use Tax as the template. Follow the skill."
The AI will use Tax as a reference and adapt it for Category.
Avoid Vague Prompts
"Make me a CRUD" → Too vague. The AI doesn't know your patterns.
"Generate full CRUD for Category. Follow the skill." → The AI knows exactly what to do.
Chain Multiple Entities
"Generate full CRUD for Category, Product, and Customer. Follow the skill."
One prompt, multiple entities. The AI processes each independently.
📖 For the complete set of ready-to-use prompts (Options 1–5), open
.ai-assisted/SKILL-SOFTWARE-ENGINEERING.md → section
"For Users: What to Say to Your AI".
Indotalent Enterprise Kit — Technical Documentation v1.0
Built with ASP.NET Core MVC 10 · Vue 3 · Hangfire · Serilog · EF Core · VSA Architecture