WCF RESTful 服务+Jquery 客户端 跨域调用 大坑及解决方案汇总

前言

         微软宣布.NET开源,于是我等夹着尾巴混迹OSC的C#程序员终于可以昂首挺胸做人了!

         不知道为何,很多人痛恨.微软的产品,其中不乏没怎么用过C#就开始黑的,只有用过才知道,微软的框架搞出问题来,找解决方案是个极为痛苦的过程,最近几天搞WCF RESTful 服务,以及使用Jquery 跨域调用,出来一系列问题,为了找解决方案,英语阅读能力直线上升,已经到了不看英文文档不舒服的程度!

         由于踩坑太多,感觉又像玩扫雷,又像打地鼠,为了防止以后继续踩坑,赶紧把遇到的一些印象深刻的问题摘录下来。

         目前这项目服务端使用 EF 6.0  WCF 4.0  ,服务端做了个动态创建服务,动态替换服务实现类DLL的机制,然后服务发布起来后出现了一系列问题:

  1 实体类作参数和返回值时,序列化和反序列化出错

        假如有这样一个实体类

       

/// <summary>
    /// 员工表
    /// </summary>
    [DataContract]
    public class Employee
    {
        /// <summary>
        /// 员工ID
        /// </summary>
        [DataMember]
        [Key, Required]
        public int EmployeeId { get; set; }
        /// <summary>
        /// 员工名
        /// </summary>
       [DataMember]
       [StringLength(32)]
        public string EmployeeName { get; set; }
       
        /// <summary>
        /// 员工号
        /// </summary>
       [DataMember]
       [StringLength(32)]
        public string EmployeeCartId { get; set; }
        /// <summary>
        /// 创建时间
        /// </summary>
       [DataMember]
        public DateTime CreatTime { get; set; }
        /// <summary>
        /// 创建者ID
        /// </summary>
        [DataMember]
        public int CreaterID { get; set; }
        /// <summary>
        /// 密码
        /// </summary>
        [DataMember]
        [StringLength(32)]
        public string Password { get; set; }
        /// <summary>
        /// 是否使用
        /// </summary>
        [DataMember]
        public bool Enable { get; set; }
     

    }

以及服务接口的部分方法

 [OperationContract]
 [WebInvoke(UriTemplate = "sys_employee", Method = "POST", ResponseFormat = WebMessageFormat.Json)]
 Employee AddEmployee(Employee employee);
 
 [OperationContract]
 [WebGet(UriTemplate = "sys_employee/{id}",ResponseFormat = WebMessageFormat.Json)]
 Employee GetEmployeeById(string id);

还有测试的JS代码

<script type="text/javascript">
        $(function () {
            var jp = { "EmployeeId": 5, "EmployeeName": "335", "EmployeeCartId": "335", "CreatTime": "/Date(1242357713797+0800)/", "CreaterID": 1, "Password": "1", "Enable": true };
            $.ajax({
                data: JSON.stringify(jp) ,
                type: "POST",
                url: "http://127.0.0.1:8766/sys_employee", 
                
                // contentType: "application/json; charset=utf-8",
                //  dataType: "jsonp", 
                dataType: "json",
                success: function (msg) {
                    alert(msg.redata);
                },
                error: function (xhr, error) {
                    alert(error);
                }
            });
        });

    </script>

这里要说明踩坑时候遇到几个小问题:

  • WCF发布 RESTful 服务需要添加System.ServiceModel.Web的引用

  • JSON的日期格式是 "/Date(1242357713797+0800)/" ,如果写了“2014-11-14”这样序列化会出错

  • 使用Jquery的ajax 跨域调用的时候,设置data="JSONP"  Method自动转为GET;设置了contentType: "application/json; charset=utf-8" Method 会自动转为 OPTIONS

  • 实体类在作返回值的时候,会自动序列化为JSON

  • 实体类在作参数的时候,需要将JSON转为字符串,我的处理方式见代码...

然后是折腾很久的麻烦问题:

       服务器处理请求时遇到错误。异常消息为“传入消息的消息格式不应为“Raw”。此操作的消息格式应为 'Xml', 'Json'。这可能是因为绑定尚未配置 WebContentTypeMapper。

       既然是绑定尚未配置,那么需要手动指定一下消息格式为Json,指定的方式是添加下面一个类

 public class JsonContentTypeMapper : WebContentTypeMapper
    {
        public override WebContentFormat GetMessageFormatForContentType(string contentType)
        {
            if (contentType == "application/x-www-form-urlencoded; charset=UTF-8")
            {
                return WebContentFormat.Json;
            }
            else
            {
                return WebContentFormat.Raw;
            }
            return WebContentFormat.Json;
        }
    }

然后,由于我是用代码方式发布服务,所以应该把他加入到WebHttpEndpoint 中:

 webHttpEndPoint.ContentTypeMapper = new JsonContentTypeMapper();

再次测试就OK了

2 跨域调用的问题

大家都知道,处于安全考虑,浏览器是不允许JS脚本跨域调用的。但是作为一个内网的系统,暂时就不管那么多了,大不了再加入身份验证,先解决有无问题,让领导早点看到成果对不对?!

于是我找了很多网站都没有简单的用POST方式能解决的方案,后来在CORS的网站上找到了,但是非常抱歉..网站域名我给忘记了.. 找到的代码如下:

 public class CustomHeaderMessageInspector : IDispatchMessageInspector
    {
        Dictionary<string, string> requiredHeaders;
        public CustomHeaderMessageInspector(Dictionary<string, string> headers)
        {
            requiredHeaders = headers ?? new Dictionary<string, string>();
        }

        public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
        {
            return null;
        }

        public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            var httpHeader = reply.Properties["httpResponse"] as HttpResponseMessageProperty;
            foreach (var item in requiredHeaders)
            {
                httpHeader.Headers.Add(item.Key, item.Value);
            }
        }
    }
    
     public class EnableCrossOriginResourceSharingBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
        {

        }

        public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
        {

        }

        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
        {
            var requiredHeaders = new Dictionary<string, string>();

            requiredHeaders.Add("Access-Control-Allow-Origin", "*");
            requiredHeaders.Add("Access-Control-Request-Method", "POST,GET,PUT,DELETE,OPTIONS");
            requiredHeaders.Add("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");

            endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new CustomHeaderMessageInspector(requiredHeaders));
        }

        public void Validate(ServiceEndpoint endpoint)
        {

        }

        public override Type BehaviorType
        {
            get { return typeof(EnableCrossOriginResourceSharingBehavior); }
        }

        protected override object CreateBehavior()
        {
            return new EnableCrossOriginResourceSharingBehavior();
        }
    }

最后同样要添加到服务的webHttpEndPoint上

    EnableCrossOriginResourceSharingBehavior crossOriginBehavior = new EnableCrossOriginResourceSharingBehavior();
    webHttpEndPoint.Behaviors.Add(crossOriginBehavior);

至此,大坑和部分小坑已经都搞定了,还有身份验证的问题,不过这个不影响给领导看DEMO,先过个愉快的周末再说!

你可能感兴趣的:(跨域,jquery,Restful,WCF)