Skip to content

AALGate/GraphQlClientGenerator

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GraphQL C# client generator

Build NuGet Badge

This simple console app generates C# GraphQL query builder and data classes for simple, compiler checked, usage of GraphQL API.


Generator app usage

GraphQlClientGenerator <GraphQlServiceUrl> <TargetFileName> <TargetNamespace>

Nuget package

Installation:

Install-Package GraphQlClientGenerator

Code example for class generation:

var schema = await GraphQlGenerator.RetrieveSchema(url);

var builder = new StringBuilder();
GraphQlGenerator.GenerateQueryBuilder(schema, builder);
GraphQlGenerator.GenerateDataClasses(schema, builder);
	
var generatedClasses = builder.ToString();

or

var schema = await GraphQlGenerator.RetrieveSchema(url);
var csharpCode = GraphQlGenerator.GenerateFullClientCSharpFile(schema, "MyGqlApiClient");
await File.WriteAllTextAsync("MyGqlApiClient.cs", csharpCode);

Query builder usage

var builder =
  new QueryQueryBuilder()
    .WithMe(
      new MeQueryBuilder()
        .WithAllScalarFields()
        .WithHome(
          new HomeQueryBuilder()
            .WithAllScalarFields()
            .WithSubscription(
              new SubscriptionQueryBuilder()
                .WithStatus()
                .WithValidFrom())
            .WithSignupStatus(
              new SignupStatusQueryBuilder().WithAllFields())
            .WithDisaggregation(
              new DisaggregationQueryBuilder().WithAllFields()),
          "b420001d-189b-44c0-a3d5-d62452bfdd42")
        .WithEnergyStatements ("2016-06", "2016-10"));

var query = builder.Build(Formatting.Indented);

results into

query {
  me {
    id
    firstName
    lastName
    fullName
    ssn
    email
    language
    tone
    home (id: "b420001d-189b-44c0-a3d5-d62452bfdd42") {
      id
      avatar
      timeZone
      subscription {
        status
        validFrom
      }
      signupStatus {
        registrationStartedTimestamp
        registrationCompleted
        registrationCompletedTimestamp
        checkCurrentSupplierPassed
        supplierSwitchConfirmationPassed
        startDatePassed
        firstReadingReceived
        firstBillingDone
        firstBillingTimestamp
      }
      disaggregation {
        year
        month
        fixedConsumptionKwh
        fixedConsumptionKwhPercent
        heatingConsumptionKwh
        heatingConsumptionKwhPercent
        behaviorConsumptionKwh
        behaviorConsumptionKwhPercent
      }
    }
    energyStatements(from: "2016-06", to: "2016-10") 
  }
}

Mutation

var mutation =
  new MutationQueryBuilder()
    .WithUpdateHome(
      new HomeQueryBuilder().WithAllScalarFields(),
      new UpdateHomeInput { HomeId = Guid.Empty, AppNickname = "My nickname", Type = HomeType.House, NumberOfResidents = 4, Size = 160, AppAvatar = HomeAvatar.Floorhouse1, PrimaryHeatingSource = HeatingSource.Electricity }
    )
    .Build(Formatting.Indented, 2);

result:

mutation {
  updateHome (input: {
      homeId: "00000000-0000-0000-0000-000000000000"
      appNickname: "My nickname"
      appAvatar: FLOORHOUSE1
      size: 160
      type: HOUSE
      numberOfResidents: 4
      primaryHeatingSource: ELECTRICITY
    }) {
    id
    timeZone
    appNickname
    appAvatar
    size
    type
    numberOfResidents
    primaryHeatingSource
    hasVentilationSystem
  }
}

Field exclusion

Sometimes there is a need to select almost all fields of a queried object except few. In that case Except methods can be used often in conjunction with WithAllFields or WithAllScalarFields.

new ViewerQueryBuilder()
  .WithHomes(
    new HomeQueryBuilder()
      .WithAllScalarFields()
      .ExceptPrimaryHeatingSource()
      .ExceptMainFuseSize()
  )
  .Build(Formatting.Indented);

