diff --git a/aspnetcore/tutorials/first-web-api2.md b/aspnetcore/tutorials/first-web-api2.md new file mode 100644 index 000000000000..aef3ca18a67d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2.md @@ -0,0 +1,175 @@ +--- +title: Create a Web API with ASP.NET Core and Visual Studio for Windows +author: rick-anderson +description: Build a web API with ASP.NET Core MVC and Visual Studio for Windows +ms.author: riande +ms.custom: mvc +ms.date: 05/17/2018 +uid: tutorials/first-web-api2 +--- + + + +## Overview + +This tutorial tests a Web API controller with the following API: + +|API | Description | Request body | Response body | +|--- | ---- | ---- | ---- | +|GET /api/todo | Get all to-do items | None | Array of to-do items| +|GET /api/todo/{id} | Get an item by ID | None | To-do item| +|POST /api/todo | Add a new item | To-do item | To-do item | +|PUT /api/todo/{id} | Update an existing item   | To-do item | None | +|DELETE /api/todo/{id}     | Delete an item     | None | None| + +The following diagram shows the basic design of the app. + +![The client is represented .](first-web-api/_static/architecture.png) + +* The client is whatever consumes the web API (mobile app, browser, etc.). This tutorial provides a client to test the web API in the browser. + +* A *model* is an object that represents the data in the app. In this case, the only model is a to-do item. Models are represented as C# classes, also known as **P**lain **O**ld **C**LR **O**bject (POCOs). + +* A *controller* is an object that handles HTTP requests and creates the HTTP response. This app uses the `TodoController` . + +* To keep the tutorial simple, the app doesn't use a persistent database. The sample app stores to-do items in an in-memory database. + +### The model + +The Web API app uses the following model to create, read, update, and delete (CRUD) data: + +[!code-csharp[](first-web-api/samples/2.0/TodoApi/Models/TodoItem.cs)] + +The model is an object representing the data in the app. + +When you create a new `TodoItem` item, you provide the name and completion status, the database generates the `Id`. + +### The Web API controller + +The following code shows the `TodoController` Web API controller: + +[!code-csharp[](first-web-api2/samples/2.1/TodoApi/Controllers/TodoController.cs?name=TodoController2)] + +The following code shows the `TodoController` class declaration and constructor: + +[!code-csharp[](first-web-api2/samples/2.1/TodoApi/Controllers/TodoController.cs?name=TodoController)] + +The preceding code shows: + +* The declaration of the web api controller. +* The `[ApiController]` attribute to enable some convenient features. For information on features enabled by the attribute, see [Annotate class with ApiControllerAttribute](xref:web-api/index#annotate-class-with-apicontrollerattribute). + +The controller's constructor uses [Dependency Injection](xref:fundamentals/dependency-injection) to inject the database context (`TodoContext`) into the controller. The database context is used in each of the [CRUD](https://wikipedia.org/wiki/Create,_read,_update_and_delete) methods in the controller. The constructor adds an item to the in-memory database if one doesn't exist. + +## Get to-do items + +The following code gets to-do items: + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=snippet_GetAll)] + +The preceding code implements the two GET endpoints: + +* `GET /api/todo` +* `GET /api/todo/{id}` + +Select **Run** with the relative URI of `/api/todo` to return all the to-do items: + +![replace this with TRY .NET code .](first-web-api2/_static/run1.png) + +Use the `ID` from the `GET /api/todo` output to test fetching a specific item. + +### Routing and URL paths + +The `[HttpGet]` attribute denotes a method that responds to an HTTP GET request. The URL path for each method is constructed as follows: + +* Take the template string in the controller's `Route` attribute: + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=TodoController&highlight=3)] + +* Replace `[controller]` with the name of the controller, which is the controller class name minus the "Controller" suffix. For this sample, the controller class name is **Todo**Controller and the root name is "todo". ASP.NET Core [routing](xref:mvc/controllers/routing) is case insensitive. +* If the `[HttpGet]` attribute has a route template (such as `[HttpGet("/products")]`, append that to the path. This sample doesn't use a template. For more information, see [Attribute routing with Http[Verb] attributes](xref:mvc/controllers/routing#attribute-routing-with-httpverb-attributes). + +In the following `GetById` method, `"{id}"` is a placeholder variable for the unique identifier of the to-do item. When `GetById` is invoked, it assigns the value of `"{id}"` in the URL to the method's `id` parameter. + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=snippet_GetByID&highlight=1-2)] + +`Name = "GetTodo"` creates a named route. Named routes: + +* Enable the app to create an HTTP link using the route name. +* Are explained later in the tutorial. + +### Return values + +The `GetAll` method returns a collection of `TodoItem` objects. MVC automatically serializes the object to [JSON](https://www.json.org/) and writes the JSON into the body of the response message. The response code for this method is 200, assuming there are no unhandled exceptions. Unhandled exceptions are translated into 5xx errors. + +In contrast, the `GetById` method returns the [ActionResult\ type](xref:web-api/action-return-types#actionresultt-type), which represents a wide range of return types. `GetById` has two different return types: + +* If no item matches the requested ID, the method returns a 404 error. Returning [NotFound](/dotnet/api/microsoft.aspnetcore.mvc.controllerbase.notfound) returns an HTTP 404 response. +* Otherwise, the method returns 200 with a JSON response body. Returning `item` results in an HTTP 200 response. + +## The create method + +The following code shows the `Create` method: + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=snippet_Create)] + +The preceding code is an HTTP POST method, as indicated by the [[HttpPost]](/dotnet/api/microsoft.aspnetcore.mvc.httppostattribute) attribute. MVC gets the value of the to-do item from the body of the HTTP request. + +The `CreatedAtRoute` method: + +* Returns a 201 response. HTTP 201 is the standard response for an HTTP POST method that creates a new resource on the server. +* Adds a Location header to the response. The Location header specifies the URI of the newly created to-do item. See [10.2.2 201 Created](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html). +* Uses the "GetTodo" named route to create the URL. The "GetTodo" named route is defined in `GetById`: + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=snippet_GetByID&highlight=1-2)] + +### Send a Create request + +* Set the HTTP method to *POST*. +* Enter a request body with a to-do item resembling the following JSON: + +```json +{ + "name":"walk dog", + "isComplete":true +} +``` + +* Click the **Run** button. + +The Location header URI can be used to access the new item. Copy the **Location** header value from the **Response** pane and use that to get the new item + +![replace this with TRY .NET code .](first-web-api2/_static/run2.png) + +### Update + +The `Update` method: + +[!code-csharp[](first-web-api/samples/2.1/TodoApi/Controllers/TodoController.cs?name=snippet_Update)] + +`Update` is similar to `Create`, except it uses HTTP PUT. The response is [204 (No Content)](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html). According to the HTTP specification, a PUT request requires the client to send the entire updated entity, not just the deltas. To support partial updates, use HTTP PATCH. + +Update the first to-do item's name and status. For example, set it to: + +```json +{ + "name":"feed cat", + "isComplete":true +} +``` + +![replace this with TRY .NET code .](first-web-api2/_static/run3.png) + +### Delete + +The `Delete` method: + +[!code-csharp[](first-web-api/samples/2.0/TodoApi/Controllers/TodoController.cs?name=snippet_Delete)] + +The `Delete` response is [204 (No Content)](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html). + +Use Try .NET to delete the to-do item: diff --git a/aspnetcore/tutorials/first-web-api2/_static/_webAPI.pdf b/aspnetcore/tutorials/first-web-api2/_static/_webAPI.pdf new file mode 100644 index 000000000000..b4f6f54e47c6 Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/_webAPI.pdf differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/architecture.png b/aspnetcore/tutorials/first-web-api2/_static/architecture.png new file mode 100644 index 000000000000..5b38753888ab Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/architecture.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/new_controller.png b/aspnetcore/tutorials/first-web-api2/_static/new_controller.png new file mode 100644 index 000000000000..8dd2f407f70d Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/new_controller.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/pmc.png b/aspnetcore/tutorials/first-web-api2/_static/pmc.png new file mode 100644 index 000000000000..e62ab98c38a6 Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/pmc.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/pmc2.png b/aspnetcore/tutorials/first-web-api2/_static/pmc2.png new file mode 100644 index 000000000000..6dca3564913e Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/pmc2.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/pmcput.png b/aspnetcore/tutorials/first-web-api2/_static/pmcput.png new file mode 100644 index 000000000000..bd8ee2b6e7b5 Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/pmcput.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/pmd.png b/aspnetcore/tutorials/first-web-api2/_static/pmd.png new file mode 100644 index 000000000000..019dbcc12a3a Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/pmd.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/run1.png b/aspnetcore/tutorials/first-web-api2/_static/run1.png new file mode 100644 index 000000000000..685361aeb59c Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/run1.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/run2.png b/aspnetcore/tutorials/first-web-api2/_static/run2.png new file mode 100644 index 000000000000..80b657f7a8f0 Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/run2.png differ diff --git a/aspnetcore/tutorials/first-web-api2/_static/run3.png b/aspnetcore/tutorials/first-web-api2/_static/run3.png new file mode 100644 index 000000000000..cd00ba7b4923 Binary files /dev/null and b/aspnetcore/tutorials/first-web-api2/_static/run3.png differ diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController.cs new file mode 100644 index 000000000000..cd6afbcd08ed --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController.cs @@ -0,0 +1,103 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using TodoApi.Models; + +#region TodoController +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + public class TodoController : ControllerBase + { + private readonly TodoContext _context; + #endregion + + public TodoController(TodoContext context) + { + _context = context; + + if (_context.TodoItems.Count() == 0) + { + _context.TodoItems.Add(new TodoItem { Name = "Item1" }); + _context.SaveChanges(); + } + } + + #region snippet_GetAll + [HttpGet] + public List GetAll() + { + return _context.TodoItems.ToList(); + } + + #region snippet_GetByID + [HttpGet("{id}", Name = "GetTodo")] + public IActionResult GetById(long id) + { + var item = _context.TodoItems.Find(id); + if (item == null) + { + return NotFound(); + } + return Ok(item); + } + #endregion + #endregion + + #region snippet_Create + [HttpPost] + public IActionResult Create([FromBody] TodoItem item) + { + if (item == null) + { + return BadRequest(); + } + + _context.TodoItems.Add(item); + _context.SaveChanges(); + + return CreatedAtRoute("GetTodo", new { id = item.Id }, item); + } + #endregion + + #region snippet_Update + [HttpPut("{id}")] + public IActionResult Update(long id, [FromBody] TodoItem item) + { + if (item == null || item.Id != id) + { + return BadRequest(); + } + + var todo = _context.TodoItems.Find(id); + if (todo == null) + { + return NotFound(); + } + + todo.IsComplete = item.IsComplete; + todo.Name = item.Name; + + _context.TodoItems.Update(todo); + _context.SaveChanges(); + return NoContent(); + } + #endregion + + #region snippet_Delete + [HttpDelete("{id}")] + public IActionResult Delete(long id) + { + var todo = _context.TodoItems.Find(id); + if (todo == null) + { + return NotFound(); + } + + _context.TodoItems.Remove(todo); + _context.SaveChanges(); + return NoContent(); + } + #endregion + } +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController2.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController2.cs new file mode 100644 index 000000000000..882a5f6e0ecd --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Controllers/TodoController2.cs @@ -0,0 +1,29 @@ +#if NEVER +// This controller is used only for documentation purposes. +#region snippet_todo1 +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using TodoApi.Models; + +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + public class TodoController : ControllerBase + { + private readonly TodoContext _context; + + public TodoController(TodoContext context) + { + _context = context; + + if (_context.TodoItems.Count() == 0) + { + _context.TodoItems.Add(new TodoItem { Name = "Item1" }); + _context.SaveChanges(); + } + } + } +} +#endregion +#endif diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoContext.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoContext.cs new file mode 100644 index 000000000000..e3f066b538c4 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace TodoApi.Models +{ + public class TodoContext : DbContext + { + public TodoContext(DbContextOptions options) + : base(options) + { + } + + public DbSet TodoItems { get; set; } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoItem.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoItem.cs new file mode 100644 index 000000000000..d3eef6e3af56 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Models/TodoItem.cs @@ -0,0 +1,9 @@ +namespace TodoApi.Models +{ + public class TodoItem + { + public long Id { get; set; } + public string Name { get; set; } + public bool IsComplete { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Program.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Program.cs new file mode 100644 index 000000000000..737da22f224e --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using System.IO; + +namespace TodoApi +{ + public class Program + { + public static void Main(string[] args) + { + var host = new WebHostBuilder() + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseStartup() + .Build(); + + host.Run(); + } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup.cs new file mode 100644 index 000000000000..2529fcbb1ba2 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using TodoApi.Models; + +namespace TodoApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => + opt.UseInMemoryDatabase("TodoList")); + services.AddMvc(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvc(); + } + } +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup2.cs b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup2.cs new file mode 100644 index 000000000000..b974afaf6b2b --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/Startup2.cs @@ -0,0 +1,29 @@ +#if NEVER +// This class is used only for documentation purposes. +using Microsoft.AspNetCore.Builder; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using TodoApi.Models; + +namespace TodoApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => + opt.UseInMemoryDatabase("TodoList")); + services.AddMvc(); + } + +#region snippet_Configure + public void Configure(IApplicationBuilder app) + { + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseMvc(); + } +#endregion + } +} +#endif \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/TodoApi.csproj b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/TodoApi.csproj new file mode 100644 index 000000000000..89a49fa2f65d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/TodoApi.csproj @@ -0,0 +1,18 @@ + + + + netcoreapp2.0 + ASP.NET Core 2.0 + + + + + + + + + + + + + diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.Development.json b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.Development.json new file mode 100644 index 000000000000..fa8ce71a97a3 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.json b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.json new file mode 100644 index 000000000000..5fff67bacc46 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Warning" + } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/index.html b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/index.html new file mode 100644 index 000000000000..784c5f2bbd3d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/index.html @@ -0,0 +1,68 @@ + + + + + To-do CRUD + + + +

