首页 | 新闻 | 交流 | 问吧 | 文档 | 手册 | 下载 | 博客

ASP.NET MVC中RESTful原教旨主义者的两个实现细节

作者:  时间: 2011-06-14

刚才无意中看到《什么是REST?》一文。文章虽然很短,短到我几乎要鄙夷一下作者的程度,但是仔细看了下,确也发现本文着实有用。

作为一名想尽量实现纯RESTful服务的人(或可称为RESTful原教旨主义者)来说,希望做出来的服务能尽量的符合RESTful原则定义,如果做出来一个RPC + ROA(面向资源的架构,其定义见《RESTful Web Services 中文版》)的服务,那还不如直接使用WCF算了,虽然,罗马不是一天建成的,我也不可能一上手就构建一个纯RESTful服务,但是尽量向标准靠拢还是很有必要的。

文中提到了一些概念,我很快发现在我的实践中有两条不符合。

其一:“REST式的Web Service使用HTTP里的方法:GET, POST, DELETE, PUT。你不需要使用URL或请求的内容来指定这个方法。”

而在我目前的做法中,我是使用了类似这样的Url:http://www.cleversoft.com/svc/log/create作为创建一条日志记录的url的,很明显,在这个Url中,create是不必要的,甚至也是不应该出现的,为什么呢,RESTful的原则之一:统一接口原则,为啥具有这个原则呢,统一接口的目的是建立一种面向程序的网站,既然是面向程序的,大家都严格的尊重约定便是一个十分重要的事情了,否则你开放了一个资源,如:http://www.anywhere.com/blog,(实际上这个资源甚至可以称为API了)别人怎么知道后面是Get还是Search才能调用呢?但是运用了统一接口原则,大家很自然就想到使用GET(Http Method)来获取数据了,这就在很大程度上简化了MetaData的定义。回到我的应用中来说,对于Log服务来说,http://www.cleversoft.com/svc/log/已经很明确的代码了Log服务(资源的表示),使用POST方法就代表是创建日志记录了,画蛇添足的加上一个Create,不是又回到RPC方式了么?

按照ASP.NET MVC的Route规则来说,想要客户端使用http://www.cleversoft.com/svc/log的方式来调用到一个POST的方法,最简单直接的方法便是创建一个方法

[HttpPost]
public ActionResult Index()
{
    //...
}

但是..这似乎也太锉了点吧,明明一个Create方法,使用Index..客户端爽了,服务器端的人该骂娘了吧,还是继续使用Create方法名称为好,代码可读性也是至上的啊。那就只能从Url Routing上想办法了,结果很快发现了一个类型HttpMethodConstraint,哈哈,看起来就是为这件事准备的吧,于是为Url Routing添加新实例,如下代码所示:

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute("Svc_default_Create",
                "Svc/{controller}/{action}",
                new { action = "Create" },
                new { httpMethod = new HttpMethodConstraint("POST") }
                );

            context.MapRoute("Svc_default_Update",
                "Svc/{controller}/{action}",
                new { action = "Update" },
                new { httpMethod = new HttpMethodConstraint("PUT") }
                );

            context.MapRoute("Svc_default_Delete",
                "Svc/{controller}/{action}",
                new { action = "Delete" },
                new { httpMethod = new HttpMethodConstraint("Delete") }
                );

            context.MapRoute(
                "Svc_default",
                "Svc/{controller}/{action}/{id}",
                new { action = "Index", id = UrlParameter.Optional }
            );
        }
将客户端Url中的Create删除,使用http://www.cleversoft.com/svc/log添加Log记录,成功!

下面说说其二,第2个相对来说更简单点。文中提到“REST式的Web Service使用HTTP状态码作为返回值。”,我看Google中的服务,很多都是返回被创建(修改)的实体的,这我倒不愿意,我向服务器发了什么我还不知道么,这一来一回的多浪费啊,所以我就返回了new EmptyResult(),当然,返回EmptyResult应该就是返回一个200(Http StatusCode)吧,但是对于Create,Update,Delete来说,都返回200似乎显的不那么专业,于是对之前我做的HttpStatusResult稍作改动,为Controller添加几个扩展方法:

        public static HttpStatusResult GetResult200(this Controller controller, params string[] content)
        {
            return new HttpStatusResult(HttpStatusCode.OK, content.Length > 0 ? content[0] : "OK");
        }

        public static HttpStatusResult GetResult201(this Controller controller, params string[] content)
        {
            return new HttpStatusResult(HttpStatusCode.Created, content.Length > 0 ? content[0] : "Created");
        }

        public static HttpStatusResult GetResult202(this Controller controller, params string[] content)
        {
            return new HttpStatusResult(HttpStatusCode.Accepted, content.Length > 0 ? content[0] : "Accepted");
        }

        public static HttpStatusResult GetResult204(this Controller controller, params string[] content)
        {
            return new HttpStatusResult(HttpStatusCode.NoContent, content.Length > 0 ? content[0] : "No Content");
        }

这样,就可以视情况向客户端如实反映操作结果了,万一以后有人使用别的客户端调我的服务,也不至于骂我NC了,呵呵

使用方法示例:

        [HttpPost]
        [ServiceError]
        public ActionResult Create([AtomEntryParameterConvert(typeof(LogEntry))]LogRecord log)
        {
            var logMng = new LogManager();

            logMng.CreateLog(log);

            return this.GetResult201();
        }
ps: 听说.NET 5.0可能会有扩展属性?如果真有的话就不会让这个操作显的那么难看了,期待...