zoukankan      html  css  js  c++  java
  • 爬坑纪——RESTful WCF

    自打学编程以来,蠢事干过不少,就“掉进坑里”这事而言,有不小心陷进去的,有心甘情愿跳下去的,还有被别人拉进去的...但是像过去两天一样一步一个坑的...真的是还没有体验过。“避之不得,弃之可惜”,人生最痛苦的事莫过于此。

    好吧,“最近”REST很热门...我那么喜欢凑热闹的人,当然也想搞一搞,其实最主要的是SharePoint 2013搜索里面有一种API采用了这种模型,为了“备着用”,也应该了解下...当然这个很大程度上是个借口。百度一下或者谷歌一下教程真不少,全部声称构建支持“CRUD”的REST WCF,但是打开一看,简直就是坑爹啊...要么只实现了查询,要么只实现了服务没有调用示例,要么就是啪用一个模版然后改下代码...最后导航到服务页,声称构建完成。当然...看到几位前辈的文章还是很全面的..只不过是比较旧的方式,节点绑定比较麻烦就没有走那条路。本文“特点”:真正的CRUD全包含以及调用,货真价实童叟无欺;详细记录本人的爬坑史(有些时候我也不知道我怎么爬出来的,so...);收录各种解决方案的链接。

    服务

    目的是完成一个叫“BookService”的服务,包含基础的增删查改操作。整个可用的解决方案只需要处理4个文件,所以没有必要提供文件下载只需要贴出源码就行。

    [数据]为了区分重点,没有必要使用数据库这样的东西,使用代码引用一块内存当数据存储区就够了,先是实体类,代码如下。

        public class Book
        {
            static int id;
            private Book(Book book)
            {
                //参数只为签名
            }
    
            public Book()
            {
                //唯一标记
                id++;
                Id = id;
            }
    
            public Book(string name, int saledCount)
                :this()
            {
                //构造函数
                Name = name;
                SaledCount = saledCount;
            }
    
            public static Book Clone(Book book)
            {
                //复制部分属性
                Book clone = new Book(book);
                clone.Id = book.Id;
                clone.Name = book.Name;
    
                return clone;
            }
    
    
            //只有能够Get/Set的属性才能被序列化
            public int Id { get; set; }
            public string Name { get; set; }
            public int SaledCount { get; set; }
        }
    View Code

    还是说明下,static Book Clone方法是为了提取部分属性的...后来了解到可以使用dynamic或者ResponseObject;另外需要注意的是,公开的具有get和set访问器的属性才能成功被序列化,本来应该private set的,这里就只能get/set了。
    [服务]关于如何从一个空的ASP.NET项目构造一个RESTful WCF服务,请参考这里,非常详细:http://geekswithblogs.net/michelotti/archive/2010/08/21/restful-wcf-services-with-no-svc-file-and-no-config.aspx。当然这样的话就需要引用几个程序集:

    • System.ServiceModel
    • System.Web.Services
    • System.Web.Routing
    • System.ServiceModel.Activation
    • System.ServiceModel.Web

    看完上面那篇文章,构建服务是没问题的,像我这么掉坑的人都完成了。以下是源码:

        [ServiceContract]
        public interface IBookService
        {
            [OperationContract]
            Book GetBook(string id);
            [OperationContract]
            Book InsertBook(Book book);
            [OperationContract]
            void DeleteBook(string id);
            [OperationContract]
            IEnumerable<Book> GetAll();
            [OperationContract]
            void UpdateBook(Book book);
        }
    
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
        [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
        public class BookService : IBookService
        {
            #region 初始化一块内存数据
            static List<Book> BooksInMemory;
            static BookService()
            {
                BooksInMemory = new List<Book> { 
                    new Book("巴黎圣母院",10086),
                    new Book("小王子",2834),
                    new Book("故事细腻",288347),
                    new Book("假如",1344),
                };
            }
            #endregion
    
            [WebGet(UriTemplate = "Book/{id}",
                ResponseFormat = WebMessageFormat.Json,
                RequestFormat = WebMessageFormat.Json)]
            public Book GetBook(string id)
            {
                //R
                var index = Int32.Parse(id);
                return BooksInMemory.FirstOrDefault(i => i.Id == index);
            }
    
            [WebInvoke(UriTemplate = "Book", Method = "POST",
                ResponseFormat = WebMessageFormat.Json,
                BodyStyle = WebMessageBodyStyle.Bare,
                RequestFormat = WebMessageFormat.Json)]
            public Book InsertBook(Book book)
            {
                //C
                var newBook = new Book()
                {
                    Name = book.Name,
                    SaledCount = book.SaledCount
                };
                BooksInMemory.Add(newBook);
                return newBook;
            }
    
            [WebInvoke(UriTemplate = "Book/{id}/", Method = "DELETE"
                //ResponseFormat = WebMessageFormat.Json,
                //BodyStyle = WebMessageBodyStyle.Bare
                //RequestFormat = WebMessageFormat.Json
             )]
            public void DeleteBook(string id)
            {
                //D
                int index = Int32.Parse(id);
                var book = BooksInMemory.FirstOrDefault(i => i.Id == index);
                if (book != null)
                    BooksInMemory.Remove(book);
            }
    
            [WebGet(UriTemplate = "Book/",
                ResponseFormat = WebMessageFormat.Json,
                RequestFormat = WebMessageFormat.Json)]
            public IEnumerable<Book> GetAll()
            {
                //R
                //可以使用ResponseObject,或者dynamic
                var set =  BooksInMemory
                    //返回部分属性
                    .Select(i => Book.Clone(i));
    
                return set;
            }
    
            [WebInvoke(UriTemplate = "Book", Method = "PUT")]
                //BodyStyle= WebMessageBodyStyle.Bare,
                //ResponseFormat = WebMessageFormat.Json,
                //RequestFormat = WebMessageFormat.Json)]
            
            public void UpdateBook(Book book)
            {
                //U
                Book oldBook = BooksInMemory.FirstOrDefault(i => i.Id == book.Id);
                if (oldBook != null)
                {
                    oldBook.SaledCount = book.SaledCount;
                    oldBook.Name = book.Name;
                }
            }
        }
    View Code

    可以看到,我在Service的实现里面声明了静态成员用来存储数据。

    [坑s]

    POST,WebInvokeAttribute有个成员叫BodyStyle,先前我看过一篇文章说在XX情况下应该将BodyStyle置为Wrapped,但是我犯2忽略了别人说的那个前置条件,所以悲剧了。所以目前的情况是,将BodyStyle设置为Bare,也就是不包装,然后在客户端使用不包装的json请求提交就可以了。

    PUT(DELETE),就服务配置而言,PUT爬坑花费的时间比较少了...主要是要配置IIS允许的动词(verb),由于我使用的VS2012所以调试的时候使用了IIS Express。关于如何配置IIS Express的动词过滤规则,参考这里:http://geekswithblogs.net/michelotti/archive/2011/05/28/resolve-404-in-iis-express-for-put-and-delete-verbs.aspx。另外,不需要声明请求格式和相应格式(后话)。这里上一张配置config文件的截图:

    DELETE,至于为什么要把DELETE单独提出来..那是因为我被深深的坑住了。 [WebInvoke(UriTemplate = "Book/{id}/", Method = "Delete")]我使用这个特性的时候,一直返回405 method not allowed,比对下就知道了..大小写问题啊。我也不清楚是什么情况了..不过我猜测是IIS上面配置的时候使用了大写的delete,所以导致被过滤了。

    另外,关于PUT和DELETE,曾经返回过两个错误,一个是404,另外一个是405。现在我已经分不清什么时候返回什么了。网上有些解决方案说要移除WebDAV和WebDAVMoudle,参考这里:http://forums.iis.net/post/1937843.aspx。其实这两个在IIS Express中是已经被注释掉的。所以主要关注点在verb配置上,我的web.config可以证明这点。

    <?xml version="1.0" encoding="utf-8"?>
    
    <configuration>
        <system.web>
          <compilation debug="true" targetFramework="4.0" />
        </system.web>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true">
        </modules>
      </system.webServer>
      <system.serviceModel>
        <!--必须的-->
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"></serviceHostingEnvironment>
        <standardEndpoints>
          <webHttpEndpoint>
            <!--1-->
            <standardEndpoint name=""
                              crossDomainScriptAccessEnabled="true"
                              helpEnabled="true" 
                              automaticFormatSelectionEnabled="true">
            </standardEndpoint>
          </webHttpEndpoint>
        </standardEndpoints>
      </system.serviceModel>
    </configuration>
    View Code

    [Global.asax]

    主要是处理路由的:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel.Activation;
    using System.Web;
    using System.Web.Routing;
    using System.Web.Security;
    using System.Web.SessionState;
    
    namespace MyRWfromEmt
    {
        public class Global : System.Web.HttpApplication
        {
            protected void Application_Start(object sender, EventArgs e)
            {
                RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(BookService)));
            }
    
            #region 
            protected void Session_Start(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_BeginRequest(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_AuthenticateRequest(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_Error(object sender, EventArgs e)
            {
    
            }
    
            protected void Session_End(object sender, EventArgs e)
            {
    
            }
    
            protected void Application_End(object sender, EventArgs e)
            {
    
            }
            #endregion
        }
    }
    View Code

    调用

    这个花样就更多了,作为一个脚本菜鸟,不想讨论太多。这里涉及一下同域调用和跨域调用。 原理请查看这里:http://www.cnblogs.com/chopper/archive/2012/03/24/2403945.html。因为是使用ajax进行请求,同域调用就确保执行脚本的页面和服务处于同一个端口,跨域就是不同端口(大概是这么一个意思吧)。我们主要处理同域调用。

    [同域调用]

    先看代码,其实就是一个html文件,然后执行了CRUD这些操作:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>
                RESTful WCF Service
            </title>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
        <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
        </head>
    
        <body>
            <div id ="actionarea">
    
            </div>
            <div id ="edit">
                    <input type="hidden" id="tid" />
                    书名:<input name="Name" /><br />
                    售量:<input name="SaledCount" /><br />
                    <input type="button" onclick="sub(); return false;" value="Submit"/>
            </div>
    
            <input type="button" onclick="viewall(); return false;" value="ViewAll"/>
            <input type ="button" onclick="ready4create(); return false;" value="Create" />
        </body>
    
        <!--脚本-->
        <script type="text/javascript">
            $(function () {
                $('#edit').hide();
            });
    
            //查看全部
            function viewall()
            {
                $('#edit').hide();
                $.getJSON('/book')
                 .done(function (data)
                 {
                     var html = "";
                     for (var i = 0 ; i < data.length; i++)
                     {
                         html += "<a href='#' onclick=\"viewsingle('"+data[i].Id+"');return false;\">" + data[i].Name + "</a><br/>";
                     }
                     $('#actionarea').html(html);
                 });
            }
    
            //查看单条记录
            function viewsingle(id)
            {
                var uri = '/book/' + id;
                $.getJSON(uri)
                .done(function (data) {
                    var html = "Id:" + data.Id + "<br/>" + "Name:" + data.Name + "<br/>" + "SaledCount:" + data.SaledCount + "<br/>";
                    html += "<a href='javascript:del(" + data.Id + ");'>Delete</a>";
                    html += "  ";
                    html += "<a href='javascript:ready4edit(" + data.Id + ");' >Edit</a>";
                    $('#actionarea').html(html);
                });
                $('#edit').hide();
            }
    
            //删除记录
            function del(id)
            {
                var uri = '/book/' + id;
                $.ajax({
                    type: "Delete",
                    url: uri,
                    dataType: "text",
                    //processData:false,
                    contentType: "application/json;charset=utf-8",
                    success: function () {
                        viewall();
                    },
                    error: function (e) { document.write(e.ResponseText) },
                });
            }
    
            //输出消息
            function writemsg(data)
            {
                alert("e" + data);
            }
    
            //提交记录
            function sub() {
                var mode = $('#tid').val() || "-1";
                var name = $('input[name=Name]').val() || "";
                var saledcount = $('input[name=SaledCount]').val() || "0";
                var id = mode;
    
                var message = "{\"Name\":\"" + name + "\",\"SaledCount\":" + saledcount + ",\"Id\":" + id + "}";
                //简单处理
                if ((!mode) || mode == -1) {
                    //新建
                    $.ajax({
                        type: "POST",
                        url: "/Book",
                        contentType: "application/json;charset=utf-8",
                      //  processData: false,
                        data: message,
                        dataType: "json",
                        success:
                            function (data) {
                                $('input[name=SaledCount]').val('');
                                $('input[name=Name]').val('');
                                $('#edit').hide();
                                var html = "Id:" + data.Id + "<br/>" + "Name:" + data.Name + "<br/>" + "SaledCount:" + data.SaledCount + "<br/>";
                                html += "<a href='javascript:del(" + data.Id + ");'>Delete</a>";
                                html += "  ";
                                html += "<a href='javascript:ready4edit(" + data.Id + ");' >Edit</a>";
                                $('#actionarea').html(html);
                            }
                    });
                }
                else {
                    //修改
                    $.ajax({
                        type: "PUT",
                        url: "/Book",
                        contentType: "application/json;charset=utf-8",
                        processData: false,
                        cache:false,
                        data: message,
                        //如果仅仅指定json则就算始终引发error
                        dataType: "text",
                        success: function () {
                            viewsingle(id);
                        },
                        error: function (e) { document.write(e.ResponseText) },
                    });
                }
            }
    
            //要求编辑
            function ready4edit(id) {
                $('#edit').show();
                $('#actionarea').html('');
                $('#tid').val(id);
    
                $.getJSON("/Book/" + id)
                    .done(function (data) {
                        $('input[name=SaledCount]').val(data.SaledCount);
                        $('input[name=Name]').val(data.Name);
                    });
            }
    
            //要求创建
            function ready4create() {
                $('#edit').show();
                $('#actionarea').html('');
                $('#tid').val(-1);
            }
    
            $.ajaxSetup({
                // Disable caching of AJAX responses
                cache: false
            });
    
        </script>
    </html>
    View Code

    备注:到此为止,所有的代码提供完毕,在本人的机子上均正常执行。(只能声明到这里了..)

    [坑s]

    GET,说来惭愧,调用GET方法的时候就已经错误频出了,主要是:1,乱码;2,提示缺少分号(;)。后来我没有使用$.ajax而是使用了$.getJSON,代码短不说,还不出错。

    POST,主要是data的格式(不包装),应该是'{"XXX":int,"XX":"string"}'这样;有些文章说应该将processData置为false,但是我发现不声明这一条现在也可以正确执行。

    PUT,前面说过不要指明PUT方法的ResponseFormat,因为返回的是void。所以如果在客户端调用的时候指明dataType为“json”,总会触发error事件,尽管返回的是200[OK],这点参考这里:

    http://stackoverflow.com/questions/6186770/ajax-request-return-200-ok-but-error-event-is-fired-instead-of-success,另外这个标题中的fired真是着实让我乐了一下,老外真好玩(英语好的人可能觉得这个词很正常吧)。

    DELETE,和PUT差不多,主要是dataType的问题。

    执行结果如图:

     

    [跨域调用]

    这里再引用一篇文章:http://www.cnblogs.com/tom-zhu/archive/2012/10/25/2739202.html(配置)。目前我们是把html文件和service放在同一个解决方案里面,然后在同个端口打开,所以属于同域调用。其实只要把html文件右键使用浏览器打开就是跨域调用的情形了。

    至于怎么调用,上个截图就可以一目了然:

    $.getJSON调用:

    $.ajax调用:

     

    $.ajax({
    type:"GET",
    url:'http://localhost:10000/book',
    dataType:'jsonp',
    success:function(e){alert(e.length)}
    })
    

    部署

    [自托管(Self-Host)]

    为了解决这个问题,几个小时又过去了。但是,方法其实是异常简单的,且微软的MSDN上有示例,非常完整的示例。请查看:http://msdn.microsoft.com/zh-cn/library/vstudio/ee662954(v=vs.100).aspx,这里是一整套的代码。好了,这里只说自托管。方法是新建一个控制台应用程序,然后使用以下代码(添加必要的引用):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ServiceModel;
    using System.ServiceModel.Description;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace WCFConsole
    {
        class Program
        {
            static void Main(string[] args)
            {
                string uri = "http://localhost:9047";
                using (WebServiceHost host = new WebServiceHost(typeof(MyRWfromEmt.BookService)
                ,new Uri(uri)))
                {
                    host.Open();
    
                    Console.WriteLine("Please input exit to close host.");
                    string key = Console.ReadLine();
                    while (key.ToLower() != "exit")
                    {
                        Console.WriteLine("Please input exit to close host.");
                        key = Console.ReadLine();
                    }
                }
            }
        }
    }
    View Code

    然后添加一个App.config文件,使用如下配置(其实和service的Web.config使用的配置相同<更少>):

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <system.serviceModel>
        <standardEndpoints>
          <webHttpEndpoint>
            <!-- the "" standard endpoint is used by WebServiceHost for auto creating a web endpoint. -->
            <standardEndpoint name=""
                              crossDomainScriptAccessEnabled="true"
                              helpEnabled="true"
                              automaticFormatSelectionEnabled="true">
            </standardEndpoint>
          </webHttpEndpoint>
        </standardEndpoints>
      </system.serviceModel>
    </configuration>

    然后就可以执行了,同样,使用一个截图来说明问题(这里是跨域的):

    另外,如果没有在配置文件中支持跨域,其实还是能够获得数据,只是javascript会报一个错:缺少分号(;),然后总是触发error事件,于是就无法取得值了。(这是使用自托管方式出现的情况)。至于为什么jsonp调用会出现问题,请参考这篇文章:http://stackoverflow.com/questions/7936610/json-uncaught-syntaxerror-unexpected-token?answertab=active#tab-top

     [IIS]

    部署到IIS上其实还挺顺利的...因为电脑曾经配置过WCF,所以一些问题就没有再出现了。需要注意的是,由于我在services站点下面新建了bookservice应用程序,所以请求URI应该修改为‘/bookservice/book’。

    然后就需要配置动词,但是这里有一个问题,(win8电脑),如果应用程序池不是集成模式,则找不到那个配置项,如果是集成模式,又提示“无法从System.ServiceModel”中加载“System.ServiceModel. Activation.HttpModule”。如果是部署在Server 2008上倒是正常,只需要参照IIS Express配置的那篇文章即可。

  • 相关阅读:
    python初接触
    Visual Studio Code 必备插件
    C# 基础知识 -- 枚举(Enum)
    Visual Studio : Console.WriteLine(); 快捷键
    C# 调用Outlook发送邮件
    C# Dos、Unix、Mac文件格式之间的相互转换
    Treeview控件失去焦点,将选择的节点设置为高亮显示
    oracle中的Exists、In、Any、All
    TreeView控件如何控制滚动条的位置
    oracle 查看最大连接数与当前连接数
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/3081410.html
Copyright © 2011-2022 走看看