graphql-dotnet / conventions

GraphQL Conventions Library for .NET
MIT License
233 stars 63 forks source link

Template/Generic types #164

Closed shoe-diamente closed 3 years ago

shoe-diamente commented 5 years ago

Sometimes it's useful to be able to define something like Type<OtherType> and use it as a GraphQL type. The GraphQL specification doesn't allow templated types, but with the original graphql-dotnet library you can use something like:

Name = typeof(OtherType).Name + "Type";

to achieve the expected effect of having Type<OtherType> and Type<SomeOtherType> be OtherTypeType and SomeOtherTypeType respectively.

With graphql-dotnet/conventions though you seem not to be able define the name as expected because:

[Name(nameof(OtherType)+"Type")]

would return always "OtherType" and not the underlying type. While:

[Name(typeof(OtherType).Name+"Type")]

is not a constant expression and therefore cannot be used in the attribute.

Are there any other ways to achieve a similar effect?

BilyachenkoOY commented 4 years ago

I used the following approach

// somewhere in the model
public CollectionModel<ProductLineModel> ProductLines { get; }

// generic type
[GenericName("{0}Collection")]
public class CollectionModel<TItem>
{
// my fields for collections paging
}

// custom attribute
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class GenericNameAttribute : MetaDataAttributeBase
{
    private readonly string format;

    /// <summary>
    /// Creates initialized attribute instance.
    /// </summary>
    /// <param name="format">Name format. "{0}" will be replaced with name of generic type argument.</param>
    public GenericNameAttribute(string format) => this.format = format;

    /// <inheritdoc />
    public override void MapType(GraphTypeInfo type, TypeInfo typeInfo)
    {
        var genericArg = typeInfo.GetGenericArguments().FirstOrDefault();
        string itemName;
        if (genericArg != null)
        {
            itemName = type.TypeResolver.DeriveType(genericArg.GetTypeInfo()).Name;
            if (!itemName.EndsWith("s"))
                itemName += 's';
        }
        else
        {
            itemName = "Base";
        }
        type.Name = format.FormatWith(itemName);
    }
}