CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl-labs/aspnet-project-structure

ASP.NET Core project structure — minimal APIs vs controllers, layer

95

2.50x
Quality

93%

Does it follow best practices?

Impact

100%

2.50x

Average score across 5 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

task.mdevals/scenario-5/

Support Ticket System — Refactor Monolithic Controller

A SaaS company has an ASP.NET Core support ticket API that grew organically. The original developer put everything in a single controller: database queries, business rules, error handling, and response formatting. The file is becoming hard to maintain and impossible to unit test.

The team lead has asked you to refactor this into a properly organized structure. The system tracks support tickets and their comments. Each ticket has a priority, status, and assigned agent.

Output

Produce the refactored project under TicketSystem/. Each file should contain valid C# code with correct namespaces. Full implementation is not required but class skeletons and method signatures must be complete.

Include a refactor-notes.md explaining which responsibility was moved where.

Input Files

The following file is provided as input. Extract it before beginning.

=============== FILE: TicketSystem/Controllers/TicketsController.cs =============== using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore;

namespace TicketSystem.Controllers;

[ApiController] [Route("api/[controller]")] public class TicketsController : ControllerBase { private readonly AppDbContext _db;

public TicketsController(AppDbContext db)
{
    _db = db;
}

[HttpGet]
public async Task<IActionResult> GetAll([FromQuery] string? status, [FromQuery] int page = 1)
{
    var query = _db.Tickets.Include(t => t.AssignedAgent).AsQueryable();
    if (!string.IsNullOrEmpty(status))
        query = query.Where(t => t.Status.ToString() == status);

    var tickets = await query
        .OrderByDescending(t => t.Priority)
        .ThenByDescending(t => t.CreatedAt)
        .Skip((page - 1) * 20)
        .Take(20)
        .Select(t => new {
            t.Id, t.Title, t.Status, t.Priority,
            Agent = t.AssignedAgent != null ? t.AssignedAgent.Name : null,
            t.CreatedAt
        })
        .ToListAsync();

    return Ok(tickets);
}

[HttpGet("{id}")]
public async Task<IActionResult> GetById(int id)
{
    var ticket = await _db.Tickets
        .Include(t => t.Comments)
        .Include(t => t.AssignedAgent)
        .FirstOrDefaultAsync(t => t.Id == id);

    if (ticket == null)
        return NotFound(new { error = "Ticket not found" });

    return Ok(new {
        ticket.Id, ticket.Title, ticket.Description,
        ticket.Status, ticket.Priority,
        Agent = ticket.AssignedAgent != null ? ticket.AssignedAgent.Name : null,
        Comments = ticket.Comments.Select(c => new {
            c.Id, c.Text, c.AuthorName, c.CreatedAt
        }),
        ticket.CreatedAt
    });
}

[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateTicketInput input)
{
    if (string.IsNullOrWhiteSpace(input.Title))
        return BadRequest(new { error = "Title is required" });
    if (string.IsNullOrWhiteSpace(input.Description))
        return BadRequest(new { error = "Description is required" });

    var ticket = new Ticket
    {
        Title = input.Title,
        Description = input.Description,
        Priority = input.Priority,
        Status = TicketStatus.Open,
        CreatedAt = DateTime.UtcNow
    };
    _db.Tickets.Add(ticket);
    await _db.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById), new { id = ticket.Id }, new {
        ticket.Id, ticket.Title, ticket.Status
    });
}

[HttpPost("{id}/comments")]
public async Task<IActionResult> AddComment(int id, [FromBody] AddCommentInput input)
{
    var ticket = await _db.Tickets.FindAsync(id);
    if (ticket == null)
        return NotFound(new { error = "Ticket not found" });

    if (ticket.Status == TicketStatus.Closed)
        return BadRequest(new { error = "Cannot add comments to closed tickets" });

    var comment = new Comment
    {
        TicketId = id,
        Text = input.Text,
        AuthorName = input.AuthorName,
        CreatedAt = DateTime.UtcNow
    };
    _db.Comments.Add(comment);
    await _db.SaveChangesAsync();

    return Ok(new { comment.Id, comment.Text, comment.AuthorName, comment.CreatedAt });
}

[HttpPatch("{id}/assign")]
public async Task<IActionResult> AssignAgent(int id, [FromBody] AssignAgentInput input)
{
    var ticket = await _db.Tickets.FindAsync(id);
    if (ticket == null)
        return NotFound(new { error = "Ticket not found" });

    var agent = await _db.Agents.FindAsync(input.AgentId);
    if (agent == null)
        return NotFound(new { error = "Agent not found" });

    ticket.AssignedAgentId = agent.Id;
    await _db.SaveChangesAsync();

    return Ok(new { ticket.Id, Agent = agent.Name });
}

}

evals

tile.json