To-do CRUD

+

Add

+
+ + +
+ +
+

Edit

+
+ + + + + +
+
+ +

+ + + + + + + + + +
Is CompleteName
+ + + + + \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/site.js b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/site.js new file mode 100644 index 000000000000..fc337745337f --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.0/TodoApi/wwwroot/site.js @@ -0,0 +1,119 @@ +// +const uri = 'api/todo'; +let todos = null; +function getCount(data) { + const el = $('#counter'); + let name = 'to-do'; + if (data) { + if (data > 1) { + name = 'to-dos'; + } + el.text(data + ' ' + name); + } else { + el.html('No ' + name); + } +} + +// +$(document).ready(function () { + getData(); +}); + +function getData() { + $.ajax({ + type: 'GET', + url: uri, + success: function (data) { + $('#todos').empty(); + getCount(data.length); + $.each(data, function (key, item) { + const checked = item.isComplete ? 'checked' : ''; + + $('' + + '' + item.name + '' + + '' + + '' + + '').appendTo($('#todos')); + }); + + todos = data; + } + }); +} +// + +// +function addItem() { + const item = { + 'name': $('#add-name').val(), + 'isComplete': false + }; + + $.ajax({ + type: 'POST', + accepts: 'application/json', + url: uri, + contentType: 'application/json', + data: JSON.stringify(item), + error: function (jqXHR, textStatus, errorThrown) { + alert('here'); + }, + success: function (result) { + getData(); + $('#add-name').val(''); + } + }); +} +// + +function deleteItem(id) { + // + $.ajax({ + url: uri + '/' + id, + type: 'DELETE', + success: function (result) { + getData(); + } + }); + // +} + +function editItem(id) { + $.each(todos, function (key, item) { + if (item.id === id) { + $('#edit-name').val(item.name); + $('#edit-id').val(item.id); + $('#edit-isComplete').val(item.isComplete); + } + }); + $('#spoiler').css({ 'display': 'block' }); +} + +$('.my-form').on('submit', function () { + const item = { + 'name': $('#edit-name').val(), + 'isComplete': $('#edit-isComplete').is(':checked'), + 'id': $('#edit-id').val() + }; + + // + $.ajax({ + url: uri + '/' + $('#edit-id').val(), + type: 'PUT', + accepts: 'application/json', + contentType: 'application/json', + data: JSON.stringify(item), + success: function (result) { + getData(); + } + }); + // + + closeInput(); + return false; +}); + +function closeInput() { + $('#spoiler').css({ 'display': 'none' }); +} +// \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController.cs new file mode 100644 index 000000000000..ab9de7253e0d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController.cs @@ -0,0 +1,96 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using TodoApi.Models; + +namespace TodoApi.Controllers +{ + #region TodoController2 + #region TodoController + [Route("api/[controller]")] + [ApiController] + public class TodoController : ControllerBase + { + private readonly TodoContext _context; + + public TodoController(TodoContext context) + { + _context = context; + + if (_context.TodoItems.Count() == 0) + { + _context.TodoItems.Add(new TodoItem { Name = "Item1" }); + _context.SaveChanges(); + } + } + #endregion + + #region snippet_GetAll + [HttpGet] + public ActionResult> GetAll() + { + return _context.TodoItems.ToList(); + } + + #region snippet_GetByID + [HttpGet("{id}", Name = "GetTodo")] + public ActionResult GetById(long id) + { + var item = _context.TodoItems.Find(id); + if (item == null) + { + return NotFound(); + } + return item; + } + #endregion + #endregion + + #region snippet_Create + [HttpPost] + public IActionResult Create(TodoItem item) + { + _context.TodoItems.Add(item); + _context.SaveChanges(); + + return CreatedAtRoute("GetTodo", new { id = item.Id }, item); + } + #endregion + + #region snippet_Update + [HttpPut("{id}")] + public IActionResult Update(long id, TodoItem item) + { + var todo = _context.TodoItems.Find(id); + if (todo == null) + { + return NotFound(); + } + + todo.IsComplete = item.IsComplete; + todo.Name = item.Name; + + _context.TodoItems.Update(todo); + _context.SaveChanges(); + return NoContent(); + } + #endregion + + #region snippet_Delete + [HttpDelete("{id}")] + public IActionResult Delete(long id) + { + var todo = _context.TodoItems.Find(id); + if (todo == null) + { + return NotFound(); + } + + _context.TodoItems.Remove(todo); + _context.SaveChanges(); + return NoContent(); + } + #endregion + } + #endregion +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController2.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController2.cs new file mode 100644 index 000000000000..aff74519fd3d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Controllers/TodoController2.cs @@ -0,0 +1,30 @@ +#if NEVER +// This controller is used only for documentation purposes. +#region snippet_todo1 +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using TodoApi.Models; + +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TodoController : ControllerBase + { + private readonly TodoContext _context; + + public TodoController(TodoContext context) + { + _context = context; + + if (_context.TodoItems.Count() == 0) + { + _context.TodoItems.Add(new TodoItem { Name = "Item1" }); + _context.SaveChanges(); + } + } + } +} +#endregion +#endif diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoContext.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoContext.cs new file mode 100644 index 000000000000..6e59e3637896 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace TodoApi.Models +{ + public class TodoContext : DbContext + { + public TodoContext(DbContextOptions options) + : base(options) + { + } + + public DbSet TodoItems { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoItem.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoItem.cs new file mode 100644 index 000000000000..d3eef6e3af56 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Models/TodoItem.cs @@ -0,0 +1,9 @@ +namespace TodoApi.Models +{ + public class TodoItem + { + public long Id { get; set; } + public string Name { get; set; } + public bool IsComplete { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Program.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Program.cs new file mode 100644 index 000000000000..f8eb8f2f6320 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Program.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace TodoApi +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup.cs new file mode 100644 index 000000000000..d1e1d3a2b261 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using TodoApi.Models; + +namespace TodoApi +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => + opt.UseInMemoryDatabase("TodoList")); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvc(); + } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup2.cs b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup2.cs new file mode 100644 index 000000000000..0ed31a6e15ee --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/Startup2.cs @@ -0,0 +1,49 @@ +#if NEVER +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using TodoApi.Models; + +namespace TodoApi +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(opt => + opt.UseInMemoryDatabase("TodoList")); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + } + +#region snippet_Configure + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseMvc(); + } +#endregion + } +} +#endif \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/TodoApi.csproj b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/TodoApi.csproj new file mode 100644 index 000000000000..523a787b6bad --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/TodoApi.csproj @@ -0,0 +1,14 @@ + + + + netcoreapp2.1 + >= ASP.NET Core 2.1 + + + + + + + + + \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.Development.json b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.Development.json new file mode 100644 index 000000000000..e203e9407e74 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.json b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.json new file mode 100644 index 000000000000..def9159a7d94 --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/index.html b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/index.html new file mode 100644 index 000000000000..784c5f2bbd3d --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/index.html @@ -0,0 +1,68 @@ + + + + + To-do CRUD + + + +

