Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use polymorphism on return type of grpc code first service? #319

Open
soroshsabz opened this issue Dec 22, 2023 · 5 comments
Open

Comments

@soroshsabz
Copy link

soroshsabz commented Dec 22, 2023

ITNOA

I want to use polymorphic return type in grpc service with code first approach, but I do not know how to do it?

I have service contract like below

    [ServiceContract]
    public interface IGreeterService
    {
        [OperationContract]
        BaseResponse SayHello(HelloRequest request,
            CallContext context = default);
    }

And I have two derived response like below

public class ResponseA : BaseResponse
{
string foo;
}

public class ResponseB: BaseResponse
{
string bar;
}

my BaseResponse exist in another library, and I do not want (and I can not) to change this library.

public abstract class BaseResponse
{
public string hoo;
}

So finally I want to implement SayHello like below

public class GreeterService : IGreeterService
{

  public BaseResponse SayHello(HelloRequest request, CallContext context = default)
  {
    if (request.Name == "Heelo")
    {
      return new ResponseA() {foo = "Hi", hoo = "Hey"};
    }
    else
      return new ResponseB() {bar = "bye", hoo = "You"};
  }
}

But I do not know how can I say to OperationContract that I have two type for return type?

@soroshsabz
Copy link
Author

I think maybe related to #300

@soroshsabz
Copy link
Author

I ask same question on Stack Overflow https://stackoverflow.com/q/77704495/1539100

@soroshsabz
Copy link
Author

related to BSVN/Commons#68

@soroshsabz
Copy link
Author

@mgravell response in stack overflow like below

If you can't alter BaseResponse, you will have to configure it manually - for example:

RuntimeTypeModel.Default.Add(typeof(BaseResponse), false)
    // fields of BaseResponse
    .Add(1, nameof(BaseResponse.hoo))
    // sub-types of BaseResponse
    .AddSubType(10, typeof(ResponseA))
    .AddSubType(11, typeof(ResponseB));

Here I'm assuming that ResponseA and ResponseB can at least be altered, i.e.

[ProtoContract]
public class ResponseA : BaseResponse
{
    [ProtoMember(1)]
    public string foo;
}

If that isn't possible, you'll also need to configure them manually, for example:

RuntimeTypeModel.Default.Add(typeof(ResponseA), false)
    .Add(1, nameof(ResponseA.foo));

@soroshsabz
Copy link
Author

soroshsabz commented Dec 22, 2023

For automated this procedure, I write some attribute like below

namespace BSN.Commons.Utilities
{
    /// <summary>
    /// TODO
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public class ProtoImplementAttribute : Attribute
    {
        /// <summary>
        /// This constructor defines one required parameter
        /// </summary>
        /// <param name="BaseType">Type of base class or interface that you want to implement it</param>
        public ProtoImplementAttribute(Type BaseType)
        {
            this.BaseType = BaseType;
        }

        internal Type BaseType { get; }
    }

    /// <summary>
    /// Active polymorphism for protobuf-net code first approach.
    /// This class is used to enable polymorphism for protobuf-net code first approach on a specific assembly based on ProtoImplementAttribute.
    /// </summary>
    /// <remarks>
    /// You must call this class in the startup of your application.
    /// </remarks>
    public static class GrpcPolymorphismActivator
    {
        /// <summary>
        /// Enable polymorphism for protobuf-net code first approach on a specific assembly based on ProtoImplementAttribute.
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="extraTypes"></param>
        /// <exception cref="Exception"></exception>
        public static void Enable(Assembly assembly, (Type, Type)[] extraListOfDeriveds)
        {
            // TODO: Use C# source generator https://stackoverflow.com/q/64926889/1539100

            // ProtoBuf.Meta.RuntimeTypeModel.Default.Add(typeof(Response), false)
            //        .Add(1, nameof(Response.StatusCode))
            //        .Add(2, nameof(Response.Message))
            //        .Add(3, nameof(Response.InvalidItems))
            //        .AddSubType(100, typeof(ErrorResponse))
            //        .AddSubType(101, typeof(Response<SayHelloViewModel>));

            Dictionary<Type, List<Type>> listOfDerivedTypes = new Dictionary<Type, List<Type>>();
            foreach (var type in from type in assembly.GetTypes()
                                 where type.GetCustomAttribute<ProtoImplementAttribute>() != null
                                 select type)
            {
                ProtoImplementAttribute protoImplementAttribute = type.GetCustomAttribute<ProtoImplementAttribute>();

                if (listOfDerivedTypes.TryGetValue(protoImplementAttribute.BaseType, out List<Type> derivedTypes))
                    derivedTypes.Add(type);
                else
                    listOfDerivedTypes.Add(protoImplementAttribute.BaseType, new List<Type>() { type });
            }

            foreach ((Type @base, Type derived) in extraListOfDeriveds)
            {
                if (listOfDerivedTypes.TryGetValue(@base, out List<Type> derivedTypes))
                    derivedTypes.Add(derived);
                else
                    listOfDerivedTypes.Add(@base, new List<Type>() { derived });
            }

            foreach (var derivedType in listOfDerivedTypes)
            {
                MetaType @base = ProtoBuf.Meta.RuntimeTypeModel.Default.Add(derivedType.Key, false);
                int maxOrder = 0;
                foreach (var property in derivedType.Key.GetProperties())
                {
                    DataMemberAttribute dataMemberAttribute = property.GetCustomAttribute<DataMemberAttribute>();
                    if (property.GetCustomAttribute<DataMemberAttribute>() == null)
                        continue;
                    @base = @base.Add(dataMemberAttribute.Order, dataMemberAttribute.Name ?? property.Name);
                    maxOrder = Math.Max(maxOrder, dataMemberAttribute.Order);
                }

                // Reserve some order for well-known derived types
                maxOrder += 100;

                foreach (var type in derivedType.Value)
                {
                    @base.AddSubType(maxOrder + 1, type);
                }
            }
        }
    }
}

@mgravell Did you want to I create a PR for this helper and attribute?

@soroshsabz soroshsabz reopened this Dec 22, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant