├── GetComfortableWith.NETCoreAndCLI.pdf ├── Demo ├── person-api │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Models │ │ ├── IPeopleProvider.cs │ │ ├── Person.cs │ │ ├── HardCodedPeopleProvider.cs │ │ ├── HardCodedPeopleProviders.cs │ │ └── CSVPeopleProvider.cs │ ├── People.txt │ ├── WeatherForecast.cs │ ├── person-api.csproj │ ├── Program.cs │ ├── Controllers │ │ └── PeopleController.cs │ ├── .vscode │ │ ├── tasks.json │ │ └── launch.json │ └── Startup.cs ├── People.txt ├── person-console │ ├── person-console.csproj │ ├── Program.cs │ ├── Person.cs │ ├── PersonReader.cs │ └── .vscode │ │ ├── launch.json │ │ └── tasks.json ├── person-api-tests │ ├── person-api-tests.csproj │ ├── PeopleControllerTests.cs │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ └── FakePeopleProvider.cs ├── CSVPeopleProvider.cs ├── snippets.txt └── CoreCLI.sln ├── StartingFiles ├── People.txt ├── CSVPeopleProvider.cs └── snippets.txt ├── README.md ├── .gitattributes ├── .gitignore └── DemoWalkthrough.md /GetComfortableWith.NETCoreAndCLI.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremybytes/core-cli-30/HEAD/GetComfortableWith.NETCoreAndCLI.pdf -------------------------------------------------------------------------------- /Demo/person-api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Demo/person-api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /Demo/person-api/Models/IPeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace person_api.Models 4 | { 5 | public interface IPeopleProvider 6 | { 7 | public List GetPeople(); 8 | public Person GetPerson(int id); 9 | } 10 | } -------------------------------------------------------------------------------- /Demo/People.txt: -------------------------------------------------------------------------------- 1 | 1,John,Koenig,1975/10/17,6, 2 | 2,Dylan,Hunt,2000/10/02,8, 3 | 3,Leela,Turanga,1999/3/28,8,{1} {0} 4 | 4,John,Crichton,1999/03/19,7, 5 | 5,Dave,Lister,1988/02/15,9, 6 | 6,Laura,Roslin,2003/12/8,6, 7 | 7,John,Sheridan,1994/01/26,6, 8 | 8,Dante,Montana,2000/11/01,5, 9 | 9,Isaac,Gampu,1977/09/10,4, 10 | 10,Jeremy,Awesome,1971/05/03,10,**{0} {1}** -------------------------------------------------------------------------------- /StartingFiles/People.txt: -------------------------------------------------------------------------------- 1 | 1,John,Koenig,1975/10/17,6, 2 | 2,Dylan,Hunt,2000/10/02,8, 3 | 3,Leela,Turanga,1999/3/28,8,{1} {0} 4 | 4,John,Crichton,1999/03/19,7, 5 | 5,Dave,Lister,1988/02/15,9, 6 | 6,Laura,Roslin,2003/12/8,6, 7 | 7,John,Sheridan,1994/01/26,6, 8 | 8,Dante,Montana,2000/11/01,5, 9 | 9,Isaac,Gampu,1977/09/10,4, 10 | 10,Jeremy,Awesome,1971/05/03,10,**{0} {1}** -------------------------------------------------------------------------------- /Demo/person-api/People.txt: -------------------------------------------------------------------------------- 1 | 1,John,Koenig,1975/10/17,6, 2 | 2,Dylan,Hunt,2000/10/02,8, 3 | 3,Leela,Turanga,1999/3/28,8,{1} {0} 4 | 4,John,Crichton,1999/03/19,7, 5 | 5,Dave,Lister,1988/02/15,9, 6 | 6,Laura,Roslin,2003/12/8,6, 7 | 7,John,Sheridan,1994/01/26,6, 8 | 8,Dante,Montana,2000/11/01,5, 9 | 9,Isaac,Gampu,1977/09/10,4, 10 | 10,Jeremy,Awesome,1971/05/03,10,**{0} {1}** -------------------------------------------------------------------------------- /Demo/person-api/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace person_api 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Demo/person-api/person-api.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | person_api 6 | 7 | 8 | 9 | 10 | Always 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Demo/person-console/person-console.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | person_console 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Demo/person-console/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace person_console 5 | { 6 | class Program 7 | { 8 | static async Task Main(string[] args) 9 | { 10 | Console.WriteLine("One moment please..."); 11 | var reader = new PersonReader(); 12 | 13 | var people = await reader.GetAsync(); 14 | foreach(var person in people) 15 | { 16 | Console.WriteLine(person); 17 | } 18 | 19 | Console.WriteLine("======================"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Demo/person-api-tests/person-api-tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | person_api_tests 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Demo/person-console/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace person_console 4 | { 5 | public class Person 6 | { 7 | public int Id { get; set; } 8 | public string GivenName { get; set; } 9 | public string FamilyName { get; set; } 10 | public DateTime StartDate { get; set; } 11 | public int Rating { get; set; } 12 | public string FormatString { get; set; } 13 | 14 | public override string ToString() 15 | { 16 | if (string.IsNullOrEmpty(FormatString)) 17 | FormatString = "{0} {1}"; 18 | return string.Format(FormatString, GivenName, FamilyName); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /Demo/person-api/Models/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace person_api.Models 4 | { 5 | public class Person 6 | { 7 | public int Id { get; set; } 8 | public string GivenName { get; set; } 9 | public string FamilyName { get; set; } 10 | public DateTime StartDate { get; set; } 11 | public int Rating { get; set; } 12 | public string FormatString { get; set; } 13 | 14 | public override string ToString() 15 | { 16 | if (string.IsNullOrEmpty(FormatString)) 17 | FormatString = "{0} {1}"; 18 | return string.Format(FormatString, GivenName, FamilyName); 19 | } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /Demo/person-api/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace person_api 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup() 24 | .UseUrls("http://localhost:9874"); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Demo/person-api-tests/PeopleControllerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using NUnit.Framework; 3 | using person_api.Controllers; 4 | 5 | namespace person_api_tests 6 | { 7 | public class PeopleControllerTests 8 | { 9 | PeopleController controller; 10 | 11 | [SetUp] 12 | public void Setup() 13 | { 14 | controller = new PeopleController(new FakePeopleProvider()); 15 | } 16 | 17 | [Test] 18 | public void GetPeople_ReturnsAllValues() 19 | { 20 | var result = controller.Get(); 21 | Assert.AreEqual(9, result.Count()); 22 | } 23 | 24 | [Test] 25 | public void GetPerson_OnValidValue_ReturnsPerson() 26 | { 27 | var result = controller.Get(2); 28 | Assert.AreEqual(2, result.Id); 29 | } 30 | 31 | [Test] 32 | public void GetPerson_OnInvalidValue_ReturnsNull() 33 | { 34 | var result = controller.Get(-1); 35 | Assert.IsNull(result); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get Comfortable with .NET Core and the CLI 2 | 3 | **Level**: Introductory 4 | 5 | **Target**: C# developers who have been working with .NET Framework and are interested in trying out .NET Core. 6 | 7 | *Note: An updated version that uses .NET 5 is available here: [https://github.com/jeremybytes/dotnet50-cli](https://github.com/jeremybytes/dotnet50-cli).* 8 | 9 | .NET Core is the future of .NET. So let's get comfortable with the creating, running, and testing applications using the command-line interface. We'll create a self-hosted web service and then write an application to use that service. Unit tests will make sure things work along the way. Whether you're new to .NET Core or have been using .NET Core with Visual Studio, this session will help you get up-to-speed in this powerful environment. 10 | 11 | ## Demo 12 | Be sure to take a look at the [**DemoWalkthrough.md**](https://github.com/jeremybytes/core-cli-30/blob/master/DemoWalkthrough.md) file. This walks through the process of starting with an empty folder and creating all of the projects in the repo. 13 | -------------------------------------------------------------------------------- /Demo/person-api/Controllers/PeopleController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using person_api.Models; 8 | 9 | namespace person_api.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class PeopleController : ControllerBase 14 | { 15 | IPeopleProvider provider; 16 | 17 | public PeopleController(IPeopleProvider provider) 18 | { 19 | this.provider = provider; 20 | } 21 | 22 | [HttpGet] 23 | public IEnumerable Get() 24 | { 25 | return provider.GetPeople(); 26 | } 27 | 28 | [HttpGet("{id}")] 29 | public Person Get(int id) 30 | { 31 | try 32 | { 33 | return provider.GetPerson(id); 34 | } 35 | catch(Exception) 36 | { 37 | return null; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Demo/person-console/PersonReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Net.Http.Headers; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | 8 | namespace person_console 9 | { 10 | public class PersonReader 11 | { 12 | HttpClient client = new HttpClient(); 13 | 14 | public PersonReader() 15 | { 16 | client.BaseAddress = new Uri("http://localhost:9874"); 17 | client.DefaultRequestHeaders.Accept.Add( 18 | new MediaTypeWithQualityHeaderValue("application/json")); 19 | } 20 | 21 | public async Task> GetAsync() 22 | { 23 | await Task.Delay(3000); 24 | 25 | HttpResponseMessage response = await client.GetAsync("people"); 26 | if (response.IsSuccessStatusCode) 27 | { 28 | var stringResult = await response.Content.ReadAsStringAsync(); 29 | return JsonConvert.DeserializeObject>(stringResult); 30 | } 31 | return new List(); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Demo/person-console/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/person-console.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Demo/person-api-tests/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/person-api-tests.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Demo/person-api/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/person-api.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/person-api.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/person-api.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Demo/person-console/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/person-console.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/person-console.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/person-console.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Demo/person-api-tests/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/person-api-tests.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/person-api-tests.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/person-api-tests.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /Demo/person-api/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.0/person-api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /Demo/person-api/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | using person_api.Models; 14 | 15 | namespace person_api 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services.AddControllers(); 30 | services.AddSingleton(); 31 | } 32 | 33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 35 | { 36 | if (env.IsDevelopment()) 37 | { 38 | app.UseDeveloperExceptionPage(); 39 | } 40 | 41 | // app.UseHttpsRedirection(); 42 | 43 | app.UseRouting(); 44 | 45 | app.UseAuthorization(); 46 | 47 | app.UseEndpoints(endpoints => 48 | { 49 | endpoints.MapControllers(); 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Demo/person-api/Models/HardCodedPeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace person_api.Models 6 | { 7 | public class HardCodedPeopleProvider : IPeopleProvider 8 | { 9 | public List GetPeople() 10 | { 11 | var p = new List() 12 | { 13 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 14 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 15 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 16 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 17 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 18 | StartDate = new DateTime(1999, 3, 28), Rating=8, 19 | FormatString = "{1} {0}" }, 20 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 21 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 22 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 23 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 24 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 25 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 26 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 27 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 28 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 29 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 30 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 31 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 32 | }; 33 | return p; 34 | } 35 | 36 | public Person GetPerson(int id) 37 | { 38 | return GetPeople().FirstOrDefault(p => p.Id == id); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Demo/person-api/Models/HardCodedPeopleProviders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace person_api.Models 6 | { 7 | public class HardCodedPeopleProvider : IPeopleProvider 8 | { 9 | public List GetPeople() 10 | { 11 | var p = new List() 12 | { 13 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 14 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 15 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 16 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 17 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 18 | StartDate = new DateTime(1999, 3, 28), Rating=8, 19 | FormatString = "{1} {0}" }, 20 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 21 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 22 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 23 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 24 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 25 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 26 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 27 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 28 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 29 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 30 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 31 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 32 | }; 33 | return p; 34 | } 35 | 36 | public Person GetPerson(int id) 37 | { 38 | return GetPeople().FirstOrDefault(p => p.Id == id); 39 | } 40 | 41 | } 42 | } -------------------------------------------------------------------------------- /Demo/person-api-tests/FakePeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using person_api.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace person_api_tests 7 | { 8 | public class FakePeopleProvider : IPeopleProvider 9 | { 10 | public List GetPeople() 11 | { 12 | var p = new List() 13 | { 14 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 15 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 16 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 17 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 18 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 19 | StartDate = new DateTime(1999, 3, 28), Rating=8, 20 | FormatString = "{1} {0}" }, 21 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 22 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 23 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 24 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 25 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 26 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 27 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 28 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 29 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 30 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 31 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 32 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 33 | }; 34 | return p; 35 | } 36 | 37 | public Person GetPerson(int id) 38 | { 39 | return GetPeople().FirstOrDefault(p => p.Id == id); 40 | } 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /Demo/CSVPeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace person_api.Models 7 | { 8 | public class CSVPeopleProvider : IPeopleProvider 9 | { 10 | public string FileData { get; private set; } 11 | 12 | public CSVPeopleProvider() 13 | { 14 | string filePath = AppDomain.CurrentDomain.BaseDirectory + "People.txt"; 15 | LoadFile(filePath); 16 | } 17 | 18 | public void LoadFile(string filePath) 19 | { 20 | using (var reader = new StreamReader(filePath)) 21 | { 22 | FileData = reader.ReadToEnd(); 23 | } 24 | } 25 | 26 | public List GetPeople() 27 | { 28 | var people = ParseString(FileData); 29 | return people; 30 | } 31 | 32 | public Person GetPerson(int id) 33 | { 34 | var people = GetPeople(); 35 | return people?.FirstOrDefault(p => p.Id == id); 36 | } 37 | 38 | private List ParseString(string csvData) 39 | { 40 | var people = new List(); 41 | 42 | var lines = csvData.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 43 | 44 | foreach (string line in lines) 45 | { 46 | try 47 | { 48 | var elems = line.Split(','); 49 | var per = new Person() 50 | { 51 | Id = Int32.Parse(elems[0]), 52 | GivenName = elems[1], 53 | FamilyName = elems[2], 54 | StartDate = DateTime.Parse(elems[3]), 55 | Rating = Int32.Parse(elems[4]), 56 | FormatString = elems[5], 57 | }; 58 | people.Add(per); 59 | } 60 | catch (Exception) 61 | { 62 | // Skip the bad record, log it, and move to the next record 63 | // log.write("Unable to parse record", per); 64 | } 65 | } 66 | return people; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /StartingFiles/CSVPeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace person_api 7 | { 8 | public class CSVPeopleProvider : IPeopleProvider 9 | { 10 | public string FileData { get; private set; } 11 | 12 | public CSVPeopleProvider() 13 | { 14 | string filePath = AppDomain.CurrentDomain.BaseDirectory + "People.txt"; 15 | LoadFile(filePath); 16 | } 17 | 18 | public void LoadFile(string filePath) 19 | { 20 | using (var reader = new StreamReader(filePath)) 21 | { 22 | FileData = reader.ReadToEnd(); 23 | } 24 | } 25 | 26 | public List GetPeople() 27 | { 28 | var people = ParseString(FileData); 29 | return people; 30 | } 31 | 32 | public Person GetPerson(int id) 33 | { 34 | var people = GetPeople(); 35 | return people?.FirstOrDefault(p => p.Id == id); 36 | } 37 | 38 | private List ParseString(string csvData) 39 | { 40 | var people = new List(); 41 | 42 | var lines = csvData.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 43 | 44 | foreach (string line in lines) 45 | { 46 | try 47 | { 48 | var elems = line.Split(','); 49 | var per = new Person() 50 | { 51 | Id = Int32.Parse(elems[0]), 52 | GivenName = elems[1], 53 | FamilyName = elems[2], 54 | StartDate = DateTime.Parse(elems[3]), 55 | Rating = Int32.Parse(elems[4]), 56 | FormatString = elems[5], 57 | }; 58 | people.Add(per); 59 | } 60 | catch (Exception) 61 | { 62 | // Skip the bad record, log it, and move to the next record 63 | // log.write("Unable to parse record", per); 64 | } 65 | } 66 | return people; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Demo/person-api/Models/CSVPeopleProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace person_api.Models 7 | { 8 | public class CSVPeopleProvider : IPeopleProvider 9 | { 10 | public string FileData { get; private set; } 11 | 12 | public CSVPeopleProvider() 13 | { 14 | string filePath = AppDomain.CurrentDomain.BaseDirectory + "People.txt"; 15 | LoadFile(filePath); 16 | } 17 | 18 | public void LoadFile(string filePath) 19 | { 20 | using (var reader = new StreamReader(filePath)) 21 | { 22 | FileData = reader.ReadToEnd(); 23 | } 24 | } 25 | 26 | public List GetPeople() 27 | { 28 | var people = ParseString(FileData); 29 | return people; 30 | } 31 | 32 | public Person GetPerson(int id) 33 | { 34 | var people = GetPeople(); 35 | return people?.FirstOrDefault(p => p.Id == id); 36 | } 37 | 38 | private List ParseString(string csvData) 39 | { 40 | var people = new List(); 41 | 42 | var lines = csvData.Split(new[] { Environment.NewLine }, StringSplitOptions.None); 43 | 44 | foreach (string line in lines) 45 | { 46 | try 47 | { 48 | var elems = line.Split(','); 49 | var per = new Person() 50 | { 51 | Id = Int32.Parse(elems[0]), 52 | GivenName = elems[1], 53 | FamilyName = elems[2], 54 | StartDate = DateTime.Parse(elems[3]), 55 | Rating = Int32.Parse(elems[4]), 56 | FormatString = elems[5], 57 | }; 58 | people.Add(per); 59 | } 60 | catch (Exception) 61 | { 62 | // Skip the bad record, log it, and move to the next record 63 | // log.write("Unable to parse record", per); 64 | } 65 | } 66 | return people; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Demo/snippets.txt: -------------------------------------------------------------------------------- 1 | 2 | public class Person 3 | { 4 | public int Id { get; set; } 5 | public string GivenName { get; set; } 6 | public string FamilyName { get; set; } 7 | public DateTime StartDate { get; set; } 8 | public int Rating { get; set; } 9 | public string FormatString { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | if (string.IsNullOrEmpty(FormatString)) 14 | FormatString = "{0} {1}"; 15 | return string.Format(FormatString, GivenName, FamilyName); 16 | } 17 | } 18 | 19 | 20 | public List GetPeople() 21 | { 22 | var p = new List() 23 | { 24 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 25 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 26 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 27 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 28 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 29 | StartDate = new DateTime(1999, 3, 28), Rating=8, 30 | FormatString = "{1} {0}" }, 31 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 32 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 33 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 34 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 35 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 36 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 37 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 38 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 39 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 40 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 41 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 42 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 43 | }; 44 | return p; 45 | } 46 | 47 | public Person GetPerson(int id) 48 | { 49 | return GetPeople().First(p => p.Id == id); 50 | } 51 | 52 | 53 | public class PersonReader 54 | { 55 | HttpClient client = new HttpClient(); 56 | 57 | public PersonReader() 58 | { 59 | client.BaseAddress = new Uri("http://localhost:9874"); 60 | client.DefaultRequestHeaders.Accept.Add( 61 | new MediaTypeWithQualityHeaderValue("application/json")); 62 | } 63 | 64 | public async Task> GetAsync() 65 | { 66 | await Task.Delay(3000); 67 | 68 | HttpResponseMessage response = await client.GetAsync("people"); 69 | if (response.IsSuccessStatusCode) 70 | { 71 | var stringResult = await response.Content.ReadAsStringAsync(); 72 | return JsonConvert.DeserializeObject>(stringResult); 73 | } 74 | return new List(); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /StartingFiles/snippets.txt: -------------------------------------------------------------------------------- 1 | 2 | public class Person 3 | { 4 | public int Id { get; set; } 5 | public string GivenName { get; set; } 6 | public string FamilyName { get; set; } 7 | public DateTime StartDate { get; set; } 8 | public int Rating { get; set; } 9 | public string FormatString { get; set; } 10 | 11 | public override string ToString() 12 | { 13 | if (string.IsNullOrEmpty(FormatString)) 14 | FormatString = "{0} {1}"; 15 | return string.Format(FormatString, GivenName, FamilyName); 16 | } 17 | } 18 | 19 | 20 | public List GetPeople() 21 | { 22 | var p = new List() 23 | { 24 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 25 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 26 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 27 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 28 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 29 | StartDate = new DateTime(1999, 3, 28), Rating=8, 30 | FormatString = "{1} {0}" }, 31 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 32 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 33 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 34 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 35 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 36 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 37 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 38 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 39 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 40 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 41 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 42 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 43 | }; 44 | return p; 45 | } 46 | 47 | public Person GetPerson(int id) 48 | { 49 | return GetPeople().First(p => p.Id == id); 50 | } 51 | 52 | 53 | public class PersonReader 54 | { 55 | HttpClient client = new HttpClient(); 56 | 57 | public PersonReader() 58 | { 59 | client.BaseAddress = new Uri("http://localhost:9874"); 60 | client.DefaultRequestHeaders.Accept.Add( 61 | new MediaTypeWithQualityHeaderValue("application/json")); 62 | } 63 | 64 | public async Task> GetAsync() 65 | { 66 | await Task.Delay(3000); 67 | 68 | HttpResponseMessage response = await client.GetAsync("people"); 69 | if (response.IsSuccessStatusCode) 70 | { 71 | var stringResult = await response.Content.ReadAsStringAsync(); 72 | return JsonConvert.DeserializeObject>(stringResult); 73 | } 74 | return new List(); 75 | } 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Demo/CoreCLI.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "person-api", "person-api\person-api.csproj", "{2C529C14-2D09-4BDB-9565-49D648E70D22}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "person-api-tests", "person-api-tests\person-api-tests.csproj", "{335AE78F-AC43-46BC-B438-0BDB19C649F4}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "person-console", "person-console\person-console.csproj", "{2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|Any CPU = Release|Any CPU 18 | Release|x64 = Release|x64 19 | Release|x86 = Release|x86 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|x64.ActiveCfg = Debug|Any CPU 28 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|x64.Build.0 = Debug|Any CPU 29 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|x86.ActiveCfg = Debug|Any CPU 30 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Debug|x86.Build.0 = Debug|Any CPU 31 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|x64.ActiveCfg = Release|Any CPU 34 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|x64.Build.0 = Release|Any CPU 35 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|x86.ActiveCfg = Release|Any CPU 36 | {2C529C14-2D09-4BDB-9565-49D648E70D22}.Release|x86.Build.0 = Release|Any CPU 37 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|x64.ActiveCfg = Debug|Any CPU 40 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|x64.Build.0 = Debug|Any CPU 41 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|x86.ActiveCfg = Debug|Any CPU 42 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Debug|x86.Build.0 = Debug|Any CPU 43 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|x64.ActiveCfg = Release|Any CPU 46 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|x64.Build.0 = Release|Any CPU 47 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|x86.ActiveCfg = Release|Any CPU 48 | {335AE78F-AC43-46BC-B438-0BDB19C649F4}.Release|x86.Build.0 = Release|Any CPU 49 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|x64.ActiveCfg = Debug|Any CPU 52 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|x64.Build.0 = Debug|Any CPU 53 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|x86.ActiveCfg = Debug|Any CPU 54 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Debug|x86.Build.0 = Debug|Any CPU 55 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|x64.ActiveCfg = Release|Any CPU 58 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|x64.Build.0 = Release|Any CPU 59 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|x86.ActiveCfg = Release|Any CPU 60 | {2A26E997-79CF-4AF2-ABFC-E34AFDE3C463}.Release|x86.Build.0 = Release|Any CPU 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /DemoWalkthrough.md: -------------------------------------------------------------------------------- 1 | Get Comfortable with .NET Core and the CLI 2 | Demo Walkthrough 3 | ================= 4 | 5 | By following this walkthrough, you can re-create the projects in this repository: [https://github.com/jeremybytes/core-cli-30](https://github.com/jeremybytes/core-cli-30). 6 | 7 | **Level**: Introductory 8 | 9 | **Target**: C# developers who have been working with .NET Framework and are interested in trying out .NET Core. 10 | 11 | **Required Software**: 12 | * .NET Core SDK 13 | [https://dotnet.microsoft.com/download](https://dotnet.microsoft.com/download) 14 | * Visual Studio Code 15 | [https://code.visualstudio.com/download](https://code.visualstudio.com/download) 16 | (You can also use Visual Studio 2019 Community Edition) 17 | * C# Extension for Visual Studio Code 18 | [https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp](https://marketplace.visualstudio.com/items?itemName=ms-vscode.csharp) 19 | (This will give you code completion and lots of other help/refactoring in Visual Studio Code). 20 | 21 | **Additional Files:** 22 | The following files will be required during the walkthrough. These files are in the [Starting Files folder](https://github.com/jeremybytes/core-cli-30/tree/master/StartingFiles) of the repository: 23 | 24 | * [snippets.txt](https://github.com/jeremybytes/core-cli-30/blob/master/StartingFiles/snippets.txt) 25 | (code snippets to pasted into the project) 26 | * [CSVPeopleProvider.cs](https://github.com/jeremybytes/core-cli-30/blob/master/StartingFiles/CSVPeopleProvider.cs) 27 | (data provider to show dependency injection) 28 | * [People.txt](https://github.com/jeremybytes/core-cli-30/blob/master/StartingFiles/People.txt) 29 | (data for the CSVPeopleProvider) 30 | 31 | At the end of the walkthrough, we will have a working web service, unit tests for the service, and a console application that calls the service. In addition, we will look at the built-in dependency injection container in ASP.NET Core. 32 | 33 | You can use Visual Studio Code or Visual Studio to edit the code files (I will use a mixture of the two). And PowerShell or cmd.exe will work for the command line. The instructions here show PowerShell commands (which may be slightly different from cmd.exe). 34 | 35 | Code to be typed at the command prompt will appear like this: 36 | 37 | ``` 38 | PS C:\CoreCLI> command 39 | ``` 40 | 41 | In addition, code blocks will be shown for the projects themselves. 42 | 43 | Hello dotnet 44 | -------------- 45 | We'll start by opening PowerShell to the root folder that we use for this project (the location and name of the root folder is up to you). 46 | 47 | The "dotnet" command will be used throughout this walkthrough. This is available and automatically in your path when you install the .NET Core SDK. 48 | 49 | ``` 50 | PS C:\CoreCLI> dotnet 51 | Usage: dotnet [options] 52 | Usage: dotnet [path-to-application] 53 | 54 | Options: 55 | -h|--help Display help. 56 | --info Display .NET Core information. 57 | --list-sdks Display the installed SDKs. 58 | --list-runtimes Display the installed runtimes. 59 | 60 | path-to-application: 61 | The path to an application .dll file to execute. 62 | PS C:\CoreCLI> 63 | ``` 64 | 65 | By typing a command, you get a list of options. To get further details on any command, just add "-h". 66 | 67 | ``` 68 | PS C:\CoreCLI> dotnet -h 69 | .NET Core SDK (3.0.100) 70 | Usage: dotnet [runtime-options] [path-to-application] [arguments] 71 | 72 | Execute a .NET Core application. 73 | 74 | runtime-options: 75 | --additionalprobingpath Path containing probing policy and assemblies to probe for. 76 | --additional-deps Path to additional deps.json file. 77 | --fx-version Version of the installed Shared Framework to use to run the application. 78 | --roll-forward Roll forward to framework version (LatestPatch, Minor, LatestMinor, Major, LatestMajor, Disable). 79 | [...Plus a lot more...] 80 | ``` 81 | 82 | To see what version of .NET Core is installed, use "--version" 83 | 84 | ``` 85 | PS C:\CoreCLI> dotnet --version 86 | 3.0.100 87 | PS C:\CoreCLI> 88 | ``` 89 | 90 | This walkthrough was built with version 3.0.100. 91 | 92 | Web Service 93 | -------------------- 94 | We'll start by creating a web service. 95 | 96 | ### Creating the Project 97 | First, create a new folder and navigate to it. 98 | 99 | ``` 100 | PS C:\CoreCLI> mkdir person-api 101 | ``` 102 | ``` 103 | PS C:\CoreCLI> cd person-api 104 | PS C:\CoreCLI\person-api> 105 | ``` 106 | 107 | Use "dotnet new" to see a list of installed templates: 108 | 109 | ``` 110 | PS C:\CoreCLI\person-api>dotnet new 111 | Usage: new [options] 112 | 113 | Options: 114 | -h, --help Displays help for this command. 115 | -l, --list Lists templates containing the specified name. If no name is specified, lists all templates. 116 | -n, --name The name for the output being created. If no name is specified, the name of the current directory is used. 117 | [...Plus a lot more, here are some templates...] 118 | ASP.NET Core Empty web 119 | ASP.NET Core Web App (Model-View-Controller) mvc 120 | ASP.NET Core Web App webapp 121 | ASP.NET Core with Angular angular 122 | ASP.NET Core with React.js react 123 | ASP.NET Core Web API webapi 124 | ASP.NET Core gRPC Service grpc 125 | [...There are a lot more templates...] 126 | ``` 127 | 128 | Notice that many command arguments have 2 options: a single dash (-h) option and a double dash (--help) option. These are equivalent. 129 | 130 | To create a web service, we will use the "webapi" template: 131 | 132 | ``` 133 | PS C:\CoreCLI\person-api>dotnet new webapi 134 | The template "ASP.NET Core Web API" was created successfully. 135 | 136 | Processing post-creation actions... 137 | Running 'dotnet restore' on C:\CoreCLI\person-api\person-api.csproj... 138 | Restore completed in 71.96 ms for C:\CoreCLI\person-api\person-api.csproj. 139 | 140 | Restore succeeded. 141 | 142 | PS C:\CoreCLI\person-api> 143 | ``` 144 | 145 | Here is a directory listing to show the files that are generated by the template: 146 | 147 | ``` 148 | PS C:\CoreCLI\person-api> dir 149 | Directory: C:\CoreCLI\person-api 150 | 151 | Mode LastWriteTime Length Name 152 | ---- ------------- ------ ---- 153 | d----- 11/22/2019 11:39 AM Controllers 154 | d----- 11/22/2019 11:39 AM Properties 155 | -a---- 11/22/2019 11:39 AM 146 appsettings.Development.json 156 | -a---- 11/22/2019 11:39 AM 192 appsettings.json 157 | -a---- 11/22/2019 11:39 AM 228 person-api.csproj 158 | -a---- 11/22/2019 11:39 AM 718 Program.cs 159 | -a---- 11/22/2019 11:39 AM 1462 Startup.cs 160 | -a---- 11/22/2019 11:39 AM 306 WeatherForecast.cs 161 | 162 | PS C:\CoreCLI\person-api> 163 | ``` 164 | 165 | Note that the project is named "person-api.csproj" (the same as the folder). We will see how to change this default value a bit later. 166 | 167 | ### Building and Running the Sample Service 168 | From here, we can build the project. 169 | 170 | ``` 171 | PS C:\CoreCLI\person-api>dotnet build 172 | Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core 173 | Copyright (C) Microsoft Corporation. All rights reserved. 174 | 175 | Restore completed in 17.39 ms for C:\CoreCLI\person-api\person-api.csproj. 176 | person-api -> C:\CoreCLI\person-api\bin\Debug\netcoreapp3.0\person-api.dll 177 | 178 | Build succeeded. 179 | 0 Warning(s) 180 | 0 Error(s) 181 | 182 | Time Elapsed 00:00:06.96 183 | PS C:\CoreCLI\person-api> 184 | ``` 185 | 186 | The service is self-hosting, so we can run the project directly. 187 | 188 | ``` 189 | PS C:\CoreCLI\person-api> dotnet run 190 | info: Microsoft.Hosting.Lifetime[0] 191 | Now listening on: https://localhost:5001 192 | info: Microsoft.Hosting.Lifetime[0] 193 | Now listening on: http://localhost:5000 194 | info: Microsoft.Hosting.Lifetime[0] 195 | Application started. Press Ctrl+C to shut down. 196 | info: Microsoft.Hosting.Lifetime[0] 197 | Hosting environment: Development 198 | info: Microsoft.Hosting.Lifetime[0] 199 | Content root path: C:\CoreCLI\person-api 200 | ``` 201 | 202 | Note that the host is listening on localhost port 5001 (for https) and port 5000 (for http). 203 | 204 | Now we can navigate to the service in the browser. 205 | 206 | [http://localhost:5000/WeatherForecast](http://localhost:5000/WeatherForecast) 207 | 208 | Using the http endpoint will automatically redirect to the https endpoint [https://localhost:5001/WeatherForecast](https://localhost:5001/WeatherForecast). If you do not have a trusted certificate installed for localhost, then you will get a browser warning. 209 | 210 | ### Changing HTTPS Redirect 211 | Instead of dealing with https and a trusted certificate, we will disable the redirect so we can continue developing easily on the local machine. 212 | 213 | For this, we'll first stop the service by using "Ctrl+C" in the PowerShell window. 214 | 215 | ``` 216 | Application is shutting down... 217 | PS C:\CoreCLI\person-api> 218 | ``` 219 | 220 | If you have Visual Studio Code installed, you can easily open the folder by using the "code ." command. 221 | 222 | ``` 223 | PS C:\CoreCLI\person-api> code . 224 | ``` 225 | 226 | With .NET Core, we do not need to open a solution file or a project file (right now, we do not even have a solution file). Instead, we can open a folder to show the contents. 227 | 228 | Inside Visual Studio Code, you will be prompted to "Add required assets". If you choose "Yes", this will create a new ".vscode" folder that has some setting files in it. These are used for debugging and running from within Visual Studio code. You can say "Yes" to add them, but we will not use them during this walkthrough. 229 | 230 | To remove the https redirection, open the "Startup.cs" file. Then comment out the following line: 231 | 232 | ```csharp 233 | // app.UseHttpsRedirection(); 234 | ``` 235 | 236 | Back at the command prompt, re-run the service. 237 | 238 | *Note that you do not need to build first, using "dotnet run" will rebuild the project if necessary.* 239 | 240 | ``` 241 | PS C:\CoreCLI\person-api> dotnet run 242 | info: Microsoft.Hosting.Lifetime[0] 243 | Now listening on: https://localhost:5001 244 | info: Microsoft.Hosting.Lifetime[0] 245 | Now listening on: http://localhost:5000 246 | info: Microsoft.Hosting.Lifetime[0] 247 | Application started. Press Ctrl+C to shut down. 248 | info: Microsoft.Hosting.Lifetime[0] 249 | Hosting environment: Development 250 | info: Microsoft.Hosting.Lifetime[0] 251 | Content root path: C:\CoreCLI\person-api 252 | ``` 253 | 254 | Now navigate to the http endpoint. 255 | 256 | [http://localhost:5000/WeatherForecast](http://localhost:5000/WeatherForecast) 257 | 258 | This time we are not redirected, and the service supplies the data. 259 | 260 | ```json 261 | [{"date":"2019-11-23T11:59:50.1563699-05:00","temperatureC":49,"temperatureF":120,"summary":"Mild"}, 262 | {"date":"2019-11-24T11:59:50.1566145-05:00","temperatureC":51,"temperatureF":123,"summary":"Scorching"}, 263 | {"date":"2019-11-25T11:59:50.1566224-05:00","temperatureC":-20,"temperatureF":-3,"summary":"Hot"}, 264 | {"date":"2019-11-26T11:59:50.1566229-05:00","temperatureC":54,"temperatureF":129,"summary":"Scorching"}, 265 | {"date":"2019-11-27T11:59:50.1566233-05:00","temperatureC":45,"temperatureF":112,"summary":"Hot"}] 266 | ``` 267 | 268 | This is dummy data that is part of the webapi template. 269 | 270 | ### Updating the Service Code 271 | With a working service, we can now go in and change it to run our code. 272 | 273 | Stop the service using "Ctrl+C". 274 | 275 | ``` 276 | Application is shutting down... 277 | PS C:\CoreCLI\person-api> 278 | ``` 279 | 280 | In Visual Studio Code, add a new folder to the project called "Models". To add a folder, put your cursor in the Explorer window (this is the one that shows the files). In the folder header (where it says "PERSON-API"), you will see a set of icons. One of these is "New Folder". 281 | 282 | The "Models" folder should be at the root of the project folder (as a sibling to "Controllers"). 283 | 284 | *As an alternate, you can add a new folder from the command prompt or File Explorer. The new folder will automatically show up as part of the project in Visual Studio Code.* 285 | 286 | ### Adding a Person Class 287 | From here, add a new file to the Models folder called "Person.cs". To add a file, click on the Models folder, and then choose the "New File" icon. 288 | 289 | Visual Studio Code creates an empty file. Start by adding the namespace 290 | 291 | ```csharp 292 | namespace person_api 293 | { 294 | 295 | } 296 | ``` 297 | 298 | Note that the default namespace for the project is the same as the project name, except the dashes have been replaced by underscores. 299 | 300 | Now locate the "snippets.txt" file and copy the "Person" class into the file. (Or just copy the code from this code block.) 301 | 302 | ```csharp 303 | namespace person_api 304 | { 305 | public class Person 306 | { 307 | public int Id { get; set; } 308 | public string GivenName { get; set; } 309 | public string FamilyName { get; set; } 310 | public DateTime StartDate { get; set; } 311 | public int Rating { get; set; } 312 | public string FormatString { get; set; } 313 | 314 | public override string ToString() 315 | { 316 | if (string.IsNullOrEmpty(FormatString)) 317 | FormatString = "{0} {1}"; 318 | return string.Format(FormatString, GivenName, FamilyName); 319 | } 320 | } 321 | } 322 | ``` 323 | 324 | *If the indentation is off when you paste in the code, use the keyboard shortcut "Shift+Alt+F" to format the file.* 325 | 326 | The "DateTime" type will have red squigglies because the using statement is missing. Just click on "DateTime", and press "Ctrl+." to bring up a list of shortcuts. The top option will add "using System;" to the top of the file. 327 | 328 | ```csharp 329 | using System; 330 | ``` 331 | 332 | ### Adding a Data Provider 333 | The Person class is the data type that for the service. To supply some data, we will add a data provider. Add a new file to the Models folder named "HardCodedPeopleProvider.cs". 334 | 335 | As above, we will add the namespace plus the class declaration. 336 | 337 | ```csharp 338 | namespace person_api 339 | { 340 | public class HardCodedPeopleProvider 341 | { 342 | 343 | } 344 | } 345 | ``` 346 | 347 | Then copy in the "GetPeople" and "GetPerson" methods from the "snippets.txt" file (or from the following code block). 348 | 349 | Be sure to use "Ctrl+." to bring in the needed "using" statements. 350 | 351 | ```csharp 352 | using System; 353 | using System.Collections.Generic; 354 | 355 | namespace person_api 356 | { 357 | public class HardCodedPeopleProvider 358 | { 359 | public List GetPeople() 360 | { 361 | var p = new List() 362 | { 363 | new Person() { Id=1, GivenName="John", FamilyName="Koenig", 364 | StartDate = new DateTime(1975, 10, 17), Rating=6 }, 365 | new Person() { Id=2, GivenName="Dylan", FamilyName="Hunt", 366 | StartDate = new DateTime(2000, 10, 2), Rating=8 }, 367 | new Person() { Id=3, GivenName="Leela", FamilyName="Turanga", 368 | StartDate = new DateTime(1999, 3, 28), Rating=8, 369 | FormatString = "{1} {0}" }, 370 | new Person() { Id=4, GivenName="John", FamilyName="Crichton", 371 | StartDate = new DateTime(1999, 3, 19), Rating=7 }, 372 | new Person() { Id=5, GivenName="Dave", FamilyName="Lister", 373 | StartDate = new DateTime(1988, 2, 15), Rating=9 }, 374 | new Person() { Id=6, GivenName="Laura", FamilyName="Roslin", 375 | StartDate = new DateTime(2003, 12, 8), Rating=6}, 376 | new Person() { Id=7, GivenName="John", FamilyName="Sheridan", 377 | StartDate = new DateTime(1994, 1, 26), Rating=6 }, 378 | new Person() { Id=8, GivenName="Dante", FamilyName="Montana", 379 | StartDate = new DateTime(2000, 11, 1), Rating=5 }, 380 | new Person() { Id=9, GivenName="Isaac", FamilyName="Gampu", 381 | StartDate = new DateTime(1977, 9, 10), Rating=4 }, 382 | }; 383 | return p; 384 | } 385 | 386 | public Person GetPerson(int id) 387 | { 388 | return GetPeople().First(p => p.Id == id); 389 | } 390 | } 391 | } 392 | ``` 393 | 394 | ### Updating the Controller 395 | The next step is to update the controller to use our new data. 396 | 397 | Inside the "Controllers" folder is the "WeatherForecastController.cs" file. We'll rename this file. To do this, click on the file, press F2, and change the name to "PeopleController.cs". 398 | 399 | Open the "PeopleController.cs" file. Here we will rename the class, remove unneeded code, and update the "Get" method. 400 | 401 | Here is the result: 402 | 403 | ```csharp 404 | [ApiController] 405 | [Route("[controller]")] 406 | public class PeopleController : ControllerBase 407 | { 408 | [HttpGet] 409 | public IEnumerable Get() 410 | { 411 | 412 | } 413 | } 414 | ``` 415 | 416 | Be sure to update the class name (PeopleController) and the return type of the "Get" method ("Person" instead of "WeatherForeacast"). 417 | 418 | To fill in the functionality, we will use the HardCodedPeopleProvider that we created earlier. Here's the completed code: 419 | 420 | ```csharp 421 | [ApiController] 422 | [Route("[controller]")] 423 | public class PeopleController : ControllerBase 424 | { 425 | HardCodedPeopleProvider provider = new HardCodedPeopleProvider(); 426 | 427 | [HttpGet] 428 | public IEnumerable Get() 429 | { 430 | return provider.GetPeople(); 431 | } 432 | } 433 | ``` 434 | 435 | Finally, we will add a "Get" method that takes an ID parameter so that we can return a single item. 436 | 437 | ```csharp 438 | [HttpGet("{id}")] 439 | public Person Get(int id) 440 | { 441 | return provider.GetPerson(id); 442 | } 443 | ``` 444 | 445 | Save all the files. 446 | 447 | Back at the command prompt, build the service. (We'll do an explicit build here just to see if there are any build problems we need to fix before running the service.) 448 | 449 | ``` 450 | PS C:\CoreCLI\person-api> dotnet build 451 | Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core 452 | Copyright (C) Microsoft Corporation. All rights reserved. 453 | 454 | Restore completed in 13.4 ms for C:\CoreCLI\person-api\person-api.csproj. 455 | person-api -> C:\CoreCLI\person-api\bin\Debug\netcoreapp3.0\person-api.dll 456 | 457 | Build succeeded. 458 | 0 Warning(s) 459 | 0 Error(s) 460 | 461 | Time Elapsed 00:00:01.20 462 | PS C:\CoreCLI\person-api> 463 | ``` 464 | 465 | Assuming you have a successful build, run the service. 466 | 467 | ``` 468 | Time Elapsed 00:00:01.20 469 | PS C:\CoreCLI\person-api> dotnet run 470 | info: Microsoft.Hosting.Lifetime[0] 471 | Now listening on: https://localhost:5001 472 | info: Microsoft.Hosting.Lifetime[0] 473 | Now listening on: http://localhost:5000 474 | info: Microsoft.Hosting.Lifetime[0] 475 | Application started. Press Ctrl+C to shut down. 476 | info: Microsoft.Hosting.Lifetime[0] 477 | Hosting environment: Development 478 | info: Microsoft.Hosting.Lifetime[0] 479 | Content root path: C:\CoreCLI\person-api 480 | ``` 481 | 482 | Now open a browser and navigate to the service location. Note that the URL is different since we changed the name of the controller. 483 | 484 | [http://localhost:5000/people](http://localhost:5000/people) 485 | 486 | This will give us the entire collection of Person objects. 487 | 488 | ```json 489 | [{"id":1,"givenName":"John","familyName":"Koenig","startDate":"1975-10-17T00:00:00","rating":6,"formatString":null}, 490 | {"id":2,"givenName":"Dylan","familyName":"Hunt","startDate":"2000-10-02T00:00:00","rating":8,"formatString":null}, 491 | {"id":3,"givenName":"Leela","familyName":"Turanga","startDate":"1999-03-28T00:00:00","rating":8,"formatString":"{1} {0}"}, 492 | {"id":4,"givenName":"John","familyName":"Crichton","startDate":"1999-03-19T00:00:00","rating":7,"formatString":null}, 493 | {"id":5,"givenName":"Dave","familyName":"Lister","startDate":"1988-02-15T00:00:00","rating":9,"formatString":null}, 494 | {"id":6,"givenName":"Laura","familyName":"Roslin","startDate":"2003-12-08T00:00:00","rating":6,"formatString":null}, 495 | {"id":7,"givenName":"John","familyName":"Sheridan","startDate":"1994-01-26T00:00:00","rating":6,"formatString":null}, 496 | {"id":8,"givenName":"Dante","familyName":"Montana","startDate":"2000-11-01T00:00:00","rating":5,"formatString":null}, 497 | {"id":9,"givenName":"Isaac","familyName":"Gampu","startDate":"1977-09-10T00:00:00","rating":4,"formatString":null}] 498 | ``` 499 | 500 | We can call the other "Get" method by adding a value to the URL. 501 | 502 | [http://localhost:5000/people/2](http://localhost:5000/people/2) 503 | 504 | ```json 505 | {"id":2,"givenName":"Dylan","familyName":"Hunt","startDate":"2000-10-02T00:00:00","rating":8,"formatString":null} 506 | ``` 507 | 508 | Now we have a working service. 509 | 510 | ### Changing the Port 511 | One last change is that we will change the port that is used for the service. This is something that I do with my localhost projects to eliminate possible collisions if I have multiple services running at the same time. 512 | 513 | To change the port, we'll go back to Visual Studio Code and open the "Program.cs" file. 514 | 515 | In the "CreateHostBuilder" method, we'll add `.UseUrls("http://localhost:9874")`. 516 | 517 | ```csharp 518 | public static IHostBuilder CreateHostBuilder(string[] args) => 519 | Host.CreateDefaultBuilder(args) 520 | .ConfigureWebHostDefaults(webBuilder => 521 | { 522 | webBuilder.UseStartup() 523 | .UseUrls("http://localhost:9874"); 524 | }); 525 | ``` 526 | 527 | Back in the PowerShell window, use "Ctrl+C" to stop the service, then "dotnet run" to restart it. 528 | 529 | ``` 530 | Application is shutting down... 531 | PS C:\CoreCLI\person-api> dotnet run 532 | info: Microsoft.Hosting.Lifetime[0] 533 | Now listening on: http://localhost:9874 534 | info: Microsoft.Hosting.Lifetime[0] 535 | Application started. Press Ctrl+C to shut down. 536 | info: Microsoft.Hosting.Lifetime[0] 537 | Hosting environment: Development 538 | info: Microsoft.Hosting.Lifetime[0] 539 | Content root path: C:\CoreCLI\person-api 540 | ``` 541 | 542 | We now see that the host is listening on port 9874. 543 | 544 | If we click "Refresh" in the browser (or navigate to the old address), we get a "connection refused" error. 545 | 546 | [http://localhost:5000/people](http://localhost:5000/people) 547 | 548 | If we update the URL to use the new port, then we have the data that we expect. 549 | 550 | [http://localhost:9874/people](http://localhost:9874/people) 551 | 552 | ```json 553 | [{"id":1,"givenName":"John","familyName":"Koenig","startDate":"1975-10-17T00:00:00","rating":6,"formatString":null}, 554 | {"id":2,"givenName":"Dylan","familyName":"Hunt","startDate":"2000-10-02T00:00:00","rating":8,"formatString":null}, 555 | {"id":3,"givenName":"Leela","familyName":"Turanga","startDate":"1999-03-28T00:00:00","rating":8,"formatString":"{1} {0}"}, 556 | {"id":4,"givenName":"John","familyName":"Crichton","startDate":"1999-03-19T00:00:00","rating":7,"formatString":null}, 557 | {"id":5,"givenName":"Dave","familyName":"Lister","startDate":"1988-02-15T00:00:00","rating":9,"formatString":null}, 558 | {"id":6,"givenName":"Laura","familyName":"Roslin","startDate":"2003-12-08T00:00:00","rating":6,"formatString":null}, 559 | {"id":7,"givenName":"John","familyName":"Sheridan","startDate":"1994-01-26T00:00:00","rating":6,"formatString":null}, 560 | {"id":8,"givenName":"Dante","familyName":"Montana","startDate":"2000-11-01T00:00:00","rating":5,"formatString":null}, 561 | {"id":9,"givenName":"Isaac","familyName":"Gampu","startDate":"1977-09-10T00:00:00","rating":4,"formatString":null}] 562 | ``` 563 | 564 | And that's our working service. 565 | 566 | 567 | Unit Tests 568 | ------------------ 569 | Next we'll create a unit test project and add some tests for the Controller class in the web service. 570 | 571 | ### Creating the Project 572 | Open a new PowerShell (or cmd.exe window) at the root of the project. 573 | 574 | ``` 575 | PS C:\CoreCLI> 576 | ``` 577 | 578 | Create a folder for the unit test project named "person-api-tests" and navigate to that folder. 579 | 580 | ``` 581 | PS C:\CoreCLI> mkdir person-api-tests 582 | 583 | Directory: C:\CoreCLI 584 | 585 | Mode LastWriteTime Name 586 | ---- ------------- ---- 587 | d----- 11/22/2019 2:37 PM person-api-tests 588 | 589 | PS C:\CoreCLI> cd .\person-api-tests\ 590 | PS C:\CoreCLI\person-api-tests> 591 | ``` 592 | 593 | If we type "dotnet new", we can see several unit test project options in the list. 594 | 595 | ``` 596 | PS C:\CoreCLI\person-api-tests> dotnet new 597 | 598 | Unit Test Project mstest 599 | NUnit 3 Test Project nunit 600 | NUnit 3 Test Item nunit-test 601 | xUnit Test Project xunit 602 | ``` 603 | 604 | For our sample, we'll use NUnit. 605 | 606 | ``` 607 | PS C:\CoreCLI\person-api-tests> dotnet new nunit 608 | The template "NUnit 3 Test Project" was created successfully. 609 | 610 | Processing post-creation actions... 611 | Running 'dotnet restore' on C:\CoreCLI\person-api-tests\person-api-tests.csproj... 612 | Restore completed in 2.7 sec for C:\CoreCLI\person-api-tests\person-api-tests.csproj. 613 | 614 | Restore succeeded. 615 | 616 | PS C:\CoreCLI\person-api-tests> 617 | ``` 618 | 619 | Open the project folder in Visual Studio Code. 620 | 621 | ``` 622 | PS C:\CoreCLI\person-api-tests> code . 623 | ``` 624 | 625 | The "UnitTest1.cs" file has a sample test. 626 | 627 | ```csharp 628 | using NUnit.Framework; 629 | 630 | namespace person_api_tests 631 | { 632 | public class Tests 633 | { 634 | [SetUp] 635 | public void Setup() 636 | { 637 | } 638 | 639 | [Test] 640 | public void Test1() 641 | { 642 | Assert.Pass(); 643 | } 644 | } 645 | } 646 | ``` 647 | 648 | ### Running the Test 649 | To run the test, we use "dotnet test" at the command prompt. 650 | 651 | ``` 652 | PS C:\CoreCLI\person-api-tests> dotnet test 653 | Test run for C:\CoreCLI\person-api-tests\bin\Debug\netcoreapp3.0\person-api-tests.dll(.NETCoreApp,Version=v3.0) 654 | Microsoft (R) Test Execution Command Line Tool Version 16.3.0 655 | Copyright (c) Microsoft Corporation. All rights reserved. 656 | 657 | Starting test execution, please wait... 658 | 659 | A total of 1 test files matched the specified pattern. 660 | 661 | Test Run Successful. 662 | Total tests: 1 663 | Passed: 1 664 | Total time: 1.2749 Seconds 665 | PS C:\CoreCLI\person-api-tests> 666 | ``` 667 | 668 | Just like with "dotnet run", we do not need to do an explicit build to run the tests. The project will be built automatically if required. 669 | 670 | ### Adding a Project Reference 671 | Since we want to test the controller from the web service, we need to add a reference to that project. We can do this from the command line using "dotnet add reference ...". 672 | 673 | ``` 674 | PS C:\CoreCLI\person-api-tests> dotnet add reference ..\person-api\person-api.csproj 675 | Reference `..\person-api\person-api.csproj` added to the project. 676 | PS C:\CoreCLI\person-api-tests> 677 | ``` 678 | 679 | ### Creating the First Test 680 | Back in Visual Studio Code, we will update the placeholder test with our own. 681 | 682 | First, rename the file from "UnitTest1.cs" to "PeopleControllerTests.cs". (As a reminder, click on the file and press F2 in Visual Studio Code). 683 | 684 | Inside the file, we will rename the class from "Tests" to "PeopleControllerTests". 685 | 686 | ```csharp 687 | public class PeopleControllerTests 688 | ``` 689 | 690 | We will use the setup method to create an instance of the controller that our tests will use. For this, we'll add a class-level field for the controller and "new" it up in the Setup method. 691 | 692 | ```csharp 693 | public class PeopleControllerTests 694 | { 695 | PeopleController controller; 696 | 697 | [SetUp] 698 | public void Setup() 699 | { 700 | controller = new PeopleController(); 701 | } 702 | ... 703 | } 704 | ``` 705 | 706 | Note: you will need to use "Ctrl+." to bring in the "using" statement for the PeopleController class. 707 | 708 | Next, we'll remove "Test1" and add something a little more useful. 709 | 710 | ```csharp 711 | [Test] 712 | public void GetPeople_ReturnsAllItems() 713 | { 714 | IEnumerable result = controller.Get(); 715 | Assert.AreEqual(9, result.Count()); 716 | } 717 | ``` 718 | 719 | (You will need to bring in some using statements, but I'll assume that you are used to doing that now.) 720 | 721 | Back at the command prompt, we'll run our new test. 722 | 723 | ``` 724 | PS C:\CoreCLI\person-api-tests> dotnet test 725 | Test run for C:\CoreCLI\person-api-tests\bin\Debug\netcoreapp3.0\person-api-tests.dll(.NETCoreApp,Version=v3.0) 726 | Microsoft (R) Test Execution Command Line Tool Version 16.3.0 727 | Copyright (c) Microsoft Corporation. All rights reserved. 728 | 729 | Starting test execution, please wait... 730 | 731 | A total of 1 test files matched the specified pattern. 732 | 733 | Test Run Successful. 734 | Total tests: 1 735 | Passed: 1 736 | Total time: 0.7540 Seconds 737 | PS C:\CoreCLI\person-api-tests> 738 | ``` 739 | 740 | The first test is passing. This test is not very useful because it is tied to the "HardCodedPeopleProvider" class that is part of the web service project. We'll separate this out later on when we look at dependency injection. 741 | 742 | ### Adding 2 More Tests 743 | Next we will add a couple of tests for the "Get(int id)" method. 744 | 745 | ```csharp 746 | [Test] 747 | public void GetPerson_WithValidId_ReturnsPerson() 748 | { 749 | Person result = controller.Get(2); 750 | Assert.AreEqual(2, result.Id); 751 | } 752 | 753 | [Test] 754 | public void GetPerson_WithInvalidId_ReturnsNull() 755 | { 756 | Person result = controller.Get(-10); 757 | Assert.IsNull(result); 758 | } 759 | ``` 760 | 761 | Now run the tests. 762 | 763 | ``` 764 | PS C:\CoreCLI\person-api-tests> dotnet test 765 | Test run for C:\CoreCLI\person-api-tests\bin\Debug\netcoreapp3.0\person-api-tests.dll(.NETCoreApp,Version=v3.0) 766 | Microsoft (R) Test Execution Command Line Tool Version 16.3.0 767 | Copyright (c) Microsoft Corporation. All rights reserved. 768 | 769 | Starting test execution, please wait... 770 | 771 | A total of 1 test files matched the specified pattern. 772 | X GetPerson_WithInvalidId_ReturnsNull [18ms] 773 | Error Message: 774 | System.InvalidOperationException : Sequence contains no matching element 775 | Stack Trace: 776 | at System.Linq.ThrowHelper.ThrowNoMatchException() 777 | at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate) 778 | at person_api.HardCodedPeopleProvider.GetPerson(Int32 id) in C:\CoreCLI\person-api\Models\HardCodedPeopleProvider.cs:line 38 779 | at person_api.Controllers.PeopleController.Get(Int32 id) in C:\CoreCLI\person-api\Controllers\PeopleController.cs:line 25 780 | at person_api_tests.PeopleControllerTests.GetPerson_WithInvalidId_ReturnsNull() in C:\CoreCLI\person-api-tests\PeopleControllerTests.cs:line 36 781 | 782 | Test Run Failed. 783 | Total tests: 3 784 | Passed: 2 785 | Failed: 1 786 | Total time: 0.7159 Seconds 787 | PS C:\CoreCLI\person-api-tests> 788 | ``` 789 | 790 | In this case, the last test fails. That is because the web service throws an exception (InvalidOperationException) rather than returning null. 791 | 792 | *We won't talk about whether returning null from the service is a good idea or not (it probably isn't). Instead, we'll focus on creating the tests and making sure they pass.* 793 | 794 | The test fails because of the way the HardCodedPeopleProvider is coded. As a reminder, here is the "GetPerson" method from that class: 795 | 796 | ```csharp 797 | public Person GetPerson(int id) 798 | { 799 | return GetPeople().First(p => p.Id == id); 800 | } 801 | ``` 802 | 803 | The "First" method looks for the first match it can find. But if it finds no matches at all, it will throw an exception. 804 | 805 | But we don't want to modify the provider. Instead we want to modify the controller. This will assure that the Get method on the controller will return "null" as appropriate regardless of how the underlying provider behaves. 806 | 807 | Let's go to the "Get(int id)" method from the PeopleController class. 808 | 809 | ```csharp 810 | [HttpGet("{id}")] 811 | public Person Get(int id) 812 | { 813 | return provider.GetPerson(id); 814 | } 815 | ``` 816 | 817 | We will wrap the "GetPerson" call in a try/catch block. Then if the provider throws an exception, we can still return "null". 818 | 819 | ```csharp 820 | [HttpGet("{id}")] 821 | public Person Get(int id) 822 | { 823 | try 824 | { 825 | return provider.GetPerson(id); 826 | } 827 | catch (Exception) 828 | { 829 | return null; 830 | } 831 | } 832 | ``` 833 | 834 | Now if we re-run the tests, we see that they all pass. 835 | 836 | ``` 837 | PS C:\CoreCLI\person-api-tests> dotnet test 838 | Test run for C:\CoreCLI\person-api-tests\bin\Debug\netcoreapp3.0\person-api-tests.dll(.NETCoreApp,Version=v3.0) 839 | Microsoft (R) Test Execution Command Line Tool Version 16.3.0 840 | Copyright (c) Microsoft Corporation. All rights reserved. 841 | 842 | Starting test execution, please wait... 843 | 844 | A total of 1 test files matched the specified pattern. 845 | 846 | Test Run Successful. 847 | Total tests: 3 848 | Passed: 3 849 | Total time: 0.8103 Seconds 850 | PS C:\CoreCLI\person-api-tests> 851 | ``` 852 | 853 | *Note: we do not have to explicitly re-build the web service project because it is referenced by the test project. So it will get re-built automatically as needed.* 854 | 855 | Our tests still need better isolation so that we can have more control over the provider during testing. But this gets us started. We will make things a bit more robust later on. 856 | 857 | Console Application 858 | -------------------- 859 | As a third project, we will build a console application that calls the web service. 860 | 861 | ### Creating the Project 862 | Open a new PowerShell (or cmd.exe window) at the root of the project. 863 | 864 | ``` 865 | PS C:\CoreCLI> 866 | ``` 867 | 868 | Create a folder for the unit test project named "person-console" and navigate to that folder. 869 | 870 | ``` 871 | PS C:\CoreCLI> mkdir person-console 872 | 873 | Directory: C:\CoreCLI 874 | 875 | Mode LastWriteTime Name 876 | ---- ------------- ---- 877 | d----- 11/22/2019 5:27 PM person-console 878 | 879 | PS C:\CoreCLI> cd person-console 880 | PS C:\CoreCLI\person-console> 881 | ``` 882 | 883 | Next create a new console application with "dotnet new console". 884 | 885 | ``` 886 | PS C:\CoreCLI\person-console> dotnet new console 887 | The template "Console Application" was created successfully. 888 | 889 | Processing post-creation actions... 890 | Running 'dotnet restore' on C:\CoreCLI\person-console\person-console.csproj... 891 | Restore completed in 75.19 ms for C:\CoreCLI\person-console\person-console.csproj. 892 | 893 | Restore succeeded. 894 | 895 | PS C:\CoreCLI\person-console> 896 | ``` 897 | 898 | ### Running the Application 899 | Run the application. 900 | 901 | ``` 902 | PS C:\CoreCLI\person-console> dotnet run 903 | Hello World! 904 | PS C:\CoreCLI\person-console> 905 | ``` 906 | 907 | We have a working console application. Unfortunately, it takes the fun out of creating a new application because "Hello, World!" has already been added. 908 | 909 | Open the project folder in Visual Studio Code 910 | 911 | ``` 912 | PS C:\CoreCLI\person-console> code . 913 | ``` 914 | 915 | Open the "Program.cs" file. 916 | 917 | ```csharp 918 | using System; 919 | 920 | namespace person_console 921 | { 922 | class Program 923 | { 924 | static void Main(string[] args) 925 | { 926 | Console.WriteLine("Hello World!"); 927 | } 928 | } 929 | } 930 | ``` 931 | 932 | This is what is included in the default template. 933 | 934 | ### Calling the Service 935 | To call the service, we will add a copy of the "Person" class and create a class that knows how to talk to the service. 936 | 937 | Using File Explorer (or the method of your choice), copy the "Person.cs" file from the "person-api" project into the root folder of the "person-console" project. 938 | 939 | *One thing to notice when you do this: the file automatically shows up in Visual Studio Code.* 940 | 941 | .NET Core uses a different project system than .NET Framework. By default, the project includes any files that are in the project folder. There is no need to explicitly add them. (We can explicitly exclude them if we need to). 942 | 943 | Open "Person.cs" in Visual Studio Code and change the namespace to match the console application: person_console. 944 | 945 | ```csharp 946 | namespace person_console 947 | { 948 | public class Person 949 | { 950 | public int Id { get; set; } 951 | public string GivenName { get; set; } 952 | public string FamilyName { get; set; } 953 | public DateTime StartDate { get; set; } 954 | public int Rating { get; set; } 955 | public string FormatString { get; set; } 956 | 957 | public override string ToString() 958 | { 959 | if (string.IsNullOrEmpty(FormatString)) 960 | FormatString = "{0} {1}"; 961 | return string.Format(FormatString, GivenName, FamilyName); 962 | } 963 | } 964 | } 965 | ``` 966 | 967 | Create a new file / class named "PersonReader.cs" / "PersonReader". 968 | 969 | ```csharp 970 | namespace person_console 971 | { 972 | public class PersonReader 973 | { 974 | 975 | } 976 | } 977 | ``` 978 | 979 | Add a field of type "HttpClient". 980 | 981 | ```csharp 982 | public class PersonReader 983 | { 984 | private HttpClient client = new HttpClient(); 985 | } 986 | ``` 987 | 988 | Copy the constructor from the "snippets.txt" file. 989 | 990 | ```csharp 991 | public class PersonReader 992 | { 993 | private HttpClient client = new HttpClient(); 994 | 995 | public PersonReader() 996 | { 997 | client.BaseAddress = new Uri("http://localhost:9874"); 998 | client.DefaultRequestHeaders.Accept.Add( 999 | new MediaTypeWithQualityHeaderValue("application/json")); 1000 | } 1001 | } 1002 | ``` 1003 | 1004 | Don't forget to add the using statements using "Ctrl+." 1005 | 1006 | Next, add the "GetAsync" method from the "snippets.txt" file. 1007 | 1008 | ```csharp 1009 | public class PersonReader 1010 | { 1011 | private HttpClient client = new HttpClient(); 1012 | 1013 | public PersonReader() 1014 | { 1015 | client.BaseAddress = new Uri("http://localhost:9874"); 1016 | client.DefaultRequestHeaders.Accept.Add( 1017 | new MediaTypeWithQualityHeaderValue("application/json")); 1018 | } 1019 | 1020 | public async Task> GetAsync() 1021 | { 1022 | await Task.Delay(3000); 1023 | 1024 | HttpResponseMessage response = await client.GetAsync("people"); 1025 | if (response.IsSuccessStatusCode) 1026 | { 1027 | var stringResult = await response.Content.ReadAsStringAsync(); 1028 | return JsonConvert.DeserializeObject>(stringResult); 1029 | } 1030 | return new List(); 1031 | } 1032 | } 1033 | ``` 1034 | 1035 | *Note: the "Task.Delay" call pauses operation for 3 seconds. This code was originally taken from a demo on Task and await. For more information, check [I'll Get Back to You: Task, Await, and Asynchronous Methods](http://www.jeremybytes.com/Demos.aspx#TaskAndAwait).* 1036 | 1037 | If you try to add the using statement for "JsonConvert", you'll see that it does not resolve. This is because the object comes from a NuGet package that we have not yet added. 1038 | 1039 | ### Adding a NuGet Package 1040 | To add the package, we'll go back to the command prompt and use "dotnet add package". 1041 | 1042 | ``` 1043 | PS C:\CoreCLI\person-console> dotnet add package Newtonsoft.Json 1044 | Writing C:\Users\jerem\AppData\Local\Temp\tmpE47F.tmp 1045 | info : Adding PackageReference for package 'Newtonsoft.Json' into project 'C:\CoreCLI\person-console\person-console.csproj'. 1046 | info : Restoring packages for C:\CoreCLI\person-console\person-console.csproj... 1047 | info : GET https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json 1048 | info : OK https://api.nuget.org/v3-flatcontainer/newtonsoft.json/index.json 53ms 1049 | info : Package 'Newtonsoft.Json' is compatible with all the specified frameworks in project 'C:\CoreCLI\person-console\person-console.csproj'. 1050 | info : PackageReference for package 'Newtonsoft.Json' version '12.0.3' added to file 'C:\CoreCLI\person-console\person-console.csproj'. 1051 | info : Committing restore... 1052 | info : Writing assets file to disk. Path: C:\CoreCLI\person-console\obj\project.assets.json 1053 | log : Restore completed in 932.82 ms for C:\CoreCLI\person-console\person-console.csproj. 1054 | PS C:\CoreCLI\person-console> 1055 | ``` 1056 | 1057 | Starting with .NET Core 3.0, a JSON serializer is included in the framework. We're using an external serializer here so we can see how to add a NuGet package. 1058 | 1059 | If you use "Ctrl+." on "JsonConvert" in the code file, it will now resolve and add the correct using statement. 1060 | 1061 | ### Updating the Program 1062 | Now that we have the data object and data reader set up, we need to call them from the Program class. 1063 | 1064 | Open "Program.cs" and update the Main method as follows. 1065 | 1066 | ```csharp 1067 | class Program 1068 | { 1069 | static void Main(string[] args) 1070 | { 1071 | Console.WriteLine("One moment please..."); 1072 | var reader = new PersonReader(); 1073 | var people = await reader.GetAsync(); 1074 | foreach(var person in people) 1075 | Console.WriteLine(person); 1076 | 1077 | Console.WriteLine("==============="); 1078 | } 1079 | } 1080 | ``` 1081 | 1082 | This creates a data reader, awaits the "GetAsync" method, then loops through the results to output to the console. 1083 | 1084 | This code will not compile at this point. Since we are using "await", we need to make the method "async". Fortunately, .NET Core supports an "async Main" method in console applications (since C# 7.1). 1085 | 1086 | ```csharp 1087 | class Program 1088 | { 1089 | static async Task Main(string[] args) 1090 | { 1091 | Console.WriteLine("One moment please..."); 1092 | var reader = new PersonReader(); 1093 | var people = await reader.GetAsync(); 1094 | foreach(var person in people) 1095 | Console.WriteLine(person); 1096 | 1097 | Console.WriteLine("==============="); 1098 | } 1099 | } 1100 | ``` 1101 | 1102 | The Main method needs to be "async Task" rather than "async void". "async void" is not allowed here. 1103 | 1104 | ### Running the Application 1105 | With everything in place, we can now run the application. First, go back to the PowerShell window with the service folder and start the service using "dotnet run". 1106 | 1107 | ``` 1108 | PS C:\CoreCLI\person-api> dotnet run 1109 | info: Microsoft.Hosting.Lifetime[0] 1110 | Now listening on: http://localhost:9874 1111 | info: Microsoft.Hosting.Lifetime[0] 1112 | Application started. Press Ctrl+C to shut down. 1113 | info: Microsoft.Hosting.Lifetime[0] 1114 | Hosting environment: Development 1115 | info: Microsoft.Hosting.Lifetime[0] 1116 | Content root path: C:\CoreCLI\person-api 1117 | ``` 1118 | 1119 | Then run the console application from the PowerShell window that is open to the console project. 1120 | 1121 | First it displays the "One moment please..." message. 1122 | 1123 | ``` 1124 | PS C:\CoreCLI\person-console> dotnet run 1125 | One moment please... 1126 | ``` 1127 | 1128 | Then after 3 seconds, it displays the data from the web service. 1129 | 1130 | ``` 1131 | PS C:\CoreCLI\person-console> dotnet run 1132 | One moment please... 1133 | John Koenig 1134 | Dylan Hunt 1135 | Turanga Leela 1136 | John Crichton 1137 | Dave Lister 1138 | Laura Roslin 1139 | John Sheridan 1140 | Dante Montana 1141 | Isaac Gampu 1142 | =============== 1143 | PS C:\CoreCLI\person-console> 1144 | ``` 1145 | 1146 | Everything works! 1147 | 1148 | Solution 1149 | --------- 1150 | Now that we have 3 projects, we will create a solution for them. We'll do this from the command line as well. 1151 | 1152 | ### Creating the Solution 1153 | Open a new PowerShell (or cmd.exe window) at the root of the project. 1154 | 1155 | ``` 1156 | PS C:\CoreCLI> 1157 | ``` 1158 | 1159 | Use "dotnet new sln" to create a solution. 1160 | 1161 | ``` 1162 | PS C:\CoreCLI> dotnet new sln -n "CoreCLI" 1163 | The template "Solution File" was created successfully. 1164 | ``` 1165 | 1166 | By using the "-n" argument, we can name the solution whatever we want. This way we can override the default naming if we want something different than the root folder. 1167 | 1168 | ``` 1169 | PS C:\CoreCLI> dir 1170 | 1171 | Directory: C:\CoreCLI 1172 | 1173 | Mode LastWriteTime Name 1174 | ---- ------------- ---- 1175 | d----- 11/22/2019 12:05 PM person-api 1176 | d----- 11/22/2019 2:55 PM person-api-tests 1177 | d----- 11/22/2019 5:39 PM person-console 1178 | -a---- 11/22/2019 6:00 PM CoreCLI.sln 1179 | 1180 | PS C:\CoreCLI> 1181 | ``` 1182 | 1183 | ### Adding Projects to the Solution 1184 | To add projects, we use "dotnet sln add". 1185 | 1186 | The following adds the web service project to the solution. 1187 | 1188 | ``` 1189 | PS C:\CoreCLI> dotnet sln add .\person-api\person-api.csproj 1190 | Project `person-api\person-api.csproj` added to the solution. 1191 | PS C:\CoreCLI> 1192 | ``` 1193 | 1194 | This may seem like a lot of work, but because of auto-completion on the command line, it does not take very long to type. Just type the first few letters of the folder or file and hit "Tab". 1195 | 1196 | We'll do the same for the other 2 projects. 1197 | 1198 | ``` 1199 | PS C:\CoreCLI> dotnet sln add .\person-api-tests\person-api-tests.csproj 1200 | Project `person-api-tests\person-api-tests.csproj` added to the solution. 1201 | PS C:\CoreCLI> dotnet sln add .\person-console\person-console.csproj 1202 | Project `person-console\person-console.csproj` added to the solution. 1203 | PS C:\CoreCLI> 1204 | ``` 1205 | 1206 | Now that we have a populated solution file, we can open the file in Visual Studio. 1207 | 1208 | With Visual Studio installed, we can type the name of the file to automatically open it. 1209 | 1210 | ``` 1211 | PS C:\CoreCLI> .\CoreCLI.sln 1212 | ``` 1213 | 1214 | In Visual Studio, we see all 3 projects, and we can run the tests in the test explorer. 1215 | 1216 | When using Visual Studio, I'll often create the projects and classes inside the IDE. But it is good to know that we can do this all from the command line in case we need to. Also, if we understand how to do things from the command line, we have a better understanding of what Visual Studio does behind the scenes. 1217 | 1218 | Dependency Injection 1219 | --------------------- 1220 | As a last stop, we will take a quick look at the built-in dependency injection container that comes with ASP.NET Core. This allows us to quickly swap out dependencies to make our code more flexible and easier to test and maintain. 1221 | 1222 | For our application, we will inject the data provider into the controller for the service. Then our controller will not need to know about any specific data provider, and we can change it in the service startup. It will also allow us to mock out the data provider for our unit tests so that we have consistent data and behavior. 1223 | 1224 | As a first step, stop the web service (if it is running). 1225 | 1226 | ``` 1227 | Application is shutting down... 1228 | PS C:\CoreCLI\person-api> 1229 | ``` 1230 | 1231 | ### Creating an Abstraction 1232 | Before changing out the data provider, we will need to create an abstraction that we can use. In this case, it will be an interface. Then the controller will reference the interface methods instead of the methods on a concrete type. 1233 | 1234 | Visual Studio Code has an "Extract Interface" shortcut, but it does not quite do what I would like it to for this application. So I'll use Visual Studio instead. 1235 | 1236 | In Visual Studio, open the "HardCodedPeopleProvider.cs" file and click on the class name. 1237 | 1238 | Then use "Ctrl+." to bring up the built-in refactoring tools. Select "Extract interface...". 1239 | 1240 | In the popup box, change the name of the interface to "IPeopleProvider" and click "OK". 1241 | 1242 | This will create a new file (IPeopleProvider.cs) with the following code. 1243 | 1244 | ```csharp 1245 | namespace person_api 1246 | { 1247 | public interface IPeopleProvider 1248 | { 1249 | List GetPeople(); 1250 | Person GetPerson(int id); 1251 | } 1252 | } 1253 | ``` 1254 | 1255 | *If you do not have Visual Studio, you can create the "IPeopleProvider.cs" file manually and add the code listed above.* 1256 | 1257 | This interface is an abstraction that represents any class that includes these 2 methods. 1258 | 1259 | In addition to creating a new file, Visual Studio updated the "HardCodedPeopleProvider" class to denote that it implements the new interface. 1260 | 1261 | ```csharp 1262 | public class HardCodedPeopleProvider : IPeopleProvider 1263 | { 1264 | ... 1265 | } 1266 | ``` 1267 | 1268 | *If you add the interface manually, you will need to add the ': IPeopleProvider' to the class yourself.* 1269 | 1270 | ### Updating the Controller 1271 | Now that we have the interface, we can update the controller to use the interface for the field. 1272 | 1273 | Here is an update to the "PeopleController" class. 1274 | 1275 | ```csharp 1276 | public class PeopleController : ControllerBase 1277 | { 1278 | IPeopleProvider provider = new HardCodedPeopleProvider(); 1279 | ... 1280 | } 1281 | ``` 1282 | 1283 | The next step is to remove the step where we "new" up the HardCodedPeopleProvider. Instead, we will create a constructor with a parameter that will set the field. 1284 | 1285 | ```csharp 1286 | public class PeopleController : ControllerBase 1287 | { 1288 | IPeopleProvider provider; 1289 | 1290 | public PeopleController(IPeopleProvider provider) 1291 | { 1292 | this.provider = provider; 1293 | } 1294 | ... 1295 | } 1296 | ``` 1297 | 1298 | With this code in place, the controller is no longer responsible for the provider. Whoever creates the controller is responsible for providing an already-instantiated provider (through the constructor parameter). 1299 | 1300 | The body of the constructor sets the private field based on the parameter coming in. 1301 | 1302 | *If you are not familiar with dependency injection, you can take a look at the materials available here: [DI Why: Getting a Grip on Dependency Injection](http://www.jeremybytes.com/Demos.aspx#DI).* 1303 | 1304 | ### Running the Application 1305 | At this point, we can build and run the application. We'll go back to the command line for this. 1306 | 1307 | When we run "dotnet build" we get a successful build. 1308 | 1309 | ``` 1310 | PS C:\CoreCLI\person-api> dotnet build 1311 | Microsoft (R) Build Engine version 16.3.0+0f4c62fea for .NET Core 1312 | Copyright (C) Microsoft Corporation. All rights reserved. 1313 | 1314 | Restore completed in 42.84 ms for C:\CoreCLI\person-api\person-api.csproj. 1315 | person-api -> C:\CoreCLI\person-api\bin\Debug\netcoreapp3.0\person-api.dll 1316 | 1317 | Build succeeded. 1318 | 0 Warning(s) 1319 | 0 Error(s) 1320 | 1321 | Time Elapsed 00:00:13.40 1322 | PS C:\CoreCLI\person-api> 1323 | ``` 1324 | 1325 | And if we use "dotnet run" the service starts successfully. 1326 | 1327 | ``` 1328 | PS C:\CoreCLI\person-api> dotnet run 1329 | info: Microsoft.Hosting.Lifetime[0] 1330 | Now listening on: http://localhost:9874 1331 | info: Microsoft.Hosting.Lifetime[0] 1332 | Application started. Press Ctrl+C to shut down. 1333 | info: Microsoft.Hosting.Lifetime[0] 1334 | Hosting environment: Development 1335 | info: Microsoft.Hosting.Lifetime[0] 1336 | Content root path: C:\CoreCLI\person-api 1337 | ``` 1338 | 1339 | But if we navigate to the service in the browser, we get a runtime error. 1340 | 1341 | [http://localhost:9874/people](http://localhost:9874/people) 1342 | 1343 | If we go back to PowerShell, it lists the error. Look for the "Fail" in the output. 1344 | 1345 | ``` 1346 | fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1] 1347 | An unhandled exception has occurred while executing the request. 1348 | System.InvalidOperationException: Unable to resolve service for type 'person_api.IPeopleProvider' while attempting to activate 'person_api.Controllers.PeopleController'. 1349 | at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired) 1350 | ``` 1351 | 1352 | An exception is thrown when trying to create an instance of "PeopleController". The error tells us that the system was unable to resolve "IPeopleProvder". 1353 | 1354 | Although we coded the controller to use the interface, we still need to tell the dependency injection container how to map that abstraction to a concrete type. 1355 | 1356 | ### Mapping the Interface in the Dependency Injection Container 1357 | We can set up this mapping in the "Startup.cs" file, specifically in the "ConfigureServices" method. 1358 | 1359 | ``` 1360 | public void ConfigureServices(IServiceCollection services) 1361 | { 1362 | services.AddControllers(); 1363 | services.AddSingleton(); 1364 | } 1365 | ``` 1366 | 1367 | The "AddSingleton" method specifies that anywhere we need an "IPeopleProvider", the system should use a "HardCodedPeopleProvider". 1368 | 1369 | "AddSingleton" will create a single instance of the HardCodedPeopleProvider regardless of how many times we need one. Other options include "AddTransient" and "AddScoped". A discussion of scopes is outside the scope of this walkthrough. 1370 | 1371 | ### Running the Service 1372 | Now if we stop and restart the service, we can navigate to the service successfully. 1373 | 1374 | ``` 1375 | Application is shutting down... 1376 | PS C:\CoreCLI\person-api> dotnet run 1377 | info: Microsoft.Hosting.Lifetime[0] 1378 | Now listening on: http://localhost:9874 1379 | info: Microsoft.Hosting.Lifetime[0] 1380 | Application started. Press Ctrl+C to shut down. 1381 | info: Microsoft.Hosting.Lifetime[0] 1382 | Hosting environment: Development 1383 | info: Microsoft.Hosting.Lifetime[0] 1384 | Content root path: C:\CoreCLI\person-api 1385 | ``` 1386 | 1387 | [http://localhost:9874/people](http://localhost:9874/people) 1388 | 1389 | ```json 1390 | [{"id":1,"givenName":"John","familyName":"Koenig","startDate":"1975-10-17T00:00:00","rating":6,"formatString":null}, 1391 | {"id":2,"givenName":"Dylan","familyName":"Hunt","startDate":"2000-10-02T00:00:00","rating":8,"formatString":null}, 1392 | {"id":3,"givenName":"Leela","familyName":"Turanga","startDate":"1999-03-28T00:00:00","rating":8,"formatString":"{1} {0}"}, 1393 | {"id":4,"givenName":"John","familyName":"Crichton","startDate":"1999-03-19T00:00:00","rating":7,"formatString":null}, 1394 | {"id":5,"givenName":"Dave","familyName":"Lister","startDate":"1988-02-15T00:00:00","rating":9,"formatString":null}, 1395 | {"id":6,"givenName":"Laura","familyName":"Roslin","startDate":"2003-12-08T00:00:00","rating":6,"formatString":null}, 1396 | {"id":7,"givenName":"John","familyName":"Sheridan","startDate":"1994-01-26T00:00:00","rating":6,"formatString":null}, 1397 | {"id":8,"givenName":"Dante","familyName":"Montana","startDate":"2000-11-01T00:00:00","rating":5,"formatString":null}, 1398 | {"id":9,"givenName":"Isaac","familyName":"Gampu","startDate":"1977-09-10T00:00:00","rating":4,"formatString":null}] 1399 | ``` 1400 | 1401 | ### Setting Up Another Provider 1402 | Let's set up another data provider so that we can see how this works. 1403 | 1404 | We will use already-created code for this. In addition to the "snippets.txt" file, there are "CSVPeopleProvider.cs" and "People.txt" files in the [Starting Files folder](https://github.com/jeremybytes/core-cli-30/tree/master/StartingFiles) of the repository. 1405 | 1406 | Copy "CSVPeopleProvider.cs" into the "Models" folder of the web service. 1407 | 1408 | Copy "People.txt" into the root folder of the web service. 1409 | 1410 | As noted above, after copying the files into the appropriate folders, they automatically show up as part of the web service project, whether we use Visual Studio or Visual Studio Code. 1411 | 1412 | "CSVPeopleProvider" implements the "IPeopleProvider" interface. 1413 | 1414 | ```csharp 1415 | public class CSVPeopleProvider : IPeopleProvider 1416 | ``` 1417 | 1418 | The class reads data from a text file on the file system (People.txt) and parses it into C# Person objects. 1419 | 1420 | One more thing we need to do is set the "People.txt" file so that it is copied to the output folder of the project. 1421 | 1422 | In Visual Studio, right-click on the "People.txt" file and select "Properties". Change the "Copy to Output Directory" setting to "Copy always". 1423 | 1424 | If you are using Visual Studio Code, you can manually change the "people-api.csproj" file to add this setting. Here is the completed project file. Note the "ItemGroup" section: 1425 | 1426 | ```xml 1427 | 1428 | 1429 | 1430 | netcoreapp3.0 1431 | person_api 1432 | 1433 | 1434 | 1435 | 1436 | Always 1437 | 1438 | 1439 | 1440 | 1441 | ``` 1442 | 1443 | ### Updating Configuration 1444 | Now that we have the code for the new data provider, we can change the configuration in the "Startup.cs" file. 1445 | 1446 | ```csharp 1447 | public void ConfigureServices(IServiceCollection services) 1448 | { 1449 | services.AddControllers(); 1450 | services.AddSingleton(); 1451 | } 1452 | ``` 1453 | 1454 | ### Running the Service 1455 | Next, stop and restart the service. 1456 | 1457 | ``` 1458 | Application is shutting down... 1459 | PS C:\CoreCLI\person-api> dotnet run 1460 | info: Microsoft.Hosting.Lifetime[0] 1461 | Now listening on: http://localhost:9874 1462 | info: Microsoft.Hosting.Lifetime[0] 1463 | Application started. Press Ctrl+C to shut down. 1464 | info: Microsoft.Hosting.Lifetime[0] 1465 | Hosting environment: Development 1466 | info: Microsoft.Hosting.Lifetime[0] 1467 | Content root path: C:\CoreCLI\person-api 1468 | ``` 1469 | 1470 | To show the updated service in action, go back to the PowerShell window for the console application. Then run the application. 1471 | 1472 | ``` 1473 | PS C:\CoreCLI\person-console> dotnet run 1474 | One moment please... 1475 | John Koenig 1476 | Dylan Hunt 1477 | Turanga Leela 1478 | John Crichton 1479 | Dave Lister 1480 | Laura Roslin 1481 | John Sheridan 1482 | Dante Montana 1483 | Isaac Gampu 1484 | **Jeremy Awesome** 1485 | =============== 1486 | PS C:\CoreCLI\person-console> 1487 | ``` 1488 | 1489 | The text file has an extra record: Jeremy Awesome. So we can tell by looking at the data that the service is now getting data from the text file instead of using the hard-coded data provider. 1490 | 1491 | ### Fixing Broken Unit Tests 1492 | Since we changed the constructor for the controller, our unit tests no longer build. 1493 | 1494 | The problem is in the Setup method of the "PeopleControllerTests" class. 1495 | 1496 | ```csharp 1497 | public class PeopleControllerTests 1498 | { 1499 | PeopleController controller; 1500 | 1501 | [SetUp] 1502 | public void Setup() 1503 | { 1504 | controller = new PeopleController(); 1505 | } 1506 | ... 1507 | } 1508 | ``` 1509 | 1510 | We need a data provider to pass as a parameter to the PeopleController constructor. We could use a mocking framework. But to keep things easier for those who are not familiar with mocking, we will use a fake object. 1511 | 1512 | ### Adding a Fake Data Reader 1513 | To create a fake data reader for testing, we will start with the HardCodedDataReader that we already have. This will save us a lot of typing. 1514 | 1515 | Copy the "HardCodedDataReader.cs" file from the web service project folder into the unit test project folder. 1516 | 1517 | After copying the file, rename it to "FakePeopleProvider.cs". 1518 | 1519 | Open the file in Visual Studio (or Visual Studio Code) and change the name of the class to "FakePeopleProvider". We will leave the rest of the class the same. In addition, we'll change the namespace to "person_api_tests". 1520 | 1521 | ```csharp 1522 | namespace person_api_tests 1523 | { 1524 | public class FakePeopleProvider : IPeopleProvider 1525 | { 1526 | public List GetPeople() ... 1527 | 1528 | public Person GetPerson(int id) ... 1529 | } 1530 | } 1531 | ``` 1532 | 1533 | This gives us a separate class that we can use for testing. Now we have more control over the test behavior. 1534 | 1535 | ### Updating the Tests 1536 | To update the tests, update the "Setup" method to use the "FakePeopleProvider". 1537 | 1538 | ```csharp 1539 | [SetUp] 1540 | public void Setup() 1541 | { 1542 | var provider = new FakePeopleProvider(); 1543 | controller = new PeopleController(provider); 1544 | } 1545 | ``` 1546 | 1547 | Since the "Setup" method runs before each test, all of our tests are now using the fake data provider. 1548 | 1549 | If we re-run the tests, we'll find that they are all passing. 1550 | 1551 | ``` 1552 | PS C:\CoreCLI\person-api-tests> dotnet test 1553 | Test run for C:\CoreCLI\person-api-tests\bin\Debug\netcoreapp3.0\person-api-tests.dll(.NETCoreApp,Version=v3.0) 1554 | Microsoft (R) Test Execution Command Line Tool Version 16.3.0 1555 | Copyright (c) Microsoft Corporation. All rights reserved. 1556 | 1557 | Starting test execution, please wait... 1558 | 1559 | A total of 1 test files matched the specified pattern. 1560 | 1561 | Test Run Successful. 1562 | Total tests: 3 1563 | Passed: 3 1564 | Total time: 2.7987 Seconds 1565 | PS C:\CoreCLI\person-api-tests> 1566 | ``` 1567 | 1568 | Wrap Up 1569 | -------- 1570 | We've seen how to use .NET Core and the command-line interface (CLI) to build a web service, unit tests, and a console application. 1571 | 1572 | In addition, we've seen how to use the built-in dependency injection container. This allows us to change out the data provider with a few small changes and also helps us isolate our code for better control over our unit tests. 1573 | 1574 | For more information, visit the GitHub repo to view the code samples and links to relevant articles. 1575 | 1576 | [GitHub: Get Comfortable with .NET Core and the CLI](https://github.com/jeremybytes/core-cli-30) 1577 | 1578 | Happy Coding! 1579 | --------------------------------------------------------------------------------