您的位置:首页 > 其它

[WCF REST] 解决资源并发修改的一个有效的手段:条件更新(Conditional Update)

2012-02-14 11:25 274 查看
[code] public class IncomingWebRequestContext

{

//其他成员

public void CheckConditionalUpdate(Guid entityTag);

public void CheckConditionalUpdate(int entityTag);

public void CheckConditionalUpdate(long entityTag);

public void CheckConditionalUpdate(string entityTag);

}

[/code]
[/code]
实现在CheckConditionalUpdate方法中的条件更新检测具有这样的逻辑:对于HTTP方法为PUT的请求,如果If-Match报头值不为“*”,则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常;对于HTTP方法为POST和DELETE的请求来说,如果If-Match报头值为“*”或者包含指定的entityTag则验证通过,否则同样则直接抛出HTTP状态为PreconditionFailed的WebFaultException异常。

表示出栈请求上下文的OutgoingWebRequestContext类型具有如下一个IfMatch属性,客户端可以通过该属性对请求消息的If-Match报头进行设置。

[code]
[code] public class OutgoingWebRequestContext

{

//其他成员

 public string IfMatch{ get; set;}

}

[/code]
[/code]

三、实例演示:通过条件更新解决对相同资源的并发修改

我们同样通过对EmployeesService进行相应的改造来模拟如何通过添加更新实现对相同资源的并发操作问题,这次我们修改的是用于获取指定ID员工信息的Get操作和用于修改员工信息的Update操作。Get操作在返回与指定员工ID匹配的Employee对象之前我们将该对象的哈希码作为了回复消息的ETag报头(Employee类型重写了GetHashCode方法)。

[code]
[code] public class EmployeesService : IEmployees

{

//其他成员

public Employee Get(string id)

{

Employee employee = employees.FirstOrDefault(e => e.Id == id);

if (null == employee)

{

throw new WebFaultException(HttpStatusCode.NotFound);

}

WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode());

return employee;

}

public void Update(Employee employee)

{

var existing = employees.FirstOrDefault(e => e.Id == employee.Id);

if (null == existing)

{

throw new WebFaultException(HttpStatusCode.NotFound);

}

//模拟并发修改

existing.Name += Guid.NewGuid().ToString();


 WebOperationContext.Current.IncomingRequest.CheckConditionalUpdate(existing.GetHashCode());

 employees.Remove(existing);

 employees.Add(employee);

WebOperationContext.Current.OutgoingResponse.SetETag(employee.GetHashCode());


}

}

[/code]
[/code]
Update方法中我们通过手工修改相应员工的Name属性的方式来模拟针对相同员工信息的并发修改。在真正实施修改之前调用当前IncomingWebRequestContext的CheckConditionalUpdate方法进行条件更新检测,而作为参数传入的ETag值为代表目前员工的Employee对象的哈希码。方法的最后我们对回复消息的ETag报头作了更新。

我们通过手工创建HTTP请求的方式对上述的两个服务操作进行调用。如下面的代码片断所示,我们首先通过创建的HttpWebRequest对象调用Get操作获得ID为001的员工信息并将其打印出来。然后创建调用Update操作的HttpWebRequest,并对HTTP方法(POST)和内容类型(application/xml)进行了相应的设置。我们之前针对员工获取请求得到ETag报头和员工数据作为本次请求的If-Match报头和主体。如果调用GetResponse方法抛出WebException异常,并且其回复状态为PreconditionFailed,则表明试图修改的员工信息已被另一个用户修改过了,所以我么打印“服务端数据已发生变化”字样。

[code]
[code] Uri address = new Uri("http://127.0.0.1:3721/employees/001");

var request = (HttpWebRequest)HttpWebRequest.Create(address);

request.Method = "GET";

var response = (HttpWebResponse)request.GetResponse();

string employee;

using (StreamReader reader = new StreamReader(response.GetResponseStream(), Encoding.UTF8))

{

 employee = reader.ReadToEnd();

 Console.WriteLine("获取员工信息:");

 Console.WriteLine(employee + "\n");

}

try

{

 address = new Uri("http://127.0.0.1:3721/employees/");

 request = (HttpWebRequest)HttpWebRequest.Create(address);

 request.Method = "POST";

 request.ContentType = "application/xml";

 byte[] buffer = Encoding.UTF8.GetBytes(employee);

 request.GetRequestStream().Write(Encoding.UTF8.GetBytes(employee), 0, buffer.Length);

 request.Headers.Add(HttpRequestHeader.IfMatch, response.Headers[HttpResponseHeader.ETag]);

 Console.WriteLine("修改员工信息:");

 request.GetResponse();

}

catch (WebException ex)

{

 response = ex.Response as HttpWebResponse;

 if (null == response)

{

 throw;

}

 if (response.StatusCode == HttpStatusCode.PreconditionFailed)

{

 Console.WriteLine("服务端数据已发生变化");

}

 else

{

 throw;

}

}

[/code]
[/code]
在服务成功寄宿的情况下调用这段程序会在控制台上输出如下的结果。由于并发错误的发生,员工信息其实并没有被真正修改。

[code]
[code] 获取员工信息:

<Employee xmlns="http://www.artech.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Department>开发部</Department><Grade>G7</Grade><Id>001</Id><Name>张三</Name></Employee>


修改员工信息:

服务端数据已发生变化

[/code]
[/code]

分类: [01] 技术剖析
绿色通道:好文要顶已关注收藏该文与我联系

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