2013-10-14 3 views
1

Я реализовал общий метод для вызова функции в моих методах веб-службы и исключениях catching. Идея заключается в том, чтобы централизовать обработку исключений и журналов, почему исключение произошло:Получение имени и значения параметра из делегата

public class WebServiceHandler 
{ 
    public T Execute<T>(Func<T> body) 
      { 
       //wrap everything in common try/catch 
       try 
       {     
        return body(); 
       } 
       catch (SoapException) 
       { 
        //rethrow any pre-generated SOAP faults 
        throw; 
       }   
       catch (Exception ex) 
       { 

        Logger.AddTextToLog(Logger.LogLevel.Error, "An error occured"); 
        var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : ""; 
        throw GenerateSoapException(
               ex.Message, 
               innerExceptionMessage, 
               SoapException.ServerFaultCode); 
       } 
      } 
} 

Я использую метод, как это в моих методах веб-службы:

[WebMethod] 
GetXXX(string param1, string param2) 
{ 
var request = new GetXXXRequest(param1, param2); 
    return _webServiceHandler.Execute(() => _controller.GetXXX(request)); 
} 

[WebMethod] 
GetYYY(string param1) 
{ 
var request = new GetYYYRequest(param1); 
    return _webServiceHandler.Execute(() => _controller.GetYYY(request)); 
} 

В случае исключений я хотел бы для регистрации имен параметров и значений, используемых в качестве входных данных для методов контроллера. GetXXX (запрос) и controller.GetYYY (запрос) в методе Execute.

Как я могу это достичь? Или есть лучший способ достичь той же цели?

ответ

0

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

