Как вернуть пользовательский код состояния http CoreWCF?

Я беру на себя устаревший проект SOAP, в котором мне нужно заменить службу SOAP решением .NET Core. Мы не можем изменить клиент SOAP, поэтому мы не можем смотреть на REST, GRPC и т. д. Я просмотрел SoapCore и CoreWCF, и оба работают с демонстрацией аутентификации имени пользователя и пароля заголовка SOAP, однако я собираюсь использовать CoreWCF для в настоящее время.

Существующая служба SOAP использует настраиваемые ответы кода состояния http, например, 202 возвращается после обработки службы и в некоторых ситуациях, когда возникает ошибка SOAP. Я понимаю, что это неправильно, однако нам нужно поддерживать существующую бизнес-логику.

Мои вопросы:

  1. Как я могу настроить свою службу для ответа с кодом состояния http 202 после завершения службы или при выполнении определенного условия? IsOneWay=True OperationContract не будет работать, так как это немедленно возвращается.
  2. Как мне настроить ошибку SOAP, чтобы она отвечала настраиваемым кодом состояния http?

Есть много старых сообщений SO, в которых упоминается WebOperationContext, однако я не могу получить к нему доступ в своей службе. OperationContext, похоже, не контролирует HttpStatusCode. Может быть, я что-то упускаю. то есть:

      WebOperationContext ctx = WebOperationContext.Current;
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.BadRequest;

Вот мой пример разбивки проекта:

Программа.cs

      using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using System.Diagnostics;

namespace CoreWcf.Samples.Http
{
    public class Program
    {
        public const int HTTP_PORT = 8088;
        public const int HTTPS_PORT = 8443;

        static void Main(string[] args)
        {
            IWebHost host = CreateWebHostBuilder(args).Build();
            host.Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
           WebHost.CreateDefaultBuilder(args)
            .UseKestrel(options =>
            {
                options.ListenLocalhost(HTTP_PORT);
                options.ListenLocalhost(HTTPS_PORT, listenOptions =>
                {
                    listenOptions.UseHttps();
                    if (Debugger.IsAttached)
                    {
                        listenOptions.UseConnectionLogging();
                    }
                });
            })
            .UseStartup<BasicHttpBindingStartup>();
    }
}

Startup.cs

      using CoreWCF;
using CoreWCF.Configuration;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;

namespace CoreWcf.Samples.Http
{
    public class BasicHttpBindingStartup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            //Enable CoreWCF Services, with metadata (WSDL) support
            services.AddServiceModelServices()
                .AddServiceModelMetadata();
        }

        public void Configure(IApplicationBuilder app)
        {
            var wsHttpBindingWithCredential = new BasicHttpBinding(CoreWCF.Channels.BasicHttpSecurityMode.TransportWithMessageCredential);
            wsHttpBindingWithCredential.Security.Message.ClientCredentialType = BasicHttpMessageCredentialType.UserName;

            app.UseServiceModel(builder =>
            {
                // Add the Demo Service
                builder.AddService<DemoService>(serviceOptions =>
                {
                    // Set a base address for all bindings to the service, and WSDL discovery
                    serviceOptions.BaseAddresses.Add(new Uri($"http://localhost:{Program.HTTP_PORT}/DemoService"));
                    serviceOptions.BaseAddresses.Add(new Uri($"https://localhost:{Program.HTTPS_PORT}/DemoService"));
                })
                // Add BasicHttpBinding endpoint
                .AddServiceEndpoint<DemoService, IDemo>(wsHttpBindingWithCredential, "/wsHttpUserPassword", ep => { ep.Name = "AuthenticatedDemoEP"; });

                builder.ConfigureServiceHostBase<DemoService>(CustomUserNamePasswordValidator.AddToHost);

                // Configure WSDL to be available over http & https
                var serviceMetadataBehavior = app.ApplicationServices.GetRequiredService<CoreWCF.Description.ServiceMetadataBehavior>();
                serviceMetadataBehavior.HttpGetEnabled = serviceMetadataBehavior.HttpsGetEnabled = true;
            });
        }
    }
}

IDemo.cs (сервисный интерфейс):

      using CoreWCF;

namespace CoreWcf.Samples.Http
{
    // Define a service contract.
    [ServiceContract]
    public interface IDemo
    {
        //[OperationContract(IsOneWay = true)]
        [OperationContract]
        string DemoRequest(string tagid, string readerid, string datetime);
    }
}

Demo.cs (Сервис):

      using CoreWCF.Channels;
using Microsoft.AspNetCore.Http;
using System.Net;


namespace CoreWcf.Samples.Http
{
    public class DemoService : IDemo
    {
        public string DemoRequest(string tagid, string readerid, string datetime)
        {
            
            return $"Received tagid: {tagid}; readerid: {readerid};  datetime: {datetime}";
        }
    }
}

CustomUserNamePasswordValidator.cs:

      using CoreWCF;
using System.Threading.Tasks;

namespace CoreWcf.Samples.Http
{
    internal class CustomUserNamePasswordValidator : CoreWCF.IdentityModel.Selectors.UserNamePasswordValidator
    {
        public override ValueTask ValidateAsync(string userName, string password)
        {
            bool valid = userName.ToLowerInvariant().EndsWith("valid")
                && password.ToLowerInvariant().EndsWith("valid");
            if (!valid)
            {
                throw new FaultException("Unknown Username or Incorrect Password");
            }
            return new ValueTask();
        }

        public static void AddToHost(ServiceHostBase host)
        {
            var srvCredentials = new CoreWCF.Description.ServiceCredentials();
            srvCredentials.UserNameAuthentication.UserNamePasswordValidationMode = CoreWCF.Security.UserNamePasswordValidationMode.Custom;
            srvCredentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator();
            host.Description.Behaviors.Add(srvCredentials);
        }
    }

}

Заранее большое спасибо за любую помощь. ваше здоровье.

2 ответа

CoreWCF имеет собственный WebOperationContext в пакете nuget CoreWCF.WebHttp, и вы можете установить там исходящий код состояния, чтобы вернуть собственный код состояния, который вы можете просто сделать

WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.YourDesiredStatusCode;

OutgoingResponse StatusCode — это место, где вы устанавливаете код ответа, но это не целочисленное значение.

Если вы все еще хотите использовать его, попробуйте режим совместимости с ASP.NET. Развертывание должно быть в IIS.

Другие вопросы по тегам