{"id":87963,"date":"2020-08-26T01:17:22","date_gmt":"2020-08-26T01:17:22","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=87963"},"modified":"2022-04-24T21:32:44","modified_gmt":"2022-04-24T21:32:44","slug":"build-a-rest-api-in-net-core","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/build-a-rest-api-in-net-core\/","title":{"rendered":"Build a REST API in .NET Core"},"content":{"rendered":"<p>One way to scale large complex solutions is to break them out into REST microservices. Microservices unlock testability and reusability of business logic that sits behind an API boundary. This allows organizations to share software modules because REST APIs can be reused by multiple clients. Clients can then call as many APIs from mobile, web, or even static assets via a single-page app.<\/p>\n<p>In this take, I will show you what it takes to build a REST API in .NET Core. I will hit this with real-world demands such as versioning, search, and logging, to name a few. REST is often employed with verbs like <code>POST<\/code>, <code>PUT<\/code>, or <code>PATCH<\/code>, so I plan to cover them all. What I hope you see is a nice, effective way to deliver value with the tools available.<\/p>\n<p>This article assumes a working grasp of ASP.NET, C#, and REST APIs so I will not cover any basics. I recommend the <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet-core\/3.1\">latest .NET Core LTS release<\/a> at the time of this writing to follow along. If you would like to start with working code, the sample code can be <a href=\"https:\/\/github.com\/beautifulcoder\/BuildRestApiNetCore\">downloaded from GitHub<\/a>.<\/p>\n<p>You can begin by creating a new folder like <em>BuildRestApiNetCore<\/em> and firing this up in a shell:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet new sln\r\ndotnet new webapi --no-https\r\ndotnet sln add .<\/pre>\n<p>This project is based on a Web API template with HTTPS disabled to make it easier for local development. Double-clicking the solution file brings it up in Visual Studio if you have it installed. For .NET Core 3.1 support, be sure to have the 2019 version of the IDE.<\/p>\n<p>APIs put a layer of separation between clients and the database, so the data is an excellent place to start. To keep data access trivial, Entity Framework has an in-memory alternative so that I can focus on the API itself.<\/p>\n<p>An in-memory database provider comes via NuGet:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet add package Microsoft.EntityFrameworkCore.InMemory<\/pre>\n<p>Then, create the following data model. I put this in the Models folder to indicate this namespace houses raw data. To use the data annotations, add <code>System.ComponentModel.DataAnnotations<\/code> in a using statement.<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class Product\r\n{\r\n  [Key]\r\n  [Required]\r\n  [Display(Name = \"productNumber\")]\r\n  public string ProductNumber { get; set; }\r\n\r\n  [Required]\r\n  [Display(Name = \"name\")]\r\n  public string Name { get; set; }\r\n\r\n  [Required]\r\n  [Range(10, 90)]\r\n  [Display(Name = \"price\")]\r\n  public double? Price { get; set; }\r\n\r\n  [Required]\r\n  [Display(Name = \"department\")]\r\n  public string Department { get; set; }\r\n}<\/pre>\n<p>In a real solution, this may go in a separate project depending on the team\u2019s needs. Pay attention to the attributes assigned to this model like <code>Required<\/code>, <code>Display<\/code>, and <code>Range<\/code>. These are data annotations in ASP.NET to validate the Product during model binding. Because I use an in-memory database, Entity Framework requires a unique <em>Key<\/em>. These attributes assign validation rules like price range or whether the property is required.<\/p>\n<p>From a business perspective, this is an e-commerce site with a product number, name, and price. Each product is also assigned a department to make searches by department easier.<\/p>\n<p>Next, set the Entity Framework <code>DbContext<\/code> in the Models namespace:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class ProductContext : DbContext\r\n{\r\n  public ProductContext(DbContextOptions&lt;ProductContext&gt; options) : base(options)\r\n  {\r\n  }\r\n\r\n  public DbSet&lt;Product&gt; Products { get; set; }\r\n}<\/pre>\n<p>This database context is the dependency injected in the controller to query or update data. To enable Dependency Injection in ASP.NET Core, crack open the <code>Startup<\/code> class and add this in <code>ConfigureServices<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services\r\n  .AddDbContext&lt;ProductContext&gt;(opt =&gt; \r\n              opt.UseInMemoryDatabase(\"Products\"));<\/pre>\n<p>This code completes the in-memory database. Be sure to add<code> Microsoft.EntityFrameworkCore<\/code> to both classes in a using statement. Because a blank back end is no fun, this needs seed data.<\/p>\n<p>Create this extension method to help iterate through seed items. This can go in an <em>Extensions<\/em> namespace or folder:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public  static class EnumerableExtensions\r\n{\r\n  public static IEnumerable&lt;T&gt; Times&lt;T&gt;(this int count, Func&lt;int, T&gt; func)\r\n  {\r\n    for (var i = 1; i &lt;= count; i++) yield return func.Invoke(i);\r\n  }\r\n}<\/pre>\n<p>The initial seed goes in <em>Models<\/em> via a static class:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public static class ProductSeed\r\n{\r\n  public static void InitData(ProductContext context)\r\n  {\r\n    var rnd = new Random();\r\n\r\n    var adjectives = new [] { \"Small\", \"Ergonomic\", \"Rustic\", \r\n                                        \"Smart\", \"Sleek\" };\r\n    var materials = new [] { \"Steel\", \"Wooden\", \"Concrete\", \"Plastic\",\r\n                                       \"Granite\", \"Rubber\" };\r\n    var names = new [] { \"Chair\", \"Car\", \"Computer\", \"Pants\", \"Shoes\" };\r\n    var departments = new [] { \"Books\", \"Movies\", \"Music\", \r\n                                       \"Games\", \"Electronics\" };\r\n\r\n    context.Products.AddRange(900.Times(x =&gt;\r\n    {\r\n      var adjective = adjectives[rnd.Next(0, 5)];\r\n      var material = materials[rnd.Next(0, 5)];\r\n      var name = names[rnd.Next(0, 5)];\r\n      var department = departments[rnd.Next(0, 5)];\r\n      var productId = $\"{x, -3:000}\";\r\n\r\n      return new Product\r\n      {\r\n        ProductNumber = \r\n           $\"{department.First()}{name.First()}{productId}\",\r\n        Name = $\"{adjective} {material} {name}\",\r\n        Price = (double) rnd.Next(1000, 9000) \/ 100,\r\n        Department = department\r\n      };\r\n    }));\r\n\r\n    context.SaveChanges();\r\n  }\r\n}<\/pre>\n<p>This code loops through a list of 900 items to create this many products. The names are picked at random with a department and price. Each product gets a \u201csmart\u201d key as the primary key which comes from department, name, and product id.<\/p>\n<p>Once this seed runs, you may get products such as \u201cSmart Wooden Pants\u201d in the Electronics department for a nominal price.<\/p>\n<p>As a preliminary step to start building endpoints, it is a good idea to set up versioning. This allows client apps to upgrade API functionality at their leisure without tight coupling.<\/p>\n<p>API versioning comes in a NuGet package:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet add package Microsoft.AspNetCore.Mvc.Versioning<\/pre>\n<p>Go back to the <code>Startup<\/code> class and add this to <code>ConfigureServices<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddApiVersioning(opt =&gt; opt.ReportApiVersions = true);<\/pre>\n<p>I opted to include available versions in the API response, so clients know when upgrades are available. I recommend using <a href=\"https:\/\/semver.org\/\">Semantic Versioning<\/a> to communicate breaking changes in the API. Letting clients know what to expect between upgrades helps everyone stay on the latest features.<\/p>\n<h2>Search endpoint in a REST API<\/h2>\n<p>To build an endpoint, spin up a Controller in ASP.NET which goes in the <em>Controllers<\/em> folder.<\/p>\n<p>Create a <code>ProductsController<\/code> with the following, making sure to add the namespace <code>Microsoft.AspNetCore.Mvc<\/code> with a using statement:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[ApiController]\r\n[ApiVersion(\"1.0\")]\r\n[Route(\"v{version:apiVersion}\/[controller]\")]\r\n[Produces(\"application\/json\")]\r\npublic class ProductsController : ControllerBase\r\n{\r\n  private readonly ProductContext _context;\r\n\r\n  public ProductsController(ProductContext context)\r\n  {\r\n    _context = context;\r\n\r\n    if (_context.Products.Any()) return;\r\n\r\n    ProductSeed.InitData(context);\r\n  }\r\n}<\/pre>\n<p>Note <code>InitData<\/code> runs the initial seed when there aren\u2019t any products in the database. I set a <code>Route<\/code> that uses versioning which is set via <code>ApiVersion<\/code>. The data context <code>ProductContext<\/code> gets injected in the constructor with Dependency Injection. The first endpoint is <em>GET<\/em> which returns a list of Products in the Controller:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[HttpGet]\r\n[Route(\"\")]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\npublic ActionResult&lt;IQueryable&lt;Product&gt;&gt; GetProducts()\r\n{\r\n  var result = _context.Products as IQueryable&lt;Product&gt;;\r\n\r\n  return Ok(result\r\n    .OrderBy(p =&gt; p.ProductNumber));\r\n}<\/pre>\n<p>Be sure to add <code>Microsoft.AspNetCore.Http<\/code> in a using statement to set status codes in the response type.<\/p>\n<p>I opted to order products by product number to make it easier to show the results. In a production system, check this sort matches the clustered index, so the database doesn\u2019t work as hard. Always review execution plans and statistics IO to confirm good performance.<\/p>\n<p>This project is ready to go for a test drive! Inside of a CLI type:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet watch run<\/pre>\n<p>Hit the endpoint with curl:<\/p>\n<pre class=\"lang:c# theme:vs2012\">curl -i -X GET \"http:\/\/localhost:5000\/v1\/products\" -H \"accept: application\/json\"<\/pre>\n<p>I run both commands in separate consoles. One runs the file watcher that automatically refreshes when I make changes. The other terminal is where I keep curl results. Postman is also useful, but curl gets the job done and comes with Windows 10.<\/p>\n<p>Below are the results:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1002\" height=\"746\" class=\"wp-image-87964\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/08\/word-image-72.png\" \/><\/p>\n<p>This request returns all products in the database, but it\u2019s not scalable. As the product list grows, clients will get slammed with unbound data, putting more pressure on SQL and network traffic.<\/p>\n<p>A better approach is to introduce limit and offset request parameters in a model:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public class ProductRequest\r\n{\r\n  [FromQuery(Name = \"limit\")]\r\n  public int Limit { get; set; } = 15;\r\n\r\n  [FromQuery(Name = \"offset\")]\r\n  public int Offset { get; set; }\r\n}\r\n<\/pre>\n<p>Wire this request parameter to the <strong>GetProducts<\/strong> endpoint:<\/p>\n<div id=\"crayon-5f4d0518124ee885110476-10\" class=\"crayon-line\">\n<pre class=\"theme:vs2012 lang:c# decode:true \">public ActionResult&lt;IQueryable&lt;Product&gt;&gt;\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 GetProducts([FromQuery] ProductRequest request)\r\n{\r\n\u00a0\u00a0\/\/ ...\r\n\r\n\u00a0\u00a0Response.Headers[\"x-total-count\"] = result.Count().ToString();\r\n\r\n\u00a0\u00a0return Ok(result\r\n\u00a0\u00a0\u00a0\u00a0.OrderBy(p =&gt; p.ProductNumber)\r\n\u00a0\u00a0\u00a0\u00a0.Skip(request.Offset)\r\n\u00a0\u00a0\u00a0\u00a0.Take(request.Limit));\r\n}<\/pre>\n<p>Note I set an HTTP header <code>x-total-count<\/code> with the <code>Count<\/code>. This helps clients that may want to page through the entire result set. When requests parameters are not specified then the API defaults to the first 15 items.<\/p>\n<\/div>\n<p>Next, add a search parameter to filter products by department:<\/p>\n<pre class=\"lang:c# theme:vs2012\">public ActionResult&lt;IQueryable&lt;Product&gt;&gt; GetProducts([FromQuery] \r\n             string department, [FromQuery] ProductRequest request)\r\n{\r\n  \/\/ ...\r\n\r\n  if (!string.IsNullOrEmpty(department))\r\n  {\r\n    result = result.Where(p =&gt; p.Department.StartsWith(department, \r\n                 StringComparison.InvariantCultureIgnoreCase));\r\n  }\r\n\r\n  \/\/ ..\r\n}<\/pre>\n<p>Search can go inside a conditional block that alters the query. Note I use <code>StartsWith<\/code> and <code>InvariantCultureIgnoreCase<\/code> to make it easier to filter products. In actual SQL, the <code>LIKE<\/code> operator is useful, and case insensitivity can be set via collation.<\/p>\n<p>To test out paging and this new filter in curl:<\/p>\n<pre class=\"lang:c# theme:vs2012\">curl -i -X GET http:\/\/localhost:5000\/v1\/products?offset=15&amp;department=electronics\r\n  -H \"accept: application\/json\"<\/pre>\n<p>Be sure to check out HTTP headers which include total count and supported versions:<\/p>\n<pre class=\"lang:none theme:none\">HTTP\/1.1 200 OK\r\nDate: Sat, 11 Jul 2020 18:35:07 GMT\r\nContent-Type: application\/json; charset=utf-8\r\nServer: Kestrel\r\nContent-Length: 1459\r\nx-total-count: 194\r\napi-supported-versions: 1.0<\/pre>\n<h2>Logging and API Documentation<\/h2>\n<p>With the API taking shape, how can I communicate endpoints to other developers? It is beneficial for teams to know what the API exposes without having to bust open code. Swagger is the tool of choice here; by using reflection, it is capable of documenting what\u2019s available.<\/p>\n<p>What if I told you everything swagger needs is already set in this API? Go ahead, take a second look:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[Produces(\"application\/json\")]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\nActionResult&lt;IQueryable&lt;Product&gt;&gt; GetProducts([FromQuery] \r\n               string department, [FromQuery] ProductRequest request)<\/pre>\n<p>ASP.NET attributes are useful for documenting endpoints. Swagger also picks up return types from controller methods to figure out what responses look like and picks up request parameters in each controller method via reflection. It produces \u201cliving documentation\u201d because it sucks up everything from working code, which reduces mishaps.<\/p>\n<p>The one dependency lacking is a NuGet:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet add package Swashbuckle.AspNetCore<\/pre>\n<p>And wire this up in <code>ConfigureServices<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddSwaggerGen(c =&gt; c.SwaggerDoc(\"v1\", new OpenApiInfo\r\n{\r\n  Title = \"Products\",\r\n  Description = \"The ultimate e-commerce store for all your needs\",\r\n  Version = \"v1\"\r\n}));<\/pre>\n<p>Then, enable this in <code>Configure<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">app.UseSwagger();\r\napp.UseSwaggerUI(opt =&gt; opt.SwaggerEndpoint(\"\/swagger\/v1\/swagger.json\", \"Products v1\"));<\/pre>\n<p>Note <code>OpenApiInfo<\/code> comes from the <code>Microsoft.OpenApi.Models<\/code> namespace. With this, navigate to <em>http:\/\/localhost:5000\/swagger<\/em> in the browser to check out the swagger doc.<\/p>\n<p>The page should look like this:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1841\" height=\"1117\" class=\"wp-image-87965\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/08\/word-image-73.png\" \/><\/p>\n<p>From the swagger doc, feel free to poke around and fire requests to the API from this tool. Fellow developers from across the organization might even buy you a cup of coffee for making their lives easier.<\/p>\n<p>Note how expanding GET \/Products picks up C# data types from the method in the controller:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1420\" height=\"1016\" class=\"wp-image-87966\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/08\/word-image-74.png\" \/><\/p>\n<p>The next stop is the logger. I will use <code>NLog<\/code> to store logs in the back end. This enables the API to save logs for further analysis. In a real environment, logs are useful for troubleshooting outages. They also aid in gathering telemetry to help understand how the API is utilized in the wild.<\/p>\n<p>To set up the logger, I am going to need the following:<\/p>\n<ul>\n<li>A NuGet package<\/li>\n<li>An <em>nlog.config<\/em> settings file<\/li>\n<li>Changes in the <code>Program<\/code> class<\/li>\n<li>Tweaks in <em>appsettings.json<\/em><\/li>\n<\/ul>\n<p>To install the NuGet package:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet add package NLog.Web.AspNetCore<\/pre>\n<p>The <em>nlog.config<\/em> file can be:<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?&gt;\r\n&lt;nlog xmlns=\"http:\/\/www.nlog-project.org\/schemas\/NLog.xsd\"\r\n      xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"\r\n      throwExceptions=\"false\"\r\n      throwConfigExceptions=\"false\"\r\n      autoReload=\"true\"\r\n      internalLogLevel=\"Warn\"\r\n      internalLogFile=\r\n           \"C:\\temp\\BuildRestApiNetCore\\RestApi-internal-nlog.txt\"&gt;\r\n\r\n  &lt;extensions&gt;\r\n    &lt;add assembly=\"NLog.Web.AspNetCore\"\/&gt;\r\n  &lt;\/extensions&gt;\r\n\r\n  &lt;targets async=\"true\"&gt;\r\n    &lt;target xsi:type=\"File\"\r\n            name=\"ownFile-web\"\r\n            fileName=\r\n              \"C:\\temp\\BuildRestApiNetCore\\RestApi-${shortdate}.log\"&gt;\r\n\r\n      &lt;layout xsi:type=\"JsonLayout\"&gt;\r\n        &lt;attribute name=\"Timestamp\" layout=\"${longdate}\" \/&gt;\r\n        &lt;attribute name=\"Level\" layout=\"${uppercase:${level}}\" \/&gt;\r\n        &lt;attribute name=\"Logger\" layout=\"${logger}\" \/&gt;\r\n        &lt;attribute name=\"Action\" layout=\"${aspnet-mvc-action}\" \/&gt;\r\n        &lt;attribute name=\"Message\" layout=\"${message}\" \/&gt;\r\n        &lt;attribute \r\n           name=\"Exception\" layout=\"${exception:format=tostring}\" \/&gt;\r\n      &lt;\/layout&gt;\r\n    &lt;\/target&gt;\r\n  &lt;\/targets&gt;\r\n\r\n  &lt;rules&gt;\r\n    &lt;logger name=\"Microsoft.*\" maxlevel=\"Info\" final=\"true\" \/&gt; \r\n                \r\n    &lt;logger name=\"*\" minlevel=\"Info\" writeTo=\"ownFile-web\" \/&gt;\r\n  &lt;\/rules&gt;\r\n&lt;\/nlog&gt;<\/pre>\n<p>Pay attention to <code>Layout<\/code> because it sets the type of log file which is set to <code>JsonLayout<\/code>. This JSON format has the most flexibility when consuming log files in different analytical tools. Logger rules do not log errors from <em>Microsoft.*<\/em> to keep chattiness down to a minimum. As a bonus, unhandled exceptions from the API get logged but do not rethrow because <code>throwExceptions<\/code> is false. Usage here may vary, but it is generally a good idea to handle all unhandled exceptions in the logger.<\/p>\n<p>Inside the <code>Program<\/code> class, enable <code>NLog<\/code>, remembering to include <code>using NLog.Web<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">Host.CreateDefaultBuilder(args)\r\n  .ConfigureWebHostDefaults(webBuilder =&gt;\r\n  {\r\n    webBuilder.UseStartup&lt;Startup&gt;();\r\n  })\r\n  .UseNLog();<\/pre>\n<p>Finally, make these tweaks to configure logging in <em>appsettings.json<\/em>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">\"Logging\": {\r\n  \"LogLevel\": {\r\n    \"Default\": \"Information\",\r\n    \"Microsoft\": \"None\",\r\n    \"Microsoft.AspNetCore\": \"Error\",\r\n    \"Microsoft.Hosting.Lifetime\": \"Information\"\r\n  }\r\n}<\/pre>\n<p>The basic idea is to cut the number of log entries which aren\u2019t relevant to this API. Feel free to poke around with the settings, so it logs exactly what the API needs.<\/p>\n<p>It\u2019s time to take this for a spin. In the <code>Controller<\/code> class,<code> add using Microsoft.Extensions.Logging<\/code> and inject a plain old ASP.NET logger:<\/p>\n<pre class=\"lang:c# theme:vs2012\">private readonly ILogger&lt;ProductsController&gt; _logger;\r\n\r\npublic ProductsController(ProductContext context, \r\n            ILogger&lt;ProductsController&gt; logger)\r\n{\r\n  _logger = logger;\r\n  \/\/ ...\r\n}<\/pre>\n<p>Say now the team decides to grab telemetry around how often clients ask for 100 records or more.<\/p>\n<p>Put this inside <code>GetProducts<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">if (request.Limit &gt;= 100)\r\n  _logger.LogInformation(\"Requesting more than 100 products.\");<\/pre>\n<p>Be sure to have a temp folder handy to check the logs, for example, <em>C:\\temp\\BuildRestApiNetCore\\<\/em>.<\/p>\n<p>This is what an entry might look like:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n  \"Timestamp\": \"2020-07-12 10:30:30.8960\",\r\n  \"Level\": \"INFO\",\r\n  \"Logger\": \"BuildRestApiNetCore.Controllers.ProductsController\",\r\n  \"Action\": \"GetProducts\",\r\n  \"Message\": \"Requesting more than 100 products.\"\r\n}<\/pre>\n<h2>REST Endpoints with Verbs<\/h2>\n<p>Take a deep breath in and breath out. This API is almost production-ready with minimal code. I will now quickly turn towards REST features such as <code>POST<\/code>, <code>PUT<\/code>, <code>PATCH<\/code>, and <code>DELETE<\/code>.<\/p>\n<p>The <code>POST<\/code> endpoint takes in a body with the new product and adds it to the list. This method is not idempotent because it creates new resources when invoked.<\/p>\n<p>Put this in <code>ProductsController<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012  \">[HttpPost]\r\n[ProducesResponseType(StatusCodes.Status201Created)]\r\n[ProducesResponseType(StatusCodes.Status400BadRequest)]\r\npublic ActionResult&lt;Product&gt; PostProduct([FromBody] Product product)\r\n{\r\n  try\r\n  {\r\n    _context.Products.Add(product);\r\n    _context.SaveChanges();\r\n\r\n    return new CreatedResult($\"\/products\/{product.ProductNumber.ToLower()}\", product);\r\n  }\r\n  catch (Exception e)\r\n  {\r\n    _logger.LogWarning(e, \"Unable to POST product.\");\r\n\r\n    return ValidationProblem(e.Message);\r\n  }\r\n}<\/pre>\n<p>ASP.NET automatically handles exceptions via <code>ValidationProblem<\/code>. This validation returns an <a href=\"https:\/\/tools.ietf.org\/html\/rfc7807\">RFC 7807 spec<\/a> compliant response with a message. In a real system, I recommend making sure this does not expose any internals about the API. Putting the exception message here helps clients troubleshoot their code, but security is also important. I opted to include the error message mostly for demonstration purposes. The exception is also logged as a warning, to avoid logging a bunch of errors. Monitoring tools might page out to whoever is on-call when there are too many exceptions. A best practice is to only log errors during catastrophic failures that might need human intervention.<\/p>\n<p>Using the swagger tool, the curl command is:<\/p>\n<pre class=\"lang:c# theme:vs2012\">curl -i -X POST http:\/\/localhost:5000\/v1\/products\r\n  -H \"accept: application\/json\"\r\n  -H \"Content-Type: application\/json\"\r\n  -d \"{\\\"productNumber\\\":\\\"string\\\",\\\"name\\\":\\\"string\\\",\\\"price\\\":10,\\\"department\\\":\\\"string\\\"}\"<\/pre>\n<p>When there are problems with the request, the API responds with:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n  \"errors\": {},\r\n  \"type\": \"https:\/\/tools.ietf.org\/html\/rfc7231#section-6.5.1\",\r\n  \"title\":\"One or more validation errors occurred.\",\r\n  \"status\": 400,\r\n  \"detail\": \"An item with the same key has already been added. Key: string\",\r\n  \"traceId\":\"|c445a403-43564e0626f9af50.\"\r\n}<\/pre>\n<p>A <em>400 (Bad Request)<\/em> response indicates a user error in the request. Because users can\u2019t be trusted to send valid data, the API logs a warning.<\/p>\n<p>Note that on success <code>POST<\/code> returns a 201 with <em>Location<\/em>:<\/p>\n<pre class=\"lang:none theme:none\">HTTP\/1.1 201 Created\r\nDate: Mon, 13 Jul 2020 22:52:46 GMT\r\nContent-Type: application\/json; charset=utf-8\r\nServer: Kestrel\r\nContent-Length: 76\r\nLocation: \/products\/bc916\r\napi-supported-versions: 1.0<\/pre>\n<p>This points the client towards the new resource. So, it is a good idea to spin up this <code>GET<\/code> endpoint:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[HttpGet]\r\n[Route(\"{productNumber}\")]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\n[ProducesResponseType(StatusCodes.Status404NotFound)]\r\npublic ActionResult&lt;Product&gt; GetProductByProductNumber([FromRoute] \r\n            string productNumber)\r\n{\r\n  var productDb = _context.Products\r\n    .FirstOrDefault(p =&gt; p.ProductNumber.Equals(productNumber, \r\n              StringComparison.InvariantCultureIgnoreCase));\r\n\r\n  if (productDb == null) return NotFound();\r\n\r\n  return Ok(productDb);\r\n}<\/pre>\n<p>A 404 response indicates the resource does not exist in the API yet but might become available at some point in the future.<\/p>\n<p><code>PUT<\/code> is similar:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[HttpPut]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\n[ProducesResponseType(StatusCodes.Status404NotFound)]\r\n[ProducesResponseType(StatusCodes.Status400BadRequest)]\r\npublic ActionResult&lt;Product&gt; PutProduct([FromBody] Product product)\r\n{\r\n  try\r\n  {\r\n    var productDb = _context.Products\r\n      .FirstOrDefault(p =&gt; p.ProductNumber.Equals(product.ProductNumber, \r\n           StringComparison.InvariantCultureIgnoreCase));\r\n\r\n    if (productDb == null) return NotFound();\r\n\r\n    productDb.Name = product.Name;\r\n    productDb.Price = product.Price;\r\n    productDb.Department = product.Department;\r\n    _context.SaveChanges();\r\n\r\n    return Ok(product);\r\n  }\r\n  catch (Exception e)\r\n  {\r\n    _logger.LogWarning(e, \"Unable to PUT product.\");\r\n\r\n    return ValidationProblem(e.Message);\r\n  }\r\n}<\/pre>\n<p>In REST design, a <code>PUT<\/code> allows updates to an entire resource. It is idempotent because multiple identical requests do not alter the number of resources.<\/p>\n<p>Like a <em>GET 404<\/em> response, the resource is unavailable for updates, but this might change later. As a bonus, ASP.NET provides model binding validation out of the box. Go ahead, try to update an existing resource with bad data.<\/p>\n<p>This JSON is the <em>Bad Request<\/em> response you might see:<\/p>\n<pre class=\"lang:c# theme:vs2012\">{\r\n  \"errors\": {\r\n    \"Price\": [\"The price field is required.\"]\r\n  },\r\n  \"type\": \"https:\/\/tools.ietf.org\/html\/rfc7231#section-6.5.1\",\r\n  \"title\": \"One or more validation errors occurred.\",\r\n  \"status\": 400,\r\n  \"traceId\": \"|c445a409-43564e0626f9af50.\"\r\n}<\/pre>\n<p><code>PATCH<\/code> is the most complex of all verbs because it only updates a part of the resource via a <a href=\"https:\/\/tools.ietf.org\/html\/rfc6902\">JSON Patch document<\/a>.<\/p>\n<p>The good news is .NET Core helps with a NuGet package:<\/p>\n<pre class=\"lang:c# theme:vs2012\">dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson<\/pre>\n<p>Then, enable this in <code>ConfigureServices<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">services.AddControllers().AddNewtonsoftJson();<\/pre>\n<p>This is the <code>PATCH<\/code> endpoint. Remember <code>using Microsoft.AspNetCore.JsonPatch<\/code>:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[HttpPatch]\r\n[Route(\"{productNumber}\")]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\n[ProducesResponseType(StatusCodes.Status404NotFound)]\r\n[ProducesResponseType(StatusCodes.Status400BadRequest)]\r\npublic ActionResult&lt;Product&gt; PatchProduct([FromRoute] \r\n      string productNumber, [FromBody] JsonPatchDocument&lt;Product&gt; patch)\r\n{\r\n  try\r\n  {\r\n    var productDb = _context.Products\r\n      .FirstOrDefault(p =&gt; p.ProductNumber.Equals(productNumber, \r\n           StringComparison.InvariantCultureIgnoreCase));\r\n\r\n    if (productDb == null) return NotFound();\r\n\r\n    patch.ApplyTo(productDb, ModelState);\r\n\r\n    if (!ModelState.IsValid || !TryValidateModel(productDb)) \r\n             return ValidationProblem(ModelState);\r\n\r\n    _context.SaveChanges();\r\n\r\n    return Ok(productDb);\r\n  }\r\n  catch (Exception e)\r\n  {\r\n    _logger.LogWarning(e, \"Unable to PATCH product.\");\r\n\r\n    return ValidationProblem(e.Message);\r\n  }\r\n}<\/pre>\n<p>I hope you see a pattern start to emerge with the different status code response types. A <em>200 OK<\/em> means success and a <em>400 Bad Request<\/em> means user error. Once a patch gets applied it appends any validation errors in <code>ModelState<\/code>. Take a closer look at <code>JsonPatchDocument<\/code><strong>,<\/strong> which does model binding, and <code>ApplyTo<\/code><strong>,<\/strong> which applies changes. This is how a JSON Patch document gets applied to an existing product in the database. Exceptions get logged and included in the response like all the other endpoints. A <em>404 (Not Found)<\/em> response indicates the same situation as all the other verbs. This consistency in response status codes helps clients deal with all possible scenarios.<\/p>\n<p>A JSON patch request body looks like the following:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[{\r\n  \"op\": \"replace\",\r\n  \"path\": \"price\",\r\n  \"value\": 13.67\r\n}]<\/pre>\n<p>Model binding validation rules still apply to the patch operation to preserve data integrity. Note the patch gets wrapped around an array, so it supports an arbitrary list of operations.<\/p>\n<p>This is <code>PATCH<\/code> in curl:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1002\" height=\"610\" class=\"wp-image-87967\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2020\/08\/word-image-75.png\" \/><\/p>\n<p>Last stop, a <code>DELETE<\/code> method:<\/p>\n<pre class=\"lang:c# theme:vs2012\">[HttpDelete]\r\n[Route(\"{productNumber}\")]\r\n[ProducesResponseType(StatusCodes.Status200OK)]\r\n[ProducesResponseType(StatusCodes.Status404NotFound)]\r\npublic ActionResult&lt;Product&gt; DeleteProduct([FromRoute] \r\n        string productNumber)\r\n{\r\n  var productDb = _context.Products\r\n    .FirstOrDefault(p =&gt; p.ProductNumber.Equals(productNumber, \r\n           StringComparison.InvariantCultureIgnoreCase));\r\n\r\n  if (productDb == null) return NotFound();\r\n\r\n  _context.Products.Remove(productDb);\r\n  _context.SaveChanges();\r\n\r\n  return NoContent();\r\n}<\/pre>\n<p>The status code response is <em>No Content<\/em>:<\/p>\n<pre class=\"lang:none theme:none\">HTTP\/1.1 204 No Content\r\nDate: Tue, 14 Jul 2020 22:59:20 GMT\r\nServer: Kestrel\r\napi-supported-versions: 1.0<\/pre>\n<p>This status signals to clients that the resource is no longer available because the response body is empty. The response can also be <em>204 (Accepted)<\/em> if this needs an async background process to clean up the data. In a real system, soft deletes are sometimes preferable to allow rollback during auditing. When deleting data, be sure to comply with GPDR or any policy that applies to the data.<\/p>\n<h2>Conclusion<\/h2>\n<p>.NET Core adds many useful features to your toolbelt to make working with REST APIs easier. Complex use cases such as documentation, validation, logging, and PATCH requests are simpler to think about.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A REST API can hide the complexity behind large scale solutions using simple verbs like POST, PUT, or PATCH. In this article, Camilo Reyes explains how to create a REST API in .NET Core.&hellip;<\/p>\n","protected":false},"author":274017,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[101707],"coauthors":[41241],"class_list":["post-87963","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-sql-data-catalog"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/87963","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/274017"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=87963"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/87963\/revisions"}],"predecessor-version":[{"id":87985,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/87963\/revisions\/87985"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=87963"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=87963"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=87963"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=87963"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}