gpt4 book ai didi

c# - 在同一 Controller 上的 Web Api 中多次 POST 调用的最佳实践

转载 作者:行者123 更新时间:2023-11-30 14:55:45 34 4
gpt4 key购买 nike

我有一个问题要问你们做 Web API REST 服务的人。您如何设计您的服务来处理单个实体的 POST,以及能够接收所述实体集合的 POST?

例如:

public IHttpActionResult Post([FromBody]User value)
{
// stuff
}

public IHttpActionResult Post([FromBody]IEnumerable<User> values)
{
// stuff
}

开箱即用,这不起作用,因为默认路由匹配这两个。

我知道有几种不同的方法可以解决这个问题,但我想学习“最佳实践”的方法。

你怎么做才能完成同样的行为?

我的想法如下:
  • 我可以这样做,所以帖子的签名只需要一个 List 作为参数。我会取消只需要一个用户的那个。任何使用该 api 调用的代码只需要知道将其实体包装在某种集合中。
  • 我可以创建两个不同的 Controller ,api/user 和 api/users,每个 Controller 都有自己的 POST。这种方法并没有真正与 REST 兼容,因为 api/user 检索所有用户,而 api/user/1 检索 Id == 1 的用户,那么 api/users 是什么意思? api/users/1 是什么意思?等等...所以可能不是这个选项。
  • 尝试使用一组自定义约束与 Controller 中的 ActionName 属性相结合,并为每个 POST 编写路由(我不确定这是否有效)。
  • 使其成为 RPC 调用。如果是这种情况,你给你的 RPC Controller 取什么名字?当有些是 REST 而有些是 RPC 时,您将它们放在解决方案中的什么位置?我应该使用什么标准来确定是否需要 RPC 调用或者是否应该将其保留为 REST?
  • 完全是别的什么?

  • 谢谢你的智慧之言。我非常感谢任何/所有人参与其中。我真的只是想了解最佳实践是什么。任何可以给出的例子也将是 super 的!

    最佳答案

    我最终使用了我最初的第三个和第四个想法的组合。

    我正在为此添加我自己的答案,以演示我是如何使其工作的。在我所做的所有谷歌搜索中,我还没有找到一个清晰的例子来说明如何做到这一点。我决定不打一个总是需要 IEnumerable 的调用,而不管要发布一个还是多个。做出这个决定的原因是我考虑的时间越长,我就越意识到插入一个或多个用户所涉及的行为是完全不同的。例如,如果我提交了一个用户并且由于没有填写必填字段而导致验证失败,我希望收到一个错误响应,其中包含服务器拒绝它的原因的详细信息。如果一次提交多个用户,还会这样吗?我需要为每个在发布过程中失败的用户提供错误原因吗?根据我的需求,答案是否定的。这需要以不同的方式处理。

    因此,我的答案是在我的 web api 解决方案中将 REST 调用与 RPC(远程过程调用)结合起来。但是,如果沿着这条路走,我的要求是 RPC 调用需要在不同的 Controller 中,但网址仍需要指向相同的整体“ Controller ”(路由的 {controller} 部分,如 api/{controller })。

    例如,这个 web api url 接受 REST 动词 Get、Post、Put 和 Delete:

    api/User



    我提交多个用户的调用需要在以下位置接受 POST:

    api/User/import



    ...但是这些调用中的每一个的逻辑都需要在不同的 Controller 中。

    我能够通过执行以下操作来实现这一目标:
  • 编写 IHttpControllerSelector 的自定义实现
  • 配置了 2 个路由映射,一个用于 REST,一个用于 RPC
  • 编写 2 个自定义路由约束来确定 REST 或 RPC 路由参数
  • 修改 DefaultApi 路由映射以使用 REST 约束
  • 添加使用 RPC 约束的“RpcApi”路由

  • 现在我将分解关于我如何实现这一点的实际代码。

    我的 ControllerSelector 如下:
    public class MyHttpControllerSelector : IHttpControllerSelector
    {
    private const string ActionKey = "action";
    private const string ControllerKey = "controller";

    private readonly HttpConfiguration _configuration;
    private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;

    public MyHttpControllerSelector(HttpConfiguration config)
    {
    _configuration = config;
    _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
    }

    private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
    {
    var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);

    var controllerTypes = GetControllerTypes();

    foreach (var type in controllerTypes)
    {
    var controllerName = type.Name.Remove(type.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);

    dictionary[controllerName] = new HttpControllerDescriptor(_configuration, type.Name, type);
    }

    return dictionary;
    }

    private IEnumerable<Type> GetControllerTypes()
    {
    var assembliesResolver = _configuration.Services.GetAssembliesResolver();
    var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();

    return controllersResolver.GetControllerTypes(assembliesResolver);
    }

    private static T GetRouteVariable<T>(IHttpRouteData routeData, string name)
    {
    object result = null;

    if (routeData.Values.TryGetValue(name, out result))
    {
    return (T)result;
    }

    return default(T);
    }

    public HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
    var routeData = GetRouteData(request);

    var controllerName = GetRequestedControllerName(routeData);

    var actionName = GetRequestedActionName(routeData);

    var isApiRoute = GetIsApiRoute(routeData);

    var controllerSelectorKey = GetControllerSelectorKey(actionName, controllerName, isApiRoute);

    return GetControllerDescriptor(request, controllerSelectorKey);
    }

    private bool GetIsApiRoute(IHttpRouteData routeData)
    {
    return routeData.Route.RouteTemplate.Contains("api/");
    }

    private static IHttpRouteData GetRouteData(HttpRequestMessage request)
    {
    var routeData = request.GetRouteData();

    if (routeData == null)
    throw new HttpResponseException(HttpStatusCode.NotFound);

    return routeData;
    }

    private HttpControllerDescriptor GetControllerDescriptor(HttpRequestMessage request, string controllerSelectorKey)
    {
    HttpControllerDescriptor controllerDescriptor = null;

    if (!_controllers.Value.TryGetValue(controllerSelectorKey, out controllerDescriptor))
    {
    throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    return controllerDescriptor;
    }

    private static string GetControllerSelectorKey(string actionName, string controllerName, bool isApi)
    {
    return string.IsNullOrWhiteSpace(actionName) || !isApi
    ? controllerName
    : string.Format("{0}{1}", controllerName, "Rpc");
    }

    private static string GetRequestedControllerName(IHttpRouteData routeData)
    {
    string controllerName = GetRouteVariable<string>(routeData, ControllerKey);

    if (controllerName == null)
    {
    throw new HttpResponseException(HttpStatusCode.NotFound);
    }

    return controllerName;
    }

    private static string GetRequestedActionName(IHttpRouteData routeData)
    {
    return GetRouteVariable<string>(routeData, ActionKey);
    }

    public IDictionary<string, HttpControllerDescriptor> GetControllerMapping()
    {
    return _controllers.Value;
    }
    }

    这是我的 IsRestConstraint:
    public class IsRestConstraint : IHttpRouteConstraint
    {
    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values,
    HttpRouteDirection routeDirection)
    {
    if (values.ContainsKey(parameterName))
    {
    string id = values[parameterName] as string;

    return string.IsNullOrEmpty(id) || IsRest(id);
    }
    else
    {
    return false;
    }
    }

    private bool IsRest(string actionName)
    {
    bool isRest = false;

    Guid guidId;
    int intId;

    if (Guid.TryParse(actionName, out guidId))
    {
    isRest = true;
    }
    else if (int.TryParse(actionName, out intId))
    {
    isRest = true;
    }

    return isRest;
    }
    }

    这是我的 IsRpcConstraint:
    public class IsRpcConstraint : IHttpRouteConstraint
    {
    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values,
    HttpRouteDirection routeDirection)
    {
    if (values.ContainsKey(parameterName))
    {
    string action = values[parameterName] as string;

    return !string.IsNullOrEmpty(action) && IsRpcAction(action);
    }
    else
    {
    return false;
    }
    }

    private bool IsRpcAction(string actionName)
    {
    bool isRpc = true;

    Guid guidId;
    int intId;

    if (Guid.TryParse(actionName, out guidId))
    {
    isRpc = false;
    }
    else if (int.TryParse(actionName, out intId))
    {
    isRpc = false;
    }

    return isRpc;
    }
    }

    在我的 WebApiConfig 中,我的路由如下所示(请注意,我还将默认的 IHttpControllerSelector 替换为 MyHttpControllerSelector,以及我使用自定义约束 IsRpcConstraint 和 IsRestConstraint 的位置):
    config.MapHttpAttributeRoutes();

    config.Services.Replace(typeof(IHttpControllerSelector), new MyHttpControllerSelector(config));

    config.Routes.MapHttpRoute(
    name: "RpcApi",
    routeTemplate: "api/{controller}/{action}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: new { action = new IsRpcConstraint() }
    );

    config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional },
    constraints: new { id = new IsRestConstraint() }
    );

    因此,当请求进来时,如果 URI 中的第三段(“RpcApi”路由中的 {action})不是整数、不是 guid 并且不是空的,则它被视为 RPC 调用。同样,如果“DefaultApi”路由模板上的第三段是整数或 Guid OR 根本未提供,则将其视为 REST 调用。

    这样,请求就会映射到正确的路由上,MyHttpControllerSelector 将相应地选择适当的 Controller 。因此,如果正在拨打以下电话:

    api/User/1



    然后 MyHttpControllerSelector 将使用名为 UserController 的 Controller 。同样,如果正在调用:

    api/User/import



    然后 MyHttpControllerSelector 将使用名为 UserRpcController 的 Controller ,调用将自动映射到其中的导入操作。

    到目前为止,这已经做到了,所以我需要为 RPC 支持做的就是添加一个带有“Rpc”的 Controller ,它具有我的域实体的前缀(在我的例子中是一个用户)。它可以是 TreeController 和 TreeRpcController、DogController 和 DogRpcController,端点是:
    api/Tree       (TreeController)
    api/Tree/1 (TreeController)
    api/Tree/grow (TreeRpcController)
    api/Dog (DogController)
    api/Dog/1 (DogController)
    api/Dog/bark (DogRpcController)

    最重要的是,我得到了一个干净的 WebApiConfig。它不会被每个路由中的大量特定路由模板和 Controller 选择所污染。无论有多少 REST Controller 和 RPC Controller 添加到解决方案中,我都只需要指定 2 个路由映射。

    这种方法确实假设 {id} 段的 REST 调用上的参数必须是一个 int 或 guid 才能被 REST Controller 考虑。有了这个设置,一个普通的 ol' 字符串将被视为一个“ Action ”,因此映射到我的 Rpc Controller 。对于我的场景,这很好。我只使用 int 和 Guid 作为 id。

    我还应该补充一点,到目前为止,这个 web api 服务中没有任何类型的 UI 要求。在 future 的某个时刻,这将会到来,所以我将 Controller 选择器 MyHttpControllerSelector 设置为在它没有检测到正在使用的路由模板中的“api/”时自动返回一个常规 Controller (非 RPC)。这是为了支持路由模板,例如:

    {controller}/{action}/{id}



    这是一个常规的 MVC 风格的 Controller 路由。

    我根据此处找到的 MyHttpControllerSelector 建模:

    http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx

    实际代码链接在文章底部,指向这里:

    http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/NamespaceHttpControllerSelector.cs

    这是一个关于如何使用自定义 Controller 选择器来使用命名空间对 Web api 服务进行版本控制的示例。这种技术有点过时了,因为据我所知,较新版本的 web api 更好地内置了对版本控制的支持。但是我使用这个类作为我的起点,因为它缓存使用反射检索到的值的结果以选择正确的 Controller ,这对于请求中的后续调用以提高性能很重要。为了我的目的,我对它进行了很多修改。

    嗯,这就是我要说的。

    关于c# - 在同一 Controller 上的 Web Api 中多次 POST 调用的最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24709402/

    34 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com