Extending types with ObjectType field - hotchocolate

I'm new to HotChocolate and GraphQL, and I have some difficulties with type extension.
Is it possible to extend type with ObjectType field? I've found only one example in the documentation with StringType:
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("Person");
descriptor.Field("address")
.Type<StringType>()
.Resolver("Address");
}
I've tried to do something similar, but I have this exception HotChocolate.SchemaException: Unable to resolve type reference Output: ObjectType.
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Name("Person");
descriptor.Field("address")
.Type<ObjectType>()
.Resolver(ctx => new ObjectType(d => d.Field("street").Type<StringType>().Resolver("Street")));
}
Could you please advice any methods to extend type with ObjectType field in my case? Or just answer whether it is possible or not?
Thanks!

Suppose, your class Book contains some JSON field 'Data'. The structure of 'Data' can be changed between restarts but is known prior to any start (i.e. you know both property names and their types at the startup).
The following code addresses that case:
using System.Linq;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace Ademchenko.GraphQLWorkshop
{
public class Book
{
public int Id { get; set; }
public JObject Data { get; set; }
}
public interface IBookService { IQueryable<Book> GetAll(); }
public class InMemoryBookService : IBookService
{
private readonly Book[] _staticBooks = {
new Book {Id = 11, Data = JObject.FromObject(new {Title = "FooBook", AuthorId = 1, Price = 10.2m})},
new Book {Id = 22, Data = JObject.FromObject(new { Title = "BarBook", AuthorId = 2, Price = 20.2m})}
};
public IQueryable<Book> GetAll() => _staticBooks.AsQueryable();
}
public class Query
{
public IQueryable<Book> GetBooks(IResolverContext ctx) => ctx.Service<IBookService>().GetAll();
}
public class BookType : ObjectType<Book>
{
protected override void Configure(IObjectTypeDescriptor<Book> descriptor)
{
descriptor.Field(d => d.Data).Type<DataType>();
}
}
public class DataType : ObjectType
{
protected override void Configure(IObjectTypeDescriptor descriptor)
{
descriptor.Field("title").Type<StringType>().Resolve((ctx, ct) => (string)ctx.Parent<JObject>()["Title"]);
descriptor.Field("authorId").Type<IntType>().Resolve((ctx, ct) => (int)ctx.Parent<JObject>()["AuthorId"]);
descriptor.Field("price").Type<DecimalType>().Resolve((ctx, ct) => (decimal)ctx.Parent<JObject>()["AuthorId"]);
}
}
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration) => Configuration = configuration;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IBookService, InMemoryBookService>();
services.AddGraphQLServer()
.AddQueryType<Query>()
.AddType<BookType>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) => app.UseRouting().UseEndpoints(endpoints => endpoints.MapGraphQL());
}
}
Making the request to the server:
{
books
{
id,
data
{
title
authorId
price
}
}
}
you will get the following response:
{
"data": {
"books": [
{
"id": 11,
"data": {
"title": "FooBook",
"authorId": 1,
"price": 1
}
},
{
"id": 22,
"data": {
"title": "BarBook",
"authorId": 2,
"price": 2
}
}
]
}
}

Related

Connection Between Kendo and SQL Server in ASP.NET Core