To-do CRUD

+

Add

+
+ + +
+ +
+

Edit

+
+ + + + + +
+
+ +

+ + + + + + + + + +
Is CompleteName
+ + + + + \ No newline at end of file diff --git a/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/site.js b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/site.js new file mode 100644 index 000000000000..27626f73119b --- /dev/null +++ b/aspnetcore/tutorials/first-web-api2/samples/2.1/TodoApi/wwwroot/site.js @@ -0,0 +1,108 @@ +const uri = 'api/todo'; +let todos = null; +function getCount(data) { + const el = $('#counter'); + let name = 'to-do'; + if (data) { + if (data > 1) { + name = 'to-dos'; + } + el.text(data + ' ' + name); + } else { + el.html('No ' + name); + } +} + +$(document).ready(function () { + getData(); +}); + +function getData() { + $.ajax({ + type: 'GET', + url: uri, + success: function (data) { + $('#todos').empty(); + getCount(data.length); + $.each(data, function (key, item) { + const checked = item.isComplete ? 'checked' : ''; + + $('' + + '' + item.name + '' + + '' + + '' + + '').appendTo($('#todos')); + }); + + todos = data; + } + }); +} + +function addItem() { + const item = { + 'name': $('#add-name').val(), + 'isComplete': false + }; + + $.ajax({ + type: 'POST', + accepts: 'application/json', + url: uri, + contentType: 'application/json', + data: JSON.stringify(item), + error: function (jqXHR, textStatus, errorThrown) { + alert('here'); + }, + success: function (result) { + getData(); + $('#add-name').val(''); + } + }); +} + +function deleteItem(id) { + $.ajax({ + url: uri + '/' + id, + type: 'DELETE', + success: function (result) { + getData(); + } + }); +} + +function editItem(id) { + $.each(todos, function (key, item) { + if (item.id === id) { + $('#edit-name').val(item.name); + $('#edit-id').val(item.id); + $('#edit-isComplete').val(item.isComplete); + } + }); + $('#spoiler').css({ 'display': 'block' }); +} + +$('.my-form').on('submit', function () { + const item = { + 'name': $('#edit-name').val(), + 'isComplete': $('#edit-isComplete').is(':checked'), + 'id': $('#edit-id').val() + }; + + $.ajax({ + url: uri + '/' + $('#edit-id').val(), + type: 'PUT', + accepts: 'application/json', + contentType: 'application/json', + data: JSON.stringify(item), + success: function (result) { + getData(); + } + }); + closeInput(); + return false; +}); + +function closeInput() { + $('#spoiler').css({ 'display': 'none' }); +} \ No newline at end of file