前言
微软宣布.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,先过个愉快的周末再说!