@joemcbride I am trying to capture the headers received on /graphql request and then make them available in resolve methods so that this information can be used for passing to next API calls made by graphql server for resolving fields.
Based on my understanding I am planning capture headers in Middleware and then pass it to the request using _.UserContext method:
return await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = graphqlRequest.Query;
_.Inputs = inputs;
_.OperationName = graphqlRequest.OperationName;
_.UserContext = request.Headers;
_.FieldNameConverter = new DefaultFieldNameConverter();
}).ConfigureAwait(false);
This becomes available in query / root field. However, how can I access these in resolver method built by me. Do I need to pass a reference or context is supposed to be auto available there:
Field<TestAPIReturnType>( "TestAPI",
arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "id"}),
resolve: context => {
var Headers = context.UserContext;
string __id = context.GetArgument<string>("IdentityID");
if(String.IsNullOrWhiteSpace(__id)){return null;}
return TestAPIResolver.GetSingle_Resolver(__id);
}
);
You have it right. context.UserContext in resolvers will be populated with the value assigned to _.UserContext.
I would suggest to create a class so you can add other information later if needed, and you won't have to go back and edit everything.
public class GraphQLUserContext
{
public IHeaderDictionary Headers { get; set; }
}
_.UserContext = new GraphQLUserContext { Headers = request.Headers };
@joemcbride That sounds cool. Thanks!
I actually ended up using new user context to share config entries and also to share unique GraphQL request identifier that can be tagged on all API calls made under a single request.
Hi Joe/Pravinhabbu4u,
I have the same requirement as mentioned in this question. After reading your suggestions, I created a class: GraphQLUserContext as mentioned by you and registered in the ConfigureServices method in the Startup.cs class.I see that even the UserContext is set with the headers in the GraphQLController but on using it in the TestQuery class, resolve method it is still returning null and leading to error. In short I am not getting the headers in the TestQuery class resolve method.
Can you please help me how to fix this issue.
One more small request like in my current project where I am using Graphql.NET to create the new endpoints, I have to always pass the locale data by reading the incoming request headers to the business logic via the resolve methods. Since this is going to be repeated in almost every resolve method can you please guide me how to encapsulate it in one centralized place and implement it instead of repeating the duplicating the code in every resolve method.
Any help on this is much appreciated.
// Utilties
public interface IGraphQLUserContext
{
IHeaderDictionary Headers
{
get;
set;
}
//string GetCookieId();
}
public class GraphQLUserContext : IGraphQLUserContext
{
public IHeaderDictionary Headers
{
get;
set;
}
}
// GraphQLController
[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
{
if (query == null)
{
throw new ArgumentNullException(nameof(query));
}
var inputs = query.Variables.ToInputs();
var executionOptions = new ExecutionOptions{Schema = _schema, Query = query.Query, Inputs = inputs, UserContext = new GraphQLUserContext{Headers = Request.Headers}};
var result = await _documentExecuter.ExecuteAsync(executionOptions).ConfigureAwait(false);
if (result.Errors?.Count > 0)
{
return BadRequest(result);
}
return Ok(result);
}
// Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration
{
get;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddScoped(provider =>
{
var connectionString = Configuration.GetConnectionString("TestDb");
return new CMSDbContext(connectionString);
}
);
services.AddSingleton<IGraphQLUserContext, GraphQLUserContext>();
services.AddSingleton<ITestService, TestService>();
services.AddSingleton<IDocumentExecuter, DocumentExecuter>();
services.AddSingleton<TestSchema>();
var sp = services.BuildServiceProvider();
services.AddSingleton<ISchema>(new TestSchema(new FuncDependencyResolver(type => sp.GetService(type))));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseGraphiQl();
app.UseHttpsRedirection();
app.UseMvc();
}
}
// TestQuery
public class TestQuery : ObjectGraphType<object>
{
public TestQuery(ITestService testService, IGraphQLUserContext graphUserContext)
{
Field<TestResultType>("result", resolve: context =>
{
var headers = graphQLUserContext.Headers;
// Process headers to get the locale
return testService.GetDetailsForLocation(locale);
}
, description: "Location details data");
}
}
Most helpful comment
You have it right.
context.UserContextin resolvers will be populated with the value assigned to_.UserContext.I would suggest to create a class so you can add other information later if needed, and you won't have to go back and edit everything.