如何正确执行 RPC 样式的 Asp.Net Web API 调用?

Posted

技术标签:

【中文标题】如何正确执行 RPC 样式的 Asp.Net Web API 调用?【英文标题】:How do I do RPC style Asp.Net Web API calls properly? 【发布时间】:2012-09-11 17:10:32 【问题描述】:

2012 年 9 月 12 日更新:

我与一位同事分享了我的代码,第一次对他来说一切正常,没有任何更改。那么,我的盒子上肯定有一些环保的东西,有人有什么想法吗?

见下方更新

设置:

.Net 4.5

自托管(控制台应用程序).​​Net 4.5 Web API 应用程序

使用 MSTest 测试工具

我的 Web API 应用程序大部分都充满了 REST ApiControllers,它们都可以正常工作,正如我对标准 CRUD 类型的东西所期望的那样。现在我有一个需求(将一些对象添加到内部队列),这似乎不太适合 REST CRUD 模型。我发现this 文章似乎说您可以在 Web API 中执行 RPC 样式的非 REST 操作就好了。

我写了一个新的控制器,看起来像这样:

public class TaskInstanceQueueController : ApiController

    public void Queue(TaskInstance taskInstance)
    
        // Do something with my taskInstance
        Console.WriteLine("Method entered!");
    

在调用它的代理类中,我的代码如下所示:

public class TaskInstanceQueueProxy : ITaskInstanceQueueProxy

    readonly HttpClient _client = new HttpClient();

    public TaskInstanceQueueProxy()
    
        var apiBaseUrl = System.Configuration.ConfigurationManager.AppSettings["APIBaseUrl"];
        _client.BaseAddress = new Uri(apiBaseUrl);
        _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    

    public void QueueTaskInstances(TaskInstance taskInstance)
    
        QueueTaskInstanceViaAPI(taskInstance);
    

    private async void QueueTaskInstanceViaAPI(TaskInstance taskInstance)
    

        var response = await _client.PostAsJsonAsync("api/TaskInstanceQueue/Queue", taskInstance);
        var msg = response.EnsureSuccessStatusCode();
    


这是我的路线:

    config.Routes.MapHttpRoute("API Default", "api/controller/id", new id = RouteParameter.Optional);
    config.Routes.MapHttpRoute("API RPC Style", "api/controller/action", new  id = RouteParameter.Optional );

当我对我的代理运行测试时,我没有收到任何错误,但我的控制器方法中没有断点,也没有输入方法!消息出现在控制台中。 var msg 行上的断线也永远不会命中。无论出于何种原因,我似乎都没有正确使用 HttpClient 对象来执行此操作。

同样,这个 web api 应用程序与许多其他 apicontroller 一起工作得很好,但它们都在做标准的 REST 工作。

谁有线索?

更新

如果我在 PostAsJsonAsync 调用周围放置一个 try/catch,我会得到以下信息:

A first chance exception of type 'System.Threading.ThreadAbortException' occurred in mscorlib.dll
System.Threading.ThreadAbortException: Thread was being aborted.
   at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)
   at System.Net.Http.Formatting.JsonMediaTypeFormatter.WriteToStreamAsync(Type type, Object value, Stream writeStream, HttpContent content, TransportContext transportContext)
   at System.Net.Http.ObjectContent.SerializeToStreamAsync(Stream stream, TransportContext context)
   at System.Net.Http.HttpContent.LoadIntoBufferAsync(Int64 maxBufferSize)
   at System.Net.Http.HttpClientHandler.PrepareAndStartContentUpload(RequestState state)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at TaskManagerProxy.TaskInstanceQueueProxy.<QueueTaskInstanceViaAPI>d__0.MoveNext() in c:\Moso\MOSO\MOSO.Infrastructure\tm\TaskManagerProxy\TaskManagerProxy\TaskInstanceQueueProxy.cs:line 30

第 30 行是调用所在的行。

【问题讨论】:

这很好,我不会发疯 - 我认为您的路线基本没问题。你用的是什么端口,有什么潜在的冲突吗? 您能否让您的同事为您重新构建它并比较您的“bin”文件夹? 我让他把整个文件夹结构压缩起来,我比较了一下,没有发现任何差异。 如果您想自己发现问题所在来回答这个问题,我可以看到这对于可能遇到类似问题的未来访问者来说很有价值。 【参考方案1】:

这个答案确实取决于您在TaskInstanceQueueController 中定义了多少其他方法。假设 Queue 是您唯一的一个,那么我相信您的路线已经可以使用(尽管它们有点不整洁)。

我刚刚构建了您的代码的示例版本,并成功地使用 Fiddler 和 Curl 成功发布到 Queue 方法并达到断点。我已经详细阐述了您的示例,并展示了如何将 RPC 操作与普通 REST 方法混合。

