在 ASP.NET Web API 中发送 HTML 表单数据:表单 url 编码的数据

第 1 部分:表单 urlencoded 数据

本文介绍如何将表单 url 编码的数据发布到 Web API 控制器。

HTML 表单概述

HTML 表单使用 GET 或 POST 将数据发送到服务器。 form 元素的 method 属性提供 HTTP 方法:

<form action="api/values" method="post">

默认方法是 GET。 如果表单使用 GET,则会在 URI 中将表单数据编码为查询字符串。 如果表单使用 POST,则表单数据将放置在请求正文中。 对于 POSTed 数据, enctype 属性指定请求正文的格式:

enctype 说明
application/x-www-form-urlencoded 表单数据编码为名称/值对,类似于 URI 查询字符串。 这是 POST 的默认格式。
multipart/form-data 表单数据编码为多部分 MIME 消息。 如果要将文件上传到服务器,请使用此格式。

本文的第 1 部分介绍 x-www-form-urlencoded 格式。 第 2 部分 介绍多部分 MIME。

发送复杂类型

通常,将发送一个复杂类型,该类型由从多个窗体控件获取的值组成。 请考虑以下表示状态更新的模型:

namespace FormEncode.Models
{
    using System;
    using System.ComponentModel.DataAnnotations;

    public class Update
    {
        [Required]
        [MaxLength(140)]
        public string Status { get; set; }

        public DateTime Date { get; set; }
    }
}

下面是通过 POST 接受 Update 对象的 Web API 控制器。

namespace FormEncode.Controllers
{
    using FormEncode.Models;
    using System;
    using System.Collections.Generic;
    using System.Net;
    using System.Net.Http;
    using System.Web;
    using System.Web.Http;

    public class UpdatesController : ApiController
    {
        static readonly Dictionary<Guid, Update> updates = new Dictionary<Guid, Update>();

        [HttpPost]
        [ActionName("Complex")]
        public HttpResponseMessage PostComplex(Update update)
        {
            if (ModelState.IsValid && update != null)
            {
                // Convert any HTML markup in the status text.
                update.Status = HttpUtility.HtmlEncode(update.Status);

                // Assign a new ID.
                var id = Guid.NewGuid();
                updates[id] = update;

                // Create a 201 response.
                var response = new HttpResponseMessage(HttpStatusCode.Created)
                {
                    Content = new StringContent(update.Status)
                };
                response.Headers.Location = 
                    new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
                return response;
            }
            else
            {
                return Request.CreateResponse(HttpStatusCode.BadRequest);
            }
        }

        [HttpGet]
        public Update Status(Guid id)
        {
            Update update;
            if (updates.TryGetValue(id, out update))
            {
                return update;
            }
            else
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }
        }

    }
}

注意

此控制器使用 基于操作的路由,因此路由模板为“api/{controller}/{action}/{id}”。 客户端会将数据发布到“/api/updates/complex”。

现在,让我们编写一个 HTML 表单,供用户提交状态更新。

<h1>Complex Type</h1>
<form id="form1" method="post" action="api/updates/complex" 
    enctype="application/x-www-form-urlencoded">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input name="status" type="text" />
    </div>
    <div>
        <label for="date">Date</label>
    </div>
    <div>
        <input name="date" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

请注意,窗体上的 操作 属性是控制器操作的 URI。 下面是在 中输入了一些值的窗体:

“复杂类型 H T M L”窗体的屏幕截图,其中“状态”字段和“日期”字段填充了值。

当用户单击“提交”时,浏览器会发送类似于以下内容的 HTTP 请求:

POST http://localhost:38899/api/updates/complex HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Content-Type: application/x-www-form-urlencoded
Content-Length: 47

status=Shopping+at+the+mall.&date=6%2F15%2F2012

请注意,请求正文包含格式为名称/值对的表单数据。 Web API 会自动将名称/值对转换为 类的 Update 实例。