I am trying to do CRUD operation using Kendo UI to show my data in a grid view.
I can read the data from my database with the following code in my controller (my tables are in SQL Server and connected through a connection string):
[Area("Admin")]
public class HeaderMenuController : Controller
{
private readonly IUnitOfWork _unitOfWork;
public HeaderMenuController(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
public IActionResult Index()
{
return View();
}
public DataSourceResult Products_Read([DataSourceRequest] DataSourceRequest request)
{
return _unitOfWork.HeaderMenu.GetAll().ToDataSourceResult(request);
}
}
index.cshtml
#(Html.Kendo().Grid<MSDACE.Models.HeaderViewModel>()
.Name("grid")
.Columns(columns =>
{
columns.Bound(product => product.HeaderMenuID).Width(100);
columns.Bound(product => product.HeaderMenuName);
columns.Bound(product => product.HeaderMenuDispOrder).Width(250);
columns.Command(commands =>
{
commands.Destroy(); // The "destroy" command removes data items.
}).Title("Commands").Width(200);
})
.ToolBar(toolbar =>
{
toolbar.Create(); // The "create" command adds new data items.
toolbar.Save(); // The "save" command saves the changed data items.
})
.Editable(editable => editable.Mode(GridEditMode.InCell)) // Use in-cell editing mode.
.DataSource(dataSource =>
dataSource.Ajax()
.Batch(true) // Enable batch updates.
.Model(model =>
{
model.Id(product => product.HeaderMenuID); // Specify the property which is the unique identifier of the model.
model.Field(product => product.HeaderMenuID).Editable(false); // Make the ProductID property not editable.
})
.Create(create => create.Action("Products_Create", "HeaderMenu")) // Action method invoked when the user saves a new data item.
.Read(read => read.Action("Products_Read", "HeaderMenu")) // Action method invoked when the Grid needs data.
.Update(update => update.Action("Products_Update", "HeaderMenu")) // Action method invoked when the user saves an updated data item.
.Destroy(destroy => destroy.Action("Products_Destroy", "HeaderMenu")) // Action method invoked when the user removes a data item.
)
.Pageable()
)
The problem is I can't the delete or create operations.
I have written the GetAll, Remove, Get and other functions, but my problem is to reflect in the Kendo Grid
IUnitOfWork.cs
public interface IUnitOfWork : IDisposable
{
IHeaderMenuRepository HeaderMenu { get; }
void Save();
}
IHeaderMenuRepository.cs
public class HeaderMenuRepository : Repository<HeaderMenu>, IHeaderMenuRepository
{
private readonly ApplicationDbContext _db;
public HeaderMenuRepository(ApplicationDbContext db) : base(db)
{
_db = db;
}
public IEnumerable<SelectListItem> GetHeaderMenuList()
{
return _db.HeaderMenu.Select(i => new SelectListItem()
{
Text = i.HeaderMenuName,
Value = i.HeaderMenuID.ToString()
});
}
public void Update(HeaderMenu headerMenu)
{
var objFrobDb = _db.HeaderMenu.FirstOrDefault(s => s.HeaderMenuID == headerMenu.HeaderMenuID);
objFrobDb.HeaderMenuName = headerMenu.HeaderMenuName;
objFrobDb.HeaderMenuDesc = headerMenu.HeaderMenuDesc;
objFrobDb.HeaderMenuActive = headerMenu.HeaderMenuActive;
objFrobDb.HeaderMenuDispOrder = headerMenu.HeaderMenuDispOrder;
_db.SaveChanges();
}
IRepository
public interface IRepository<T> where T : class
{
T Get(int id);
IEnumerable<T> GetAll(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = null
);
T GetFirstOrDefault(
Expression<Func<T, bool>> filter = null,
string includeProperties = null
);
void Add(T entity);
void Remove(int id);
void Remove(T entity);
}
UnitOfWork.cs
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _db;
public UnitOfWork(ApplicationDbContext db)
{
_db = db;
HeaderMenu = new HeaderMenuRepository(_db);
}
public IHeaderMenuRepository HeaderMenu { get; private set; }
public void Dispose()
{
_db.Dispose();
}
public void Save()
{
_db.SaveChanges();
}
}
Repository.cs
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext Context;
internal DbSet<T> dbSet;
public Repository(DbContext context)
{
Context = context;
this.dbSet = context.Set<T>();
}
public void Add(T entity)
{
dbSet.Add(entity);
}
public T Get(int id)
{
return dbSet.Find(id);
}
public IEnumerable<T> GetAll(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
//include properties will be comma seperated
if (includeProperties != null)
{
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
return query.ToList();
}
public T GetFirstOrDefault(Expression<Func<T, bool>> filter = null, string includeProperties = null)
{
IQueryable<T> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
//include properties will be comma seperated
if (includeProperties != null)
{
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
return query.FirstOrDefault();
}
public void Remove(int id)
{
T entityToRemove = dbSet.Find(id);
Remove(entityToRemove);
}
public void Remove(T entity)
{
dbSet.Remove(entity);
}
}
HeaderMenuRepository.cs
public class HeaderMenuRepository : Repository<HeaderMenu>, IHeaderMenuRepository
{
private readonly ApplicationDbContext _db;
public HeaderMenuRepository(ApplicationDbContext db) : base(db)
{
_db = db;
}
public IEnumerable<SelectListItem> GetHeaderMenuList()
{
return _db.HeaderMenu.Select(i => new SelectListItem()
{
Text = i.HeaderMenuName,
Value = i.HeaderMenuID.ToString()
});
}
public void Update(HeaderMenu headerMenu)
{
var objFrobDb = _db.HeaderMenu.FirstOrDefault(s => s.HeaderMenuID == headerMenu.HeaderMenuID);
objFrobDb.HeaderMenuName = headerMenu.HeaderMenuName;
objFrobDb.HeaderMenuDesc = headerMenu.HeaderMenuDesc;
objFrobDb.HeaderMenuActive = headerMenu.HeaderMenuActive;
objFrobDb.HeaderMenuDispOrder = headerMenu.HeaderMenuDispOrder;
_db.SaveChanges();
}
}
ApplicationDbContext.cs
public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// Customize the ASP.NET Identity model and override the defaults if needed.
// For example, you can rename the ASP.NET Identity table names and more.
// Add your customizations after calling base.OnModelCreating(builder);
}
public DbSet<HeaderMenu> HeaderMenu { get; set; }
}
The problem is I cant to the Delete, Create or Edit operation!
I Belive that I can not make the connection between the Kendo and my Database and have to implement something in my controller.
I try the Kendo.MVC examples but the problem are the northwind and sqllite the used!
Could please help me by this issue to implement the CRUD on Grid using the data in my sqlserver tables.

