ASP.NET MVC + RESTful服务之HttpStatusResult

时间:2022-03-30 04:13:32

开篇语与本主题无关,我非常尊敬的一个导师好几天没有见到人,今天听说原来是病了,人也出现了。在此祝愿他身体康健,长命百岁!

使用ASP.NET MVC构建RESTful服务时,想到一个问题:在使用POST,PUT,DELETE方法发送请求时服务器端如何回传响应?如果在操作过程中发生了异常情况,如何通知客户端?

带着这个问题,尝试着构建了一个ActionResult的派生类:

namespace System.Web.Mvc
{
    public class HttpStatusResult : ActionResult
    {
        public HttpStatusResult()
            : this(HttpStatusCode.OK)
        {
        }

        public HttpStatusResult(HttpStatusCode statusCode)
            : this(statusCode, "")
        {
        }

        public HttpStatusResult(HttpStatusCode statusCode, string content)
        {
            this.StatusCode = statusCode;
            this.Content = content;
        }

        public string Content { get; set; }
        public HttpStatusCode StatusCode { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.StatusCode = (int)StatusCode;
            context.HttpContext.Response.ContentType = "text/xml, charset=\"utf-8\"";

            if (string.IsNullOrEmpty(context.HttpContext.Request.Headers["Accept-Encoding"]) == false)
            {
                string acceptEncoding = context.HttpContext.Request.Headers["Accept-Encoding"].ToLower();

                if (acceptEncoding.Contains("gzip"))
                {
                    context.HttpContext.Response.Filter = new GZipStream(context.HttpContext.Response.Filter, CompressionMode.Compress);
                    context.HttpContext.Response.AppendHeader("Content-Encoding", "gzip");
                }
            }

            using (var writer = XmlWriter.Create(context.HttpContext.Response.Output))
            {
                this.ResponseContent.WriteTo(writer);
            }
        }

        private XmlDocument ResponseContent
        {
            get
            {
                XmlDocument xml = new XmlDocument();
                xml.LoadXml(string.Format("<?xml version=\"1.0\"?><response xmlns=\"http://schema.cleversoft.com/webutil/httpstatusresult/1.0/\"><content>{0}</content></response>", this.Content));
                return xml;
            }
        }
    }
}

使用这个类,可以向请求的客户端返回一个Http Status信息,如果操作正常,则视情况使用200,202,204。情况是什么?据HTTP/1.1定义:

POST:

The action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either 200 (OK) or 204 (No Content) is the appropriate response status, depending on whether or not the response includes an entity that describes the result.

If a resource has been created on the origin server, the response SHOULD be 201 (Created) and contain an entity which describes the status of the request and refers to the new resource, and a Location header.

PUT:

If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.

DELETE:

A successful response SHOULD be 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has not yet been enacted, or 204 (No Content) if the action has been enacted but the response does not include an entity.

总而言之,看你真正做了什么操作,返回一个与之匹配的HTTP Status Code。在此过程中也有可能遇到需要使用3XX系统的情况,不过我要做的服务中还没有这么复杂的规划,据以前的学习发现,发现Google的服务中3XX用的比较多,整的比较复杂。

上面这些只仅仅是发送POST,PUT,DELETE成功的响应,如果仅是为了这个原因,似乎就没有必要去做HttpStatusResult类型了。自已去实现RESTful服务,异常处理肯定是必须要考虑的内容。在我现在的设想中,如果一些操作发生了服务器端的错误,那就得使用HttpStatusResult类型来向客户端传递错误信息,即HttpStatusResult.Content属性。使用方法如下:

  return new HttpStatusResult(System.Net.HttpStatusCode.InternalServerError, "Exception Message.");  

在发送异常时我使用了InternalServerError(500)。

问题来了,使用了500以后,客户端并不知道这是你“处心积虑”的发出来了,它只知道服务器端出错了,然后在WebRequest.GetResponse()时直接抛出异常了,而且没有返回WebResponse,那我们使用Content返回的异常信息岂不成了笑话了?所幸,M$也没有这么笨,大家可以仔细看下WebException的定义就会发现,原来使用WebException.Response就可以获得刚才服务器返回的响应,当然这时WebException.Status的状态必须不能为ConnectFailure(值为2),这个Response属性也必须不能为空(废话),接下来,你就使用XmlReader从ResponseStream中拿数据吧,数据结构的定义可以视个人喜好定义:)。这里比较推荐的方法是在响应的xml中定义一个NameSpace,为什么nia?因为这样你就可以确定这是你自已,而不是他人发来的响应。

最后,转一下Http Status Code定义:

100 Continue

101 Switching Protocols

102 Processing

200 OK

201 Created

202 Accepted

203 Non-Authoritative Information

204 No Content

205 Reset Content

206 Partial Content

207 Multi-Status

226 IM Used

300 Multiple Choices

301 Moved Permanently

302 Found

303 See Other

304 Not Modified

305 Use Proxy

306 (Unused)

307 Temporary Redirect

400 Bad Request

401 Unauthorized

402 Payment Required

403 Forbidden

404 Not Found

405 Method Not Allowed

406 Not Acceptable

407 Proxy Authentication Required

408 Request Timeout

409 Conflict

410 Gone

411 Length Required

412 Precondition Failed

413 Request Entity Too Large

414 Request-URI Too Long

415 Unsupported Media Type

416 Requested Range Not Satisfiable

417 Expectation Failed

418 I'm a teapot

422 Unprocessable Entity

423 Locked

424 Failed Dependency

425 (Unordered Collection)

426 Upgrade Required

500 Internal Server Error

501 Not Implemented

502 Bad Gateway

503 Service Unavailable

504 Gateway Timeout

505 HTTP Version Not Supported

506 Variant Also Negotiates

507 Insufficient Storage

510 Not Extended