result:

query {
  homes {
    id
    timeZone
    appNickname
    appAvatar
    size
    type
    numberOfResidents
    hasVentilationSystem
  }
}

Aliases

Queried fields can be freely renamed to match target data classes using GraphQL aliases.

new ViewerQueryBuilder()
  .WithHome(
    new HomeQueryBuilder("primaryHome")
      .WithType()
      .WithSize()
      .WithAddress(new AddressQueryBuilder("primaryAddress").WithAddress1("primaryAddressText").WithCountry()),
    Guid.NewGuid())
  .WithHome(
    new HomeQueryBuilder("secondaryHome")
      .WithType()
      .WithSize()
      .WithAddress(new AddressQueryBuilder("secondaryAddress").WithAddress1("secondaryAddressText").WithCountry()),
    Guid.NewGuid())
  .Build(Formatting.Indented);

result:

query {
  primaryHome: home (id: "120efe4a-6839-45fc-beed-27455d29212f") {
    type
    size
    primaryAddress: address {
      primaryAddressText: address1
      country
    }
  }
  secondaryHome: home (id: "0c735830-be56-4a3d-a8cb-d0189037f221") {
    type
    size
    secondaryAddress: address {
      secondaryAddressText: address1
      country
    }
  }
}

Query parameters

var homeIdParameter = new GraphQlQueryParameter<Guid>("homeId", "ID", homeId);

var builder =
  new TibberQueryBuilder()
    .WithViewer(
      new ViewerQueryBuilder()
        .WithHome(new HomeQueryBuilder().WithAllScalarFields(), homeIdParameter)
    )
    .WithParameter(homeIdParameter);

result:

query ($homeId: ID = "c70dcbe5-4485-4821-933d-a8a86452737b") {
  viewer{
    home(id: $homeId) {
      id
      timeZone
      appNickname
      appAvatar
      size
      type
      numberOfResidents
      primaryHeatingSource
      hasVentilationSystem
      mainFuseSize
    }
  }
}

Directives

var includeDirectParameter = new GraphQlQueryParameter<bool>("direct", "Boolean", true);
var includeDirective = new IncludeDirective(includeDirectParameter);
var skipDirective = new SkipDirective(true);

var builder =
  new TibberQueryBuilder()
    .WithViewer(
       new ViewerQueryBuilder()
         .WithName(include: includeDirective)
         .WithAccountType(skip: skipDirective)
         .WithHomes(new HomeQueryBuilder(skip: skipDirective).WithId())
    )
    .WithParameter(includeDirectParameter);

result:

query (
  $direct: Boolean = true) {
  viewer {
    name @include(if: $direct)
    accountType @skip(if: true)
    homes @skip(if: true) {
      id
    }
  }
}

Custom scalar types

GraphQL supports custom scalar types. By default these are mapped to object type. To ensure appropriate .NET types are generated for data class properties custom mapping function can be used:

GraphQlGeneratorConfiguration.CustomScalarFieldTypeMapping =
    (baseType, valueType, valueName) =>
    {
        valueType = valueType is GraphQlFieldType fieldType ? fieldType.UnwrapIfNonNull() : valueType;

        // DateTime and Byte
        switch (valueType.Name)
        {
            case "Byte": return "byte?";
            case "DateTime": return "DateTime?";
        }

        // fallback - not needed if you cover all possible cases or you are ok with object type	
        return GraphQlGeneratorConfiguration.DefaultScalarFieldTypeMapping(baseType, valueType, valueName);
    };

Generated class example:

public class OrderType
{
    public DateTime? CreatedDateTimeUtc { get; set; }
    public byte? SomeSmallNumber { get; set; }
}

vs.

public class OrderType
{
    public object CreatedDateTimeUtc { get; set; }
    public object SomeSmallNumber { get; set; }
}

About

GraphQL C# client generator

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C# 100.0%