Предоставить исключения для wcf webhttpbinding - PullRequest
0 голосов
/ 12 февраля 2020

Мне нужно изменить привязку для веб-сервисов wcf с tcpbinding на webhttpbinding с базовой аутентификацией c и ssl.

Веб-сервисы размещаются самостоятельно в консольном приложении и в сервисе 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);
    }
}

Все модульные тесты, которые перехватили исключение, не были выполнены, поскольку я изменил привязку.

Спасибо за помощь.

Ответы [ 2 ]

0 голосов
/ 18 февраля 2020

После нескольких поисков я увидел, что у многих людей такая же проблема.

Вот мое решение:

На стороне сервера всегда генерируйте исключение WebFaultException, например, с правильным кодом состояния HTTP:

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

На стороне клиента (только для модульных тестов или MVC project), приведите исключение для вызова GetResponseStream для объекта Response для получения настраиваемого сообщения:

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

В методе ProvideFault из IErrorHandler я просто добавляю код для записи ошибок в файл, но не создаю сообщение с методом Message.CreateMessage.

Он работает правильно, но генерирует исключение EndPointNotFoundException после ProvideFault, в некоторых других публикациях я видел, что может быть выдано исключение ProtocolException.

Спасибо за ваши замечания.

0 голосов
/ 14 февраля 2020

Я сомневаюсь, что ваш сервис работает правильно. Связываете ли вы сертификат с портом 443 по умолчанию из-за безопасности транспортного уровня (с использованием HTTPS)? Пожалуйста, используйте приведенную ниже инструкцию для привязки сертификата к порту 443.

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

перейдите по этой ссылке.
https://docs.microsoft.com/en-us/windows/win32/http/add-sslcert
Здесь приведено соответствующее обсуждение.
Как отключить ввод учетных данных для вызова HTTPS на мой WCF, размещенный в windows сервисе
Кроме того, я не видел, чтобы вы применяли GlobalErrorHandler к автономному сервису. Обычно это реализуется поведением конечной точки службы.

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

Я написал пример, wi sh, это полезно для вас.

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

Результат.
enter image description here
Не стесняйтесь, дайте мне знать, если есть что-то, с чем я могу помочь.

...