asp mvc code first update model without lossing data

I have the following DbContext:
namespace Tasks.Models
{
public class TaskDBInitializer : DropCreateDatabaseIfModelChanges<TasksContext>
{
protected override void Seed(TasksContext context)
{
var projects = new List<Projects>
{
new Projects{ Title="proTitle", Describe="proDescribe" },
};
projects.ForEach(p => context.Projects.Add(p));
context.SaveChanges();
base.Seed(context);;
}
}
public class TasksContext : DbContext
{
public TasksContext() : base("name=TaskDB")
{
Database.SetInitializer(new TaskDBInitializer());
}
public DbSet<Task> Task { get; set; }
public DbSet<Projects> Projects { set; get; }
}
}
I now want to add another model but don't want to lose data that exists within the current database.
How can I add a model to my DbContext without losing data?
Instead of using DropCreateDatabaseIfModelChanges<TContext> as your IDatabaseInitializer<TContext> use MigrateDatabaseToLatestVersion<TContext,TMigrationsConfiguration> which will determine changes within your DbContext then update your existing database to be compatible.
Here is an example of how to implement the MigrateDatabaseToLatestVersion initializer:
namespace Tasks.Models
{
public sealed class TaskDBConfiguration : DbMigrationsConfiguration<TasksContext>
{
public TaskDBConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = false;
}
protected override void Seed(TasksContext context)
{
var projects = new List<Projects>
{
new Projects { Title = "proTitle", Describe = "proDescribe" },
};
projects.ForEach(p => context.Projects.Add(p));
context.SaveChanges();
base.Seed(context);
}
}
public class TasksContext : DbContext
{
public TasksContext() : base("name=TaskDB")
{
Database.SetInitializer<TasksContext>(
new MigrateDatabaseToLatestVersion<TasksContext, TaskDBConfiguration>()
);
}
public DbSet<Task> Task { get; set; }
public DbSet<Projects> Projects { set; get; }
}
}

Is there a simple way to customize IdentityServer4's configuration and operational dababase mapping?

