Provide exceptions for wcf webhttpbinding

一曲冷凌霜 提交于 2021-01-29 14:16:27

问题


I have to change a binding for wcf webservices from tcpbinding to webhttpbinding with basic authentication and ssl.

Webservices are self hosted in a console application and in a windows service for production version. Some of local services are with named pipe binding, just if a service call another service.

All works perfectly but not the global error manager (a class that implement IErrorHandler interface)

Some of DAL or business methods throw an exception with a custom message and this message was correctly provide to client (unit test for a while). But since I change binding, exceptions caught in unit test are always a 500 error, internal server error and custom messages are not in exception object.

Server code :

// 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();

Business method that throw exception :

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);
   }
}

Error global manager :

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);
    }
}

Unit test :

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);
    }
}

All unit tests that catch exception failed since I change binding.

Thank you for your help.


回答1:


I doubt whether your service is running correctly. Do you bind the certificate to the default port 443 due to transport layer security (using HTTPS)? Please use the below statement to bind a certificate to the 443 port.

netsh http add sslcert ipport=0.0.0.0:443 certhash=c20ed305ea705cc4e36b317af6ce35dc03cfb83d appid={c9670020-5288-47ea-70b3-5a13da258012}

please refer to this link.
https://docs.microsoft.com/en-us/windows/win32/http/add-sslcert
Here is a relevant discussion.
How to disable credentials input for HTTPS call to my WCF hosted in windows service
Besides, I didn’t see you apply the GlobalErrorHandler to the self-hosted service. This is usually implemented by service endpoint behavior.

ServiceEndpoint se = sh.AddServiceEndpoint(typeof(IService),new WebHttpBinding(), "");
                MyEndpointBehavior bhv = new MyEndpointBehavior();
                se.EndpointBehaviors.Add(bhv);

I wrote an example, wish it is useful to you.

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;
        }
}

Result.

Feel free to let me know if there is anything I can help with.




回答2:


After several search, I saw that a lot of people have same problem.

Here is my solution :

On server side, always throw a WebFaultException like this with correct HTTP Status code :

throw new WebFaultException<string>(myStringMessage, HttpStatusCode.NotFound);

On client side (only for unit tests or MVC project), cast exception to call GetResponseStream on Response object to get custom message :

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>", "");
    }
}

In ProvideFault method from IErrorHandler, I just add code to write errors in a file but not create a message with Message.CreateMessage method.

It works correctly but generate an EndPointNotFoundException after ProvideFault, in some other posts I saw that a ProtocolException could be thrown.

Thank you for your remarks.



来源:https://stackoverflow.com/questions/60190222/provide-exceptions-for-wcf-webhttpbinding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!