HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Proxy web service which handles three different input structures

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
threeproxyhandlesinputdifferentservicewebwhichstructures

Problem

I'm a newbie and I'm new to C# (thus statics and copy-paste) and I'm painfully aware that this code smells. I'm trying to implement a web service that handles a similar pass-through logic with three different data structures that are processed by three corresponding different methods of an external service.

How can I avoid copy-paste here?

```
public class OutBoundSvcSender {

public static string sendDataToOutBoundSvc(AbcRecs[] request) {
StringBuilder result = new StringBuilder();
try {
AbcImportResponse response = sendAbcDataToOutBoundSvc(request);
} catch (Exception ex) {
result.Append("\nException Message:\n");
result.Append(ex.Message);
result.Append("\nStack Trace:\n");
result.Append(ex.StackTrace);
return result.ToString();
}
return "OK";
}

public static string sendDataToOutBoundSvc(BcdRecs[] request) {
StringBuilder result = new StringBuilder();
try {
BcdImportResponse response = sendBcdDataToOutBoundSvc(request);
} catch (Exception ex) {
result.Append("\nException Message:\n");
result.Append(ex.Message);
result.Append("\nStack Trace:\n");
result.Append(ex.StackTrace);
return result.ToString();
}
return "OK";
}

public static string sendDataToOutBoundSvc(CdeRecs[] request) {
StringBuilder result = new StringBuilder();
try {
CdeImportResponse response = sendCdeDataToOutBoundSvc(request);
} catch (Exception ex) {
result.Append("\nException Message:\n");
result.Append(ex.Message);
result.Append("\nStack Trace:\n");
result.Append(ex.StackTrace);
return result.ToString();
}
return "OK";
}

private static AbcImportResponse sendAbcDataToOutBoundSvc(AbcRecs[] request) {
var Recs = new AbcRecord[request.Length];
OutBoundSvcImportServiceClient client = new OutBoundSvcImportServiceClient();
string token = client.Login(new AuthorizationRequest {
Login = ConfigurationMan

Solution

A few things you can do. To help in the future (in case of Def being added, etc.) you can make some of these functions generic.

The caveat, is that the way you have it set now you would have to make an interface that AbcImportRequest, etc. would implement.

Let's assume all the ImportRequest objects implement the following interface:

public interface IImportRequest
{
    string AuthorizationToken { get; set; }
    TRecord[] Records { get; set; }
}


And we'll assume all the Recs objects implement this interface:

public interface IRecs
{
    TRecord BuildRecord();
}


This eases our code greatly.

Next, we'll adjust our sendDataToOutBoundSvc method (and rename it at the same time, to follow conventions of Pascal Case).

public static string SendDataToOutboundSvc(TRecs[] request, Func buildResultFunc)
        where TImportRequest : IImportRequest, new()
        where TRecs : IRecs
    {
        StringBuilder result = new StringBuilder();
        try
        {
            TResult response = SendDataToOutboundSvcInternal(request, buildResultFunc);
        }
        catch (Exception ex)
        {
            result.Append("\nException Message:\n");
            result.Append(ex.Message);
            result.Append("\nStack Trace:\n");
            result.Append(ex.StackTrace);
            return result.ToString();
        }

        return "OK";
    }


So what did we do? We replaced all the strongly-typed calls with some generic ones. We also added a Func parameter that allows us to specify a certain buildResultFunc to build our result.

We could extract that Func out, but I'll leave that up to you to decide.

Next, we rewrite the other sendDataToOutBoundSvc as:

public static TResult SendDataToOutboundSvcInternal(TRecs[] request, Func buildResultFunc)
        where TImportRequest : IImportRequest, new()
        where TRecs : IRecs
    {
        var Recs = new TRecord[request.Length];
        OutBoundSvcImportServiceClient client = new OutBoundSvcImportServiceClient();
        string token = client.Login(new AuthorizationRequest
        {
            Login = ConfigurationManager.AppSettings["OutBoundSvcServiceLogin"],
            Password = ConfigurationManager.AppSettings["OutBoundSvcServicePassword"]
        }).Token;
        for (int i = 0; i (token, Recs);
        return buildResultFunc(client, tResultRequest);
    }


More generics. Starting to get the point?

Now, you'll notice that I replaced all that explicit record building code with a received.BuildRecord(). That is part of the second interface we implemented, for AbcRecs it looks like:

public AbcRecord BuildRecord()
{
    AbcRecord rec = new AbcRecord { };
    rec.Id = Id;
    rec.Model = Model;
    rec.Type = Type;
    rec.Category = Category;
    rec.YearOfManufacture = YearOfMan.ToString();
    rec.Company = CompanyId;
    rec.AdditionalDocs = AddDocs;
    return rec;
}


While the class is:

public class AbcRecs : IRecs
{


Etc.

Then we have a BuildImportRequest method:

public static TImportRequest BuildImportRequest(string token, TRecord[] recs)
    where TImportRequest : IImportRequest, new()
{
    return new TImportRequest { AuthorizationToken = token, Records = recs };
}


More generics. Again, these reduce code duplication heavily.

And lastly, our Import___Data methods:

public static AbcImportResponse ImportAbcData(OutBoundSvcImportServiceClient client, AbcImportRequest request)
{
    return client.AbcImport(request);
}

public static BcdImportResponse ImportBcdData(OutBoundSvcImportServiceClient client, BcdImportRequest request)
{
    return client.BcdImport(request);
}

public static CdeImportResponse ImportCdeData(OutBoundSvcImportServiceClient client, CdeImportRequest request)
{
    return client.CdeImport(request);
}


So, now do we use all this? As follows:

OutBoundSvcSender.SendDataToOutboundSvc(new AbcRecs[0], OutBoundSvcSender.ImportAbcData);
OutBoundSvcSender.SendDataToOutboundSvc(new BcdRecs[0], OutBoundSvcSender.ImportBcdData);
OutBoundSvcSender.SendDataToOutboundSvc(new CdeRecs[0], OutBoundSvcSender.ImportCdeData);


This makes it much easier to implement other types as well, all you have to do is create the appropriate objects, and an Import___Data method, and you can tell it what to do from there.

You could also consider (that is, if you have the ability to modify the webserver) using Data Contract to dynamically serialize and deserialize the data. The DataContract is very powerful.

Code Snippets

public interface IImportRequest<TRecord>
{
    string AuthorizationToken { get; set; }
    TRecord[] Records { get; set; }
}
public interface IRecs<TRecord>
{
    TRecord BuildRecord();
}
public static string SendDataToOutboundSvc<TRecs, TImportRequest, TRecord, TResult>(TRecs[] request, Func<OutBoundSvcImportServiceClient, TImportRequest, TResult> buildResultFunc)
        where TImportRequest : IImportRequest<TRecord>, new()
        where TRecs : IRecs<TRecord>
    {
        StringBuilder result = new StringBuilder();
        try
        {
            TResult response = SendDataToOutboundSvcInternal<TRecs, TRecord, TImportRequest, TResult>(request, buildResultFunc);
        }
        catch (Exception ex)
        {
            result.Append("\nException Message:\n");
            result.Append(ex.Message);
            result.Append("\nStack Trace:\n");
            result.Append(ex.StackTrace);
            return result.ToString();
        }

        return "OK";
    }
public static TResult SendDataToOutboundSvcInternal<TRecs, TRecord, TImportRequest, TResult>(TRecs[] request, Func<OutBoundSvcImportServiceClient, TImportRequest, TResult> buildResultFunc)
        where TImportRequest : IImportRequest<TRecord>, new()
        where TRecs : IRecs<TRecord>
    {
        var Recs = new TRecord[request.Length];
        OutBoundSvcImportServiceClient client = new OutBoundSvcImportServiceClient();
        string token = client.Login(new AuthorizationRequest
        {
            Login = ConfigurationManager.AppSettings["OutBoundSvcServiceLogin"],
            Password = ConfigurationManager.AppSettings["OutBoundSvcServicePassword"]
        }).Token;
        for (int i = 0; i < Recs.Length; i++)
        {
            TRecs received = request[i];

            Recs[i] = received.BuildRecord();
        }
        TImportRequest tResultRequest = BuildImportRequest<TImportRequest, TRecord>(token, Recs);
        return buildResultFunc(client, tResultRequest);
    }
public AbcRecord BuildRecord()
{
    AbcRecord rec = new AbcRecord { };
    rec.Id = Id;
    rec.Model = Model;
    rec.Type = Type;
    rec.Category = Category;
    rec.YearOfManufacture = YearOfMan.ToString();
    rec.Company = CompanyId;
    rec.AdditionalDocs = AddDocs;
    return rec;
}

Context

StackExchange Code Review Q#105681, answer score: 4

Revisions (0)

No revisions yet.