I'm implementing an authentication server using IS4 with an Oracle database to store the configuration and operational data.
As far as I know, Oracle has a limitation of 30 characters for tables and columns names. Because of that, some of IS4's properties causes errors when applying the database migrations, such as:
AlwaysIncludeUserClaimsInIdToken
FrontChannelLogoutSessionRequired
BackChannelLogoutSessionRequired
UpdateAccessTokenClaimsOnRefresh
In order to override the default database mapping I've:
1 - made a copy of the original ConfigurationDbContext:
public class CustomConfigurationDbContext : CustomConfigurationDbContext<CustomConfigurationDbContext>
{
public CustomConfigurationDbContext(DbContextOptions<CustomConfigurationDbContext> options, ConfigurationStoreOptions storeOptions) : base(options, storeOptions){}
}
public class CustomConfigurationDbContext<TContext> : DbContext, IConfigurationDbContext
where TContext : DbContext, IConfigurationDbContext
{
private readonly ConfigurationStoreOptions storeOptions;
public CustomConfigurationDbContext(DbContextOptions<TContext> options, ConfigurationStoreOptions storeOptions)
: base(options)
{
this.storeOptions = storeOptions ?? throw new ArgumentNullException(nameof(storeOptions));
}
public DbSet<Client> Clients { get; set; }
public DbSet<IdentityResource> IdentityResources { get; set; }
public DbSet<ApiResource> ApiResources { get; set; }
public Task<int> SaveChangesAsync()
{
return base.SaveChangesAsync();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
base.OnConfiguring(optionsBuilder);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(storeOptions.DefaultSchema);
modelBuilder.ApplyConfiguration(new ClientEntityTypeConfiguration());
}
private class ClientEntityTypeConfiguration : IEntityTypeConfiguration<Client>
{
public void Configure(EntityTypeBuilder<Client> builder)
{
builder.Property(p => p.AlwaysIncludeUserClaimsInIdToken).HasColumnName("ALWAYS_INC_USR_CLAIM_IN_TKN");
builder.Property(p => p.FrontChannelLogoutSessionRequired).HasColumnName("FRONT_LOGOUT_SESSION_REQ");
builder.Property(p => p.BackChannelLogoutSessionRequired).HasColumnName("BACK_LOGOUT_SESSION_REQ");
builder.Property(p => p.UpdateAccessTokenClaimsOnRefresh).HasColumnName("UP_ACCESS_TKN_CLAIM_ON_REFRESH");
}
}
}
2 - changed the Startup.cs:
var builder = services
.AddIdentityServer(options =>
{
...
})
.AddConfigurationStore<CustomConfigurationDbContext>(options =>
{
options.ConfigureDbContext = b =>
b.UseOracle(Configuration.GetConnectionString("Auth2Connection"), sql =>
sql.MigrationsAssembly(migrationsAssembly)
.MigrationsHistoryTable(Configuration.GetConnectionString("AppMigrationTable"), Configuration.GetConnectionString("AppSchema")));
options.DefaultSchema = Configuration.GetConnectionString("AppSchema");
})
It have actually worked, but I wonder if is there an official or simpler way to solve this problem.
You've done it in the correct way as there is no out of the box option to control the column name mapping since it is hardcoded.
There only exists an option to control the tablename mapping:
var builder = services
.AddIdentityServer(options =>
{
...
})
.AddConfigurationStore<CustomConfigurationDbContext>(options =>
{
...
options.ApiResource = new TableConfiguration("MyTableName", "MySchemaName");
})

How to call Grpahql with .Net core from a React component using Axios?

I am new to graphql and trying to implement Graphql with dot net core using graphql-dotnet library.
We do not have a dedicated database in this application. The high level flow of the application is
Front End(React)
(Calls) > GraphQlController (.Net core)
(Calls) > Sales force api
Send data back to front end.
Graphql Setup.
public class GraphQLController : ControllerBase
{
private readonly IOptions<ApplicationConfiguration> _configuration;
public GraphQLController(IOptions<ApplicationConfiguration> config)
{
this._configuration = config;
}
public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
{
var inputs = query.Variables.ToInputs();
var schema = new Schema()
{
Query = new OrderQuery(_configuration)
};
var result = await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = query.Query;
_.OperationName = query.OperationName;
_.Inputs = inputs;
}).ConfigureAwait(false);
if (result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result);
}
}
Query class
public class GraphQLQuery
{
public string OperationName { get; set; }
public string NamedQuery { get; set; }
public string Query { get; set; }
public JObject Variables { get; set; }
}
Model Class which used for the de-serialization
public class OrderModel
{
public string Id { get; set; }
public string Name { get; set; }
}
Equivalent type in Graphql
public class OrderType : ObjectGraphType<OrderModel>
{
public OrderType()
{
Name = "Order";
Field(x => x.Id).Description("The ID of the order.");
Field(x => x.Name).Description("The name of the order");
}
}
The Query class to call the sales force service
public class OrderQuery : ObjectGraphType
{
public OrderQuery(IOptions<ApplicationConfiguration> config)
{
Field<OrderType>(
"Order",
arguments: new QueryArguments(
new QueryArgument<IdGraphType> { Name = "id" }),
resolve: context =>
{
var id = context.GetArgument<object>("id");
var service = new SalesForceService(config);
var data = service.GetAccountByAccountID(id.ToString());
return data;
});
}
}
The application compiles fine in visual studio. when i press f5 and run this in the browser. I get this response
http://localhost:61625/api/graphql
{"":["The input was not valid."]}
When i try to run in postman by passing the following parameters in the body
{
OperationName:"test",
NamedQuery: "Orders",
Query:{},
Variables:{id:"123"}
}
i get this response ""A non-empty request body is required."
Can some one explain to me how do you make a request to graphql end point and what values should be passed in the below parms in postman.
{
OperationName:
NamedQuery:
Query:,
Variables:
}
How do you make a similar call from react , We are using axios:.
like below example how are parameters set for the call.
doRestCall = (id) => {
const model = {
variable: id
};
const headers = {
'Content-Type': 'application/json'
}
Axios.post('http://localhost:49776/api/graphql', model, headers)
.then(result => {
debugger;
console.log(result);
});
console.log(this.state);
};
Many thanks for the help.
It appears you're trying to use "named queries" with the use of NamedQuery, which is a design pattern with GraphQL. That design pattern is implemented by having well known queries that are pre-defined and cached on the server. Looking at your Controller you do not have named queries implemented. You will need to do a regular GraphQL query.
This is what the JavaScript would look like:
{
query: "query MyOrderQuery($id: ID) { order(id: $id) { id name } }",
variables: {
id: "123"
}
}
This would be the JSON:
{
"query": "query MyOrderQuery($id: ID) { order(id: $id) { id name } }",
"variables": {
"id": "123"
}
}
See https://graphql-dotnet.github.io/docs/getting-started/variables
I also suggest to use Apollo with your React components.
https://www.apollographql.com/docs/react/essentials/get-started.html
https://www.apollographql.com/docs/react/essentials/get-started.html#request

