您的位置:首页 > 其它

WCF4.0 –- RESTful WCF Services (2) (实现增,删,改,查)

2013-07-19 17:19 148 查看
RESTful服务就是为了实现一个易于整合的系统,可以跨平台跨语言的调用(如下图),【上篇】介绍了如何用WCF构建一个RESTful的服务。


本篇进一步通过一个实例记录如何实施一个具体的RESTful WCF服务以及客户端调用服务进行增,删,改,查。

WCF 4.0 其新功能之一就是 WCF 更容易以 REST API 来呈现,在 WCF 3.5 中的 WebGetAttribute 与 WebInvokeAttribute 中的 UriTemplate 参数原本不支持 REST URL 格式,为了 REST 功能,微软还特意发布了 WCF REST Starter Kit 组件,让开发人员可以利用 WCF 3.5 开发真正 REST-based 的应用程序,由 URL 对应到指定的 Service Contract 中的 Operation Contract,在
WCF 4.0 中,WCF 的核心已经融入了 REST Starter Kit 中的 URL 引擎,在 WebGetAttribute 与 WebInvokeAttribute 已经可以支持 REST 的功能,Windows Azure 许多服务的 REST API 就是利用 WCF 来开发的。

主要涉及以下内容:

1. 如何通过 JSON 数据进行交互;

2. 如何进行服务端的错误处理,并在客户端区分不同的异常;

3. 如何利用 Microsoft.HttpClient (微软提供的第3方组件);

本次示例的工程:





PS: 上面虽然在一个solution里有两个工程,但是工程之前没有任何引用。完全靠Http消息传递。

1. 创建服务端工程:

通过VS2010的Extension Manager,可以下载一个“WCF REST Service Template”。通过这个我们可以快速创建一个WCF REST服务。它是一个创建在Web Application工程里的服务。和前一篇介绍的WCF服务不同的是在Globel.asax中的Application_Start事件中注册服务。并且注册的"TaskService"自动成为服务的基地址,即
http://<machine_name>:<port>/TaskService/

[c-sharp]
view plaincopyprint?

public class Global : HttpApplication

{
void Application_Start(object sender, EventArgs e)

{
RegisterRoutes();
}

private void RegisterRoutes()

{
RouteTable.Routes.Add(new ServiceRoute("TaskService",

new WebServiceHostFactory(),
typeof(TaskService)));
}
}

[c-sharp]
view plaincopyprint?

namespace WcfRestService2.Model

{
[DataContract]
public class PocoTask

{
[DataMember]
public virtual
int ID { get;
set; }
[DataMember]
public virtual
string Title { get;
set; }
[DataMember]
public virtual
string Detail { get;
set; }
[DataMember]
public virtual
int State { get;
set; }
[DataMember]
public virtual DateTime UpdatedDate {
get; set; }

}
}

namespace WcfRestService2.Model
{
[DataContract]
public class PocoTask
{
[DataMember]
public virtual int ID { get; set; }
[DataMember]
public virtual string Title { get; set; }
[DataMember]
public virtual string Detail { get; set; }
[DataMember]
public virtual int State { get; set; }
[DataMember]
public virtual DateTime UpdatedDate { get; set; }
}
}


REST中很好的利用了HTTP的GET/POST/PUT/DELETE方式,绑定到服务的不同方法上。比如GET方法不用客户端提供太多数据,正适合查询只提供主键或者查询字段的场景。POST则适合数据的插入,PUT则应用在数据更新,DELETE则直接用在数据删除上。当然通过URI的区别,也可以全部用POST或者PUT,只是语义符合调用场景的话,是的服务使用更易于理解和习惯。

服务端实现片段:

(1) 查询(Http/GET),这里定义了访问的UriTemplate,完整访问地址就是"基地址+UriTemplate",比如:

http://localhost:port/TaskService/Tasks/State/{state} 另外ResponseFormat设定为Json格式。

也可以在配置文件中,修改 <standardEndpoint> 节点的 defaultOutgoingResponseFormat 属性控制Response的格式。

[c-sharp]
view plaincopyprint?

