{"action":"created","issue":{"url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/14","labels_url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/14/labels{/name}","comments_url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/14/comments","events_url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/14/events","html_url":"https://github.com/joemcbride/graphql-dotnet/issues/14","id":106828339,"number":14,"title":"Convention-based approach leveraging .NET's type system","user":{"login":"moserware","id":93533,"avatar_url":"https://avatars.githubusercontent.com/u/93533?v=3","gravatar_id":"","url":"https://api.github.com/users/moserware","html_url":"https://github.com/moserware","followers_url":"https://api.github.com/users/moserware/followers","following_url":"https://api.github.com/users/moserware/following{/other_user}","gists_url":"https://api.github.com/users/moserware/gists{/gist_id}","starred_url":"https://api.github.com/users/moserware/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/moserware/subscriptions","organizations_url":"https://api.github.com/users/moserware/orgs","repos_url":"https://api.github.com/users/moserware/repos","events_url":"https://api.github.com/users/moserware/events{/privacy}","received_events_url":"https://api.github.com/users/moserware/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":2,"created_at":"2015-09-16T18:07:28Z","updated_at":"2015-09-16T21:04:37Z","closed_at":null,"body":"This project looks interesting!\r\n\r\nWhat do you think about supporting a convention-based approach mapping Plain Old CLR Objects (with optional attribute annotations) using .NET's existing type system where possible?\r\n\r\nI'm just starting to look into this approach, but I thought I'd get your feedback early before getting too deep into a possible implementation.\r\n\r\nHere's a rough sketch of the idea:\r\n\r\n```\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.ComponentModel;\r\nusing System.Threading.Tasks;\r\nusing Newtonsoft.Json;\r\nusing Newtonsoft.Json.Serialization;\r\n\r\nnamespace GraphQLPlayground {\r\n    class Program {\r\n        static void Main(string[] args) {\r\n            var repository = new StarWarsRepository();\r\n            var endpoint = new GraphQLWrapper<StarWarsSchema>(new StarWarsSchema(repository));\r\n            var query = endpoint.ExecuteAsync(@\"query HeroNameAndFriendsQuery {\r\n                  hero {\r\n                    id\r\n                    name\r\n                    friends {\r\n                      name\r\n                    }\r\n                  }\r\n                }\");\r\n        }\r\n    }\r\n\r\n    public class GraphQLWrapper<TSchema> {\r\n        // TODO: can use reflection to recursively examine the TSchema to generate an internal representation if needed\r\n\r\n        public GraphQLWrapper(TSchema schema) {\r\n        }\r\n\r\n        public async Task<ExecutionResult> ExecuteAsync(string query) {\r\n            throw new NotImplementedException();\r\n        }\r\n    }\r\n\r\n    public class StarWarsSchema {\r\n        private readonly StarWarsRepository _Repository;\r\n\r\n        public StarWarsSchema(StarWarsRepository repository) {\r\n            _Repository = repository;\r\n        }\r\n\r\n        // Although we follow .NET's convention of PascalCasing, these names \r\n        // can get automatically converted to lowerCamelCase in a similiar\r\n        // mechanism as JSON.NET's CamelCasePropertyNamesContractResolver:\r\n        public async Task<StarWarsCharacter> Hero() {\r\n            return await _Repository.GetDroidAsync(3);\r\n        }\r\n\r\n        // Function arguments would get automatically mapped.\r\n        // Note the use of System.ComponentModel.DescriptionAttribute to tag things;\r\n        // this would automatically be extracted by the GraphQLWrapper.\r\n        public async Task<Human> Human([Description(\"id of the human\")] int id) {\r\n            return await _Repository.GetHumanAsync(id);\r\n        }\r\n\r\n        public async Task<Droid> Droid([Description(\"id of the droid\")] int id) {\r\n            return await _Repository.GetDroidAsync(id);\r\n        }\r\n    }\r\n    \r\n    // Abstract classes (and interfaces) can be mapped to a GraphQL interface:\r\n    public abstract class StarWarsCharacter {\r\n        private readonly StarWarsRepository _Repository;\r\n        protected StarWarsCharacter(StarWarsRepository repository) {\r\n            _Repository = repository;\r\n        }\r\n\r\n        // Fields can be either simple properties or functions.\r\n\r\n        [NotNull]\r\n        [Description(\"The id of the character.\")]\r\n        public int Id { get; set; }\r\n\r\n        [Description(\"The name of the character.\")]\r\n        public string Name { get; set; }\r\n\r\n        // If the function returns a Task, the executor will execute it asynchronously\r\n        public async Task<IList<StarWarsCharacter>> Friends() {\r\n            return await _Repository.GetFriendsForIdAsync(Id);\r\n        }\r\n\r\n        [Description(\"Which movie they appear in.\")]\r\n        public async Task<IList<Episode>> AppearsIn() {\r\n            return await _Repository.GetAppearsInById(Id);\r\n        }\r\n    }\r\n\r\n    public class Human : StarWarsCharacter {\r\n        public Human(StarWarsRepository repository) : base(repository) {\r\n        }\r\n\r\n        [Description(\"The home planet of the human.\")]\r\n        public string HomePlanet { get; set; }\r\n    }\r\n\r\n    public class Droid : StarWarsCharacter {\r\n        public Droid(StarWarsRepository repository) : base(repository) {\r\n        }\r\n\r\n        [Description(\"The primary function of the droid.\")]\r\n        public string PrimaryFunction { get; set; }\r\n    }\r\n\r\n    [Description(\"One of the films in the Star Wars Trilogy.\")]\r\n    public enum Episode {\r\n        // These could automatically get converted to SCREAMING_CAPS in the generated schema if needed:\r\n\r\n        [Description(\"Released in 1977.\")]\r\n        NewHope = 4,\r\n\r\n        [Description(\"Released in 1980.\")]\r\n        Empire = 5,\r\n\r\n        [Description(\"Released in 1983.\")]\r\n        Jedi = 6\r\n    }\r\n\r\n    // Likely defined elsewhere:\r\n\r\n    public class StarWarsRepository {\r\n        private readonly JsonSerializerSettings _JsonSerializerSettings = new JsonSerializerSettings {\r\n            ContractResolver = new CamelCasePropertyNamesContractResolver()\r\n        };\r\n\r\n        public async Task<Human> GetHumanAsync(int id) {\r\n            return await GetCharacterFromServerAsync<Human>(\"/humans/\" + id);\r\n        }\r\n\r\n        public async Task<Droid> GetDroidAsync(int id) {\r\n            return await GetCharacterFromServerAsync<Droid>(\"/droids/\" + id);\r\n        }\r\n\r\n        public async Task<IList<StarWarsCharacter>> GetFriendsForIdAsync(int id) {\r\n            // TODO: implement real logic\r\n            return new[] { await GetDroidAsync(3) };\r\n        }\r\n\r\n        public async Task<IList<Episode>> GetAppearsInById(int id) {\r\n            // TODO: implement real logic\r\n            await Task.Delay(200);\r\n            return new[] {\r\n                Episode.Empire,\r\n                Episode.Jedi,\r\n                Episode.NewHope\r\n            };\r\n        }\r\n\r\n        private async Task<T> GetCharacterFromServerAsync<T>(string relativePath) where T : StarWarsCharacter {\r\n            // TODO: implement real logic\r\n            await Task.Delay(200);\r\n            var json = @\"{\r\n                id: 3,\r\n                name: \"\"R2-D2\"\",                \r\n                primaryFunction: \"\"Astromech\"\"\r\n            }\";\r\n            var result = new Droid(this);\r\n            JsonConvert.PopulateObject(json, result, _JsonSerializerSettings);\r\n            return result as T;\r\n        }\r\n    }\r\n\r\n    public class ExecutionResult {\r\n        // TODO\r\n    }\r\n\r\n    public class NotNullAttribute : Attribute {\r\n        // TODO\r\n    }\r\n}\r\n```"},"comment":{"url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/comments/140894120","html_url":"https://github.com/joemcbride/graphql-dotnet/issues/14#issuecomment-140894120","issue_url":"https://api.github.com/repos/joemcbride/graphql-dotnet/issues/14","id":140894120,"user":{"login":"joemcbride","id":255007,"avatar_url":"https://avatars.githubusercontent.com/u/255007?v=3","gravatar_id":"","url":"https://api.github.com/users/joemcbride","html_url":"https://github.com/joemcbride","followers_url":"https://api.github.com/users/joemcbride/followers","following_url":"https://api.github.com/users/joemcbride/following{/other_user}","gists_url":"https://api.github.com/users/joemcbride/gists{/gist_id}","starred_url":"https://api.github.com/users/joemcbride/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/joemcbride/subscriptions","organizations_url":"https://api.github.com/users/joemcbride/orgs","repos_url":"https://api.github.com/users/joemcbride/repos","events_url":"https://api.github.com/users/joemcbride/events{/privacy}","received_events_url":"https://api.github.com/users/joemcbride/received_events","type":"User","site_admin":false},"created_at":"2015-09-16T21:04:37Z","updated_at":"2015-09-16T21:04:37Z","body":"I think this is a pretty good idea and I think there will be a demand for this.  Are you thinking the wrapper would just build up a traditional Schema and pass that to the Executor?"}}