
Research
2025 Report: Destructive Malware in Open Source Packages
Destructive malware is rising across open source registries, using delays and kill switches to wipe code, break builds, and disrupt CI/CD.
GraphQlClientGenerator
Advanced tools
This simple console app generates C# GraphQL query builder and data classes for simple, compiler checked, usage of a GraphQL API.
GraphQlClientGenerator.Console --serviceUrl <GraphQlServiceUrl> --outputPath <TargetPath> --namespace <TargetNamespace> [--header <header value>]
Installation:
Install-Package GraphQlClientGenerator
dotnet tool install GraphQlClientGenerator.Tool --global
graphql-client-generator --serviceUrl <GraphQlServiceUrl> --outputPath <TargetPath> --namespace <TargetNamespace> [--header <header value>]
Code example for class generation:
var schema = await GraphQlGenerator.RetrieveSchema("https://my-graphql-api/gql");
var generator = new GraphQlGenerator();
var generatedClasses = generator.GenerateFullClientCSharpFile(schema);
or using full blown setup:
var schema = await GraphQlGenerator.RetrieveSchema("https://my-graphql-api/gql");
var configuration = new GraphQlGeneratorConfiguration { TargetNamespace = "MyGqlApiClient", ... };
var generator = new GraphQlGenerator(configuration);
var builder = new StringBuilder();
using var writer = new StringWriter(builder);
var generationContext = new SingleFileGenerationContext(schema, writer) { LogMessage = Console.WriteLine };
generator.Generate(generationContext);
var csharpCode = builder.ToString();
C# 9 introduced source generators that can be attached to compilation process. Generated classes will be automatically included in project.
Project file example:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<!-- GraphQL generator properties -->
<GraphQlClientGenerator_ServiceUrl>https://api.tibber.com/v1-beta/gql</GraphQlClientGenerator_ServiceUrl>
<!-- GraphQlClientGenerator_Namespace is optional; if omitted the first compilation unit namespace will be used -->
<GraphQlClientGenerator_Namespace>$(RootNamespace)</GraphQlClientGenerator_Namespace>
<GraphQlClientGenerator_CustomClassMapping>Consumption:ConsumptionEntry|Production:ProductionEntry|RootMutation:TibberMutation|Query:Tibber</GraphQlClientGenerator_CustomClassMapping>
<!-- other GraphQL generator property values -->
</PropertyGroup>
<ItemGroup>
<PackageReference Include="GraphQlClientGenerator" Version="0.9.*" IncludeAssets="analyzers" />
<!-- AdditionalFiles and GraphQlClientGenerator_ServiceUrl are mutually exclusive -->
<!-- <AdditionalFiles Include="GqlSchemaTibberApi.gql.schema.json" CacheObjects="true" /> --> <!-- schema file name must end with ".gql.schema.json" -->
<CompilerVisibleProperty Include="GraphQlClientGenerator_ServiceUrl" />
<CompilerVisibleProperty Include="GraphQlClientGenerator_Namespace" />
<!-- other configuration properties -->
<!--<CompilerVisibleProperty Include="GraphQlClientGenerator_{ConfigurationProperty}" />-->
</ItemGroup>
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")
}
}
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
}
}
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
}
}
Queried fields can be freely renamed to match target data classes using GraphQL aliases.
new ViewerQueryBuilder("MyQuery")
.WithHome(
new HomeQueryBuilder()
.WithType()
.WithSize()
.WithAddress(new AddressQueryBuilder().WithAddress1("primaryAddressText").WithCountry(), "primaryAddress"),
Guid.NewGuid(),
"primaryHome")
.WithHome(
new HomeQueryBuilder()
.WithType()
.WithSize()
.WithAddress(new AddressQueryBuilder().WithAddress1("secondaryAddressText").WithCountry(), "secondaryAddress"),
Guid.NewGuid(),
"secondaryHome")
.Build(Formatting.Indented);
result:
query MyQuery {
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
}
}
}
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
}
}
}
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().WithId(), skip: skipDirective)
)
.WithParameter(includeDirectParameter);
result:
query (
$direct: Boolean = true) {
viewer {
name @include(if: $direct)
accountType @skip(if: true)
homes @skip(if: true) {
id
}
}
}
var builder =
new RootQueryBuilder("InlineFragments")
.WithUnion(
new UnionTypeQueryBuilder()
.WithConcreteType1Fragment(new ConcreteType1QueryBuilder().WithAllFields())
.WithConcreteType2Fragment(new ConcreteType2QueryBuilder().WithAllFields())
.WithConcreteType3Fragment(
new ConcreteType3QueryBuilder()
.WithName()
.WithConcreteType3Field("alias")
.WithFunction("my value", "myResult1")
)
)
.WithInterface(
new NamedTypeQueryBuilder()
.WithName()
.WithConcreteType3Fragment(
new ConcreteType3QueryBuilder()
.WithName()
.WithConcreteType3Field()
.WithFunction("my value")
),
Guid.Empty
);
result:
query InlineFragments {
union {
__typename
... on ConcreteType1 {
name
concreteType1Field
}
... on ConcreteType2 {
name
concreteType2Field
}
... on ConcreteType3 {
__typename
name
alias: concreteType3Field
myResult1: function(value: "my value")
}
}
interface(parameter: "00000000-0000-0000-0000-000000000000") {
name
... on ConcreteType3 {
__typename
name
concreteType3Field
function(value: "my value")
}
}
}
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 interface can be used:
var configuration = new GraphQlGeneratorConfiguration();
configuration.ScalarFieldTypeMappingProvider = new MyCustomScalarFieldTypeMappingProvider();
public class MyCustomScalarFieldTypeMappingProvider : IScalarFieldTypeMappingProvider
{
public ScalarFieldTypeDescription GetCustomScalarFieldType(ScalarFieldTypeProviderContext context)
{
var unwrappedType = context.FieldType.UnwrapIfNonNull();
return
unwrappedType.Name switch
{
"Byte" => new ScalarFieldTypeDescription { NetTypeName = GenerationContext.GetNullableNetTypeName(context, "byte", false), FormatMask = null },
"DateTime" => new ScalarFieldTypeDescription { NetTypeName = GenerationContext.GetNullableNetTypeName(context, "DateTime", false), FormatMask = null },
_ => DefaultScalarFieldTypeMappingProvider.GetFallbackFieldType(context)
};
}
}
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; }
}
Source generator supports RegexScalarFieldTypeMappingProvider rules using JSON configuration file. Example:
[
{
"patternBaseType": ".+",
"patternValueType": ".+",
"patternValueName": "^((timestamp)|(.*(f|F)rom)|(.*(t|T)o))$",
"netTypeName": "DateTimeOffset",
"isReferenceType": false,
"formatMask": "O"
}
]
All pattern values must be specified. Null values are not accepted.
The file must be named RegexScalarFieldTypeMappingProvider.gql.config.json and included as additional file.
<ItemGroup>
<AdditionalFiles Include="RegexScalarFieldTypeMappingProvider.gql.config.json" CacheObjects="true" />
</ItemGroup>
FAQs
A simple strongly typed C# GraphQL client generator library
We found that graphqlclientgenerator demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
Destructive malware is rising across open source registries, using delays and kill switches to wipe code, break builds, and disrupt CI/CD.

Security News
Socket CTO Ahmad Nassri shares practical AI coding techniques, tools, and team workflows, plus what still feels noisy and why shipping remains human-led.

Research
/Security News
A five-month operation turned 27 npm packages into durable hosting for browser-run lures that mimic document-sharing portals and Microsoft sign-in, targeting 25 organizations across manufacturing, industrial automation, plastics, and healthcare for credential theft.