WCF實現RESETFUL架構很容易,說白了,就是使WCF能夠響應HTTP請求並返回所需的資源,如果有人不知道如何實現WCF支持HTTP請求的,可參見我之前的文章《實現jquery.ajax及原生的XMLHttpRequest調用WCF服務的方法》、《實現jquery.ajax及原生的XMLHttpRequest跨域調用WCF服務的方法》,在此就不作重述。
實現WCF支持HTTP請求調用容易,但要實現類似MVC的ACTION及WEB API那樣的靈活,那就得花費點功夫,為什么這樣說呢?因為如果WCF的參數為普通類型(即:值類型),那么調用很容易,也支持HTTP的多種請求方法,比如常見的:GET,POST,例如:
[ServiceContract]
public interface IService1
{
[OperationContract]
[WebInvoke(Method = "*", UriTemplate = "Get/{value}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
string GetData(string value);
}
public class Service1 : IService1
{
public string GetData(string value)
{
return string.Format("You entered: {0}", value);
}
}
我們只需要通過瀏覽器訪問如:http://localhost:14719/Service1.svc/Get/test 注意我加深的部份,需要與該服務方法上約定的UriTemplate相匹配,與MVC的ROUTE URL類似,POST也很簡單,在此就不再說明。
上面的調用成功了,你是否就認為這樣就完了呢?有沒有想過如果WCF的服務方法參數為對象(復合類型),例如:
[OperationContract]
[WebInvoke(Method = "*",RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.WrappedRequest)]
Point GetPoint(Point point);
Point定義:
[DataContract(Namespace="http://www.zuowenjun.cn/")]
public class Point
{
[DataMember]
public int X { get; set; }
[DataMember]
public int Y { get; set; }
[DataMember]
public string Value { get; set; }
}
public Point GetPoint(Point point)
{
if (point == null)
{
throw new ArgumentNullException("point");
}
return point;
}
你是否可以像上面那樣來進行GET或POST請求呢?如果可以,那么該如何調用呢?本人(夢在旅途)是一個遇到了問題,就必需弄明白及必需解決的人,所以我進行了試驗,首先試一下POST方法,示例代碼如下:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="jquery-1.4.4.min.js" type="text/javascript"></script>
</head>
<body>
X:<input id="Text1" type="text" />
Y:<input id="Text2" type="text" />
Value:<input id="Text3" type="text" />
<input id="Button1" type="button" value="button" />
<fieldset>
<legend>結果輸出:</legend>
<div id="resultbox">
</div>
</fieldset>
<script type="text/javascript">
$(function () {
$("#Button1").click(function () {
$.ajax({
url: "Service1.svc/GetPoint",
contentType:"application/json",
type: "post",
datatype: "json",
data: JSON.stringify({ 'point': { 'X': $("#Text1").val(), 'Y': $("#Text2").val(), 'Value': $("#Text3").val()} }),
success: function (r,s,x) {
$("#resultbox").append("<p>" + JSON.stringify(r) + "</p>");
}
});
});
});
</script>
</body>
</html>
經調用成功,結果如下圖示:

也可以參見DUDU的文章:jQuery調用WCF服務時如何傳遞對象參數
這里順便提醒一下,在POST請求WCF服務方法參數為對象時,注意AJAX請求的Data必需是JSON字符串,不能是JSON對象,如果不明白兩者的關系,我這里簡要說明一下,
{X:1,Y:2,Value:'test'} 、{'X':1,'Y':2,'Value':'test'}--這些不論KEY加不加引號,都是JSON對象,可以通過JSON.stringify方法轉換成JSON字符串
"{X:1,Y:2,Value:'test'}"、"{'X':1,'Y':2,'Value':'test'}"--這些就是JSON字符串,可以通過$.parseJSON方法轉換成JSON對象
然后就試一下GET方法,在此我就有點難了,該如何進行GET請求呢?參數該如何對應呢?我將AJAX的TYPE改為GET,然后調用,最后結果返回如下圖:

一看就知道,調用失敗,無法為WCF的服務方法參數point賦值,也就是WCF無法正確將請求的參數解析成POINT類型對象實例,我嘗試着直接在瀏覽器中訪問:
http://localhost:14719/Service1.svc/GetPoint?point={x=1&y=2&value=test},結果仍是報同樣的錯誤,這個問題卻是困擾了我幾天,我也曾請教過DUDU,QQ群,博問:http://q.cnblogs.com/q/77775/,都沒有人能回答我,有人說復合類型不支持GET,用不了GET就不要用之類的話,我就想,我的服務契約上定義的是Method = "*",而這里GET卻不行,難道不能用*,我不放棄,經過多方查證,這篇文章給了我明確的思路:http://www.cnblogs.com/huangxincheng/p/4621971.html,可以通過自定義MessageFormatter來實現自己想要的解析,好了廢話不多說,直接上代碼。
自定義PointMessageFormatter:
public class PointMessageFormatter:IDispatchMessageFormatter
{
private IDispatchMessageFormatter innerFormatter;
public PointMessageFormatter(IDispatchMessageFormatter innerFormatter)
{
this.innerFormatter = innerFormatter;
}
public void DeserializeRequest(System.ServiceModel.Channels.Message message, object[] parameters)
{
innerFormatter.DeserializeRequest(message, parameters);
if (message.Properties["HttpOperationName"].ToString().Equals("GetPoint",StringComparison.OrdinalIgnoreCase) && parameters.Count() > 0 && parameters[0] == null)
{
var request = message.Properties.Values.ElementAt(1) as HttpRequestMessageProperty;
if (request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase))
{
parameters[0] = DeserializeFromQueryString(request.QueryString);
}
}
}
private object DeserializeFromQueryString(string queryString)
{
if (string.IsNullOrEmpty(queryString)) return null;
var t=typeof(Point);
var point = new Point();
foreach (var p in t.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Regex regx = new Regex(string.Format(@"(?<={0}=)(.*?)(?=\&|$)", p.Name), RegexOptions.IgnoreCase);
string value = regx.Match(queryString).Groups[1].Value;
try
{
var pValue = Convert.ChangeType(value,p.PropertyType);
p.SetValue(point, pValue, null);
}
catch
{ }
}
return point;
}
public System.ServiceModel.Channels.Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
return innerFormatter.SerializeReply(messageVersion, parameters, result);
}
}
自定義MyOperationBehavior:
public class MyOperationBehavior : Attribute, IOperationBehavior
{
public int MaxLength { get; set; }
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{
}
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new PointMessageFormatter(dispatchOperation.Formatter);
}
public void Validate(OperationDescription operationDescription)
{
}
}
然后修改GetPoint服務方法實現:
[MyOperationBehavior]
public Point GetPoint(Point point)
{
if (point == null)
{
throw new ArgumentNullException("point");
}
return point;
}
其實方法實現內容沒有變,就是在其方法上增加了一個特性:MyOperationBehavior,目的是:當請求調用該方法時,會使用我上面定義的PointMessageFormatter,從而實現了自定義解析。
最后我們再試驗通過GET請求,這里我就直接通過瀏覽器訪問:http://localhost:14719/Service1.svc/GetPoint?x=12&y=232&value=test,最后返回的結果如下:

可以看出,調用成功,已經能正常解析到我的參數了,當然我上面的實現可能比較粗糙,也有很多的限制,但我這里只是提供一種思路,一種解決方案,大家可以基於此思路實現更牛B的功能。其實WCF的可擴展的地方非常多,可以參見artech大牛關於WCF的系列文章,我感覺他的文章寫得比較深入,但就是不易懂。這篇文章我也是花了很多精力來思考與解決,希望能幫助到大家,如果覺得不錯,給個推薦吧,謝謝!
