Передача метода интерфейса в качестве параметра - PullRequest
0 голосов
/ 24 мая 2018

Примечание : Это вполне может быть очень C# конкретный языковой вопрос, вообще не связанный с WCF или web services.

Существует 3-сторонняя ASMX веб-сервис, который будет использоваться для поиска данных.Я создал обобщенный метод ExecuteCommand(), который используется для каждого запроса к веб-сервису.Целью этого метода является обработка сеанса / исключений cookie и другой общей логики.Для каждого запроса должен использоваться новый канал, чтобы упростить удаление неиспользуемых ресурсов.

Проблема заключается в том, что для использования метода ExecuteCommand() мне нужно каждый раз инициализировать канал, чтобыбыть в состоянии передать метод для выполнения в качестве аргумента.Извините, если это звучит слишком сложно.Вот пример использования:

string color = "blue";
var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();
var cars = WcfHelper.ExecuteCommand(channel, () => channel.GetCars(color));
// channel is null here. Channel was closed/aborted, depending on Exception type.

После вызова ExecuteCommand() - channel уже утилизирован.Причина, по которой объект channel нужен вообще, заключается в возможности предоставить метод, который будет выполняться в качестве параметра!т.е. () => channel.GetCars().Для дальнейшей поддержки этих слов, вот внутреннее устройство класса WcfHelper:

public static class WcfHelper
{
    public static Cookie Cookie { get; set; }

    public static T ExecuteCommand<T>(IClientChannel channel, Expression<Func<T>> method)
    {
        T result = default(T);

        try
        {
            // init operation context
            using (new OperationContextScope(channel))
            {
                // set the session cookie to header
                if (Cookie != null) {
                    HttpRequestMessageProperty request = new HttpRequestMessageProperty();
                    request.Headers["Cookie"] = cookie.ToString();
                    OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = request;  
                }

                // execute method
                var compiledMethod = method.Compile();
                result = compiledMethod.Invoke();
            }
        }
        // do different logic for FaultException, CommunicationException, TimeoutException
        catch (Exception)
        {
            throw;
        }
        finally
        {
            CloseOrAbortServiceChannel(channel);
            channel = null;
        }

        return result;
    }

    private static void CloseOrAbortServiceChannel(ICommunicationObject communicationObject)
    {
        bool isClosed = false;

        if (communicationObject == null || communicationObject.State == CommunicationState.Closed)
            return;

        try
        {
            if (communicationObject.State != CommunicationState.Faulted)
            {
                communicationObject.Close();
                isClosed = true;
            }
        }
        catch (Exception)
        {
            throw;
        }
        finally
        {
            if (!isClosed)
                AbortServiceChannel(communicationObject);
        }
    }

    private static void AbortServiceChannel(ICommunicationObject communicationObject)
    {
        try
        {
            communicationObject.Abort();
        }
        catch (Exception)
        {
            throw;
        }
    }
}

Итак, короткий вопрос - можно ли инициализировать переменную channel внутри самого метода ExecuteCommand, имея при этомВозможность определить, какой метод должен выполняться внутри ExecuteCommand для данного канала?

Я пытаюсь выполнить что-то вроде этого:

string color = "blue";
var cars = WcfHelper.ExecuteCommand<Car[], CarServiceSoapChannel>(channel => channel.GetCars(color));

или даже

string color = "blue";
var cars = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.GetCars(color));

Любые другие предложения по улучшению кода приветствуются, но не обязательно, конечно.

PS ASMX добавляется как Service reference в Visual Studio.Поэтому были некоторые сущности, которые автоматически генерировались для «CarService», такие как - CarServiceSoapChannel интерфейс, CarServiceSoapClient класс и, конечно, CarService интерфейс, содержащий методы веб-службы.В приведенном выше примере ChannelFactory используется для создания канала для интерфейса CarServiceSoapChannel, поэтому отсюда и название вопроса: Passing an interface method as a parameter.Это может вводить в заблуждение, но я надеюсь, что из самого описания понятно, чего я пытаюсь достичь.

Обновление 25.05.2018 Я последовал совету @nvoigt и смог добитьсярезультат, который я хотел:

public static TResult ExecuteCommand<TInterface, TResult>(Func<TInterface, TResult> method)
    where TInterface : IClientChannel
{
    TResult result = default(TResult);
    IClientChannel channel = null;

    try
    {
        channel = StrategyFactory.CreateChannel<TInterface>();

        // init operation context
        using (new OperationContextScope(channel))
        {
            // set the session cookie to header
            if (Cookie != null)
                Cookie.SetCookieForSession();

            // execute method
            result = method((TInterface)channel);
        }
    }
    catch (Exception)
    {
        throw;
    }
    finally
    {
        CloseOrAbortServiceChannel(channel);
        channel = null;
    }

    return result;
}

Это уже достигает начальной цели.Однако у этого подхода есть одна проблема.Чтобы вызвать метод - вы должны явно указать возвращаемый параметр метода.

Так сказать, если вы хотите вызвать метод - написания этого недостаточно:

var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel>(channel => channel.IsBlue())

Вам нужно будет конкретно указать, что тип возвращаемого значения должен быть boolean

var result = WcfHelper.ExecuteCommand<CarServiceSoapChannel, bool>(channel => channel.IsBlue())

Лично я не против написать дополнительный бит кода, так как это все еще большое преимущество перед моей первоначальной реализацией методаоднако, мне интересно, что в новой версии можно улучшить?

Например, когда у меня был только 1 универсальный тип TResult в методе - возвращаемый тип <TResult> можно было бы опустить.Это больше не относится к 2 дженерикам.В любом случае, спасибо @ nvoigt !

1 Ответ

0 голосов
/ 24 мая 2018

Ваша подпись метода должна быть:

public static TResult ExecuteCommand<TInterface>(Func<TInterface, TResult> method)

Затем в вашем WcfHelper (который, вероятно, больше не будет static, потому что ему нужен член _strategyFactory), вы создаете канал, как и раньше:

{
    var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();

    return method(channel);
}

Очевидно, вам нужно снова добавить все модные вещи try / finally.


Поскольку у вас все равно должны быть экземпляры с фабрикой в ​​классе в качестве члена, вы можетедобавьте в ваш класс универсальный контракт на обслуживание, чтобы пользователям было проще:

public class ConnectionToService<TInterface> : where TInterface : class
{
    public TResult ExecuteCommand<TResult>(Func<TInterface, TResult> method)
    {
        var channel = _strategyFactory.CreateChannel<CarServiceSoapChannel>();

        return method(channel);
    }
}

Использование:

var service = new ConnectionToService<ICarService>();

var color = service.ExecuteCommand(s => s.GetColor());
...