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

时间:2012-09-11 17:10:32

标签: asp.net rest asp.net-web-api rpc

更新9/12/2012:

我与同事分享了我的代码,第一次一切正常,没有任何变化。所以,我的盒子上一定有环保的东西,任何人有什么想法吗?

请参阅下面的更新

设置:

.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应用程序与其他一些apicontrollers一起工作得很好,但他们都在做标准的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行是通话线路。

2 个答案:

答案 0 :(得分:6)

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

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

示例代码位于GitHub here

基本上问题不是与WebApi元素(路由,配置等)有关,尽管您应该删除Optional id并将HttpPost属性添加到队列方法中,而是因为您的初始问题表明它是您的方式调用服务器,这应该是另一个问题。

目前还不清楚你是否有两个项目以及如何托管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!");
    }
}

答案 1 :(得分:4)

你有不明确的路线。

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,因此落入正确的基于行动的路线。