[WebGet(UriTemplate = "Tasks/State/{state}",

ResponseFormat = WebMessageFormat.Json)]
public List<PocoTask> GetTasksByState(string state)

{
using (var db =
new TasksEntities())
{
int s = Int32.Parse(state);

var query = db.Task.Where(t => t.State == s || -1 == s);

return GetPocoData(query);

}
}

[c-sharp]
view plaincopyprint?

[WebInvoke(UriTemplate = "Tasks/Add", Method =
"POST",
RequestFormat=WebMessageFormat.Json)]
public void Create(PocoTask pocoTask)

{
var ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;

try
{
using (var db =
new TasksEntities())
{
var task = new Task();

CopyValue(pocoTask, task);
task.UpdatedDate = DateTime.Now;
db.AddToTask(task);
db.SaveChanges();
}
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Created;

}
catch (Exception ex)

{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;

ctx.OutgoingResponse.StatusDescription = ex.Message;
}
}

[WebInvoke(UriTemplate = "Tasks/Add", Method = "POST",
RequestFormat=WebMessageFormat.Json)]
public void Create(PocoTask pocoTask)
{
var ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
try
{
using (var db = new TasksEntities())
{
var task = new Task();
CopyValue(pocoTask, task);
task.UpdatedDate = DateTime.Now;
db.AddToTask(task);
db.SaveChanges();
}
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Created;
}
catch (Exception ex)
{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;
ctx.OutgoingResponse.StatusDescription = ex.Message;
}
}


(3) 更新(Http/PUT),先通过id查出Entity,再将客户端的数据更新到Entity上。

[c-sharp]
view plaincopyprint?

[WebInvoke(UriTemplate = "Tasks/{id}", Method =
"PUT",
RequestFormat=WebMessageFormat.Json)]
public void Update(string id, PocoTask pocoTask)

{
var ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;

try
{
using (var db =
new TasksEntities())
{
var nId = Convert.ToInt32(id);
var target = db.Task.SingleOrDefault(t => t.ID == nId);

target.Title = pocoTask.Title;
target.Detail = pocoTask.Title;
target.State = pocoTask.State;
target.UpdatedDate = DateTime.Now;
db.SaveChanges();
}
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Accepted;

}
catch (Exception ex)

{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;

ctx.OutgoingResponse.StatusDescription = ex.Message;
}
}

[c-sharp]
view plaincopyprint?

[WebInvoke(UriTemplate = "Tasks/{id}", Method =
"DELETE")]
public void Delete(string id)

{
var ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;

try
{
using (var db = new TasksEntities())

{
var nId = Convert.ToInt32(id);
var task = db.Task.SingleOrDefault(t => t.ID == nId);

db.Task.DeleteObject(task);
db.SaveChanges();
}
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Accepted;

}
catch (Exception ex)

{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;

ctx.OutgoingResponse.StatusDescription = ex.Message;
}
}

[WebInvoke(UriTemplate = "Tasks/{id}", Method = "DELETE")]
public void Delete(string id)
{
var ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
try
{
using (var db = new TasksEntities())
{
var nId = Convert.ToInt32(id);
var task = db.Task.SingleOrDefault(t => t.ID == nId);
db.Task.DeleteObject(task);
db.SaveChanges();
}
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Accepted;
}
catch (Exception ex)
{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.ExpectationFailed;
ctx.OutgoingResponse.StatusDescription = ex.Message;
}
}


服务端的异常处理中通过 OutgoingResponse.StatusCode 返回不同的Code,这样客户端通过这些Code就知道服务端出现了什么错误。但是目前我还不知道OutgoingResponse.StatusDescription如何在客户端获得。如果可以的话,我们就可以知道错误的详细内容。

本示例中:

a) 查询成功 —— System.Net.HttpStatusCode.OK (默认)

b) 创建成功 —— System.Net.HttpStatusCode.Created

c) 更新成功 —— System.Net.HttpStatusCode.Accepted

d) 删除成功 —— System.Net.HttpStatusCode.Accepted

System.Net.HttpStatusCode的枚举可以参看 MSDN: http://msdn.microsoft.com/en-us/library/system.net.httpstatuscode.aspx。
3. 客户端实现:



