【问题标题】:generic methods for API controllerAPI 控制器的通用方法
【发布时间】:2020-01-01 22:15:40
【问题描述】:

我正在为我的游戏编写一个 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,但答案似乎暗示这不是一个好主意。

这可能吗?

谢谢!

【问题讨论】:

  • 它会是一个网页游戏还是你只是为你的游戏制作WEB API服务器?
  • 如果@Morasiu 的问题的答案是Web API,那么另一种方法是使用工具来搭建重复的位。 dotnet aspnet-codegenerator 工具可以以繁琐的 GET/PUT/POST 作为起点来创建模型和控制器。见mattmillican.com/blog/aspnetcore-controller-scaffolding
  • 你已经提到的类似问题中的方法没有错,我在我的 web api 项目中使用了相同的方法,它是一个实时节省
  • @LazZiya 请问你是怎么做到的?即使在阅读了我包含的问题链接后,我仍然试图弄清楚。谢谢!
  • @SkyeBoniwell 我会在周末发布一个样本:)

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


【解决方案1】:

我猜你可以传递参数类型的名称并做这样的事情(未测试):

// PUT: api/AnyLists/5
[HttpPut("{id}")]
public async Task<IActionResult> PutAnyList(string id, object anyList, string anyListType)
{
    var anyListObject = Convert.ChangeType(anyList, Type.GetType(anyListType)));
    if (id != anyListObject.AnyId)
    {
        return BadRequest();
    }

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

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        // Whatever error handling you need
    }
    return NoContent();
}

但是,我不建议在生产代码中使用它。可能会发生的情况是,您最终将需要为不同类型创建相当多的异常 - 与每种类型只有单独的方法相比,您最终会得到更加复杂且难以支持的代码.

另外,我不确定测试它是否容易。

【讨论】:

  • 只是好奇,但是 Convert.ChangeType 是做什么的?谢谢!
  • @SkyeBoniwell 好吧,它就像名字所说的那样 - 它创建一个给定类型的新对象并尝试为其分配第一个参数的值。文档可在此处获得:docs.microsoft.com/en-us/dotnet/api/…
【解决方案2】:

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

例如,您可以有一些具有 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)
    {

    }
}

TreasuresController:

[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

【讨论】:

    猜你喜欢
    • 2013-01-28
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-23
    • 2017-09-20
    相关资源
    最近更新 更多