Parse JSON array with resttemplate

I think I have a very common problem but I cant find a solution :(
I am using spring with restTemplate to recover a JSON object like this:
ResponseEntity<Download_urls> result= restTemplate.exchange(URL, HttpMethod.GET, entity, Download_urls.class);
Where "Download_urls " class have a JSON array inside:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Download_urls {
private Video[] video;
}
And Video.class
#JsonIgnoreProperties(ignoreUnknown = true)
public class Video {
private String type;
private String label;
private String file;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getFile() {
return file;
}
public void setFile(String file) {
this.file = file;
}
}
Obviously Video[] doesn't work to map JSON array. Any help?
Thanks
UPDATE:
Example JSON payload:
{
"id": 737132,
"asset": {
"_class": "asset",
"id": 538362,
"download_urls": {
"Video": [{
"type": "video/mp4",
"label": "360"
}, {
"type": "video/mp4",
"label": "720"
}]
}
}
}
Your Java class names and its properties should follow Java naming conventions. Then your code is much more readable and nicer. And to convert JSON field names to and fro you can use naming strategies, e.g.: LowerCaseWithUnderscoresStrategy, PascalCaseStrategy, etc.
Here you are how I thing classes should look like:
Video.java - same as yours.
DownloadUrls.java:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.PascalCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
#JsonIgnoreProperties(ignoreUnknown = true)
// PascalCaseStrategy is used here because of "Video" JSON field. I would expect
// it to be called "video".
#JsonNaming(PascalCaseStrategy.class)
public class DownloadUrls {
private Video[] video;
public Video[] getVideo() {
return video;
}
public void setVideo(Video[] video) {
this.video = video;
}
}
Asset.java:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.PropertyNamingStrategy.LowerCaseWithUnderscoresStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
#JsonIgnoreProperties(ignoreUnknown = true)
// LowerCaseWithUnderscoresStrategy is common strategy when used with JSON, but
// in this case it is used because of "download_url" JSON field only.
#JsonNaming(LowerCaseWithUnderscoresStrategy.class)
public class Asset {
int id;
DownloadUrls downloadUrls;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public DownloadUrls getDownloadUrls() {
return downloadUrls;
}
public void setDownloadUrls(DownloadUrls downloadUrls) {
this.downloadUrls = downloadUrls;
}
}
and outer type just for completeness sake:
public class OuterType {
int id;
Asset asset;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Asset getAsset() {
return asset;
}
public void setAsset(Asset asset) {
this.asset = asset;
}
}
Michal
Solved!!
It was my:
private Video[] video;
tried with public attribute and it works:
public Video[] video;

Resources