通过 AJAX 发送表单数据

当用户提交表单时,浏览器会离开当前页并呈现响应消息的正文。 当响应为 HTML 页面时,这没关系。 但是,使用 Web API 时,响应正文通常为空或包含结构化数据,例如 JSON。 在这种情况下,使用 AJAX 请求发送表单数据更有意义,以便页面可以处理响应。

以下代码演示如何使用 jQuery 发布表单数据。

<script type="text/javascript">
    $("#form1").submit(function () {
        var jqxhr = $.post('api/updates/complex', $('#form1').serialize())
            .success(function () {
                var loc = jqxhr.getResponseHeader('Location');
                var a = $('<a/>', { href: loc, text: loc });
                $('#message').html(a);
            })
            .error(function () {
                $('#message').html("Error posting the update.");
            });
        return false;
    });
</script>

jQuery submit 函数将表单操作替换为新函数。 这会替代“提交”按钮的默认行为。 serialize 函数将表单数据序列化为名称/值对。 若要将表单数据发送到服务器,请调用 $.post()

请求完成后, .success().error() 处理程序向用户显示相应的消息。

“复杂类型 H T M L”窗体的屏幕截图,其中以粗体文本向用户显示本地主机错误。

发送简单类型

在前面的部分中,我们发送了一个复杂类型,该类型 Web API 反序列化为模型类的实例。 还可以发送简单类型,例如字符串。

注意

在发送简单类型之前,请考虑改为将值包装在复杂类型中。 这为你提供了服务器端模型验证的优势,并在需要时更轻松地扩展模型。

发送简单类型的基本步骤是相同的,但有两个细微的差异。 首先,在控制器中,必须使用 FromBody 属性修饰参数名称。

[HttpPost]
[ActionName("Simple")]
public HttpResponseMessage PostSimple([FromBody] string value)
{
    if (value != null)
    {
        Update update = new Update()
        {
            Status = HttpUtility.HtmlEncode(value),
            Date = DateTime.UtcNow
        };

        var id = Guid.NewGuid();
        updates[id] = update;

        var response = new HttpResponseMessage(HttpStatusCode.Created)
        {
            Content = new StringContent(update.Status)
        };
        response.Headers.Location = 
            new Uri(Url.Link("DefaultApi", new { action = "status", id = id }));
        return response;
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.BadRequest);
    }

默认情况下,Web API 会尝试从请求 URI 获取简单类型。 FromBody 属性告知 Web API 从请求正文中读取值。

注意

Web API 最多读取一次响应正文,因此一个操作的一个参数只能来自请求正文。 如果需要从请求正文获取多个值,请定义一个复杂类型。

其次,客户端需要使用以下格式发送值:

=value

具体而言,对于简单类型,名称/值对的名称部分必须为空。 并非所有浏览器都支持 HTML 表单的此格式,但可以在脚本中创建此格式,如下所示:

$.post('api/updates/simple', { "": $('#status1').val() });

下面是一个示例窗体:

<h1>Simple Type</h1>
<form id="form2">
    <div>
        <label for="status">Status</label>
    </div>
    <div>
        <input id="status1" type="text" />
    </div>
    <div>
        <input type="submit" value="Submit" />
    </div>
</form>

下面是用于提交表单值的脚本。 与上一个脚本的唯一区别是传递到 post 函数的参数。

$('#form2').submit(function () {
    var jqxhr = $.post('api/updates/simple', { "": $('#status1').val() })
        .success(function () {
            var loc = jqxhr.getResponseHeader('Location');
            var a = $('<a/>', { href: loc, text: loc });
            $('#message').html(a);
        })
        .error(function () {
            $('#message').html("Error posting the update.");
        });
    return false;
});

可以使用同一方法发送简单类型的数组:

$.post('api/updates/postlist', { "": ["update one", "update two", "update three"] });

其他资源

第 2 部分:文件上传和多部分 MIME