0

We have a wcf web services API, which has some common code which we have wrapped into a generic method to save from having to write the same code in every web service method. This looks like so:

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;
    }

Now I am trying to write the auditing functionality, and there is a particular parameter that is almost always part of the request or the response classes, so I can get this parameter using reflection so I can log it to the database.

 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???


    }

Each web service method has it's own version of the response and request classes which inherit from the base ones referenced in this method.

My problem is that for the one or two web service methods where I do not have access to the parameter I want to log, but instead I need to do some work to get it, I'm not sure how to pass it into the generic method to deal with it.

I could do this by using global variables or by adding it to the response classes, but either of those approaches seem quite shoddy from a programming style point of view.

I was wondering if anyone had any other suggestions for a "nice" way of handling this?

jazza1000
  • 4,099
  • 3
  • 44
  • 65

3 Answers3

1

First, you shouldn't use reflection to extract the values. This could be solved nicely using a common interface.

Now to your question. You can pass signinId as an optional parameter to SafeMethodCall so you don't break your existing contract. And from there pass it to 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);

        // ...
}

In AddAuditData you check if signinId is provided, if not then extract it from request/response:

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
}

Alternatively you can pass a function which will return the signinId or an object that will provide the value.

TomT
  • 971
  • 7
  • 13
  • Hi Tom, yes, that is what I was trying to get at - what is the best way to format the method calls to pass in the parameter that I need (or indeed whether a different delegate structure is required for the body method). I will see if I can get it working as you suggest – jazza1000 Jan 13 '14 at 02:01
0

As I understand, you need to call generic method via reflection.

If yes please use System.ReflectionMethodInfo.MakeGenericMethod(params type[])

so you have to do something like this

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

here is relevant post How do I use reflection to call a generic method?

here is msdn page on this topic http://msdn.microsoft.com/ru-ru/library/system.reflection.methodinfo.makegenericmethod(v=vs.110).aspx

Community
  • 1
  • 1
potehin143
  • 541
  • 4
  • 9
0

I thought I might as well detail my solution to this. The problem was really that I needed to be able to pass more data out of the body of the function call into my auditing procedure, and I wasn't sure how to do this for a generic function.

So, the first part was realising that

 Func<T, TResult, TResult> body

is just a built in delegate type, so I needed to replace it with my own version, that also contained an out parameter.

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

Then, I could change the code in SafeMethodCall to use this delegate

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;
    }

And that meant that in the web service methods that I called this function, I could just write some code to produce the extra parameter that I needed for auditing

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);
    }
jazza1000
  • 4,099
  • 3
  • 44
  • 65