如何在c#中将额外数据传递给泛型方法

时间:2014-01-12 21:13:25

标签: c# generics

我们有一个wcf Web服务API,它有一些公共代码,我们已经将其包装到一个通用方法中,以避免在每个Web服务方法中编写相同的代码。这看起来像这样:

TResult SafeMethodCall<T, TResult>(Func<T, TResult, TResult> body, T request)
        where TResult : ServiceResponse, new()
        where T : RequestBase
    {
        if (request == null)
            throw new ArgumentNullException("request");

        var response = new TResult();

        try
        {
            response = body(request, response);
        }
        catch (Exception ex)
        {
            AddServiceError(response, ex);
        }
        finally
        {
            AddAuditData(request, response);
        }

        return response;
    }

现在我正在尝试编写审计功能,并且有一个特定的参数几乎总是请求或响应类的一部分,所以我可以使用反射获取此参数,以便我可以将其记录到数据库中。 / p>

 private void AddAuditData(RequestBase request, ServiceResponse response)
    {
        string signinId = "";
        Type t = request.GetType();
        PropertyInfo info = t.GetProperty("SignInIdentifier");
        if (info != null)
        {
            signinId = info.GetValue(request).ToString();
        }

        Type r = response.GetType();
        info = r.GetProperty("SignInIdentifier");
        if (info != null)
        {
            signinId = info.GetValue(response).ToString();
        }

        //now log signinid, and method name, etc to the database
        //how do I pass signinid into this method if it isn't part of the request or response???


    }

每个Web服务方法都有自己的响应版本和请求类,这些类继承自此方法中引用的基本版本。

我的问题是,对于我无法访问我想要记录的参数的一个或两个Web服务方法,但我需要做一些工作来获取它,我不知道如何传递它进入处理它的通用方法。

我可以通过使用全局变量或将其添加到响应类来实现这一点,但从编程风格的角度来看,这些方法中的任何一种都显得非常粗制。

我想知道是否有人有任何其他建议以“好”的方式处理这个?

3 个答案:

答案 0 :(得分:1)

首先,您不应使用反射来提取值。这可以使用通用接口很好地解决。

现在回答你的问题。您可以将signinId作为可选参数传递给SafeMethodCall,这样您就不会违反现有合同。然后从那里传递给AddAuditData

TResult SafeMethodCall<T, TResult>(Func<T, TResult, TResult> body, T request, string signinId = null)
    where TResult : ServiceResponse, new()
    where T : RequestBase
{
        // ...

        AddAuditData(request, response, signinId);

        // ...
}

AddAuditData中检查是否提供了signinId,如果没有,则从请求/响应中提取它:

private void AddAuditData(RequestBase request, ServiceResponse response, string signinId)
{
    if(signinId == null)
    {
      // extract from request or response
    }

    // still null? throw an exception or log error
}

或者,您可以传递一个函数,该函数将返回signinId或将提供该值的对象。

答案 1 :(得分:0)

据我了解,您需要通过反射调用泛型方法。

如果是,请使用System.ReflectionMethodInfo.MakeGenericMethod(params type [])

所以你必须做这样的事情

  typeof(TargetClass).GetMethod("TargetMethod").MakeGenericMethod(typeof(T1),typeof(T2)....).Invoke(obj,args);

这里是相关帖子How do I use reflection to call a generic method?

这是关于此主题http://msdn.microsoft.com/ru-ru/library/system.reflection.methodinfo.makegenericmethod(v=vs.110).aspx

的msdn页面

答案 2 :(得分:0)

我想我也可以详细说明我对此的解决方案。问题实际上是我需要能够将更多数据从函数调用的主体传递到我的审计过程中,而我不知道如何为泛型函数执行此操作。

所以,第一部分是实现

 Func<T, TResult, TResult> body

只是一个内置的委托类型,所以我需要用我自己的版本替换它,它还包含一个out参数。

private delegate TResult MyFunc< T1,  T2,  T3,  TResult>(T1 arg1, T2 arg2, out T3 arg3);

然后,我可以更改SafeMethodCall中的代码以使用此委托

TResult SafeMethodCall<T, TResult>(MyFunc<T, TResult, string, TResult> body, T request)
        where TResult : ServiceResponse, new()
        where T : RequestBase
    {
        if (request == null)
            throw new ArgumentNullException("request");

        var response = new TResult();
        string id = null;
        try
        {
            LogServiceEntry(request);

            response = body(request, response,out id); //from the delegate
        }
        catch (Exception ex)
        {
            AddServiceError(response, ex);
        }
        finally
        {
            AddAuditData(request, response, id);
            LogServiceExit(response);
        }

        return response;
    }

这意味着在我调用此函数的Web服务方法中,我可以编写一些代码来生成审计所需的额外参数

public ResetPasswordResponse ResetPassword(ResetPasswordRequest resetPasswordRequest)
    {

        return SafeMethodCall<ResetPasswordRequest, ResetPasswordResponse>
            ((ResetPasswordRequest request,
                ResetPasswordResponse response, out string signInIdentifier) =>
        {
            signInIdentifier = null;
            if (!_validator.ValidateModel(request, response))
                return response;
            var signIn =
                _manager.GetSignInByPasswordResetToken(request.PasswordResetToken);
            if (signIn != null)
            {
                signInIdentifier = signIn.SignInIdentifier.ToString();              
            }

            var result = _manager.ResetPassword(request.SiteIdentifier,
            request.PasswordResetToken, request.Password);

            if (!result)
            {
                AddServiceError(response, "The password could not be reset",
                    ErrorType.GeneralError);
            }

            return response;
        }, resetPasswordRequest);
    }