方法一
WCF Service
[ServiceBehavior( InstanceContextMode = InstanceContextMode.PerCall , ConcurrencyMode = ConcurrencyMode.Multiple , UseSynchronizationContext = false)]//禁用UI线程 class CrossService : ICrossService { //http://localhost:13333/CrossService/CrossCall?pJson=abcd1234 public System.IO.Stream GetCrossCall(string pJson) { return PostCrossCall(pJson); } //http://localhost:13333/CrossService/CrossCall public System.IO.Stream PostCrossCall(string pJson) { string _origin = WebOperationContext.Current.IncomingRequest.Headers["Origin"]; if (_origin != null) { WebOperationContext.Current.OutgoingResponse.Headers.Add("Access-Control-Allow-Origin", _origin); } return new System.IO.MemoryStream(System.Text.UTF8Encoding.UTF8.GetBytes(string.Format("{0}", pJson))); } public Message OptionsCrossCall() { return CreateOptionsMessage(); } /// <summary> /// 创建OPTIONS报文 /// </summary> /// <returns></returns> Message CreateOptionsMessage() { string _origin = WebOperationContext.Current.IncomingRequest.Headers["Origin"]; string _replyAction = "OPTIONS"; Message _reply = Message.CreateMessage(MessageVersion.None, _replyAction); HttpResponseMessageProperty _httpResponse = new HttpResponseMessageProperty(); _reply.Properties.Add(HttpResponseMessageProperty.Name, _httpResponse); _httpResponse.SuppressEntityBody = true; _httpResponse.StatusCode = HttpStatusCode.OK; if (_origin != null) { _httpResponse.Headers.Add("Access-Control-Allow-Origin", _origin); } _httpResponse.Headers.Add( "Access-Control-Allow-Methods", "POST, PUT, DELETE");//client post header "Access-Control-Request-Method" _httpResponse.Headers.Add( "Access-Control-Allow-Headers", "Content-Type, Accept");//client post header "Access-Control-Request-Headers" return _reply; } /***********客户端js调用方式*********** var xhttp = new XMLHttpRequest(); xhttp.onload=function(){console.log(xhttp.responseText);}; xhttp.open("POST", "http://localhost:13333/CrossService/CrossCall"); xhttp.setRequestHeader('Content-Type', 'application/json');//没有设置application/json的话,会报异常消息 xhttp.send(12345);//字符要使用转义字符串:"\" {pJson:\\\"adfb\\\"} \"" */ /* 可以把参数设置为System.IO.Stream类型,WEB客户端Content-Type设置为multipart/form-data *这样可以直接按数据流上传 class CrossService : ICrossService { public void DoWork(System.IO.Stream input) { System.IO.StreamReader sr = new StreamReader(input); string s = sr.ReadToEnd(); sr.Dispose(); // Do work here } } */ } [ServiceContract] public interface ICrossService { //[OperationContract] [WebGet(UriTemplate = "/CrossCall?pJson={pJson}")] System.IO.Stream GetCrossCall(string pJson); [WebInvoke(UriTemplate = "/CrossCall", Method = "POST")] System.IO.Stream PostCrossCall(string pJson); /// <summary> /// 非GET操作跨域要增加OPTIONS请求处理 /// </summary> /// <param name="pJson"></param> /// <returns></returns> [WebInvoke(UriTemplate = "/CrossCall",//路径和POST(非GET)路径相同 Method = "OPTIONS")] Message OptionsCrossCall(); }
config增加webHttpBinding和webHttp behavior
<services> <service name="Test.CrossService" ..................................> <endpoint address="http://localhost:13333/CrossService" binding="webHttpBinding" behaviorConfiguration="HttpWcfBehavior" contract="Test.CrossService"/> ................................... </service> </services> <behaviors> <endpointBehaviors> <behavior name="HttpWcfBehavior"> <!--webHttpBehavior:启用 Windows Communication Foundation (WCF) 服务的 Web 编程模型。 WebHttpBehavior 行为与 WebHttpBinding 绑定一起使用时,支持 WCF 公开和访问 Web 样式服务。--> <webHttp automaticFormatSelectionEnabled="true" /> </behavior> </endpointBehaviors> ............. </behaviors>
方法二
实现一个endpotin behavior通过endpoint拦截消息,处理OPTIONS请求
例子:
public class MessageInspector : IDispatchMessageInspector { string BindingName; public MessageInspector(string bindingName) { BindingName = bindingName; } /// <summary> /// invoke操作之前执行 /// </summary> /// <param name="request"></param> /// <param name="channel"></param> /// <param name="instanceContext"></param> /// <returns></returns> public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { HttpRequestMessageProperty _httpProps = (HttpRequestMessageProperty)request.Properties[HttpRequestMessageProperty.Name]; if (null != _httpProps && "OPTIONS".Equals(_httpProps.Method)) { return "TRUE"; } return null; } /// <summary> /// 发送reply给客户端之前执行 /// </summary> /// <param name="reply"></param> /// <param name="correlationState"></param> public void BeforeSendReply(ref Message reply, object correlationState) { if ("TRUE".Equals(correlationState)) { reply = T8Service.CreateOptionsMessage(); } } } public class MessageInspectorBehavior : IEndpointBehavior { string BindingName; public MessageInspectorBehavior(string bindingName) { BindingName = bindingName; } public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.DispatchRuntime.MessageInspectors.Add(new MessageInspector(BindingName)); } public void Validate(ServiceEndpoint endpoint) { } }
在启动ServiceHost时加入Behavior
private static ServiceHost CrossServiceHost { get { if (_CrossServiceHost == null || _CrossServiceHost.State == CommunicationState.Closed) { _CrossServiceHost = null; _CrossServiceHost = new ServiceHost(typeof(CrossService)); var endpoints = _CrossServiceHost.Description.Endpoints; foreach (var _endpoint in endpoints) { if (_endpoint.Binding is WebHttpBinding) ; { _endpoint.Behaviors.Add(new MessageInspectorBehavior(_endpoint.Binding.Name)); } } } return _CrossServiceHost; } } private static ServiceHost _CrossServiceHost = null;
备注
在.net Window Program程序中使用HttpWebRequest 模拟HTTP请求调用WCF可以进入basicHttpBinding和webHttpBinding,
调用basicHttpBinding时
报文Content-Type 需设置为 "text/xml; charset=utf-8",
同时需增加一个SOAPAction报头声明调用服务端方法的命名url,传递报文数据为SOAP 格式的XML
由于 BasicHttpBinding接收不了 OPTIONS 报文(空报文直接报错,进不了BasicHttpBinding EndPoint),所以BasicHttpBinding不能跨域。
C# 使用HttpWebRequest通过BasicHttpBinding调用 WCF 方法例子:
public static string CallWCFBasicHttpBinding( string baseAddress, //BasicHttpBinding address string soapAction, //调用方法的SOAP 声明 string metodName, //方法名称(生成XML报文用) string paramName, //方法参数名称(生成XML报文用,格式中使用一个参数) string paramValue //参数值 ) { string _url = baseAddress; string _sendMsg = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<s:Body><{0} xmlns=\"http://tempuri.org/\"><{1}>{2}</{1}></{0}></s:Body>" + "</s:Envelope>"; _sendXml = string.Format(_sendXml, metodName, paramName, paramValue); HttpWebRequest _webRequest = System.Net.WebRequest.Create(_url) as HttpWebRequest; _webRequest.Method = "POST"; _webRequest.ContentLength = _sendXml.Length; _webRequest.ContentType = "text/xml; charset=utf-8";//这里必需是text/xml _webRequest.Headers["SOAPAction"] = soapAction;// "http://tempuri.org/IService1/方法名称"; Stream _smWebReq = null; try { _smWebReq = _webRequest.GetRequestStream(); } catch (System.Exception er) { return er.Message; } StreamWriter _requestWriter = null; try { _requestWriter = new StreamWriter(_smWebReq); _requestWriter.Write(_sendXml); } catch (System.Exception er) { return er.Message; } finally { if (null != _requestWriter) { _requestWriter.Close(); _requestWriter = null; } if (null != _smWebReq) { _smWebReq.Close(); _smWebReq = null; } } StreamReader _respReader = null; Stream _smWebResp = null; string _retString = ""; try { _smWebResp = _webRequest.GetResponse().GetResponseStream(); _respReader = new StreamReader(_smWebResp); _retString = _respReader.ReadToEnd(); } catch (System.Exception er) { return er.Message; } finally { if (null != _respReader) { _respReader.Close(); _respReader = null; } if (null != _smWebResp) { _smWebResp.Close(); _webRequest = null; } } return _retString;//返回XML格式信息 }
如果C# 使用HttpWebRequest通过WebHttpBinding调用 WCF 方法,与浏览器调用相同,通过方法路径调用,
报文Content-Type 需设置为 "application/json",不需要SOAPAction报头
参考
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS