Примечание : Это вполне может быть очень 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 !