0

I am writing a service in Core 3.1. In the service that I am writing, I create a connected service to a legacy SOAP service that interfaces with an even older system. The SOAP service provides basic CRUD operations and uses classes as data containers. The problem is that the generated code for the service reference creates separate partial classes for objects that have the exact same properties.

public partial class REQUEST1
{
    public string PropertyName {get; set;}
}
public partial class REQUEST2
{
    public string PropertyName {get; set;}
}

I find that I am writing the same code over and over with respect to preparing the request object.

private void SetProperties(MyClass parameters, REQUEST1 request)
{
    request.PropertyName = parameters.MyParamValue;
}
private void SetProperties(MyClass parameters, REQUEST2 request)
{
    request.PropertyName = parameters.MyParamValue;
}

I don't want to modify the generated code as the next time it gets generated someone will have to remember to do that. Interfaces and base classes aren't really an option as that is modifying generated code. So I'm looking for suggestions on how to write a method that can take one of these classes and set values without the cookie-cutter code writing.

UPDATE: So this got more complicated. In the create/update services, I have complex objects.

public partial class ADDRESS
{
    // Address Properties created by the service reference
}

public partial class PERSON
{
    ADDRESS[] ADDRESSES { get; set;}
    // Other properties created by the service reference
}

I can create the interface for address and person but that creates another problem.

public interface IAddress
{
    // Address properties 
}
public interface IPerson
{
    IAddress[] ADDRESSES {get;set;}
    // Other properties
}
public partial class ADDRESS : IAddress
{
}
public partial class PERSON : IPerson
{
}

This creates an error stating that PERSON doesn't implement the member IAddress[] ADDRESSES because it doesn't have the correct return type. This makes sense but I'm not sure how to get around it. For individual objects with primitive types this approach works but for more complex types it seems to require another solution.

Randall W
  • 209
  • 2
  • 12

3 Answers3

3

I suggest adding an interface to the classes using partial classes.

public interface IRequest
{
   string PropertyName {get; set;}
}

public partial class REQUEST1
{
    public string PropertyName {get; set;}
}
public partial class REQUEST2
{
    public string PropertyName {get; set;}
}

public partial class REQUEST1 : IRequest
{
}

public partial class REQUEST2 : IRequest
{
}

You can just have a method like this

private void SetProperties(MyClass parameters, IRequest request)
{
    request.PropertyName = parameters.MyParamValue;
}
Legacy Code
  • 650
  • 6
  • 10
  • This is _almost_ a perfect solution...but adding the `partial` modifier to the original classes still requires modifying the generated code. (And, just to remind myself whether all "parts" have to be declared `partial`, [they do](https://learn.microsoft.com/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods): "All the parts must use the `partial` keyword.") – Lance U. Matthews Jul 17 '20 at 17:26
  • Yeah but it’s a little change. After that you have a lot of freedom in adding or removing interfaces or base classes without modifying the generator. – Legacy Code Jul 17 '20 at 17:43
  • Actually this might work well. The generated code is all partial classes so there's no modification needed. – Randall W Jul 17 '20 at 20:28
  • That was certainly an important piece of information to have omitted from the question. I have edited it to reflect that. – Lance U. Matthews Jul 18 '20 at 17:16
1

If you don't want to do it by hand.........consider

https://automapper.org/

https://www.nuget.org/packages/automapper/

example (very easy when the property names overlap)

MapperConfigurationconfig = new MapperConfiguration(cfg => {
            cfg.CreateMap<Request1, Request2>();
        });

IMapper iMapper = config.CreateMapper();
Request1 source = new Request1();
Request2 destination = iMapper.Map<Request1, Request2>(source);

if the property names are not perfect matches.....you adjust the "config".

Here is another article:

https://www.infoworld.com/article/3192900/how-to-work-with-automapper-in-csharp.html

granadaCoder
  • 26,328
  • 10
  • 113
  • 146
0

If you don't want to dig deeper in the code generation and add interfaces or sub classes maybe extension methods can do the trick (Documentation) combined with a bit reflection :) Other SO Answer

In your case:

public static class RequestExtensions 
{
    public static void SetProperty(this REQUEST1 request, YourClass parameters)
    {
        SetProperty(request, "PropertyName", parameters.MyParamValue;
    }

    public static void SetProperty(this object obj, string propertyName, object value)
    {
        // Very generic. Always try to not use reflection :)
        // No exception handling or null checks here because of example code.
        obj.GetType().InvokeMember(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty, Type.DefaultBinder, obj, value);
    }
}

Here you can see you can add extension methods to already generated classes. The other option is to use directly the SetProperty (not recommanded).

In my optinion the best solution would be to dig deeper in the generator and add interfaces or base classes to support generic access to your classes in all other ways reflection is your best friend.

Martin
  • 3,096
  • 1
  • 26
  • 46
  • 1
    I'm not sure I will use this for this particular problem but I am putting that snippet in my code toolbox. :) – Randall W Jul 17 '20 at 20:30