首页 > 解决方案 > API 控制器的通用方法

问题描述

我正在为我的游戏编写一个 API,我开始意识到 GET、POST 和 PUT API 方法的数量确实可以加起来。

所以现在,我正在尝试使其更通用,这样我就不必编写单独的方法,如 GetMonsterList、GetTreasureList、GetPlayerInfo 等。

但我不太确定如何去做。

这是我目前拥有的非通用 PUT 方法。

    // PUT: api/MonsterLists/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutMonsterList(string id, MonsterList monsterList)
    {
        if (id != monsterList.MonsterId)
        {
            return BadRequest();
        }

        _context.Entry(monsterList).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!MonsterListExists(id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return NoContent();
    }

这是我概述通用方法的尝试:

    // PUT: api/AnyLists/5
    [HttpPut("{id}")]
    public async Task<IActionResult> PutAnyList(string id, AnyList anyList)
    {
        if (id != anyList.AnyId)
        {
            return BadRequest();
        }

        _context.Entry(anyList).State = EntityState.Modified;

        return NoContent();
    }

我不明白的问题是,如何将模型传递给这样的通用控件?就像我有 MonsterList、TreasureList、PlayerInfo、WeaponList 等的模型。

我怎么能对所有这些都使用一种通用方法?

我确实在这里找到了一个类似的问题,Generic Web Api controller to support any model,但答案似乎暗示这不是一个好主意。

那可能吗?

谢谢!

标签: c#asp.net-coreentity-framework-coreasp.net-core-webapi

解决方案


在我们创建通用控制器之前,值得一提的是,您的实体的结构模型对于轻松或难以构建通用控制器非常重要。

例如,您可能有一些具有 int id 的模型和其他具有字符串 id 的模型,因此我们需要为这两种类型提供一个共同的基础。

首先为 Id 属性创建通用接口以在通用接口中处理 int 或字符串 Id:

public interface IHasId<TKey> 
    where TKey : IEquatable<TKey>
{
    TKey Id { get; set; }
}

要考虑的另一件事是对实体进行排序,在查询实体列表时,我们需要对它们进行排序以获得正确的分页实体。因此,我们可以创建另一个接口来指定排序属性,例如名称。

public interface IOrdered
{
    string Name { get; set; }
}

我们的对象必须实现如下的通用接口:

public class Player : IHasId<string>, IOrdered
{
    public string Id { get; set; }
    public string Name { get; set; }
    ...
}

public class Treasure : IHasId<int>, IOrdered
{
    public int Id { get; set; }
    public string Name { get; set; }
    ...
}

现在创建一个通用的基本 api 控制器,确保将方法标记为虚拟,以便我们可以在必要时在继承的 api 控制器中覆盖它们。

[Route("api/[controller]")]
[ApiController]
public class GenericBaseController<T, TKey> : ControllerBase
    where T : class, IHasId<TKey>, IOrdered
    where TKey : IEquatable<TKey>
{
    private readonly ApplicationDbContext _context;

    public GenericBaseController(ApplicationDbContext context)
    {
        _context = context;
    }

    // make methods as virtual, 
    // so they can be overridden in inherited api controllers
    [HttpGet("{id}")]
    public virtual T Get(TKey id)
    {
        return _context.Set<T>().Find(id);
    }

    [HttpPost]
    public virtual bool Post([FromBody] T value)
    {
        _context.Set<T>().Add(value);
        return _context.SaveChanges() > 0;
    }

    [HttpPut("{id}")]
    public virtual bool Put(TKey id)
    {
        var entity = _context.Set<T>().AsNoTracking().SingleOrDefault(x => x.Id.Equals(id));
        if (entity != null)
        {
            _context.Entry<T>(value).State = EntityState.Modified;
            return _context.SaveChanges() > 0;
        }

        return false;
    }

    [HttpDelete("{id}")]
    public virtual bool Delete(TKey id)
    {
        var entity = _context.Set<T>().Find(id);
        if (entity != null)
        {
            _context.Entry<T>(entity).State = EntityState.Deleted;
            return _context.SaveChanges() > 0;
        }

        return false;
    }

    [HttpGet("list/{pageNo}-{pageSize}")]
    public virtual (IEnumerable<T>, int) Get(int pageNo, int pageSize)
    {
        var query = _context.Set<T>();

        var totalRecords = query.Count();
        var items = query.OrderBy(x => x.Name)
            .Skip((pageNo - 1) * pageSize)
            .Take(pageSize)
            .AsEnumerable();

        return (items, totalRecords);
    }
}

其余的很简单,只需创建从基本通用控制器继承的 api 控制器:

玩家控制器:

[Route("api/[controller]")]
[ApiController]
public class PlayersController : GenericBaseController<Player, string>
{
    public PlayersController(ApplicationDbContext context) : base(context)
    {

    }
}

宝藏控制器:

[Route("api/[controller]")]
[ApiController]
public class TreasuresController : GenericBaseController<Treasure, int>
{
    public TreasuresController(ApplicationDbContext context) : base(context)
    {

    }
}

您不必创建任何方法,但您仍然可以覆盖基本方法,因为我们将它们标记为虚拟,例如:

[Route("api/[controller]")]
[ApiController]
public class TreasuresController : GenericBaseController<Treasure, int>
{
    public TreasuresController(ApplicationDbContext context) : base(context)
    {
        public ovedrride Treasure Get(int id)
        {
            // custom logic ….

            return base.Get(id);
        }
    }
}

您可以从 GitHub 下载示例项目:https ://github.com/LazZiya/GenericApiSample


推荐阅读