public T Execute<T>(Func<T> body, Action<string> callback) 
      { 
       //wrap everything in common try/catch 
       try 
       { 
        //do stuff    
        callback("results are..."); 
       } 

Вы можете также использовать делегат:

public void CallbackDelegate(string str); 


public T Execute<T>(Func<T> body, CallbackDelegate callback) 
      { 
       //wrap everything in common try/catch 
       try 
       { 
        //do stuff    
        callback("results are..."); 
       } 
0

Может быть что-то вроде этого:

sealed class Param 
{ 
    public string Name 
    { 
     get; 
     private set; 
    } 

    public object Value 
    { 
     get; 
     private set; 
    } 

    public Param(string name, object value) 
    { 
     Name = name; 
     Value = value; 
    } 
} 

public T Execute<T>(Func<T> body, params Param[] parameters) 
{ 
    //wrap everything in common try/catch 
    try 
    { 
     return body(); 
    } 
    catch (SoapException) 
    { 
     //rethrow any pre-generated SOAP faults 
     throw; 
    } 
    catch (Exception ex) 
    { 

     Logger.AddTextToLog(Logger.LogLevel.Error, "An error occured"); 

     foreach (var parameter in parameters) 
     { 
      Logger.AddTextToLog(
           Logger.LogLevel.Error, 
           string.Format(
               "{0} : {1}", 
               parameter.Name, 
               parameter.Value ?? "null")); 
     } 

     var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : ""; 
     throw GenerateSoapException(
            ex.Message, 
            innerExceptionMessage, 
            SoapException.ServerFaultCode); 
    } 
} 

И тогда вы звоните:

[WebMethod] 
GetXXX(string param1, string param2) 
{ 
    var request = new GetXXXRequest(param1, param2); 
    return _webServiceHandler.Execute(() => _controller.GetXXX(request) 
             new Parameter("param1", param1), 
             new Parameter("param2", param2)); 
} 
+0

Да, это способ сделать это. Я надеялся достичь своей цели, не обращаясь к параметрам явно, но время также является параметром :) – OKB

1

Прежде всего вы не можете запросить время выполнения для этой информации магически, неявно.

Это правда, что вы можете неявно узнать полную идентификацию текущего исполняемого метода, его вызывающего абонента, вызывающего абонента и всех элементов stacktrace (за исключением конкретных общих аргументов в случае общих методов) путем создания экземпляра класса StackTrace или путем вызова метода MethodBase.GetCurrentMethod().

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

Что вы, можете сделать, немного помочь себе, с помощью класса Expression<Lambda> и его окружения. Это будет немного медленнее, но вы можете выбрать, хотите ли вы просматривать и легко управлять кодом, или очень сложно управлять, и очень быстрый код.

Expression<Lambda> как LINQ удается не сделать полное сканирование таблицы таблиц базы данных, а понять, что вы сделали с вашим запросом, и перевести это (во время выполнения) в SQL или любой другой язык, который вы можете себе представить (в зависимости на фактическом провайдере LINQ).

Прежде всего, я хотел бы предложить проблемы расщепления на 2 категории:

  1. Получение имен и значений (как неявно, как это возможно)
  2. Использование имен и значений (там, где вы хотите)

Чтобы это произошло, вам нужно подумать о сущности, которая может удерживать результаты пункта 1. В моем предположении, что это будет своего рода Dictionary<string, object>, но вы можете делать все, что вам подходит.

Мое предложение может быть использовано следующим образом:

public void SomeMethod(string x, int y) { 

    IDictionary<string, object> paramValues = Helper.TapInto(
     () => x, 
     () => y 
    ); 

    // paramValues["x"] an paramValues["y"] will hold the values of x and y 

} 

Так, на биту кодирования. Вы можете написать Helper класс следующим образом:

public static class Helper { 
} 

В этом Helper классе вы могли бы придумать статический метод (я назвал мое TapInto, возможно, это не лучшее название для него), который принимает примитивный массив Expression<Func<object>> экземпляров. Он делает это с модификатором params, так что вы можете легко передать им неявные лямбды. В качестве возврата он дает хеш-таблицу от string до object, представляющую имена «декомпилированных» переменных и связанные с ними значения.

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

public static class Helper { 

    // ... an overload of the TapInto method is about to appear right here 

    public static IDictionary<string, object> TapInto(params Expression<Func<object>>[] parameterTouchers) { 
     var result = new Dictionary<string, object>(); 

     foreach (var toucher in parameterTouchers) { 
      string name; 
      object value; 
      toucher.TapInto(out name, out value); 
      result[name] = value; 
     } 

     return result; 

    } 

Таким образом, весь общедоступный метод выполняет перебор по списку и накапливает полученные результаты в словаре.

Теперь давайте посмотрим на настоящее волшебство, которое происходит в toucher.TapInto(out name, out value) вызова:

public static class Helper { 

    private static void TapInto(this Expression<Func<object>> @this, out string name, out object value) { 

     Expression expression = @this.Body; 
     if (expression is UnaryExpression) 
      expression = (expression as UnaryExpression).Operand; 

     name = (expression as MemberExpression).Member.Name; 

     Func<object> compiledLambda = @this.Compile(); 
     value = compiledLambda(); 

    } 

    // ... public helper method right here 
} 

Что мы делаем здесь «мы ищем внутри» лямбда с увеличительным стеклом. Поскольку мы собираемся использовать материал, кроме object переменных это неизбежно наблюдать неявное преобразование типа

.. int someParameter .. 
object obj = someParameter; 

, которая является неявной только в реальном коде C#, но на самом деле скомпилирован как явного преобразования:

object obj = (object)someParameter; 

Но у вас может быть простой параметр object, например object anotherParam, и в этом случае конверсии вообще не будет.

Вот почему, наблюдая за сложными деталями выражения, я предполагаю, что могу найти конверсию (представленную классом UnaryExpression) или нет.

На самом деле это, как говорят: В данном конкретном случае, мой контракт на код вызова является то, что он может послать мне только материал, который попадает в эти 2 категории:

  1. Немедленное object переменная ссылка: () => someObjectVariable
  2. Variable ссылки с конверсией: () => (object)x

в контракте также случайно говорится, что «преобразование» бит могут быть заменены на UnaryExpression, например: () => !someBool.

Он также утверждает, что вы не можете сделать что-то вроде:

  • () => 123
  • или () => a + b + c + 100
  • или что-нибудь еще в тех направлениях

Таким образом, чтобы обернуть его:

  1. Yo вы могли бы написать свой симпатичный помощник
  2. Вы можете использовать его везде, где хотите его использовать, для создания карт между именами параметров и их значениями, хотя это не 100% неявное, , но, по крайней мере, оно не будет компилироваться, если вы переименуете параметр без полного рефакторинга или он позволит вам переименовать ссылки параметров, если вы решите переименовать параметры с помощью рефакторинга (он также работает с полями, локальными переменными и т. д.)
  3. Передайте словари между интересующими вас частями вашего кода в них и используйте их соответственно!
0

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

public T Execute<T, TU>(Func<T> body, TU parameterClass) where TU : class 
{ 
//wrap everything in common try/catch 
     try 
     {     
      return body(); 
     } 
     catch (SoapException) 
     { 
      //rethrow any pre-generated SOAP faults 
      throw; 
     } 
     catch (Exception ex) 
     { 
      var serializedObject = ParameterUtil.GetPropertyNamesAndValues(parameterClass);     
      Logger.AddTextToLog(Logger.LogLevel.Error, string.Format("An error occured when calling braArkiv Web Services. Web service method arguments: {0}", serializedObject), ex); 
      var innerExceptionMessage = ex.InnerException != null ? ex.InnerException.Message : ""; 
      throw GenerateSoapException(
             ex.Message, 
             innerExceptionMessage, 
             SoapException.ServerFaultCode); 
     } 
} 


public static class ParameterUtil 
    {  
     public static string GetPropertyNamesAndValues<T>(T o) where T : class 
     { 
      using (var stringWriter = new StringWriter()) 
      { 
       var xmlSerializer = new XmlSerializer(o.GetType()); 
       xmlSerializer.Serialize(stringWriter, o); 
       stringWriter.Close(); 
       return stringWriter.ToString(); 
      }    
     }   
    } 

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

[WebMethod] 
GetXXX(string param1, string param2) 
{ 
var request = new GetXXXRequest(param1, param2); 
    return _webServiceHandler.Execute(() => _controller.GetXXX(request), request); 
} 

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

Благодарим вас за конструктивный вклад в мой вопрос!

1

Я не пробовал с выражениями, но у меня есть решение для делегатов;

public static List<object> GetMethodParameterValues(Delegate method) 
{ 
    var target = method.Target; 
    if (target == null) return null; 
    var fields = target.GetType().GetFields(); 
    var valueList = fields.Select(field => field.GetValue(target)).ToList(); 
    return valueList; 
} 
Смежные вопросы