You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET WebAPI:将控制器业务逻辑抽离为带接口的Handler类

Alright, let's break down how to refactor your EmployeeDetailController to move business logic into a dedicated handler class with an interface—this will make your controller cleaner, more maintainable, and much easier to test with xUnit. Here's the step-by-step solution:

Step 1: Define the IEmployeeDetailHandler Interface

First, we'll create an interface that abstracts all business operations for EmployeeDetail. This decouples the controller from the concrete implementation, making testing straightforward.

public interface IEmployeeDetailHandler
{
    // Get all employee details
    IEnumerable<EmployeeDetail> GetAll();
    
    // Get a single employee by ID
    Task<EmployeeDetail?> GetByIdAsync(int id);
    
    // Update an existing employee
    Task<(bool Success, bool IsNotFound)> UpdateAsync(EmployeeDetail employeeDetail);
    
    // Create a new employee
    Task<EmployeeDetail> CreateAsync(EmployeeDetail employeeDetail);
    
    // Delete an employee by ID
    Task<EmployeeDetail?> DeleteAsync(int id);
    
    // Check if an employee exists by ID
    bool Exists(int id);
}
Step 2: Implement the EmployeeDetailHandler Class

Next, build the concrete handler that implements the interface. This class directly injects AuthenticationContext (no controller dependencies) and handles all data access and business logic.

using Microsoft.EntityFrameworkCore;

public class EmployeeDetailHandler : IEmployeeDetailHandler
{
    private readonly AuthenticationContext _context;

    public EmployeeDetailHandler(AuthenticationContext context)
    {
        _context = context;
    }

    public IEnumerable<EmployeeDetail> GetAll()
    {
        return _context.EmployeeDetails;
    }

    public async Task<EmployeeDetail?> GetByIdAsync(int id)
    {
        return await _context.EmployeeDetails.FindAsync(id);
    }

    public async Task<(bool Success, bool IsNotFound)> UpdateAsync(EmployeeDetail employeeDetail)
    {
        _context.Entry(employeeDetail).State = EntityState.Modified;
        
        try
        {
            await _context.SaveChangesAsync();
            return (true, false);
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!Exists(employeeDetail.EId))
            {
                return (false, true);
            }
            // Re-throw the exception if it's not a "not found" concurrency issue
            throw;
        }
    }

    public async Task<EmployeeDetail> CreateAsync(EmployeeDetail employeeDetail)
    {
        _context.EmployeeDetails.Add(employeeDetail);
        await _context.SaveChangesAsync();
        return employeeDetail;
    }

    public async Task<EmployeeDetail?> DeleteAsync(int id)
    {
        var employeeDetail = await _context.EmployeeDetails.FindAsync(id);
        if (employeeDetail == null)
        {
            return null;
        }

        _context.EmployeeDetails.Remove(employeeDetail);
        await _context.SaveChangesAsync();
        return employeeDetail;
    }

    public bool Exists(int id)
    {
        return _context.EmployeeDetails.Any(e => e.EId == id);
    }
}
Step 3: Refactor the EmployeeDetailController

Update the controller to inject IEmployeeDetailHandler instead of AuthenticationContext. The controller now only handles HTTP request validation and response formatting—all business logic is delegated to the handler.

[Route("api/[controller]")]
[ApiController]
public class EmployeeDetailController : ControllerBase
{
    private readonly IEmployeeDetailHandler _handler;

    public EmployeeDetailController(IEmployeeDetailHandler handler)
    {
        _handler = handler;
    }

    // GET: api/EmployeeDetail
    [HttpGet]
    public IEnumerable<EmployeeDetail> GetEmployeeDetails()
    {
        return _handler.GetAll();
    }

    // GET: api/EmployeeDetail/5
    [HttpGet("{id}")]
    public async Task<IActionResult> GetEmployeeDetail([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var employeeDetail = await _handler.GetByIdAsync(id);
        if (employeeDetail == null)
        {
            return NotFound();
        }

        return Ok(employeeDetail);
    }

    // PUT: api/EmployeeDetail/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutEmployeeDetail([FromRoute] int id, [FromBody] EmployeeDetail employeeDetail)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        if (id != employeeDetail.EId)
        {
            return BadRequest();
        }

        var updateResult = await _handler.UpdateAsync(employeeDetail);
        if (updateResult.IsNotFound)
        {
            return NotFound();
        }

        return NoContent();
    }

    // POST: api/EmployeeDetail
    [HttpPost]
    public async Task<IActionResult> PostEmployeeDetail([FromBody] EmployeeDetail employeeDetail)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var createdEmployee = await _handler.CreateAsync(employeeDetail);
        return CreatedAtAction("GetEmployeeDetail", new { id = createdEmployee.EId }, createdEmployee);
    }

    // DELETE: api/EmployeeDetail/5
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteEmployeeDetail([FromRoute] int id)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        var deletedEmployee = await _handler.DeleteAsync(id);
        if (deletedEmployee == null)
        {
            return NotFound();
        }

        return Ok(deletedEmployee);
    }
}
Step 4: Register the Handler with Dependency Injection

Don't forget to register the interface and its implementation in your application's DI container (usually in Program.cs):

builder.Services.AddScoped<IEmployeeDetailHandler, EmployeeDetailHandler>();

Why This Works

  • Testability: You can easily mock IEmployeeDetailHandler for controller tests, or mock AuthenticationContext (using an in-memory database or a framework like Moq) to test the handler's business logic with xUnit.
  • Separation of Concerns: The controller focuses on HTTP-related tasks (validation, status codes, response formatting), while the handler handles data access and business rules.
  • Maintainability: Changes to business logic only need to be made in the handler, not scattered across controller methods.

内容的提问来源于stack exchange,提问作者Jeyamini Madara

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:02:24