Инкапсуляция нескольких операций со ссылками на службы в повторно используемом классе

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

I типичный вызов выглядит примерно так:

// instantiate the api and set credentials
ApiClient api = new ApiClient();
api.Credentials.UserName.UserName = "blah";
api.Credentials.UserName.Password = "blahblah";

// operation-specific search parameter object
SomethingSearch search = new SomethingSearch();
search.Key = "blah";

Result[] result = api.GetSomething(search);

api.Close();

Другие вызовы различаются как вызываемой операцией, так и объектом параметра поиска.

Дело в том, что я не знаю, как передать в класс как имя операции API (т.е. GetSomething(), так и объект поиска для конкретной операции (SomethingSearch).

Как я могу это сделать? Я не прошу, чтобы работа была сделана за меня, но я не знаю, с чего начать. Я считаю, что это как-то связано с Func<T> и делегатами, но я смущающе неопытен с ними.


person Chad Levy    schedule 10.07.2012    source источник
comment
В Интернете доступно множество шаблонов для обработки этого, вам просто нужно убедиться, что вы правильно закрываете свои службы и не создаете ошибка использования оператора using.   -  person Jaime Torres    schedule 11.07.2012
comment
Эта инкапсуляция службы также может помочь. Но больше, чтобы инкапсулировать все это.   -  person DanielV    schedule 09.09.2016


Ответы (2)


Мой коллега разработал это решение:

/// <summary>
/// Proxy for executing generic service methods
/// </summary>
public class ServiceProxy
{
    /// <summary>
    /// Execute service method and get return value
    /// </summary>
    /// <typeparam name="C">Type of service</typeparam>
    /// <typeparam name="T">Type of return value</typeparam>
    /// <param name="action">Delegate for implementing the service method</param>
    /// <returns>Object of type T</returns>
    public static T Execute<C, T>(Func<C, T> action) where C : class, ICommunicationObject, new()
    {
        C svc = null;

        T result = default(T);

        try
        {
            svc = new C();

            result = action.Invoke(svc);

            svc.Close();
        }
        catch (FaultException ex)
        {
            // Logging goes here
            // Service Name: svc.GetType().Name
            // Method Name: action.Method.Name
            // Duration: You could note the time before/after the service call and calculate the difference
            // Exception: ex.Reason.ToString()

            if (svc != null)
            {
                svc.Abort();
            }

            throw;
        }
        catch (Exception ex)
        {
            // Logging goes here

            if (svc != null)
            {
                svc.Abort();
            }

            throw;
        }

        return result;
    }
}

И пример его использования:

public class SecurityServiceProxy
{

    public static UserInformation GetUserInformation(Guid userId)
    {
        var result = ServiceProxy.Execute<MySecurityService, UserInformation>
        (
            svc => svc.GetUserInformation(userId)
        );

        return result;
    }

    public static bool IsUserAuthorized(UserCredentials creds, ActionInformation actionInfo)
    {
        var result = ServiceProxy.Execute<MySecurityService, bool>
        (
            svc => svc.IsUserAuthorized(creds, actionInfo)
        );

        return result;
    }
 }

В этом поддельном случае мы используем два метода fromMySecurityService, GetUserInformation и IsUserAuthorized. GetUserInformation принимает Guid в качестве аргумента и возвращает объект UserInformation. IsUserAuthorized принимает объекты UserCredentials и ActionInformation и возвращает bool независимо от того, авторизован ли пользователь.

Этот прокси-сервер также является идеальным местом для кэширования результатов вызовов службы.

Если вам нужно отправить параметры на сервер, может быть более общий способ сделать это, но я думаю, вам нужно создать для него специальный прокси. Пример:

public interface ISecuredService
{
   public UserCredentials Credentials { get; set; }
}

/// <summary>
/// Proxy for executing generic UserCredentials  secured service methods
/// </summary>
public class SecuredServiceProxy
{
    /// <summary>
    /// Execute service method and get return value
    /// </summary>
    /// <typeparam name="C">Type of service</typeparam>
    /// <typeparam name="T">Type of return value</typeparam>
    /// <param name="credentials">Service credentials</param>
    /// <param name="action">Delegate for implementing the service method</param>
    /// <returns>Object of type T</returns>
    public static T Execute<C, T>(UserCredentials credentials, Func<C, T> action) where C : class, ICommunicationObject, ISecuredService, new()
    {
        C svc = null;

        T result = default(T);

        try
        {
            svc = new C();
            svc.Credentials = credentials;

            result = action.Invoke(svc);

            svc.Close();
        }
        catch (FaultException ex)
        {
            // Logging goes here
            // Service Name: svc.GetType().Name
            // Method Name: action.Method.Name
            // Duration: You could note the time before/after the service call and calculate the difference
            // Exception: ex.Reason.ToString()

            if (svc != null)
            {
                svc.Abort();
            }

            throw;
        }
        catch (Exception ex)
        {
            // Logging goes here

            if (svc != null)
            {
                svc.Abort();
            }

            throw;
        }

        return result;
    }
}
person Jaime Torres    schedule 10.07.2012
comment
Любопытно, как бы вы передали параметр в прокси? В моем конкретном случае svc требуется объект, содержащий имя пользователя и пароль. - person Chad Levy; 14.09.2012
comment
@Paperjam Я обновил, чтобы включить более конкретный пример того, как передавать различные аргументы в вызовы методов службы. Пожалуйста, дайте мне знать, если вы хотите получить дополнительные разъяснения. - person Jaime Torres; 14.09.2012
comment
В вашем примере UserCredentials передаются как параметр для IsUserAuthorized. В моем конкретном случае UserCredentials является параметром для svc. - person Chad Levy; 14.09.2012
comment
@Paperjam В последнем обновлении, которое я сделал, я включил SecuredServiceProxy. Если ваша служба наследуется от базового класса, вы можете определить C как этот базовый класс и вместо передачи учетных данных в качестве свойства вы можете передать его с помощью конструктора (при условии, что базовый класс требует этого конструктора). - person Jaime Torres; 14.09.2012
comment
Я получаю сообщение об ошибке: The type 'ApiClient' cannot be used as type parameter 'C' in the generic type or method 'SecuredServiceProxy.Execute<C,T>(System.Func<C,T>, System.ServiceModel.Description.ClientCredentials)'. There is no implicit reference conversion from 'StandardApiClient' to 'ISecuredService' - person Chad Levy; 14.09.2012
comment
@Paperjam Не видя вашего кода, его очень сложно диагностировать. Основываясь на МНОЖЕСТВЕ предположений (главным из которых является то, что ApiClient является абстрактным/базовым классом для ваших сервисов), замените ISecuredService из списка ограничений на ApiClient и отдайте его назад. - person Jaime Torres; 14.09.2012

Вы можете применить аналогичный подход к большинству реализаций WCF и создать интерфейс, определяющий функциональность API и скрывающий реализацию за этим интерфейсом. Вот краткий пример использования вашего примера кода:

    class APIEngine :IApiProvider
    {
        //...Private stuff & other methods
        T[] Search<T>(SearchArgs args)
        {
           //Error handling ommitted
           T[] result;

           switch(args.SearchType)
           {
               case(SearchType.GetSomething)
                    result = GetSomethingSearch(args.Key);
                    break;
               // and so on
           }     


           api.Close();
          return result;
       }
       Result[] GetSomethingSearch(Key searchKey)
       {   
           ApiClient api = new ApiClient(); 
           api.Credentials.UserName.UserName = "blah";
           api.Credentials.UserName.Password = "blahblah";   

           object SomethingSearch search = new SomethingSearch(); 
           search.Key = searchKey;

           result = api.GetSomething(search);  
       }
    }


class SearchArgs
{
    SearchType SearchType {get; set;} //Enum of search types
    SearchKey Key {get; set;} //SearchKey would be parent class for different key types
{

Вы бы назвали это так же, как и любой другой интерфейс:

IApiProvider.Search(keyValue);

Все остальное можно установить во время строительства или переустановить позже с помощью специальных методов. Дайте мне знать, если это на самом деле не отвечает на ваш вопрос.

РЕДАКТИРОВАТЬ:

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

person Chris    schedule 10.07.2012
comment
Дело в том, что мне нужно указать, какие операции API и объекты поиска использовать. Например, SomethingSearch может быть SomethingElseSearch, а GetSomething может быть GetSomethingElse. Различные объекты поиска также имеют разное количество параметров. - person Chad Levy; 11.07.2012