因为REST 是基于HTTP的, 所以对于 REST 的客户端的开发者,无法像传统的 WebService或者其他的WCF服务通过引用wsdl,享受“奢侈”的代码生成,而使用强类型的本地代理调用服务。 开发者只能通过 Http Request 的组装, 但正因为这种直接的HttpRequest组装,而使得客户端真正是语言无关的。这里不得不提一下 Microsoft.Http.dll 和 Microsoft.Http.Extensions.dll,它们是微软提供的REST客户端包。可以更加方便地操作
HttpRequest/Response,你可以在这里下到: http://aspnet.codeplex.com/releases/view/24644
客户端片段:

(1) 查询(HTTP/GET), 使用 HttpClient.Get 方法,返回的是HttpResponseMessage,HttpResponseMessage.Content 返回的是Json数据。再通过 Json.NET 第3方组件进行反序列化。另外,为了是在客户端里能通过实例化的类来操作数据,所以在客户端单独再定义了 Task 类。(因为客户端无法通过wsdl生成代理)

[c-sharp]
view plaincopyprint?

// Get Data by state

var client = new HttpClient();

var strUrl = "http://localhost:1180/TaskService/Tasks/State/{0}";

strUrl = string.Format(strUrl, comboBox1.SelectedValue);

var response = client.Get(strUrl);
response.EnsureStatusIsSuccessful();
var json = response.Content.ReadAsString();
var data = JsonConvert.DeserializeObject<List<Task>>(json);

[c-sharp]
view plaincopyprint?

// Add Task
var task = GetTask();
var client = new HttpClient();

var strUrl = "http://localhost:1180/TaskService/Tasks/Add";

var response = client.Post(strUrl, GetContent(task));

response.EnsureStatusIsSuccessful();

// Add Task
var task = GetTask();
var client = new HttpClient();
var strUrl = "http://localhost:1180/TaskService/Tasks/Add";
var response = client.Post(strUrl, GetContent(task));
response.EnsureStatusIsSuccessful();


(3) 更新(HTTP/PUT)

[c-sharp]
view plaincopyprint?

// Update Task
var task = GetTask();
var client = new HttpClient();

var strUrl = "http://localhost:1180/TaskService/Tasks/{0}";

strUrl = string.Format(strUrl, task.ID);

var response = client.Put(strUrl, GetContent(task));
response.EnsureStatusIsSuccessful();

[c-sharp]
view plaincopyprint?

// Delete Task
var task = GetTask();
var client = new HttpClient();

var strUrl = "http://localhost:1180/TaskService/Tasks/{0}";

strUrl = string.Format(strUrl, task.ID);

var response = client.Delete(strUrl);
response.EnsureStatusIsSuccessful();

// Delete Task
var task = GetTask();
var client = new HttpClient();
var strUrl = "http://localhost:1180/TaskService/Tasks/{0}";
strUrl = string.Format(strUrl, task.ID);
var response = client.Delete(strUrl);
response.EnsureStatusIsSuccessful();


哦,还漏了个 GetContent(Task task) 方法:

[c-sharp]
view plaincopyprint?

private HttpContent GetContent(Task task)

{
var strContent = JsonConvert.SerializeObject(task);

var data = System.Text.Encoding.UTF8.GetBytes(strContent);

return HttpContent.Create(data,
"application/json");
}

private HttpContent GetContent(Task task)
{
var strContent = JsonConvert.SerializeObject(task);
var data = System.Text.Encoding.UTF8.GetBytes(strContent);
return HttpContent.Create(data, "application/json");
}

response.EnsureStatusIsSuccessful 用来检查 Response.StatusCode。

最后,留下些关于REST WCF设计的思考:

(1) 貌似不支持事务?

(2) 类型不能过于复杂?

(3) 元数据重复定义?

总结 —— REST 让你的设计简化,但其实要做到很不简单。。。

【REST WCF系列】

RESTful WCF Services (1) (入门)

RESTful WCF Services (2) (实现增,删,改,查)

RESTful WCF Services (3) (Raw Stream)

RESTful WCF Services (4) (Basic Security)

RESTful WCF Services (实例) (并发同步服务 SyncService)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