├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── GitVersion.yml ├── GraphQL.Annotations.sln ├── LICENSE.md ├── README.md ├── build.ps1 ├── global.json ├── pre-build.ps1 ├── samples └── GraphQL.Annotations.StarWarsApp │ ├── GraphQL.Annotations.StarWarsApp.csproj │ ├── Migrations │ ├── 20161111024839_Initial.Designer.cs │ ├── 20161111024839_Initial.cs │ ├── 20161113151618_Enum.Designer.cs │ ├── 20161113151618_Enum.cs │ └── StarWarsContextModelSnapshot.cs │ ├── Model │ ├── Droid.cs │ ├── DroidAppearance.cs │ ├── Episode.cs │ ├── Friendship.cs │ ├── Human.cs │ ├── HumanAppearance.cs │ └── ICharacter.cs │ ├── Program.cs │ ├── QueryRoot.cs │ └── StarWarsContext.cs ├── src └── GraphQL.Annotations │ ├── Attributes │ ├── GraphQLArgumentAttribute.cs │ ├── GraphQLEnumAttribute.cs │ ├── GraphQLFieldAttribute.cs │ ├── GraphQLFuncAttribute.cs │ ├── GraphQLInputObjectAttribute.cs │ ├── GraphQLInterfaceAttribute.cs │ ├── GraphQLObjectAttribute.cs │ ├── GraphQLScalarAttribute.cs │ └── GraphQLTypeAttribute.cs │ ├── GraphQL.Annotations.csproj │ ├── GraphTypeExtensions.cs │ ├── MethodResolver.cs │ ├── PropertyResolver.cs │ ├── QueryArgumentParameterInfo.cs │ ├── TypeExtensions.cs │ └── Types │ ├── EnumerationGraphType.cs │ ├── InputObjectGraphType.cs │ ├── InterfaceGraphType.cs │ ├── ObjectGraphType.cs │ ├── ScalarGraphType.cs │ └── Schema.cs ├── test └── GraphQL.Annotations.Tests │ ├── GraphQL.Annotations.Tests.csproj │ ├── MethodResolverTests.cs │ └── Types.cs └── tools ├── dotnet-install.ps1 └── dotnet-install.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific files 2 | *.suo 3 | *.user 4 | 5 | # Build files 6 | [Bb]in/ 7 | [Oo]bj/ 8 | 9 | # Project files 10 | .vs/ 11 | project.lock.json 12 | _ReSharper*/ 13 | *.[Rr]e[Ss]harper 14 | *.DotSettings.user 15 | *.sln.iml 16 | .idea/ 17 | 18 | # Miscellaneous 19 | .DS_STORE 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": ".NET Core Launch (console)", 6 | "preLaunchTask": "build", 7 | "type": "coreclr", 8 | "request": "launch", 9 | "program": "${workspaceRoot}/samples/GraphQL.Annotations.StarWarsApp/bin/Debug/netcoreapp1.0/GraphQL.Annotations.StarWarsApp.dll", 10 | "args": [], 11 | "cwd": "${workspaceRoot}/samples/GraphQL.Annotations.StarWarsApp/bin/Debug/netcoreapp1.0/", 12 | "stopAtEntry": false, 13 | "externalConsole": false 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "command": "dotnet", 6 | "args": [], 7 | "tasks": [ 8 | { 9 | "label": "build", 10 | "type": "shell", 11 | "command": "dotnet", 12 | "args": [ 13 | "build", 14 | "GraphQL.Annotations.sln" 15 | ], 16 | "problemMatcher": "$msCompile", 17 | "group": "build" 18 | }, 19 | { 20 | "label": "test", 21 | "type": "shell", 22 | "command": "dotnet", 23 | "args": [ 24 | "test", 25 | "test/GraphQL.Annotations.Tests" 26 | ], 27 | "problemMatcher": "$msCompile", 28 | "group": "test" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: ContinuousDeployment 2 | ignore: 3 | sha: [] -------------------------------------------------------------------------------- /GraphQL.Annotations.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.31515.178 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Annotations", "src\GraphQL.Annotations\GraphQL.Annotations.csproj", "{B3156391-32A5-43F7-8E17-11D8EAF54E2A}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Annotations.StarWarsApp", "samples\GraphQL.Annotations.StarWarsApp\GraphQL.Annotations.StarWarsApp.csproj", "{D1185652-4CA3-48B0-BB5A-59548BF44267}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphQL.Annotations.Tests", "test\GraphQL.Annotations.Tests\GraphQL.Annotations.Tests.csproj", "{599F8950-BD3C-4079-8C09-EB1E13BDD0B4}" 10 | EndProject 11 | Global 12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 13 | Debug|Any CPU = Debug|Any CPU 14 | Debug|x64 = Debug|x64 15 | Debug|x86 = Debug|x86 16 | Release|Any CPU = Release|Any CPU 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|x64.ActiveCfg = Debug|Any CPU 24 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|x64.Build.0 = Debug|Any CPU 25 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|x86.ActiveCfg = Debug|Any CPU 26 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Debug|x86.Build.0 = Debug|Any CPU 27 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|x64.ActiveCfg = Release|Any CPU 30 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|x64.Build.0 = Release|Any CPU 31 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|x86.ActiveCfg = Release|Any CPU 32 | {B3156391-32A5-43F7-8E17-11D8EAF54E2A}.Release|x86.Build.0 = Release|Any CPU 33 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|x64.ActiveCfg = Debug|Any CPU 36 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|x64.Build.0 = Debug|Any CPU 37 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|x86.ActiveCfg = Debug|Any CPU 38 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Debug|x86.Build.0 = Debug|Any CPU 39 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|x64.ActiveCfg = Release|Any CPU 42 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|x64.Build.0 = Release|Any CPU 43 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|x86.ActiveCfg = Release|Any CPU 44 | {D1185652-4CA3-48B0-BB5A-59548BF44267}.Release|x86.Build.0 = Release|Any CPU 45 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|x64.ActiveCfg = Debug|x64 48 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|x64.Build.0 = Debug|x64 49 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|x86.ActiveCfg = Debug|x86 50 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Debug|x86.Build.0 = Debug|x86 51 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU 52 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|Any CPU.Build.0 = Release|Any CPU 53 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|x64.ActiveCfg = Release|x64 54 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|x64.Build.0 = Release|x64 55 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|x86.ActiveCfg = Release|x86 56 | {599F8950-BD3C-4079-8C09-EB1E13BDD0B4}.Release|x86.Build.0 = Release|x86 57 | EndGlobalSection 58 | GlobalSection(SolutionProperties) = preSolution 59 | HideSolutionNode = FALSE 60 | EndGlobalSection 61 | GlobalSection(ExtensibilityGlobals) = postSolution 62 | SolutionGuid = {724F18BC-BD11-4C80-81ED-CC011F7186E2} 63 | EndGlobalSection 64 | EndGlobal 65 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Daniel Zimmermann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GraphQL.Annotations 2 | 3 | [![NuGet](https://img.shields.io/nuget/v/DataLoader.svg)](https://nuget.org/packages/GraphQL.Annotations) 4 | [![MyGet Pre Release](https://img.shields.io/myget/dlukez/vpre/GraphQL.Annotations.svg?label=myget)](https://www.myget.org/feed/dlukez/package/nuget/GraphQL.Annotations) 5 | [![MyGet Build Status](https://www.myget.org/BuildSource/Badge/dlukez?identifier=265cd302-0184-43af-abc8-6041143cfc91)](https://www.myget.org/feed/dlukez/package/nuget/GraphQL.Annotations) 6 | 7 | Attribute-based schema definitions for [GraphQL](https://github.com/graphql-dotnet/graphql-dotnet) in .NET. 8 | 9 | Have a look through the sample app and leave some feedback. 10 | 11 | ## Running the sample app 12 | 13 | ``` 14 | cd samples/GraphQL.Annotations.StarWarsApp/ 15 | dotnet build 16 | dotnet ef database update 17 | dotnet run 18 | ``` 19 | 20 | ## Annotating your models 21 | 22 | [Full example](tree/master/samples/GraphQL.Annotations.StarWarsApp/) 23 | 24 | ```csharp 25 | // Model/QueryRoot.cs 26 | 27 | [GraphQLObject] 28 | public class QueryRoot : IDisposable 29 | { 30 | public StarWarsContext Db = new StarWarsContext(); 31 | 32 | [GraphQLFunc] 33 | public IEnumerable Droids(ResolveFieldContext context) 34 | { 35 | var db = context.GetDataContext(); 36 | return db.Droids.ToList(); 37 | } 38 | 39 | [GraphQLFunc] 40 | public IEnumerable Humans(ResolveFieldContext context) 41 | { 42 | var db = context.GetDataContext(); 43 | return db.Humans.ToList(); 44 | } 45 | 46 | public void Dispose() 47 | { 48 | Db.Dispose(); 49 | } 50 | } 51 | 52 | // Model/Droid.cs 53 | 54 | [GraphQLObject] 55 | public class Droid : ICharacter 56 | { 57 | [GraphQLField] 58 | public int DroidId { get; set; } 59 | 60 | [GraphQLField] 61 | public string Name { get; set; } 62 | 63 | [GraphQLField] 64 | public string PrimaryFunction { get; set; } 65 | 66 | [GraphQLFunc] 67 | public IEnumerable Friends(ResolveFieldContext context) 68 | { 69 | var db = context.GetDataContext(); 70 | return db.Friendships 71 | .Where(f => f.DroidId == ((Droid)context.Source).DroidId) 72 | .Select(f => f.Human); 73 | } 74 | } 75 | ``` 76 | 77 | ## Todo 78 | + Include Scalar example. 79 | + Fill out this readme. 80 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | # Setup 2 | $ErrorActionPreference = "Stop" 3 | if (-not $env:Configuration) { $env:Configuration = "Release" } 4 | if (-not $env:BuildRunner) { $env:PackageVersion = (gitversion | ConvertFrom-Json).NuGetVersionV2 } 5 | 6 | # Helper functions 7 | function Invoke-BuildStep { param([scriptblock]$cmd) & $cmd; if ($LASTEXITCODE -ne 0) { exit 1 } } 8 | 9 | # Build 10 | Invoke-BuildStep { dotnet restore src/GraphQL.Annotations/GraphQL.Annotations.csproj } 11 | Invoke-BuildStep { dotnet pack src/GraphQL.Annotations/GraphQL.Annotations.csproj --include-symbols } 12 | 13 | Invoke-BuildStep { dotnet restore test/GraphQL.Annotations.Tests/GraphQL.Annotations.Tests.csproj } 14 | Invoke-BuildStep { dotnet test test/GraphQL.Annotations.Tests/GraphQL.Annotations.Tests.csproj } 15 | 16 | # End 17 | exit 18 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": [ "src", "test" ], 3 | "sdk": { 4 | "version": "5.0.302" 5 | } 6 | } -------------------------------------------------------------------------------- /pre-build.ps1: -------------------------------------------------------------------------------- 1 | # Run GitVersion 2 | Invoke-Expression "$env:GitVersion /output buildserver" 3 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/GraphQL.Annotations.StarWarsApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | GraphQL.Annotations.StarWarsApp 5 | net5.0 6 | Exe 7 | portable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Migrations/20161111024839_Initial.Designer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using Microsoft.EntityFrameworkCore.Metadata; 4 | using Microsoft.EntityFrameworkCore.Migrations; 5 | 6 | namespace GraphQL.Annotations.StarWarsApp.Migrations 7 | { 8 | [DbContext(typeof(StarWarsContext))] 9 | [Migration("20161111024839_Initial")] 10 | partial class Initial 11 | { 12 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 13 | { 14 | modelBuilder 15 | .HasAnnotation("ProductVersion", "1.0.1"); 16 | 17 | modelBuilder.Entity("GraphQL.Annotations.StarWars.Droid", b => 18 | { 19 | b.Property("DroidId") 20 | .ValueGeneratedOnAdd(); 21 | 22 | b.Property("Name"); 23 | 24 | b.Property("PrimaryFunction"); 25 | 26 | b.HasKey("DroidId"); 27 | 28 | b.ToTable("Droids"); 29 | }); 30 | 31 | modelBuilder.Entity("GraphQL.Annotations.StarWars.Friendship", b => 32 | { 33 | b.Property("FriendshipId") 34 | .ValueGeneratedOnAdd(); 35 | 36 | b.Property("DroidId"); 37 | 38 | b.Property("HumanId"); 39 | 40 | b.HasKey("FriendshipId"); 41 | 42 | b.HasIndex("DroidId"); 43 | 44 | b.HasIndex("HumanId"); 45 | 46 | b.ToTable("Friendships"); 47 | }); 48 | 49 | modelBuilder.Entity("GraphQL.Annotations.StarWars.Human", b => 50 | { 51 | b.Property("HumanId") 52 | .ValueGeneratedOnAdd(); 53 | 54 | b.Property("HomePlanet"); 55 | 56 | b.Property("Name"); 57 | 58 | b.HasKey("HumanId"); 59 | 60 | b.ToTable("Humans"); 61 | }); 62 | 63 | modelBuilder.Entity("GraphQL.Annotations.StarWars.Friendship", b => 64 | { 65 | b.HasOne("GraphQL.Annotations.StarWars.Droid", "Droid") 66 | .WithMany("Friendships") 67 | .HasForeignKey("DroidId") 68 | .OnDelete(DeleteBehavior.Cascade); 69 | 70 | b.HasOne("GraphQL.Annotations.StarWars.Human", "Human") 71 | .WithMany("Friendships") 72 | .HasForeignKey("HumanId") 73 | .OnDelete(DeleteBehavior.Cascade); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Migrations/20161111024839_Initial.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace GraphQL.Annotations.StarWarsApp.Migrations 4 | { 5 | public partial class Initial : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.CreateTable( 10 | name: "Droids", 11 | columns: table => new 12 | { 13 | DroidId = table.Column(nullable: false) 14 | .Annotation("Autoincrement", true), 15 | Name = table.Column(nullable: true), 16 | PrimaryFunction = table.Column(nullable: true) 17 | }, 18 | constraints: table => 19 | { 20 | table.PrimaryKey("PK_Droids", x => x.DroidId); 21 | }); 22 | 23 | migrationBuilder.CreateTable( 24 | name: "Humans", 25 | columns: table => new 26 | { 27 | HumanId = table.Column(nullable: false) 28 | .Annotation("Autoincrement", true), 29 | HomePlanet = table.Column(nullable: true), 30 | Name = table.Column(nullable: true) 31 | }, 32 | constraints: table => 33 | { 34 | table.PrimaryKey("PK_Humans", x => x.HumanId); 35 | }); 36 | 37 | migrationBuilder.CreateTable( 38 | name: "Friendships", 39 | columns: table => new 40 | { 41 | FriendshipId = table.Column(nullable: false) 42 | .Annotation("Autoincrement", true), 43 | DroidId = table.Column(nullable: false), 44 | HumanId = table.Column(nullable: false) 45 | }, 46 | constraints: table => 47 | { 48 | table.PrimaryKey("PK_Friendships", x => x.FriendshipId); 49 | table.ForeignKey( 50 | name: "FK_Friendships_Droids_DroidId", 51 | column: x => x.DroidId, 52 | principalTable: "Droids", 53 | principalColumn: "DroidId", 54 | onDelete: ReferentialAction.Cascade); 55 | table.ForeignKey( 56 | name: "FK_Friendships_Humans_HumanId", 57 | column: x => x.HumanId, 58 | principalTable: "Humans", 59 | principalColumn: "HumanId", 60 | onDelete: ReferentialAction.Cascade); 61 | }); 62 | 63 | migrationBuilder.CreateIndex( 64 | name: "IX_Friendships_DroidId", 65 | table: "Friendships", 66 | column: "DroidId"); 67 | 68 | migrationBuilder.CreateIndex( 69 | name: "IX_Friendships_HumanId", 70 | table: "Friendships", 71 | column: "HumanId"); 72 | } 73 | 74 | protected override void Down(MigrationBuilder migrationBuilder) 75 | { 76 | migrationBuilder.DropTable( 77 | name: "Friendships"); 78 | 79 | migrationBuilder.DropTable( 80 | name: "Droids"); 81 | 82 | migrationBuilder.DropTable( 83 | name: "Humans"); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Migrations/20161113151618_Enum.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using GraphQL.Annotations.StarWarsApp; 7 | 8 | namespace GraphQL.Annotations.StarWarsApp.Migrations 9 | { 10 | [DbContext(typeof(StarWarsContext))] 11 | [Migration("20161113151618_Enum")] 12 | partial class Enum 13 | { 14 | protected override void BuildTargetModel(ModelBuilder modelBuilder) 15 | { 16 | modelBuilder 17 | .HasAnnotation("ProductVersion", "1.0.1"); 18 | 19 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Droid", b => 20 | { 21 | b.Property("DroidId") 22 | .ValueGeneratedOnAdd(); 23 | 24 | b.Property("Name"); 25 | 26 | b.Property("PrimaryFunction"); 27 | 28 | b.HasKey("DroidId"); 29 | 30 | b.ToTable("Droids"); 31 | }); 32 | 33 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.DroidAppearance", b => 34 | { 35 | b.Property("DroidAppearanceId") 36 | .ValueGeneratedOnAdd(); 37 | 38 | b.Property("DroidId"); 39 | 40 | b.Property("Episode"); 41 | 42 | b.Property("Title"); 43 | 44 | b.HasKey("DroidAppearanceId"); 45 | 46 | b.HasIndex("DroidId"); 47 | 48 | b.ToTable("DroidAppearances"); 49 | }); 50 | 51 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Friendship", b => 52 | { 53 | b.Property("FriendshipId") 54 | .ValueGeneratedOnAdd(); 55 | 56 | b.Property("DroidId"); 57 | 58 | b.Property("HumanId"); 59 | 60 | b.HasKey("FriendshipId"); 61 | 62 | b.HasIndex("DroidId"); 63 | 64 | b.HasIndex("HumanId"); 65 | 66 | b.ToTable("Friendships"); 67 | }); 68 | 69 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Human", b => 70 | { 71 | b.Property("HumanId") 72 | .ValueGeneratedOnAdd(); 73 | 74 | b.Property("HomePlanet"); 75 | 76 | b.Property("Name"); 77 | 78 | b.HasKey("HumanId"); 79 | 80 | b.ToTable("Humans"); 81 | }); 82 | 83 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.HumanAppearance", b => 84 | { 85 | b.Property("HumanAppearanceId") 86 | .ValueGeneratedOnAdd(); 87 | 88 | b.Property("Episode"); 89 | 90 | b.Property("HumanId"); 91 | 92 | b.Property("Title"); 93 | 94 | b.HasKey("HumanAppearanceId"); 95 | 96 | b.HasIndex("HumanId"); 97 | 98 | b.ToTable("HumanAppearances"); 99 | }); 100 | 101 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.DroidAppearance", b => 102 | { 103 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Droid", "Droid") 104 | .WithMany("Appearances") 105 | .HasForeignKey("DroidId") 106 | .OnDelete(DeleteBehavior.Cascade); 107 | }); 108 | 109 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Friendship", b => 110 | { 111 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Droid", "Droid") 112 | .WithMany() 113 | .HasForeignKey("DroidId") 114 | .OnDelete(DeleteBehavior.Cascade); 115 | 116 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Human", "Human") 117 | .WithMany() 118 | .HasForeignKey("HumanId") 119 | .OnDelete(DeleteBehavior.Cascade); 120 | }); 121 | 122 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.HumanAppearance", b => 123 | { 124 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Human", "Human") 125 | .WithMany("Appearances") 126 | .HasForeignKey("HumanId") 127 | .OnDelete(DeleteBehavior.Cascade); 128 | }); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Migrations/20161113151618_Enum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.EntityFrameworkCore.Migrations; 4 | 5 | namespace GraphQL.Annotations.StarWarsApp.Migrations 6 | { 7 | public partial class Enum : Migration 8 | { 9 | protected override void Up(MigrationBuilder migrationBuilder) 10 | { 11 | migrationBuilder.CreateTable( 12 | name: "DroidAppearances", 13 | columns: table => new 14 | { 15 | DroidAppearanceId = table.Column(nullable: false) 16 | .Annotation("Autoincrement", true), 17 | DroidId = table.Column(nullable: false), 18 | Episode = table.Column(nullable: false), 19 | Title = table.Column(nullable: true) 20 | }, 21 | constraints: table => 22 | { 23 | table.PrimaryKey("PK_DroidAppearances", x => x.DroidAppearanceId); 24 | table.ForeignKey( 25 | name: "FK_DroidAppearances_Droids_DroidId", 26 | column: x => x.DroidId, 27 | principalTable: "Droids", 28 | principalColumn: "DroidId", 29 | onDelete: ReferentialAction.Cascade); 30 | }); 31 | 32 | migrationBuilder.CreateTable( 33 | name: "HumanAppearances", 34 | columns: table => new 35 | { 36 | HumanAppearanceId = table.Column(nullable: false) 37 | .Annotation("Autoincrement", true), 38 | Episode = table.Column(nullable: false), 39 | HumanId = table.Column(nullable: false), 40 | Title = table.Column(nullable: true) 41 | }, 42 | constraints: table => 43 | { 44 | table.PrimaryKey("PK_HumanAppearances", x => x.HumanAppearanceId); 45 | table.ForeignKey( 46 | name: "FK_HumanAppearances_Humans_HumanId", 47 | column: x => x.HumanId, 48 | principalTable: "Humans", 49 | principalColumn: "HumanId", 50 | onDelete: ReferentialAction.Cascade); 51 | }); 52 | 53 | migrationBuilder.CreateIndex( 54 | name: "IX_DroidAppearances_DroidId", 55 | table: "DroidAppearances", 56 | column: "DroidId"); 57 | 58 | migrationBuilder.CreateIndex( 59 | name: "IX_HumanAppearances_HumanId", 60 | table: "HumanAppearances", 61 | column: "HumanId"); 62 | } 63 | 64 | protected override void Down(MigrationBuilder migrationBuilder) 65 | { 66 | migrationBuilder.DropTable( 67 | name: "DroidAppearances"); 68 | 69 | migrationBuilder.DropTable( 70 | name: "HumanAppearances"); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Migrations/StarWarsContextModelSnapshot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore.Infrastructure; 4 | using Microsoft.EntityFrameworkCore.Metadata; 5 | using Microsoft.EntityFrameworkCore.Migrations; 6 | using GraphQL.Annotations.StarWarsApp; 7 | 8 | namespace GraphQL.Annotations.StarWarsApp.Migrations 9 | { 10 | [DbContext(typeof(StarWarsContext))] 11 | partial class StarWarsContextModelSnapshot : ModelSnapshot 12 | { 13 | protected override void BuildModel(ModelBuilder modelBuilder) 14 | { 15 | modelBuilder 16 | .HasAnnotation("ProductVersion", "1.0.1"); 17 | 18 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Droid", b => 19 | { 20 | b.Property("DroidId") 21 | .ValueGeneratedOnAdd(); 22 | 23 | b.Property("Name"); 24 | 25 | b.Property("PrimaryFunction"); 26 | 27 | b.HasKey("DroidId"); 28 | 29 | b.ToTable("Droids"); 30 | }); 31 | 32 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.DroidAppearance", b => 33 | { 34 | b.Property("DroidAppearanceId") 35 | .ValueGeneratedOnAdd(); 36 | 37 | b.Property("DroidId"); 38 | 39 | b.Property("Episode"); 40 | 41 | b.Property("Title"); 42 | 43 | b.HasKey("DroidAppearanceId"); 44 | 45 | b.HasIndex("DroidId"); 46 | 47 | b.ToTable("DroidAppearances"); 48 | }); 49 | 50 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Friendship", b => 51 | { 52 | b.Property("FriendshipId") 53 | .ValueGeneratedOnAdd(); 54 | 55 | b.Property("DroidId"); 56 | 57 | b.Property("HumanId"); 58 | 59 | b.HasKey("FriendshipId"); 60 | 61 | b.HasIndex("DroidId"); 62 | 63 | b.HasIndex("HumanId"); 64 | 65 | b.ToTable("Friendships"); 66 | }); 67 | 68 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Human", b => 69 | { 70 | b.Property("HumanId") 71 | .ValueGeneratedOnAdd(); 72 | 73 | b.Property("HomePlanet"); 74 | 75 | b.Property("Name"); 76 | 77 | b.HasKey("HumanId"); 78 | 79 | b.ToTable("Humans"); 80 | }); 81 | 82 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.HumanAppearance", b => 83 | { 84 | b.Property("HumanAppearanceId") 85 | .ValueGeneratedOnAdd(); 86 | 87 | b.Property("Episode"); 88 | 89 | b.Property("HumanId"); 90 | 91 | b.Property("Title"); 92 | 93 | b.HasKey("HumanAppearanceId"); 94 | 95 | b.HasIndex("HumanId"); 96 | 97 | b.ToTable("HumanAppearances"); 98 | }); 99 | 100 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.DroidAppearance", b => 101 | { 102 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Droid", "Droid") 103 | .WithMany("Appearances") 104 | .HasForeignKey("DroidId") 105 | .OnDelete(DeleteBehavior.Cascade); 106 | }); 107 | 108 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.Friendship", b => 109 | { 110 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Droid", "Droid") 111 | .WithMany() 112 | .HasForeignKey("DroidId") 113 | .OnDelete(DeleteBehavior.Cascade); 114 | 115 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Human", "Human") 116 | .WithMany() 117 | .HasForeignKey("HumanId") 118 | .OnDelete(DeleteBehavior.Cascade); 119 | }); 120 | 121 | modelBuilder.Entity("GraphQL.Annotations.StarWarsApp.Model.HumanAppearance", b => 122 | { 123 | b.HasOne("GraphQL.Annotations.StarWarsApp.Model.Human", "Human") 124 | .WithMany("Appearances") 125 | .HasForeignKey("HumanId") 126 | .OnDelete(DeleteBehavior.Cascade); 127 | }); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/Droid.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using GraphQL.Annotations.Attributes; 4 | using GraphQL.Types; 5 | 6 | namespace GraphQL.Annotations.StarWarsApp.Model 7 | { 8 | [GraphQLObject] 9 | public class Droid : ICharacter 10 | { 11 | [GraphQLField] 12 | public int DroidId { get; set; } 13 | 14 | [GraphQLField] 15 | public string Name { get; set; } 16 | 17 | [GraphQLField] 18 | public string PrimaryFunction { get; set; } 19 | 20 | public List Appearances { get; set; } 21 | 22 | [GraphQLFunc] 23 | public IEnumerable AppearsIn(IResolveFieldContext context) 24 | { 25 | var db = context.GetDataContext(); 26 | var droid = (Droid)context.Source; 27 | return db.DroidAppearances 28 | .Where(a => droid.DroidId == a.DroidId) 29 | .Select(a => a.Episode); 30 | } 31 | 32 | [GraphQLFunc] 33 | public IEnumerable Friends(IResolveFieldContext context) 34 | { 35 | var db = context.GetDataContext(); 36 | return db.Friendships 37 | .Where(f => f.DroidId == ((Droid)context.Source).DroidId) 38 | .Select(f => f.Human); 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return $"{GetType().Name} {DroidId}"; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/DroidAppearance.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Annotations.StarWarsApp.Model 2 | { 3 | public class DroidAppearance 4 | { 5 | public int DroidAppearanceId { get; set; } 6 | public Episode Episode { get; set; } 7 | public string Title { get; set; } 8 | public int DroidId { get; set; } 9 | public Droid Droid { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/Episode.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Annotations.Attributes; 2 | 3 | namespace GraphQL.Annotations.StarWarsApp.Model 4 | { 5 | [GraphQLEnum] 6 | public enum Episode 7 | { 8 | ANewHope = 4, 9 | EmpireStrikesBack = 5, 10 | ReturnOfTheJedi = 6 11 | } 12 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/Friendship.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Annotations.Attributes; 2 | 3 | namespace GraphQL.Annotations.StarWarsApp.Model 4 | { 5 | [GraphQLObject] 6 | public class Friendship 7 | { 8 | public int FriendshipId { get; set; } 9 | public int HumanId { get; set; } 10 | public int DroidId { get; set; } 11 | public Human Human { get; set; } 12 | public Droid Droid { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/Human.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using GraphQL.Annotations.Attributes; 4 | using GraphQL.Types; 5 | 6 | namespace GraphQL.Annotations.StarWarsApp.Model 7 | { 8 | [GraphQLObject] 9 | public class Human : ICharacter 10 | { 11 | [GraphQLField] 12 | public int HumanId { get; set; } 13 | 14 | [GraphQLField] 15 | public string Name { get; set; } 16 | 17 | [GraphQLField] 18 | public string HomePlanet { get; set; } 19 | 20 | public List Appearances { get; set; } 21 | 22 | [GraphQLFunc] 23 | public IEnumerable AppearsIn(IResolveFieldContext context) 24 | { 25 | var db = context.GetDataContext(); 26 | var human = this; 27 | return db.HumanAppearances 28 | .Where(a => human.HumanId == a.HumanId) 29 | .Select(a => a.Episode); 30 | } 31 | 32 | [GraphQLFunc] 33 | public IEnumerable Friends(IResolveFieldContext context) 34 | { 35 | var db = context.GetDataContext(); 36 | return db.Friendships 37 | .Where(f => f.HumanId == HumanId) 38 | .Select(f => f.Droid); 39 | } 40 | 41 | public override string ToString() 42 | { 43 | return $"{GetType().Name} {HumanId}"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/HumanAppearance.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Annotations.StarWarsApp.Model 2 | { 3 | public class HumanAppearance 4 | { 5 | public int HumanAppearanceId { get; set; } 6 | public Episode Episode { get; set; } 7 | public string Title { get; set; } 8 | public int HumanId { get; set; } 9 | public Human Human { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Model/ICharacter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using GraphQL.Annotations.Attributes; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.Annotations.StarWarsApp.Model 6 | { 7 | [GraphQLInterface] 8 | public interface ICharacter 9 | { 10 | [GraphQLField] 11 | string Name { get; set; } 12 | 13 | [GraphQLFunc] 14 | IEnumerable Friends(IResolveFieldContext context); 15 | } 16 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using GraphQL.Annotations.StarWarsApp.Model; 5 | using GraphQL.Annotations.Types; 6 | using GraphQL.SystemTextJson; 7 | 8 | namespace GraphQL.Annotations.StarWarsApp 9 | { 10 | public class Program 11 | { 12 | public static void Main(string[] args) 13 | { 14 | InitTestData(); 15 | Console.WriteLine(); 16 | RunQuery(); 17 | Console.WriteLine(); 18 | } 19 | 20 | private static async void RunQuery() 21 | { 22 | var query = @" { 23 | droids { 24 | droidId 25 | name 26 | primaryFunction 27 | appearsIn 28 | friends { 29 | name 30 | ... on Human { 31 | humanId 32 | appearsIn 33 | } 34 | } 35 | } 36 | }"; 37 | 38 | var executer = new DocumentExecuter(); 39 | var writer = new DocumentWriter(true); 40 | string output1, output2; 41 | 42 | // Example 1 - QueryRoot. 43 | using (var root = new QueryRoot()) 44 | using (var schema = new Schema()) 45 | { 46 | var result = executer.ExecuteAsync(new ExecutionOptions() 47 | { 48 | Schema = schema, 49 | Root = root, 50 | Query = query 51 | }); 52 | 53 | output1 = await writer.WriteToStringAsync(result.Result); 54 | Console.WriteLine("Example 1 output (QueryRoot):"); 55 | Console.WriteLine("-----------------------------"); 56 | Console.WriteLine(output1); 57 | Console.WriteLine(); 58 | } 59 | 60 | // Example 2 - DbContext. 61 | // I get the feeling there are reasons why wouldn't 62 | // want to do this but for simple scenarios it seems to suffice. 63 | await using (var db = new StarWarsContext()) 64 | using (var schema = new Schema()) 65 | { 66 | var result = executer.ExecuteAsync(new ExecutionOptions() 67 | { 68 | Schema = schema, 69 | Root = db, 70 | Query = query 71 | }); 72 | 73 | output2 = await writer.WriteToStringAsync(result.Result); 74 | Console.WriteLine("Example 2 output (StarWarsContext):"); 75 | Console.WriteLine("-----------------------------------"); 76 | Console.WriteLine(output2); 77 | Console.WriteLine(); 78 | } 79 | 80 | // Confirm we got the same result, just 'cause... 81 | var defaultColor = Console.ForegroundColor; 82 | if (output1 == output2) 83 | { 84 | Console.ForegroundColor = ConsoleColor.DarkGreen; 85 | Console.WriteLine("✓ Outputs are the same"); 86 | } 87 | else 88 | { 89 | Console.ForegroundColor = ConsoleColor.DarkRed; 90 | Console.WriteLine("× Outputs are different"); 91 | } 92 | Console.ForegroundColor = defaultColor; 93 | } 94 | 95 | private static void InitTestData() 96 | { 97 | using (var db = new StarWarsContext()) 98 | { 99 | if (db.Humans.Any()) 100 | return; 101 | 102 | db.Droids.RemoveRange(db.Droids); 103 | db.Humans.RemoveRange(db.Humans); 104 | db.Friendships.RemoveRange(db.Friendships); 105 | db.DroidAppearances.RemoveRange(db.DroidAppearances); 106 | db.HumanAppearances.RemoveRange(db.HumanAppearances); 107 | 108 | var luke = new Human 109 | { 110 | HumanId = 1, 111 | Name = "Luke", 112 | HomePlanet = "Tatooine", 113 | Appearances = new List 114 | { 115 | new HumanAppearance { Episode = Episode.EmpireStrikesBack }, 116 | new HumanAppearance { Episode = Episode.ANewHope } 117 | } 118 | }; 119 | 120 | var vader = new Human 121 | { 122 | HumanId = 2, 123 | Name = "Vader", 124 | HomePlanet = "Tatooine" 125 | }; 126 | 127 | var ash = new Human 128 | { 129 | HumanId = 3, 130 | Name = "Ash", 131 | HomePlanet = "Cromwell" 132 | }; 133 | 134 | db.Humans.Add(luke); 135 | db.Humans.Add(vader); 136 | db.Humans.Add(ash); 137 | 138 | var r2d2 = new Droid 139 | { 140 | DroidId = 1, 141 | Name = "R2-D2", 142 | PrimaryFunction = "Astromech" 143 | }; 144 | 145 | var c3p0 = new Droid 146 | { 147 | DroidId = 2, 148 | Name = "C-3PO", 149 | PrimaryFunction = "Protocol", 150 | Appearances = new List 151 | { 152 | new DroidAppearance { Episode = Episode.ReturnOfTheJedi } 153 | } 154 | }; 155 | 156 | db.Droids.Add(r2d2); 157 | db.Droids.Add(c3p0); 158 | 159 | db.Friendships.Add(new Friendship 160 | { 161 | HumanId = luke.HumanId, 162 | DroidId = r2d2.DroidId 163 | }); 164 | 165 | db.Friendships.Add(new Friendship 166 | { 167 | HumanId = luke.HumanId, 168 | DroidId = c3p0.DroidId 169 | }); 170 | 171 | db.Friendships.Add(new Friendship 172 | { 173 | HumanId = vader.HumanId, 174 | DroidId = r2d2.DroidId 175 | }); 176 | 177 | db.Friendships.Add(new Friendship 178 | { 179 | HumanId = ash.HumanId, 180 | DroidId = c3p0.DroidId 181 | }); 182 | 183 | var count = db.SaveChanges(); 184 | Console.WriteLine("{0} records saved to database", count); 185 | } 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/QueryRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using GraphQL.Annotations.Attributes; 5 | using GraphQL.Annotations.StarWarsApp.Model; 6 | using GraphQL.Types; 7 | 8 | namespace GraphQL.Annotations.StarWarsApp 9 | { 10 | [GraphQLObject] 11 | public class QueryRoot : IDisposable 12 | { 13 | public StarWarsContext Db { get; } = new StarWarsContext(); 14 | 15 | [GraphQLFunc] 16 | public IEnumerable Droids(IResolveFieldContext context) 17 | { 18 | var db = context.GetDataContext(); 19 | return db.Droids.ToList(); 20 | } 21 | 22 | [GraphQLFunc] 23 | public IEnumerable Humans(IResolveFieldContext context) 24 | { 25 | var db = context.GetDataContext(); 26 | return db.Humans.ToList(); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | Db.Dispose(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /samples/GraphQL.Annotations.StarWarsApp/StarWarsContext.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Annotations.Attributes; 2 | using GraphQL.Annotations.StarWarsApp.Model; 3 | using GraphQL.Types; 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace GraphQL.Annotations.StarWarsApp 7 | { 8 | [GraphQLObject] 9 | public class StarWarsContext : DbContext 10 | { 11 | [GraphQLField] 12 | public DbSet Humans { get; set; } 13 | 14 | [GraphQLField] 15 | public DbSet Droids { get; set; } 16 | 17 | public DbSet Friendships { get; set; } 18 | public DbSet HumanAppearances { get; set; } 19 | public DbSet DroidAppearances { get; set; } 20 | 21 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 22 | { 23 | optionsBuilder.UseSqlite("Filename=./starwars.db"); 24 | } 25 | } 26 | 27 | public static class ContextExtensions 28 | { 29 | public static StarWarsContext GetDataContext(this IResolveFieldContext context) 30 | { 31 | return context.RootValue as StarWarsContext ?? (context.RootValue as QueryRoot)?.Db; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLArgumentAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.Annotations.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Parameter)] 6 | public class GraphQLArgumentAttribute : Attribute 7 | { 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public Type GraphType { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLEnumAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Annotations.Types; 3 | 4 | namespace GraphQL.Annotations.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Enum)] 7 | public sealed class GraphQLEnumAttribute : GraphQLTypeAttribute 8 | { 9 | public GraphQLEnumAttribute() : base(typeof(EnumerationGraphType<>)) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLFieldAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.Annotations.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Property)] 6 | public class GraphQLFieldAttribute : Attribute 7 | { 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public string ColumnName { get; set; } 11 | public Type ReturnType { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLFuncAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.Annotations.Attributes 4 | { 5 | [AttributeUsage(AttributeTargets.Method, Inherited = false)] 6 | public class GraphQLFuncAttribute : Attribute 7 | { 8 | public string Name { get; set; } 9 | public string Description { get; set; } 10 | public Type ReturnType { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLInputObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Annotations.Types; 3 | 4 | namespace GraphQL.Annotations.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] 7 | public class GraphQLInputObjectAttribute : GraphQLTypeAttribute 8 | { 9 | public GraphQLInputObjectAttribute() : base(typeof (InputObjectGraphType<>)) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLInterfaceAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Annotations.Types; 3 | 4 | namespace GraphQL.Annotations.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, Inherited = false)] 7 | public class GraphQLInterfaceAttribute : GraphQLTypeAttribute 8 | { 9 | public GraphQLInterfaceAttribute() : base(typeof(InterfaceGraphType<>)) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLObjectAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Annotations.Types; 3 | 4 | namespace GraphQL.Annotations.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] 7 | public class GraphQLObjectAttribute : GraphQLTypeAttribute 8 | { 9 | public GraphQLObjectAttribute() : base(typeof (ObjectGraphType<>)) 10 | { 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLScalarAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Annotations.Types; 3 | 4 | namespace GraphQL.Annotations.Attributes 5 | { 6 | [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, Inherited = false)] 7 | public class GraphQLScalarAttribute : GraphQLTypeAttribute 8 | { 9 | public GraphQLScalarAttribute() : base(typeof(ScalarGraphType<>)) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Attributes/GraphQLTypeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace GraphQL.Annotations.Attributes 4 | { 5 | public abstract class GraphQLTypeAttribute : Attribute 6 | { 7 | protected GraphQLTypeAttribute(Type type) 8 | { 9 | if (!type.IsGraphType()) 10 | throw new ArgumentException("Expected a GraphType.", "type"); 11 | 12 | GraphType = type; 13 | } 14 | 15 | public Type GraphType { get; private set; } 16 | public string Name { get; set; } 17 | public string Description { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/GraphQL.Annotations.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | GraphQL.Annotations 5 | Attribute-based schema definitions for GraphQL in .NET 6 | Daniel Zimmermann 7 | graphql conventions annotations attributes schema generation 8 | https://github.com/dlukez/graphql-dotnet-annotations 9 | https://github.com/dlukez/graphql-dotnet-annotations/blob/master/LICENSE.md 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/GraphTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using GraphQL.Annotations.Attributes; 6 | using GraphQL.Types; 7 | 8 | namespace GraphQL.Annotations 9 | { 10 | public static class GraphTypeExtensions 11 | { 12 | public static void ApplyTypeData(this GraphType instance) 13 | { 14 | var type = typeof (TModelType); 15 | var metadata = type.GetTypeInfo().GetCustomAttribute(); 16 | 17 | if (metadata == null) 18 | { 19 | var message = 20 | $"{type.Name} is not marked as a GraphQL type - did you forget to mark it with a GraphQLTypeAttribute?"; 21 | throw new NotSupportedException(message); 22 | } 23 | 24 | instance.Name = !string.IsNullOrWhiteSpace(metadata.Name) ? metadata.Name : type.Name; 25 | instance.Description = metadata.Description; 26 | } 27 | 28 | public static void ApplyProperties(this IComplexGraphType instance) 29 | { 30 | var type = typeof (TModelType); 31 | foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance)) 32 | { 33 | var fieldAttr = prop.GetCustomAttribute(); 34 | if (fieldAttr == null) 35 | continue; 36 | 37 | try 38 | { 39 | instance.AddField(new FieldType 40 | { 41 | Name = fieldAttr.Name ?? prop.Name.FirstCharacterToLower(), 42 | Type = fieldAttr.ReturnType ?? prop.PropertyType.ToGraphType(), 43 | Resolver = new PropertyResolver(prop), 44 | Description = fieldAttr.Description 45 | }); 46 | } 47 | catch (ArgumentOutOfRangeException exception) 48 | { 49 | throw new NotSupportedException( 50 | $"Unable to register field for property {prop.Name}", 51 | exception); 52 | } 53 | } 54 | } 55 | 56 | public static void ApplyMethods(this IComplexGraphType instance, object[] injectedDependencies, bool shouldResolve) 57 | { 58 | var type = typeof (TModelType); 59 | foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance).Where(m => !m.IsSpecialName)) 60 | { 61 | var funcAttr = method.GetCustomAttribute(); 62 | if (funcAttr == null) 63 | continue; 64 | 65 | var methodDescription = $"`{method.Name}` on type '{type.Name}'"; 66 | var methodParams = method.GetParameters(); 67 | var methodQueryParameters = new List(); 68 | 69 | try 70 | { 71 | var injectedCount = injectedDependencies.Length; 72 | 73 | // Optional ResolveFieldContext 74 | if (methodParams.FirstOrDefault()?.ParameterType == typeof(IResolveFieldContext)) 75 | injectedCount++; 76 | 77 | // Ensure query parameters are annotated 78 | foreach (var param in methodParams.Skip(injectedCount)) 79 | { 80 | var attr = param.GetCustomAttribute(); 81 | if (attr == null) 82 | throw new ArgumentException($"Parameter `{param.Name}` is missing a required GraphQLArgumentAttribute"); 83 | 84 | methodQueryParameters.Add(new QueryArgumentParameterInfo(param) 85 | { 86 | Name = attr.Name ?? param.Name, 87 | Description = attr.Description 88 | }); 89 | } 90 | 91 | // Create the field 92 | instance.AddField(new FieldType 93 | { 94 | Type = funcAttr.ReturnType ?? method.ReturnType.ToGraphType(), 95 | Name = funcAttr.Name ?? method.Name.FirstCharacterToLower(), 96 | Description = funcAttr.Description, 97 | Arguments = new QueryArguments(methodQueryParameters), 98 | Resolver = shouldResolve 99 | ? new MethodResolver(method, injectedDependencies, methodQueryParameters) 100 | : null 101 | }); 102 | } 103 | catch (ArgumentOutOfRangeException exception) 104 | { 105 | throw new NotSupportedException( 106 | $"Unable to register field for {methodDescription} - unsupported type in method signature", 107 | exception); 108 | } 109 | } 110 | } 111 | 112 | private static string FirstCharacterToLower(this string s) 113 | { 114 | return s.Substring(0, 1).ToLower() + s.Substring(1); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/MethodResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using GraphQL.Resolvers; 6 | using GraphQL.Types; 7 | 8 | namespace GraphQL.Annotations 9 | { 10 | public class MethodResolver : IFieldResolver 11 | { 12 | private readonly MethodInfo _methodInfo; 13 | private readonly object[] _argumentsTemplate; 14 | private readonly bool _passContext; 15 | private readonly int _queryParametersStartPosition; 16 | private readonly IEnumerable _queryParameters; 17 | 18 | public MethodResolver(MethodInfo methodInfo) 19 | : this (methodInfo, null, null) 20 | { 21 | } 22 | 23 | public MethodResolver(MethodInfo methodInfo, object[] dependenciesToInject) 24 | : this(methodInfo, dependenciesToInject, null) 25 | { 26 | } 27 | 28 | public MethodResolver(MethodInfo methodInfo, object[] dependenciesToInject, IEnumerable queryParameters) 29 | { 30 | if (methodInfo == null) throw new ArgumentNullException(nameof(methodInfo)); 31 | _methodInfo = methodInfo; 32 | 33 | var methodParameters = methodInfo.GetParameters(); 34 | _queryParameters = queryParameters ?? Enumerable.Empty(); 35 | _argumentsTemplate = new object[methodParameters.Length]; 36 | _queryParametersStartPosition = 0; 37 | 38 | // Validate signature: 39 | // object SomeMethod([IResolveFieldContext], [dependencies...], [queryParameters...]) 40 | 41 | // Return type 42 | if (methodInfo.ReturnType == typeof(void)) 43 | throw new ArgumentException("Method has no return value"); 44 | 45 | // ResolveFieldContext 46 | if (methodInfo.GetParameters().FirstOrDefault()?.ParameterType == typeof(IResolveFieldContext)) 47 | { 48 | _passContext = true; 49 | _queryParametersStartPosition = 1; 50 | } 51 | 52 | // Dependencies 53 | if (dependenciesToInject != null) 54 | { 55 | dependenciesToInject.CopyTo(_argumentsTemplate, _queryParametersStartPosition); 56 | _queryParametersStartPosition += dependenciesToInject.Length; 57 | } 58 | 59 | // Query parameters 60 | var invalidParameters = _queryParameters.Where(p => p.Position < _queryParametersStartPosition); 61 | if (invalidParameters.Any()) 62 | { 63 | var badParameterMessages = string.Join(", ", invalidParameters.Select(p => $"{p.Name} (position {p.Position})")); 64 | throw new ArgumentException($"Query argument parameters must be specified last in the method definition. Offending parameters: {badParameterMessages}"); 65 | } 66 | } 67 | 68 | private object ResolveInternal(IResolveFieldContext context) 69 | { 70 | // Prepopulated with dependencies 71 | var arguments = (object[])_argumentsTemplate.Clone(); 72 | 73 | // Set IResolveFieldContext 74 | if (_passContext) arguments[0] = context; 75 | 76 | // Fill in parameters 77 | foreach (var param in _queryParameters) 78 | arguments[param.Position] = ConvertArgument(context.GetArgument(param.Name), param.ParameterType); 79 | 80 | return _methodInfo.Invoke(_methodInfo.IsStatic ? null : context.Source, arguments); 81 | } 82 | 83 | public object Resolve(IResolveFieldContext context) 84 | { 85 | return ResolveInternal(context); 86 | } 87 | 88 | object IFieldResolver.Resolve(IResolveFieldContext context) 89 | { 90 | return ResolveInternal(context); 91 | } 92 | 93 | private static object ConvertArgument(object argument, Type type) 94 | { 95 | // object[] => T[] 96 | var rawArray = argument as object[]; 97 | if (rawArray != null) 98 | { 99 | var arrayType = type.GetElementType(); 100 | var typedArray = Array.CreateInstance(arrayType, rawArray.Length); 101 | Array.Copy(rawArray, typedArray, rawArray.Length); 102 | return typedArray; 103 | } 104 | 105 | // Dictionary => T 106 | var dictionary = argument as Dictionary; 107 | if (dictionary != null) 108 | { 109 | var instance = Activator.CreateInstance(type); 110 | foreach (var kvp in dictionary) 111 | { 112 | var property = type.GetProperty(kvp.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance); 113 | property.SetValue(instance, ConvertArgument(kvp.Value, property.PropertyType)); 114 | } 115 | return instance; 116 | } 117 | 118 | return argument; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/PropertyResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using GraphQL.Resolvers; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.Annotations 6 | { 7 | public class PropertyResolver : IFieldResolver 8 | { 9 | private readonly PropertyInfo _prop; 10 | 11 | public PropertyResolver(PropertyInfo prop) 12 | { 13 | _prop = prop; 14 | } 15 | 16 | public object Resolve(IResolveFieldContext context) 17 | { 18 | return _prop.GetValue(context.Source, null); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/QueryArgumentParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.Annotations 6 | { 7 | public class QueryArgumentParameterInfo : QueryArgument 8 | { 9 | public QueryArgumentParameterInfo(ParameterInfo parameter) : base (parameter.ParameterType.ToGraphType(parameter.HasDefaultValue)) 10 | { 11 | Name = parameter.Name; 12 | DefaultValue = parameter.HasDefaultValue ? parameter.DefaultValue : null; 13 | Position = parameter.Position; 14 | ParameterType = parameter.ParameterType; 15 | } 16 | 17 | public int Position { get; set; } 18 | public Type ParameterType { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/GraphQL.Annotations/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using GraphQL.Annotations.Attributes; 7 | using GraphQL.Types; 8 | 9 | namespace GraphQL.Annotations 10 | { 11 | public static class TypeExtensions 12 | { 13 | /// 14 | /// Resolves a Type to its equivalent GraphType. 15 | /// 16 | public static Type ToGraphType(this Type type, bool nullableValueTypes = false) 17 | { 18 | try 19 | { 20 | // Already a graph type 21 | if (type.IsGraphType()) 22 | return type; 23 | 24 | // Unwrap a Task type 25 | if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Task<>)) 26 | type = type.GetGenericArguments()[0]; 27 | 28 | // Collection types 29 | var enumerableType = type.IsArray ? type.GetElementType() : type.GetEnumerableType(); 30 | if (enumerableType != null) 31 | return typeof(ListGraphType<>).MakeGenericType(enumerableType.ToGraphType()); 32 | 33 | // Nullable value types 34 | var nullableType = Nullable.GetUnderlyingType(type); 35 | if (nullableType != null) 36 | return GetGraphTypeInternal(nullableType); 37 | 38 | // Value types 39 | if (type.GetTypeInfo().IsValueType && !nullableValueTypes) 40 | return typeof(NonNullGraphType<>).MakeGenericType(GetGraphTypeInternal(type)); 41 | 42 | // Everything else 43 | return GetGraphTypeInternal(type); 44 | } 45 | catch (ArgumentOutOfRangeException exception) 46 | { 47 | throw new ArgumentOutOfRangeException($"Unsupported type: {type.Name}", exception); 48 | } 49 | } 50 | 51 | /// 52 | /// Gets the type T for a type implementing IEnumerable<T>. 53 | /// 54 | public static Type GetEnumerableType(this Type type) 55 | { 56 | if (type == typeof(string)) 57 | return null; 58 | 59 | if (type.IsGenericEnumerable()) 60 | return type.GetGenericArguments()[0]; 61 | 62 | return type 63 | .GetInterfaces() 64 | .Where(t => t.IsGenericEnumerable()) 65 | .Select(t => t.GetGenericArguments()[0]) 66 | .FirstOrDefault(); 67 | } 68 | 69 | private static bool IsGenericEnumerable(this Type type) 70 | { 71 | return type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>); 72 | } 73 | 74 | /// 75 | /// Retrieves all the annotated types in the same namespace as the given type. 76 | /// All types should exist in the same namespace to prevent naming conflicts. 77 | /// 78 | public static IEnumerable GraphTypesInNamespace(this Type type) 79 | { 80 | var typeInfo = type.GetTypeInfo(); 81 | var assembly = typeInfo.Assembly; 82 | return assembly.GetTypes() 83 | // ReSharper disable once PossibleNullReferenceException 84 | .Where(t => t.Namespace.Equals(type.Namespace)) 85 | .Select(GetGraphTypeFromAttribute) 86 | .Where(t => t != null) 87 | .ToList(); 88 | } 89 | 90 | /// 91 | /// Resolve the GraphType for a Type annotated with a GraphQLTypeAttribute. 92 | /// 93 | public static Type GetGraphTypeFromAttribute(this Type type) 94 | { 95 | return type.GetGraphTypeFromAttribute(); 96 | } 97 | 98 | /// 99 | /// Resolve the GraphType for a Type annotated with a GraphQLTypeAttribute. 100 | /// 101 | public static Type GetGraphTypeFromAttribute(this Type type) 102 | where TAttribute : GraphQLTypeAttribute 103 | { 104 | return type.GetTypeInfo().GetCustomAttribute()?.GraphType.MakeGenericType(type); 105 | } 106 | 107 | /// 108 | /// Resolve the GraphType for a Type, first looking for any attributes 109 | /// then falling back to the default library implementation. 110 | /// 111 | /// 112 | /// 113 | private static Type GetGraphTypeInternal(Type type) 114 | { 115 | // Support enum types 116 | if (type.GetTypeInfo().IsEnum) 117 | return typeof(EnumerationGraphType<>).MakeGenericType(type); 118 | 119 | return type.GetGraphTypeFromAttribute() ?? type.GetGraphTypeFromType(true); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/EnumerationGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Text.RegularExpressions; 5 | using GraphQL.Types; 6 | 7 | namespace GraphQL.Annotations.Types 8 | { 9 | /// 10 | /// Copy of EnumType[T] from GraphQL.Tests. 11 | /// See https://github.com/graphql-dotnet/graphql-dotnet/blob/master/src/GraphQL.Tests/Bugs/Bug68NonNullEnumGraphTypeTests.cs#L89. 12 | /// 13 | public class EnumerationGraphType : EnumerationGraphType 14 | where T : struct 15 | { 16 | public EnumerationGraphType() 17 | { 18 | if (!typeof(T).GetTypeInfo().IsEnum) 19 | { 20 | throw new ArgumentException(typeof(T).Name + " must be of type enum"); 21 | } 22 | 23 | var type = typeof(T); 24 | Name = DeriveGraphQlName(type.Name); 25 | 26 | foreach (var enumName in Enum.GetNames(type)) 27 | { 28 | var enumMember = type 29 | .GetMember(enumName, BindingFlags.Static | BindingFlags.Public | BindingFlags.DeclaredOnly) 30 | .First(); 31 | 32 | var name = DeriveEnumValueName(enumMember.Name); 33 | 34 | AddValue(name, null, Enum.Parse(type, enumName)); 35 | } 36 | } 37 | 38 | public override object ParseValue(object value) 39 | { 40 | var found = Values.FirstOrDefault( 41 | v => 42 | StringComparer.OrdinalIgnoreCase.Equals(PureValue(v.Name), PureValue(value)) || 43 | StringComparer.OrdinalIgnoreCase.Equals(PureValue(v.Value.ToString()), PureValue(value))); 44 | return found != null ? found.Value : null; 45 | } 46 | 47 | public object GetValue(object value) 48 | { 49 | var found = 50 | Values.FirstOrDefault( 51 | v => StringComparer.OrdinalIgnoreCase.Equals(PureValue(v.Name), PureValue(value))); 52 | return found != null ? found.Value : null; 53 | } 54 | 55 | public override string ToString() 56 | { 57 | return Name + " - Enum Type"; 58 | } 59 | 60 | static string PureValue(object value) 61 | { 62 | return value.ToString().Replace("\"", "").Replace("'", "").Replace("_", ""); 63 | } 64 | 65 | static string DeriveGraphQlName(string name) 66 | { 67 | return char.ToUpperInvariant(name[0]) + name.Substring(1); 68 | } 69 | 70 | static string DeriveEnumValueName(string name) 71 | { 72 | return Regex 73 | .Replace(name, @"([A-Z])([A-Z][a-z])|([a-z0-9])([A-Z])", "$1$3_$2$4") 74 | .ToUpperInvariant(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/InputObjectGraphType.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | 3 | namespace GraphQL.Annotations.Types 4 | { 5 | public class InputObjectGraphType : InputObjectGraphType 6 | { 7 | public InputObjectGraphType() 8 | { 9 | this.ApplyTypeData(); 10 | this.ApplyProperties(); 11 | } 12 | 13 | public override string ToString() 14 | { 15 | return Name + " - Input Object Type"; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/InterfaceGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace GraphQL.Annotations.Types 5 | { 6 | public class InterfaceGraphType : GraphQL.Types.InterfaceGraphType 7 | { 8 | public InterfaceGraphType(params object[] injectedParameters) 9 | { 10 | var type = typeof (TModelType); 11 | this.ApplyTypeData(); 12 | this.ApplyProperties(); 13 | this.ApplyMethods(injectedParameters, false); 14 | Name = GetInterfaceName(type); 15 | } 16 | 17 | private static string GetInterfaceName(Type type) 18 | { 19 | return type.GetTypeInfo().IsInterface ? type.Name.Substring(1) : type.Name; 20 | } 21 | 22 | public override string ToString() 23 | { 24 | return Name + " - Interface Type"; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/ObjectGraphType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using GraphQL.Annotations.Attributes; 6 | 7 | namespace GraphQL.Annotations.Types 8 | { 9 | public class ObjectGraphType : GraphQL.Types.ObjectGraphType 10 | where TModelType : class 11 | { 12 | public ObjectGraphType(params object[] injectedParameters) 13 | { 14 | this.ApplyTypeData(); 15 | this.ApplyProperties(); 16 | this.ApplyMethods(injectedParameters, true); 17 | ImplementInterfaces(); 18 | } 19 | 20 | private void ImplementInterfaces() 21 | { 22 | var type = typeof (TModelType); 23 | var abstractBaseTypes = GetBaseTypes(type).Where(t => t.GetTypeInfo().IsAbstract); 24 | var interfaces = type.GetInterfaces(); 25 | foreach (var item in abstractBaseTypes.Concat(interfaces) 26 | .Select(t => t.GetGraphTypeFromAttribute()) 27 | .Where(t => t != null)) 28 | { 29 | Interfaces.Add(item); 30 | } 31 | } 32 | 33 | private static IEnumerable GetBaseTypes(Type type) 34 | { 35 | while ((type = type.GetTypeInfo().BaseType) != null) 36 | yield return type; 37 | } 38 | 39 | public override string ToString() 40 | { 41 | return Name + " - Object Type"; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/ScalarGraphType.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using GraphQL.Language.AST; 3 | using GraphQL.Types; 4 | 5 | namespace GraphQL.Annotations.Types 6 | { 7 | public class ScalarGraphType : ScalarGraphType 8 | { 9 | public ScalarGraphType() 10 | { 11 | this.ApplyTypeData(); 12 | } 13 | 14 | public override string ToString() 15 | { 16 | return Name + " - Scalar Type"; 17 | } 18 | 19 | public override object Serialize(object value) 20 | { 21 | return ParseValue(value); 22 | } 23 | 24 | public override object ParseValue(object value) 25 | { 26 | if (value is TModelType) 27 | return value; 28 | 29 | var converter = TypeDescriptor.GetConverter(typeof(TModelType)); 30 | if (converter.CanConvertFrom(value.GetType())) 31 | return converter.ConvertFrom(value); 32 | 33 | return null; 34 | } 35 | 36 | public override object ParseLiteral(IValue value) 37 | { 38 | var stringValue = value as StringValue; 39 | if (stringValue != null) 40 | return ParseValue(stringValue.Value.Trim(' ', '"')); 41 | 42 | return null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/GraphQL.Annotations/Types/Schema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using GraphQL.Types; 3 | 4 | namespace GraphQL.Annotations.Types 5 | { 6 | public class Schema : Schema where TRootQuery : class 7 | { 8 | public Schema(params object[] injectedObjects) : base(new FuncServiceProvider(t => 9 | { 10 | var genericType = t.IsConstructedGenericType ? t.GetGenericTypeDefinition() : null; 11 | if (genericType == typeof(ObjectGraphType<>) || genericType == typeof(InterfaceGraphType<>)) 12 | return (GraphType)Activator.CreateInstance(t, injectedObjects); 13 | return (GraphType)Activator.CreateInstance(t); 14 | }), false) 15 | { 16 | Query = (IObjectGraphType)((IServiceProvider)this).GetService(typeof(ObjectGraphType)); 17 | } 18 | 19 | public override string ToString() 20 | { 21 | return "Schema - " + typeof (TRootQuery).FullName; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/GraphQL.Annotations.Tests/GraphQL.Annotations.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers; buildtransitive 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/GraphQL.Annotations.Tests/MethodResolverTests.cs: -------------------------------------------------------------------------------- 1 | using GraphQL.Types; 2 | using Shouldly; 3 | using System.Reflection; 4 | using Xunit; 5 | using System.Collections.Generic; 6 | 7 | namespace GraphQL.Annotations.Tests 8 | { 9 | public class MethodResolverTests 10 | { 11 | [Fact] 12 | public void MethodResolver_ResolvesCorrectly() 13 | { 14 | // Arrange 15 | var method = typeof(Counter).GetMethod(nameof(Counter.Increment)); 16 | var methodResolver = new MethodResolver(method); 17 | 18 | var counter = new Counter(); 19 | var context = new ResolveFieldContext() { Source = counter }; 20 | 21 | // Act 22 | var result = (int)methodResolver.Resolve(context); 23 | 24 | // Assert 25 | result.ShouldBe(1); 26 | counter.Value.ShouldBe(1); 27 | } 28 | 29 | [Fact] 30 | public void MethodResolver_SupportsEnumArguments() 31 | { 32 | // Arrange 33 | var method = typeof(Counter).GetMethod(nameof(Counter.HasParity)); 34 | var methodResolver = new MethodResolver(method, null, new [] { new QueryArgumentParameterInfo(method.GetParameters()[0]) }); 35 | 36 | var counter = new Counter { Value = 6 }; 37 | var context = new ResolveFieldContext 38 | { 39 | Source = counter, 40 | Arguments = new Dictionary { { "parity", new Execution.ArgumentValue(Parity.Even, Execution.ArgumentSource.Literal) } } 41 | }; 42 | 43 | // Act 44 | var result = (bool)methodResolver.Resolve(context); 45 | 46 | // Assert 47 | result.ShouldBe(true); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /test/GraphQL.Annotations.Tests/Types.cs: -------------------------------------------------------------------------------- 1 | namespace GraphQL.Annotations.Tests 2 | { 3 | public class Counter 4 | { 5 | public int Value { get; set; } 6 | 7 | public int Increment() 8 | { 9 | return ++Value; 10 | } 11 | 12 | public bool HasParity(Parity parity) 13 | { 14 | return parity == Parity.Even 15 | ? Value % 2 == 0 16 | : Value % 2 == 1; 17 | } 18 | } 19 | 20 | public enum Parity 21 | { 22 | Even, 23 | Odd 24 | } 25 | } -------------------------------------------------------------------------------- /tools/dotnet-install.ps1: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) .NET Foundation and contributors. All rights reserved. 3 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 4 | # 5 | 6 | <# 7 | .SYNOPSIS 8 | Installs dotnet cli 9 | .DESCRIPTION 10 | Installs dotnet cli. If dotnet installation already exists in the given directory 11 | it will update it only if the requested version differs from the one already installed. 12 | .PARAMETER Channel 13 | Default: preview 14 | Channel is the way of reasoning about stability and quality of dotnet. This parameter takes one of the values: 15 | - future - Possibly unstable, frequently changing, may contain new finished and unfinished features 16 | - preview - Pre-release stable with known issues and feature gaps 17 | - production - Most stable releases 18 | .PARAMETER Version 19 | Default: latest 20 | Represents a build version on specific channel. Possible values: 21 | - 4-part version in a format A.B.C.D - represents specific version of build 22 | - latest - most latest build on specific channel 23 | - lkg - last known good version on specific channel 24 | Note: LKG work is in progress. Once the work is finished, this will become new default 25 | .PARAMETER InstallDir 26 | Default: %LocalAppData%\Microsoft\dotnet 27 | Path to where to install dotnet. Note that binaries will be placed directly in a given directory. 28 | .PARAMETER Architecture 29 | Default: - this value represents currently running OS architecture 30 | Architecture of dotnet binaries to be installed. 31 | Possible values are: , x64 and x86 32 | .PARAMETER SharedRuntime 33 | Default: false 34 | Installs just the shared runtime bits, not the entire SDK 35 | .PARAMETER DebugSymbols 36 | If set the installer will include symbols in the installation. 37 | .PARAMETER DryRun 38 | If set it will not perform installation but instead display what command line to use to consistently install 39 | currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link 40 | with specific version so that this command can be used deterministicly in a build script. 41 | It also displays binaries location if you prefer to install or download it yourself. 42 | .PARAMETER NoPath 43 | By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder. 44 | If set it will display binaries location but not set any environment variable. 45 | .PARAMETER Verbose 46 | Displays diagnostics information. 47 | .PARAMETER AzureFeed 48 | Default: https://dotnetcli.azureedge.net/dotnet 49 | This parameter should not be usually changed by user. It allows to change URL for the Azure feed used by this installer. 50 | .PARAMETER ProxyAddress 51 | If set, the installer will use the proxy when making web requests 52 | #> 53 | [cmdletbinding()] 54 | param( 55 | [string]$Channel="rel-1.0.0", 56 | [string]$Version="Latest", 57 | [string]$InstallDir="", 58 | [string]$Architecture="", 59 | [switch]$SharedRuntime, 60 | [switch]$DebugSymbols, # TODO: Switch does not work yet. Symbols zip is not being uploaded yet. 61 | [switch]$DryRun, 62 | [switch]$NoPath, 63 | [string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet", 64 | [string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet", 65 | [string]$ProxyAddress 66 | ) 67 | 68 | Set-StrictMode -Version Latest 69 | $ErrorActionPreference="Stop" 70 | $ProgressPreference="SilentlyContinue" 71 | 72 | $BinFolderRelativePath="" 73 | 74 | # example path with regex: shared/1.0.0-beta-12345/somepath 75 | $VersionRegEx="/\d+\.\d+[^/]+/" 76 | $OverrideNonVersionedFiles=$true 77 | 78 | function Say($str) { 79 | Write-Host "dotnet-install: $str" 80 | } 81 | 82 | function Say-Verbose($str) { 83 | Write-Verbose "dotnet-install: $str" 84 | } 85 | 86 | function Say-Invocation($Invocation) { 87 | $command = $Invocation.MyCommand; 88 | $args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ") 89 | Say-Verbose "$command $args" 90 | } 91 | 92 | function Get-Machine-Architecture() { 93 | Say-Invocation $MyInvocation 94 | 95 | # possible values: AMD64, IA64, x86 96 | return $ENV:PROCESSOR_ARCHITECTURE 97 | } 98 | 99 | # TODO: Architecture and CLIArchitecture should be unified 100 | function Get-CLIArchitecture-From-Architecture([string]$Architecture) { 101 | Say-Invocation $MyInvocation 102 | 103 | switch ($Architecture.ToLower()) { 104 | { $_ -eq "" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) } 105 | { ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" } 106 | { $_ -eq "x86" } { return "x86" } 107 | default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" } 108 | } 109 | } 110 | 111 | function Get-Version-Info-From-Version-Text([string]$VersionText) { 112 | Say-Invocation $MyInvocation 113 | 114 | $Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries)); 115 | 116 | $VersionInfo = @{} 117 | $VersionInfo.CommitHash = $Data[0].Trim() 118 | $VersionInfo.Version = $Data[1].Trim() 119 | return $VersionInfo 120 | } 121 | 122 | function Load-Assembly([string] $Assembly) { 123 | try { 124 | Add-Type -Assembly $Assembly | Out-Null 125 | } 126 | catch { 127 | # On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd. 128 | # Loading the base class assemblies is not unnecessary as the types will automatically get resolved. 129 | } 130 | } 131 | 132 | function GetHTTPResponse([Uri] $Uri) 133 | { 134 | $HttpClient = $null 135 | 136 | try { 137 | # HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet. 138 | Load-Assembly -Assembly System.Net.Http 139 | if($ProxyAddress){ 140 | $HttpClientHandler = New-Object System.Net.Http.HttpClientHandler 141 | $HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress} 142 | $HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler 143 | } 144 | else { 145 | $HttpClient = New-Object System.Net.Http.HttpClient 146 | } 147 | # Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out 148 | # 5 minutes allows it to work over much slower connections. 149 | $HttpClient.Timeout = New-TimeSpan -Minutes 5 150 | $Response = $HttpClient.GetAsync($Uri).Result 151 | if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode))) 152 | { 153 | $ErrorMsg = "Failed to download $Uri." 154 | if ($Response -ne $null) 155 | { 156 | $ErrorMsg += " $Response" 157 | } 158 | 159 | throw $ErrorMsg 160 | } 161 | 162 | return $Response 163 | } 164 | finally { 165 | if ($HttpClient -ne $null) { 166 | $HttpClient.Dispose() 167 | } 168 | } 169 | } 170 | 171 | 172 | function Get-Latest-Version-Info([string]$AzureFeed, [string]$AzureChannel, [string]$CLIArchitecture) { 173 | Say-Invocation $MyInvocation 174 | 175 | $VersionFileUrl = $null 176 | if ($SharedRuntime) { 177 | $VersionFileUrl = "$UncachedFeed/$AzureChannel/dnvm/latest.sharedfx.win.$CLIArchitecture.version" 178 | } 179 | else { 180 | $VersionFileUrl = "$UncachedFeed/Sdk/$AzureChannel/latest.version" 181 | } 182 | 183 | $Response = GetHTTPResponse -Uri $VersionFileUrl 184 | $StringContent = $Response.Content.ReadAsStringAsync().Result 185 | 186 | switch ($Response.Content.Headers.ContentType) { 187 | { ($_ -eq "application/octet-stream") } { $VersionText = [Text.Encoding]::UTF8.GetString($StringContent) } 188 | { ($_ -eq "text/plain") } { $VersionText = $StringContent } 189 | default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." } 190 | } 191 | 192 | $VersionInfo = Get-Version-Info-From-Version-Text $VersionText 193 | 194 | return $VersionInfo 195 | } 196 | 197 | # TODO: AzureChannel and Channel should be unified 198 | function Get-Azure-Channel-From-Channel([string]$Channel) { 199 | Say-Invocation $MyInvocation 200 | 201 | # For compatibility with build scripts accept also directly Azure channels names 202 | switch ($Channel.ToLower()) { 203 | { ($_ -eq "future") -or ($_ -eq "dev") } { return "dev" } 204 | { $_ -eq "production" } { throw "Production channel does not exist yet" } 205 | default { return $_ } 206 | } 207 | } 208 | 209 | function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$AzureChannel, [string]$CLIArchitecture, [string]$Version) { 210 | Say-Invocation $MyInvocation 211 | 212 | switch ($Version.ToLower()) { 213 | { $_ -eq "latest" } { 214 | $LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -AzureChannel $AzureChannel -CLIArchitecture $CLIArchitecture 215 | return $LatestVersionInfo.Version 216 | } 217 | { $_ -eq "lkg" } { throw "``-Version LKG`` not supported yet." } 218 | default { return $Version } 219 | } 220 | } 221 | 222 | function Get-Download-Links([string]$AzureFeed, [string]$AzureChannel, [string]$SpecificVersion, [string]$CLIArchitecture) { 223 | Say-Invocation $MyInvocation 224 | 225 | $ret = @() 226 | 227 | if ($SharedRuntime) { 228 | $PayloadURL = "$AzureFeed/$AzureChannel/Binaries/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip" 229 | } 230 | else { 231 | $PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip" 232 | } 233 | 234 | Say-Verbose "Constructed payload URL: $PayloadURL" 235 | $ret += $PayloadURL 236 | 237 | return $ret 238 | } 239 | 240 | function Get-User-Share-Path() { 241 | Say-Invocation $MyInvocation 242 | 243 | $InstallRoot = $env:DOTNET_INSTALL_DIR 244 | if (!$InstallRoot) { 245 | $InstallRoot = "$env:LocalAppData\Microsoft\dotnet" 246 | } 247 | return $InstallRoot 248 | } 249 | 250 | function Resolve-Installation-Path([string]$InstallDir) { 251 | Say-Invocation $MyInvocation 252 | 253 | if ($InstallDir -eq "") { 254 | return Get-User-Share-Path 255 | } 256 | return $InstallDir 257 | } 258 | 259 | function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) { 260 | Say-Invocation $MyInvocation 261 | 262 | $VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile 263 | Say-Verbose "Local version file: $VersionFile" 264 | 265 | if (Test-Path $VersionFile) { 266 | $VersionText = cat $VersionFile 267 | Say-Verbose "Local version file text: $VersionText" 268 | return Get-Version-Info-From-Version-Text $VersionText 269 | } 270 | 271 | Say-Verbose "Local version file not found." 272 | 273 | return $null 274 | } 275 | 276 | function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) { 277 | Say-Invocation $MyInvocation 278 | 279 | $DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion 280 | Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath" 281 | return Test-Path $DotnetPackagePath -PathType Container 282 | } 283 | 284 | function Get-Absolute-Path([string]$RelativeOrAbsolutePath) { 285 | # Too much spam 286 | # Say-Invocation $MyInvocation 287 | 288 | return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath) 289 | } 290 | 291 | function Get-Path-Prefix-With-Version($path) { 292 | $match = [regex]::match($path, $VersionRegEx) 293 | if ($match.Success) { 294 | return $entry.FullName.Substring(0, $match.Index + $match.Length) 295 | } 296 | 297 | return $null 298 | } 299 | 300 | function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) { 301 | Say-Invocation $MyInvocation 302 | 303 | $ret = @() 304 | foreach ($entry in $Zip.Entries) { 305 | $dir = Get-Path-Prefix-With-Version $entry.FullName 306 | if ($dir -ne $null) { 307 | $path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir) 308 | if (-Not (Test-Path $path -PathType Container)) { 309 | $ret += $dir 310 | } 311 | } 312 | } 313 | 314 | $ret = $ret | Sort-Object | Get-Unique 315 | 316 | $values = ($ret | foreach { "$_" }) -join ";" 317 | Say-Verbose "Directories to unpack: $values" 318 | 319 | return $ret 320 | } 321 | 322 | # Example zip content and extraction algorithm: 323 | # Rule: files if extracted are always being extracted to the same relative path locally 324 | # .\ 325 | # a.exe # file does not exist locally, extract 326 | # b.dll # file exists locally, override only if $OverrideFiles set 327 | # aaa\ # same rules as for files 328 | # ... 329 | # abc\1.0.0\ # directory contains version and exists locally 330 | # ... # do not extract content under versioned part 331 | # abc\asd\ # same rules as for files 332 | # ... 333 | # def\ghi\1.0.1\ # directory contains version and does not exist locally 334 | # ... # extract content 335 | function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) { 336 | Say-Invocation $MyInvocation 337 | 338 | Load-Assembly -Assembly System.IO.Compression.FileSystem 339 | Set-Variable -Name Zip 340 | try { 341 | $Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) 342 | 343 | $DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath 344 | 345 | foreach ($entry in $Zip.Entries) { 346 | $PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName 347 | if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) { 348 | $DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName) 349 | $DestinationDir = Split-Path -Parent $DestinationPath 350 | $OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath)) 351 | if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) { 352 | New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null 353 | [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles) 354 | } 355 | } 356 | } 357 | } 358 | finally { 359 | if ($Zip -ne $null) { 360 | $Zip.Dispose() 361 | } 362 | } 363 | } 364 | 365 | function DownloadFile([Uri]$Uri, [string]$OutPath) { 366 | $Stream = $null 367 | 368 | try { 369 | $Response = GetHTTPResponse -Uri $Uri 370 | $Stream = $Response.Content.ReadAsStreamAsync().Result 371 | $File = [System.IO.File]::Create($OutPath) 372 | $Stream.CopyTo($File) 373 | $File.Close() 374 | } 375 | finally { 376 | if ($Stream -ne $null) { 377 | $Stream.Dispose() 378 | } 379 | } 380 | } 381 | 382 | function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) { 383 | $BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath) 384 | if (-Not $NoPath) { 385 | Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process." 386 | $env:path = "$BinPath;" + $env:path 387 | } 388 | else { 389 | Say "Binaries of dotnet can be found in $BinPath" 390 | } 391 | } 392 | 393 | $AzureChannel = Get-Azure-Channel-From-Channel -Channel $Channel 394 | $CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture 395 | $SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -AzureChannel $AzureChannel -CLIArchitecture $CLIArchitecture -Version $Version 396 | $DownloadLinks = Get-Download-Links -AzureFeed $AzureFeed -AzureChannel $AzureChannel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture 397 | 398 | if ($DryRun) { 399 | Say "Payload URLs:" 400 | foreach ($DownloadLink in $DownloadLinks) { 401 | Say "- $DownloadLink" 402 | } 403 | Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir" 404 | exit 0 405 | } 406 | 407 | $InstallRoot = Resolve-Installation-Path $InstallDir 408 | Say-Verbose "InstallRoot: $InstallRoot" 409 | 410 | $IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion 411 | Say-Verbose ".NET SDK installed? $IsSdkInstalled" 412 | if ($IsSdkInstalled) { 413 | Say ".NET SDK version $SpecificVersion is already installed." 414 | Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath 415 | exit 0 416 | } 417 | 418 | New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null 419 | 420 | $free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "$((Get-Item $InstallRoot).PSDrive.Name):" 421 | if ($free.Freespace / 1MB -le 250 ) { 422 | Say "there is not enough disk space on drive c:" 423 | exit 0 424 | } 425 | 426 | foreach ($DownloadLink in $DownloadLinks) { 427 | $ZipPath = [System.IO.Path]::GetTempFileName() 428 | Say "Downloading $DownloadLink" 429 | DownloadFile -Uri $DownloadLink -OutPath $ZipPath 430 | 431 | Say "Extracting zip from $DownloadLink" 432 | Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot 433 | 434 | Remove-Item $ZipPath 435 | } 436 | 437 | Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath 438 | 439 | Say "Installation finished" 440 | exit 0 441 | -------------------------------------------------------------------------------- /tools/dotnet-install.sh: -------------------------------------------------------------------------------- 1 | # Copyright (c) .NET Foundation and contributors. All rights reserved. 2 | # Licensed under the MIT license. See LICENSE file in the project root for full license information. 3 | # 4 | 5 | # Note: This script should be compatible with the dash shell used in Ubuntu. So avoid bashisms! See https://wiki.ubuntu.com/DashAsBinSh for more info 6 | 7 | # Stop script on NZEC 8 | set -e 9 | # Stop script if unbound variable found (use ${var:-} if intentional) 10 | set -u 11 | # By default cmd1 | cmd2 returns exit code of cmd2 regardless of cmd1 success 12 | # This is causing it to fail 13 | set -o pipefail 14 | 15 | # Use in the the functions: eval $invocation 16 | invocation='say_verbose "Calling: ${yellow:-}${FUNCNAME[0]} ${green:-}$*${normal:-}"' 17 | 18 | # standard output may be used as a return value in the functions 19 | # we need a way to write text on the screen in the functions so that 20 | # it won't interfere with the return value. 21 | # Exposing stream 3 as a pipe to standard output of the script itself 22 | exec 3>&1 23 | 24 | # Setup some colors to use. These need to work in fairly limited shells, like the Ubuntu Docker container where there are only 8 colors. 25 | # See if stdout is a terminal 26 | if [ -t 1 ]; then 27 | # see if it supports colors 28 | ncolors=$(tput colors) 29 | if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then 30 | bold="$(tput bold || echo)" 31 | normal="$(tput sgr0 || echo)" 32 | black="$(tput setaf 0 || echo)" 33 | red="$(tput setaf 1 || echo)" 34 | green="$(tput setaf 2 || echo)" 35 | yellow="$(tput setaf 3 || echo)" 36 | blue="$(tput setaf 4 || echo)" 37 | magenta="$(tput setaf 5 || echo)" 38 | cyan="$(tput setaf 6 || echo)" 39 | white="$(tput setaf 7 || echo)" 40 | fi 41 | fi 42 | 43 | say_err() { 44 | printf "%b\n" "${red:-}dotnet_install: Error: $1${normal:-}" >&2 45 | } 46 | 47 | say() { 48 | # using stream 3 (defined in the beginning) to not interfere with stdout of functions 49 | # which may be used as return value 50 | printf "%b\n" "${cyan:-}dotnet-install:${normal:-} $1" >&3 51 | } 52 | 53 | say_verbose() { 54 | if [ "$verbose" = true ]; then 55 | say "$1" 56 | fi 57 | } 58 | 59 | get_current_os_name() { 60 | eval $invocation 61 | 62 | local uname=$(uname) 63 | if [ "$uname" = "Darwin" ]; then 64 | echo "osx" 65 | return 0 66 | else 67 | if [ -e /etc/os-release ]; then 68 | . /etc/os-release 69 | 70 | case "$ID.$VERSION_ID" in 71 | "centos.7") 72 | echo "centos" 73 | return 0 74 | ;; 75 | "debian.8") 76 | echo "debian" 77 | return 0 78 | ;; 79 | "fedora.23") 80 | echo "fedora.23" 81 | return 0 82 | ;; 83 | "fedora.24") 84 | echo "fedora.24" 85 | return 0 86 | ;; 87 | "opensuse.13.2") 88 | echo "opensuse.13.2" 89 | return 0 90 | ;; 91 | "opensuse.42.1") 92 | echo "opensuse.42.1" 93 | return 0 94 | ;; 95 | "rhel.7.0" | "rhel.7.1" | "rhel.7.2") 96 | echo "rhel" 97 | return 0 98 | ;; 99 | "ubuntu.14.04") 100 | echo "ubuntu" 101 | return 0 102 | ;; 103 | "ubuntu.16.04") 104 | echo "ubuntu.16.04" 105 | return 0 106 | ;; 107 | "ubuntu.16.10") 108 | echo "ubuntu.16.10" 109 | return 0 110 | ;; 111 | "alpine.3.4.3") 112 | echo "alpine" 113 | return 0 114 | ;; 115 | esac 116 | fi 117 | fi 118 | 119 | say_err "OS name could not be detected: $ID.$VERSION_ID" 120 | return 1 121 | } 122 | 123 | machine_has() { 124 | eval $invocation 125 | 126 | which "$1" > /dev/null 2>&1 127 | return $? 128 | } 129 | 130 | check_min_reqs() { 131 | if ! machine_has "curl"; then 132 | say_err "curl is required to download dotnet. Install curl to proceed." 133 | return 1 134 | fi 135 | 136 | return 0 137 | } 138 | 139 | check_pre_reqs() { 140 | eval $invocation 141 | 142 | local failing=false; 143 | 144 | if [ "${DOTNET_INSTALL_SKIP_PREREQS:-}" = "1" ]; then 145 | return 0 146 | fi 147 | 148 | if [ "$(uname)" = "Linux" ]; then 149 | if ! [ -x "$(command -v ldconfig)" ]; then 150 | echo "ldconfig is not in PATH, trying /sbin/ldconfig." 151 | LDCONFIG_COMMAND="/sbin/ldconfig" 152 | else 153 | LDCONFIG_COMMAND="ldconfig" 154 | fi 155 | 156 | [ -z "$($LDCONFIG_COMMAND -p | grep libunwind)" ] && say_err "Unable to locate libunwind. Install libunwind to continue" && failing=true 157 | [ -z "$($LDCONFIG_COMMAND -p | grep libssl)" ] && say_err "Unable to locate libssl. Install libssl to continue" && failing=true 158 | [ -z "$($LDCONFIG_COMMAND -p | grep libcurl)" ] && say_err "Unable to locate libcurl. Install libcurl to continue" && failing=true 159 | [ -z "$($LDCONFIG_COMMAND -p | grep libicu)" ] && say_err "Unable to locate libicu. Install libicu to continue" && failing=true 160 | fi 161 | 162 | if [ "$failing" = true ]; then 163 | return 1 164 | fi 165 | 166 | return 0 167 | } 168 | 169 | # args: 170 | # input - $1 171 | to_lowercase() { 172 | #eval $invocation 173 | 174 | echo "$1" | tr '[:upper:]' '[:lower:]' 175 | return 0 176 | } 177 | 178 | # args: 179 | # input - $1 180 | remove_trailing_slash() { 181 | #eval $invocation 182 | 183 | local input=${1:-} 184 | echo "${input%/}" 185 | return 0 186 | } 187 | 188 | # args: 189 | # input - $1 190 | remove_beginning_slash() { 191 | #eval $invocation 192 | 193 | local input=${1:-} 194 | echo "${input#/}" 195 | return 0 196 | } 197 | 198 | # args: 199 | # root_path - $1 200 | # child_path - $2 - this parameter can be empty 201 | combine_paths() { 202 | eval $invocation 203 | 204 | # TODO: Consider making it work with any number of paths. For now: 205 | if [ ! -z "${3:-}" ]; then 206 | say_err "combine_paths: Function takes two parameters." 207 | return 1 208 | fi 209 | 210 | local root_path=$(remove_trailing_slash $1) 211 | local child_path=$(remove_beginning_slash ${2:-}) 212 | say_verbose "combine_paths: root_path=$root_path" 213 | say_verbose "combine_paths: child_path=$child_path" 214 | echo "$root_path/$child_path" 215 | return 0 216 | } 217 | 218 | get_machine_architecture() { 219 | eval $invocation 220 | 221 | # Currently the only one supported 222 | echo "x64" 223 | return 0 224 | } 225 | 226 | # args: 227 | # architecture - $1 228 | get_normalized_architecture_from_architecture() { 229 | eval $invocation 230 | 231 | local architecture=$(to_lowercase $1) 232 | case $architecture in 233 | \) 234 | echo "$(get_normalized_architecture_from_architecture $(get_machine_architecture))" 235 | return 0 236 | ;; 237 | amd64|x64) 238 | echo "x64" 239 | return 0 240 | ;; 241 | x86) 242 | say_err "Architecture ``x86`` currently not supported" 243 | return 1 244 | ;; 245 | esac 246 | 247 | say_err "Architecture ``$architecture`` not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" 248 | return 1 249 | } 250 | 251 | # version_info is a conceptual two line string representing commit hash and 4-part version 252 | # format: 253 | # Line 1: # commit_hash 254 | # Line 2: # 4-part version 255 | 256 | # args: 257 | # version_text - stdin 258 | get_version_from_version_info() { 259 | eval $invocation 260 | 261 | cat | tail -n 1 262 | return 0 263 | } 264 | 265 | # args: 266 | # version_text - stdin 267 | get_commit_hash_from_version_info() { 268 | eval $invocation 269 | 270 | cat | head -n 1 271 | return 0 272 | } 273 | 274 | # args: 275 | # install_root - $1 276 | # relative_path_to_package - $2 277 | # specific_version - $3 278 | is_dotnet_package_installed() { 279 | eval $invocation 280 | 281 | local install_root=$1 282 | local relative_path_to_package=$2 283 | local specific_version=${3//[$'\t\r\n']} 284 | 285 | local dotnet_package_path=$(combine_paths $(combine_paths $install_root $relative_path_to_package) $specific_version) 286 | say_verbose "is_dotnet_package_installed: dotnet_package_path=$dotnet_package_path" 287 | 288 | if [ -d "$dotnet_package_path" ]; then 289 | return 0 290 | else 291 | return 1 292 | fi 293 | } 294 | 295 | # args: 296 | # azure_feed - $1 297 | # azure_channel - $2 298 | # normalized_architecture - $3 299 | get_latest_version_info() { 300 | eval $invocation 301 | 302 | local azure_feed=$1 303 | local azure_channel=$2 304 | local normalized_architecture=$3 305 | 306 | local osname 307 | osname=$(get_current_os_name) || return 1 308 | 309 | local version_file_url=null 310 | if [ "$shared_runtime" = true ]; then 311 | version_file_url="$uncached_feed/$azure_channel/dnvm/latest.sharedfx.$osname.$normalized_architecture.version" 312 | else 313 | version_file_url="$uncached_feed/Sdk/$azure_channel/latest.version" 314 | fi 315 | say_verbose "get_latest_version_info: latest url: $version_file_url" 316 | 317 | download $version_file_url 318 | return $? 319 | } 320 | 321 | # args: 322 | # channel - $1 323 | get_azure_channel_from_channel() { 324 | eval $invocation 325 | 326 | local channel=$(to_lowercase $1) 327 | case $channel in 328 | future|dev) 329 | echo "dev" 330 | return 0 331 | ;; 332 | production) 333 | say_err "Production channel does not exist yet" 334 | return 1 335 | esac 336 | 337 | echo $channel 338 | return 0 339 | } 340 | 341 | # args: 342 | # azure_feed - $1 343 | # azure_channel - $2 344 | # normalized_architecture - $3 345 | # version - $4 346 | get_specific_version_from_version() { 347 | eval $invocation 348 | 349 | local azure_feed=$1 350 | local azure_channel=$2 351 | local normalized_architecture=$3 352 | local version=$(to_lowercase $4) 353 | 354 | case $version in 355 | latest) 356 | local version_info 357 | version_info="$(get_latest_version_info $azure_feed $azure_channel $normalized_architecture)" || return 1 358 | say_verbose "get_specific_version_from_version: version_info=$version_info" 359 | echo "$version_info" | get_version_from_version_info 360 | return 0 361 | ;; 362 | lkg) 363 | say_err "``--version LKG`` not supported yet." 364 | return 1 365 | ;; 366 | *) 367 | echo $version 368 | return 0 369 | ;; 370 | esac 371 | } 372 | 373 | # args: 374 | # azure_feed - $1 375 | # azure_channel - $2 376 | # normalized_architecture - $3 377 | # specific_version - $4 378 | construct_download_link() { 379 | eval $invocation 380 | 381 | local azure_feed=$1 382 | local azure_channel=$2 383 | local normalized_architecture=$3 384 | local specific_version=${4//[$'\t\r\n']} 385 | 386 | local osname 387 | osname=$(get_current_os_name) || return 1 388 | 389 | local download_link=null 390 | if [ "$shared_runtime" = true ]; then 391 | download_link="$azure_feed/$azure_channel/Binaries/$specific_version/dotnet-$osname-$normalized_architecture.$specific_version.tar.gz" 392 | else 393 | download_link="$azure_feed/Sdk/$specific_version/dotnet-dev-$osname-$normalized_architecture.$specific_version.tar.gz" 394 | fi 395 | 396 | echo "$download_link" 397 | return 0 398 | } 399 | 400 | get_user_share_path() { 401 | eval $invocation 402 | 403 | if [ ! -z "${DOTNET_INSTALL_DIR:-}" ]; then 404 | echo $DOTNET_INSTALL_DIR 405 | else 406 | echo "$HOME/.dotnet" 407 | fi 408 | return 0 409 | } 410 | 411 | # args: 412 | # install_dir - $1 413 | resolve_installation_path() { 414 | eval $invocation 415 | 416 | local install_dir=$1 417 | if [ "$install_dir" = "" ]; then 418 | local user_share_path=$(get_user_share_path) 419 | say_verbose "resolve_installation_path: share_path=$user_share_path" 420 | echo "$user_share_path" 421 | return 0 422 | fi 423 | 424 | echo "$install_dir" 425 | return 0 426 | } 427 | 428 | # args: 429 | # install_root - $1 430 | get_installed_version_info() { 431 | eval $invocation 432 | 433 | local install_root=$1 434 | local version_file=$(combine_paths "$install_root" "$local_version_file_relative_path") 435 | say_verbose "Local version file: $version_file" 436 | if [ ! -z "$version_file" ] | [ -r "$version_file" ]; then 437 | local version_info="$(cat $version_file)" 438 | echo "$version_info" 439 | return 0 440 | fi 441 | 442 | say_verbose "Local version file not found." 443 | return 0 444 | } 445 | 446 | # args: 447 | # relative_or_absolute_path - $1 448 | get_absolute_path() { 449 | eval $invocation 450 | 451 | local relative_or_absolute_path=$1 452 | echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1") 453 | return 0 454 | } 455 | 456 | # args: 457 | # input_files - stdin 458 | # root_path - $1 459 | # out_path - $2 460 | # override - $3 461 | copy_files_or_dirs_from_list() { 462 | eval $invocation 463 | 464 | local root_path=$(remove_trailing_slash $1) 465 | local out_path=$(remove_trailing_slash $2) 466 | local override=$3 467 | local override_switch=$(if [ "$override" = false ]; then printf -- "-n"; fi) 468 | 469 | cat | uniq | while read -r file_path; do 470 | local path=$(remove_beginning_slash ${file_path#$root_path}) 471 | local target=$out_path/$path 472 | if [ "$override" = true ] || (! ([ -d "$target" ] || [ -e "$target" ])); then 473 | mkdir -p $out_path/$(dirname $path) 474 | cp -R $override_switch $root_path/$path $target 475 | fi 476 | done 477 | } 478 | 479 | # args: 480 | # zip_path - $1 481 | # out_path - $2 482 | extract_dotnet_package() { 483 | eval $invocation 484 | 485 | local zip_path=$1 486 | local out_path=$2 487 | 488 | local temp_out_path=$(mktemp -d $temporary_file_template) 489 | 490 | local failed=false 491 | tar -xzf "$zip_path" -C "$temp_out_path" > /dev/null || failed=true 492 | 493 | local folders_with_version_regex='^.*/[0-9]+\.[0-9]+[^/]+/' 494 | find $temp_out_path -type f | grep -Eo $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path false 495 | find $temp_out_path -type f | grep -Ev $folders_with_version_regex | copy_files_or_dirs_from_list $temp_out_path $out_path true 496 | 497 | rm -rf $temp_out_path 498 | 499 | if [ "$failed" = true ]; then 500 | say_err "Extraction failed" 501 | return 1 502 | fi 503 | } 504 | 505 | # args: 506 | # remote_path - $1 507 | # [out_path] - $2 - stdout if not provided 508 | download() { 509 | eval $invocation 510 | 511 | local remote_path=$1 512 | local out_path=${2:-} 513 | 514 | local failed=false 515 | if [ -z "$out_path" ]; then 516 | curl --fail -s $remote_path || failed=true 517 | else 518 | curl --fail -s -o $out_path $remote_path || failed=true 519 | fi 520 | 521 | if [ "$failed" = true ]; then 522 | say_err "Download failed" 523 | return 1 524 | fi 525 | } 526 | 527 | calculate_vars() { 528 | eval $invocation 529 | 530 | azure_channel=$(get_azure_channel_from_channel "$channel") 531 | say_verbose "azure_channel=$azure_channel" 532 | 533 | normalized_architecture=$(get_normalized_architecture_from_architecture "$architecture") 534 | say_verbose "normalized_architecture=$normalized_architecture" 535 | 536 | specific_version=$(get_specific_version_from_version $azure_feed $azure_channel $normalized_architecture $version) 537 | say_verbose "specific_version=$specific_version" 538 | if [ -z "$specific_version" ]; then 539 | say_err "Could not get version information." 540 | return 1 541 | fi 542 | 543 | download_link=$(construct_download_link $azure_feed $azure_channel $normalized_architecture $specific_version) 544 | say_verbose "download_link=$download_link" 545 | 546 | install_root=$(resolve_installation_path $install_dir) 547 | say_verbose "install_root=$install_root" 548 | } 549 | 550 | install_dotnet() { 551 | eval $invocation 552 | 553 | if is_dotnet_package_installed $install_root "sdk" $specific_version; then 554 | say ".NET SDK version $specific_version is already installed." 555 | return 0 556 | fi 557 | 558 | mkdir -p $install_root 559 | zip_path=$(mktemp $temporary_file_template) 560 | say_verbose "Zip path: $zip_path" 561 | 562 | say "Downloading $download_link" 563 | download "$download_link" $zip_path 564 | say_verbose "Downloaded file exists and readable? $(if [ -r $zip_path ]; then echo "yes"; else echo "no"; fi)" 565 | 566 | say "Extracting zip" 567 | extract_dotnet_package $zip_path $install_root 568 | 569 | return 0 570 | } 571 | 572 | local_version_file_relative_path="/.version" 573 | bin_folder_relative_path="" 574 | temporary_file_template="${TMPDIR:-/tmp}/dotnet.XXXXXXXXX" 575 | 576 | channel="rel-1.0.0" 577 | version="Latest" 578 | install_dir="" 579 | architecture="" 580 | debug_symbols=false 581 | dry_run=false 582 | no_path=false 583 | azure_feed="https://dotnetcli.azureedge.net/dotnet" 584 | uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet" 585 | verbose=false 586 | shared_runtime=false 587 | 588 | while [ $# -ne 0 ] 589 | do 590 | name=$1 591 | case $name in 592 | -c|--channel|-[Cc]hannel) 593 | shift 594 | channel=$1 595 | ;; 596 | -v|--version|-[Vv]ersion) 597 | shift 598 | version="$1" 599 | ;; 600 | -i|--install-dir|-[Ii]nstall[Dd]ir) 601 | shift 602 | install_dir="$1" 603 | ;; 604 | --arch|--architecture|-[Aa]rch|-[Aa]rchitecture) 605 | shift 606 | architecture="$1" 607 | ;; 608 | --shared-runtime|-[Ss]hared[Rr]untime) 609 | shared_runtime=true 610 | ;; 611 | --debug-symbols|-[Dd]ebug[Ss]ymbols) 612 | debug_symbols=true 613 | ;; 614 | --dry-run|-[Dd]ry[Rr]un) 615 | dry_run=true 616 | ;; 617 | --no-path|-[Nn]o[Pp]ath) 618 | no_path=true 619 | ;; 620 | --verbose|-[Vv]erbose) 621 | verbose=true 622 | ;; 623 | --azure-feed|-[Aa]zure[Ff]eed) 624 | shift 625 | azure_feed="$1" 626 | ;; 627 | -?|--?|-h|--help|-[Hh]elp) 628 | script_name="$(basename $0)" 629 | echo ".NET Tools Installer" 630 | echo "Usage: $script_name [-c|--channel ] [-v|--version ] [-p|--prefix ]" 631 | echo " $script_name -h|-?|--help" 632 | echo "" 633 | echo "$script_name is a simple command line interface for obtaining dotnet cli." 634 | echo "" 635 | echo "Options:" 636 | echo " -c,--channel Download from the CHANNEL specified (default: $channel)." 637 | echo " -Channel" 638 | echo " -v,--version Use specific version, ``latest`` or ``lkg``. Defaults to ``latest``." 639 | echo " -Version" 640 | echo " -i,--install-dir Install under specified location (see Install Location below)" 641 | echo " -InstallDir" 642 | echo " --architecture Architecture of .NET Tools. Currently only x64 is supported." 643 | echo " --arch,-Architecture,-Arch" 644 | echo " --shared-runtime Installs just the shared runtime bits, not the entire SDK." 645 | echo " -SharedRuntime" 646 | echo " --debug-symbols,-DebugSymbols Specifies if symbols should be included in the installation." 647 | echo " --dry-run,-DryRun Do not perform installation. Display download link." 648 | echo " --no-path, -NoPath Do not set PATH for the current process." 649 | echo " --verbose,-Verbose Display diagnostics information." 650 | echo " --azure-feed,-AzureFeed Azure feed location. Defaults to $azure_feed" 651 | echo " -?,--?,-h,--help,-Help Shows this help message" 652 | echo "" 653 | echo "Install Location:" 654 | echo " Location is chosen in following order:" 655 | echo " - --install-dir option" 656 | echo " - Environmental variable DOTNET_INSTALL_DIR" 657 | echo " - /usr/local/share/dotnet" 658 | exit 0 659 | ;; 660 | *) 661 | say_err "Unknown argument \`$name\`" 662 | exit 1 663 | ;; 664 | esac 665 | 666 | shift 667 | done 668 | 669 | check_min_reqs 670 | calculate_vars 671 | if [ "$dry_run" = true ]; then 672 | say "Payload URL: $download_link" 673 | say "Repeatable invocation: ./$(basename $0) --version $specific_version --channel $channel --install-dir $install_dir" 674 | exit 0 675 | fi 676 | 677 | check_pre_reqs 678 | install_dotnet 679 | 680 | bin_path=$(get_absolute_path $(combine_paths $install_root $bin_folder_relative_path)) 681 | if [ "$no_path" = false ]; then 682 | say "Adding to current process PATH: ``$bin_path``. Note: This change will be visible only when sourcing script." 683 | export PATH=$bin_path:$PATH 684 | else 685 | say "Binaries of dotnet can be found in $bin_path" 686 | fi 687 | 688 | say "Installation finished successfully." 689 | --------------------------------------------------------------------------------