Silverlight 会话管理、错误处理和 WCF 服务的单元测试






4.25/5 (3投票s)
Silverlight 会话管理、错误处理和 WCF 服务单元测试
要求
我需要满足以下要求
-
会话管理。
在第二次 Web 服务调用中,我想从第一次调用中获取一些信息。 -
模拟用户
如果我的服务需要以我的用户身份工作,我不希望输入我的凭据。 -
错误处理
发生错误后看到一个空白的网站并不是很酷。 -
WCF 服务的单元测试
对以上内容进行测试会很好。
让我们开始吧。我将分别实现这些主题,并希望在本文结束前获得一个可行的解决方案。您可以下载随附的 ZIP 文件,其中包含上述要求的解决方案。
引言
我只是在 Visual Studio 2008 中开始了一个新的 Silverlight 解决方案。我从原始版本中唯一更改的是为内置的 Web 服务器分配一个静态端口。这使得 WCF 的生活更加轻松。然后向 Web 项目中添加一个新的 Silverlight 启用的 WCF 服务,创建一些操作契约,并向 Silverlight 项目中添加一个服务引用。
Using the Code
1. 会话管理(使用 aspNetCompatibilityEnabled)
为此,有两种可能性
- 自己动手(编写类似会话代理服务的程序)
- 使用 ASP 会话管理
我想有很多理由不使用 aspNetCompatibility
(最明显的原因可能是性能),但其简单性是我选择它的原因。我必须说我没有发明这个,我只是从 Silverlight 论坛的 SpoonStomper 的一篇文章中复制过来的。
我想达到以下目标。我在 Silverlight 应用程序中按下一个按钮,该按钮将一个字符串发送到我的服务。然后我想按下另一个按钮并再次取回这个字符串(听起来真的很简单)。
要启用 ASP 会话管理,您首先必须编辑 web.config。
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
//this should be default since Silverlight 3
在您的服务类中,您必须将 RequirementsMode
从 Allowed
更改为 Required
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Required)]
public class MyCoolService{
通过此修改,您就可以开始了。现在您可以使用 HttpContext.Current.Session
来处理会话相关内容。
[OperationContract]
public void ServiceCallOne(string message)
{
HttpContext.Current.Session["Test"] = message;
}
[OperationContract]
public string ServiceCallTwo()
{
return (string)HttpContext.Current.Session["Test"];
}
2. 模拟用户
用户模拟是建立在之前主题的基础上的。由于我们由 ASP.NET 控制,我们可以使用 HttpContext
来模拟。在这里您可以找到来自 Microsoft 的描述
[OperationContract]
public string WhoAmIAfterImpersonate()
{
((WindowsIdentity)HttpContext.Current.User.Identity).Impersonate();
return HttpContext.Current.User.Identity.Name;
}
模拟后,服务内容将以调用它的用户身份执行。
顺便说一句,据我所知,这需要 NTLM 身份验证。
3. 错误处理
由于无法在 Silverlight 应用程序中捕获来自 WCF 服务的常规异常,因此还有另一种方法可以实现。
我只是用一个简短的故事来解释它,并给出示例。您可以在这里找到来自 Microsoft 的完整故事。
首先:在您的 Web 项目中创建一个 FaultBehavior 类
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Web;
namespace SilverlightWCF.Web
{
public class MyFaultBehavior : BehaviorExtensionElement, IEndpointBehavior
{
public void ApplyDispatchBehavior(ServiceEndpoint endpoint,
EndpointDispatcher endpointDispatcher)
{
SilverlightFaultMessageInspector inspector =
new SilverlightFaultMessageInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
}
public class SilverlightFaultMessageInspector : IDispatchMessageInspector
{
public void BeforeSendReply(ref Message reply, object correlationState)
{
if (reply.IsFault)
{
HttpResponseMessageProperty property =
new HttpResponseMessageProperty();
// Here the response code is changed to 200.
property.StatusCode = System.Net.HttpStatusCode.OK;
reply.Properties[HttpResponseMessageProperty.Name] = property;
}
}
public object AfterReceiveRequest(ref Message request,
IClientChannel channel, InstanceContext instanceContext)
{
// Do nothing to the incoming message.
return null;
}
}
// The following methods are stubs and not relevant.
public void AddBindingParameters(ServiceEndpoint endpoint,
BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint,
ClientRuntime clientRuntime)
{
}
public void Validate(ServiceEndpoint endpoint)
{
}
public override System.Type BehaviorType
{
get { return typeof(MyFaultBehavior); }
}
protected override object CreateBehavior()
{
return new MyFaultBehavior();
}
}
}
第二:编辑 web.config
<system.serviceModel>
<!--Add a behavior extension within the service model-->
<extensions>
<behaviorExtensions>
<add name="myFault" type="SilverlightWCF.Web.MyFaultBehavior,
SilverlightWCF.Web, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
...
<behaviors>
<!--Add a endpointBehavior below the behaviors-->
<endpointBehaviors>
<behavior name="myFaultBehavior">
<myFault/>
</behavior>
</endpointBehaviors>
...
<!--Set the behaviorConfiguration of the endpoint-->
<endpoint address="" binding="customBinding" bindingConfiguration="customBinding0"
contract="SilverlightWCF.Web.MyCoolService" behaviorConfiguration="myFaultBehavior" />
...
<!--For debugging, it might be cool to have some more error information.
to get this, set includeExceptionDetailInFaults to true-->
<serviceDebug includeExceptionDetailInFaults="true" />
第三:创建一个抛出异常的操作契约。
确保不要抛出常规异常,而是 FaultException
[OperationContract]
public void ThrowException()
{
throw new FaultException("this is my Exception");
}
第四:在您的 Silverlight 应用程序中处理错误
private void btnThrowException_Click(object sender, RoutedEventArgs e)
{
serviceClient.ThrowExceptionCompleted +=
new EventHandler<System.ComponentModel.AsyncCompletedEventArgs>
(serviceClient_ThrowExceptionCompleted);
serviceClient.ThrowExceptionAsync();
}
void serviceClient_ThrowExceptionCompleted
(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
if (e.Error == null)
{
// In case of success
}
else if (e.Error is FaultException)
{
FaultException fault = e.Error as FaultException;
txtThrowException.Text = e.Error.Message;
}
}
4. 单元测试 WCF 服务(使用会话)
在这里我不得不说,对单个 WCF 方法进行单元测试并不是那么困难(就像你对一个库进行单元测试一样)。
但是如果您使用 ASP.NET 会话来存储信息,客户端(浏览器)会使用 cookie 来处理它。如果要对需要会话值的 方法进行单元测试,您必须手动处理 cookie。在这里您可以找到一个关于在 ASMX/WCF 服务中处理 cookie 的非常好的解释。
首先,您必须创建一个单元测试项目。我必须手动完成此操作,因为在方法上创建单元测试并没有创建新项目。
然后使用“添加服务引用”将 WEB/WCF 项目的引用添加到您的单元测试项目中。
现在实现测试方法
[TestMethod]
public void TestMethod1()
{
string _sharedCookie = string.Empty;
//Creates connection to the WCF service
//It's done by code, not via config file
//The config file would be overwritten all the time
EndpointAddress objEndpoint = null;
CustomBinding cb = new CustomBinding();
//binding depends how your web server is set up
BinaryMessageEncodingBindingElement bmebe =
new BinaryMessageEncodingBindingElement();
//TextMessageEncodingBindingElement tebe =
new TextMessageEncodingBindingElement(MessageVersion.Default, Encoding.UTF8);
HttpTransportBindingElement htbe = new HttpTransportBindingElement();
cb.Elements.Add(bmebe);
cb.Elements.Add(htbe);
objEndpoint = new EndpointAddress("https://:11111/MyCoolService.svc");
MyCoolServiceClient _serviceClient = new MyCoolServiceClient(cb, objEndpoint);
//howto manage cookies:
//http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/
using (new OperationContextScope(_serviceClient.InnerChannel))
{
_serviceClient.ServiceCallOne("my first message to the server");
// Extract the cookie embedded in the received web service response
// and stores it locally
HttpResponseMessageProperty response = (HttpResponseMessageProperty)
OperationContext.Current.IncomingMessageProperties
[HttpResponseMessageProperty.Name];
_sharedCookie = response.Headers["Set-Cookie"];
}
using (new OperationContextScope(_serviceClient.InnerChannel))
{
//this sets the cookie for the next service request
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers["Cookie"] = _sharedCookie;
OperationContext.Current.OutgoingMessageProperties
[HttpRequestMessageProperty.Name] = request;
string returnValue = _serviceClient.ServiceCallTwo();
Assert.AreEqual("my first message to the server", returnValue);
}
}
正如您所看到的,我分别调用了两种方法,并且我使用第二种方法返回了在第一次方法调用中设置的会话值。
关注点
- Silverlight 4 与托管可扩展性框架
- Silverlight 4 绑定
- 在 Silverlight 4 中动态下载 XAP
历史
- 2010 年 9 月 15 日:初始帖子