示例代码位于 GitHub here。

基本上,问题与 WebApi 元素(路由、配置等,尽管您可能应该删除 Optional id 并将 HttpPost 属性添加到 queue 方法)相反,因为您的初始问题表明它是您的样子调用服务器,这应该是另一个问题。

不清楚您是否有两个项目以及如何托管 MS 测试代码等?...但是有一个很好的 WebApi integration test here 示例,您可以遵循它,并且在使用 Fiddler 等工具调试 API 时可以快速帮助消除和调试路由配置问题。

工作控制台程序:

static void Main(string[] args)
    
       // Set up server configuration 
        HttpSelfHostConfiguration config = new HttpSelfHostConfiguration("http://localhost:8080");

        //Route Catches the GET PUT DELETE typical REST based interactions (add more if needed)
        config.Routes.MapHttpRoute("API Default", "api/controller/id",
            new  id = RouteParameter.Optional ,
            new  httpMethod = new HttpMethodConstraint(HttpMethod.Get, HttpMethod.Put, HttpMethod.Delete) );

        //This allows POSTs to the RPC Style methods http://api/controller/action
        config.Routes.MapHttpRoute("API RPC Style", "api/controller/action",
            new  httpMethod = new HttpMethodConstraint(HttpMethod.Post) );

        //Finally this allows POST to typeical REST post address http://api/controller/
        config.Routes.MapHttpRoute("API Default 2", "api/controller/action",
            new  action = "Post" ,
            new  httpMethod = new HttpMethodConstraint(HttpMethod.Post) );

        using (HttpSelfHostServer server = new HttpSelfHostServer(config))
        
            server.OpenAsync().Wait();
            Console.WriteLine("Press Enter to quit.");
            Console.ReadLine();
        
    

工作控制器

public class TaskInstanceQueueController : ApiController


     public void Get(string id)
    
        // Do something with my taskInstance
        Console.WriteLine("Method entered!" + id);
    

    [ActionName("Post")]
    [HttpPost]
    public void Post(TaskInstance taskInstance)
    
        // Do something with my taskInstance
        Console.WriteLine("REST Post Method entered!");
    

    [ActionName("Queue")]
    [HttpPost]
    public void Queue(TaskInstance taskInstance)
    
        // Do something with my taskInstance
        Console.WriteLine("Queue Method entered!");
    

    [ActionName("Another")]
    [HttpPost]
    public void Another(TaskInstance taskInstance)
    
        Console.WriteLine("Another Method entered!");
    

【讨论】:

太棒了!谢谢马克!请看我上面的评论。不过,我要去看看你的东西,看看有没有我遗漏的东西。 优秀。不仅是对问题的直接回答,而且清晰简洁的解释和示例代码使读者可以轻松地将推理应用于自己的情况。谢谢。【参考方案2】:

您的路线不明确。

config.Routes.MapHttpRoute("API Default", "api/controller/id", new id = RouteParameter.Optional);
config.Routes.MapHttpRoute("API RPC Style", "api/controller/action", new  id = RouteParameter.Optional );

当请求进入 /api/TaskInstanceQueue/Queue 时,它​​匹配第一个路由,因此路由数据包含 controller = "TaskInstanceQueue", id = "Queue" 。然后系统尝试发现 Post 方法,但无法这样做,因为您没有 Post(或 PostXxx)方法,因此您的 HTTP 调用失败。

你有几个选择。一个是您可以为这个控制器放置一个显式路由(第一个):

config.Routes.MapHttpRoute("API RPC Style", "api/TaskInstanceQueue/action", new  controller = "TaskInstanceQueue" );
config.Routes.MapHttpRoute("API Default", "api/controller/id", new id = RouteParameter.Optional);

另一个是,如果你知道你的 ID 总是只有数字,你可以给基于 ID 的路由添加一个约束,这将导致它不匹配“队列”作为一个 ID,因此落入正确的基于动作的路线。

【讨论】:

感谢您的评论!我会检查并修复我的路线。请看我上面的更新。 我尝试了您的第一个建议,但行为保持不变。我现在几乎确信我有某种环境问题,但它打败了我。

以上是关于如何正确执行 RPC 样式的 Asp.Net Web API 调用?的主要内容,如果未能解决你的问题,请参考以下文章

如何让 Visual Studio 的设计器在 ASP.NET Web 用户控件中正确呈现 CSS?

在 ASP.Net Core 3 中使用 Rabbitmq 的 RPC

如何将 css 样式设置为 asp.net 按钮?

如何更改 createuserwizard 的样式? ASP.NET

ASP.Net,母版页:每页样式表

如何使用 CSS 设置 asp.net 菜单的样式