提问人:user1069516 提问时间:2/12/2020 更新时间:2/18/2020 访问量:196
为 wcf webhttpbinding 提供例外
Provide exceptions for wcf webhttpbinding
问:
我必须将 wcf Web 服务的绑定从 tcpbinding 更改为具有基本身份验证和 ssl 的 webhttpbinding。
Web 服务在控制台应用程序和 Windows 服务中自托管,用于生产版本。某些本地服务具有命名管道绑定,只是当一个服务调用另一个服务时。
一切都很完美,但全局错误管理器(实现 IErrorHandler 接口的类)不能正常工作
某些 DAL 或业务方法会引发带有自定义消息的异常,并且此消息已正确提供给客户端(一段时间的单元测试)。但是由于我更改了绑定,因此在单元测试中捕获的异常始终是 500 错误,内部服务器错误和自定义消息不在异常对象中。
服务器代码 :
// Création de l'URI
var baseAddress = new Uri($"https://localhost/blablabla/{typeof(TBusiness).Name}");
// Création du Host avec le type de la classe Business
var host = new ServiceHost(typeof(TBusiness), baseAddress);
// Liaison WebHttpBinding sécurité transport
var binding = new WebHttpBinding
{
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647,
Security = new WebHttpSecurity
{
Mode = WebHttpSecurityMode.Transport
},
};
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
// Permet de renvoyer du xml et du json
var webBehavior = new WebHttpBehavior
{
AutomaticFormatSelectionEnabled = true
};
var ep = host.AddServiceEndpoint(typeof(TContracts), binding, "");
ep.Behaviors.Add(webBehavior);
var sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
// Activation https
var smb = new ServiceMetadataBehavior
{
HttpGetEnabled = false,
HttpsGetEnabled = true,
};
host.Description.Behaviors.Add(smb);
// Ajout de l'authentification
var customAuthenticationBehavior = new ServiceCredentials();
customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new SessionAuthentication();
host.Description.Behaviors.Add(customAuthenticationBehavior);
// Démarrage du host
host.Open();
抛出异常的业务方法:
public TOUser GetUserByLogin(string login)
{
using (var service = new ServiceProviderNamedPipe<IBFSessionManager, BSSessionManager>())
{
// Récupération de la DALUsers
var dal = service.Channel.GetDALUsers(OperationContext.Current.ServiceSecurityContext.PrimaryIdentity.Name);
var user = dal.GetUserByLogin(login);
if (user == null) throw new FaultException(Errors.DALUsers_Err001);
return BMToolsEntitiesToTO.UserToTOUser(user);
}
}
错误全局管理器:
public class GlobalErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
// Empèche la propagation de l'erreur
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
var msg = error.Message;
// Création de l'exception de retour
var newEx = new FaultException(msg);
var msgFault = newEx.CreateMessageFault();
fault = Message.CreateMessage(version, msgFault, newEx.Action);
}
}
单元测试:
public void GetUserByLoginWithUnknownLoginTest()
{
TOUser user = null;
using (var service = new ServiceProviderHTTP<IBFUsers, BSUsers>(_user))
{
try
{
user = service.Channel.GetUserByLogin("1234");
}
catch (Exception e)
{
// e.message always provide "Internal server error instead of custom message (Errors.DALUsers_Err001)
Assert.AreEqual(Errors.DALUsers_Err001, e.Message);
}
Assert.IsNull(user);
}
}
自从我更改绑定以来,所有捕获异常的单元测试都失败了。
谢谢你的帮助。
答:
我怀疑您的服务是否正常运行。是否由于传输层安全性(使用 HTTPS)而将证书绑定到默认端口 443?请使用以下语句将证书绑定到 443 端口。
netsh http add sslcert ipport=0.0.0.0:443 certhash=c20ed305ea705cc4e36b317af6ce35dc03cfb83d appid={c9670020-5288-47ea-70b3-5a13da258012}
请参阅此链接。
https://learn.microsoft.com/en-us/windows/win32/http/add-sslcert
这是一个相关的讨论。
如何禁用对 Windows 服务中托管的 WCF 的 HTTPS 调用的凭据输入此外,我没有看到您将 应用于自承载服务
。这通常由服务终结点行为实现。GlobalErrorHandler
ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IService),new WebHttpBinding(), "");
MyEndpointBehavior bhv = new MyEndpointBehavior();
se.EndpointBehaviors.Add(bhv);
我写了一个例子,希望它对你有用。
class Program
{
static void Main(string[] args)
{
//I have already bound a certificate to the 21011 port.
var baseAddress = new Uri($"https://localhost:21011");
var host = new ServiceHost(typeof(MyService), baseAddress);
var binding = new WebHttpBinding
{
MaxBufferSize = 2147483647,
MaxReceivedMessageSize = 2147483647,
Security = new WebHttpSecurity
{
Mode = WebHttpSecurityMode.Transport
},
};
//basic authentication use windows login account located on the server-side instead of the below configuration(UserNamePasswordValidationMode.Custom)
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
// Permet de renvoyer du xml et du json
var webBehavior = new WebHttpBehavior
{
AutomaticFormatSelectionEnabled=true
};
var ep = host.AddServiceEndpoint(typeof(IService), binding, "");
ep.Behaviors.Add(webBehavior);
MyEndpointBehavior bhv = new MyEndpointBehavior();
ep.EndpointBehaviors.Add(bhv);
var sdb = host.Description.Behaviors.Find<ServiceDebugBehavior>();
sdb.HttpHelpPageEnabled = false;
// Activation https
var smb = new ServiceMetadataBehavior
{
HttpGetEnabled = true,
HttpsGetEnabled = true,
};
host.Description.Behaviors.Add(smb);
// Ajout de l'authentification
//var customAuthenticationBehavior = new ServiceCredentials();
//customAuthenticationBehavior.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
//customAuthenticationBehavior.UserNameAuthentication.CustomUserNamePasswordValidator = new SessionAuthentication();
//host.Description.Behaviors.Add(customAuthenticationBehavior);
// Démarrage du host
host.Open();
Console.WriteLine("service is running....");
Console.ReadLine();
Console.WriteLine("Closing.....");
host.Close();
}
}
[ServiceContract(ConfigurationName = "isv")]
public interface IService
{
[OperationContract]
[WebGet]
string Delete(int value);
}
[ServiceBehavior(ConfigurationName = "sv")]
public class MyService : IService
{
public string Delete(int value)
{
if (value <= 0)
{
throw new ArgumentException("Parameter should be greater than 0");
}
return "Hello";
}
}
public class MyError
{
public string Details { get; set; }
public string Error { get; set; }
}
public class MyCustomErrorHandler : IErrorHandler
{
public bool HandleError(Exception error)
{
return true;
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault)
{
MyError myerror = new MyError()
{
Details = error.Message,
Error = "An error occured"
};
fault = Message.CreateMessage(version, "messsagefault", myerror);
}
}
public class MyEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
return;
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
return;
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
MyCustomErrorHandler myCustomErrorHandler = new MyCustomErrorHandler();
endpointDispatcher.ChannelDispatcher.ErrorHandlers.Add(myCustomErrorHandler);
}
public void Validate(ServiceEndpoint endpoint)
{
return;
}
}
评论
经过几次搜索,我发现很多人都有同样的问题。
这是我的解决方案:
在服务器端,始终使用正确的 HTTP 状态代码抛出如下所示的 WebFaultException:
throw new WebFaultException<string>(myStringMessage, HttpStatusCode.NotFound);
在客户端(仅适用于单元测试或 MVC 项目),强制转换异常以在 Response 对象上调用 GetResponseStream 以获取自定义消息:
var err = (WebException)e;
using (Stream respStream = err.Response.GetResponseStream())
{
using (var reader = new StreamReader(respStream))
{
var serializer = new XmlSerializer(typeof(string));
var response = reader.ReadToEnd();
return response.Substring(response.IndexOf('>') + 1).Replace("</string>", "");
}
}
在 IErrorHandler 的 ProvideFault 方法中,我只是添加代码以在文件中写入错误,而不是使用 Message.CreateMessage 方法创建消息。
它可以正常工作,但在 ProvideFault 之后生成一个 EndPointNotFoundException,在其他一些帖子中,我看到可能会抛出 ProtocolException。
谢谢你的发言。
评论