With .NET 5.0, two small features were introduced to Asp.NET and were almost unnoticed: Open Api and HTTPRepl. Open Api is not something new, it’s been available for a long time, but it had to be included explicitly in a new project. Now, when you create a new project, it’s automatically included in the project and you can get the Api documentation using Swagger.
Now, when you create a new project with
dotnet new webapi
You will create a new WebApi project with a Controller, WeatherController, that shows 10 values of a weather forecast:
It’s a simple app, but it already comes with the OpenApi (Swagger) for documentation. Once you type the address:
https://localhost:5001/swagger
You will get the Swagger documentation and will be able to test the service:
But there is more. Microsoft introduced also HttpRepl, a REPL (Read-Eval-Print Loop) for testing REST services. It will scan your service and allow you to test the service using simple commands, like the file commands.
To test this new feature, in new folder create a webapi app with
dotnet new webapi
Then, open Visual Studio Code with
code .
You will get something like that:
Then, delete the WeatherForecast.cs file and add a new folder and name it Model. In it, add a new file and name it Customer.cs and add this code:
public class Customer
{
public string CustomerId { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Fax { get; set; }
}
Create a new file and name it CustomerRepository.cs and add this code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
namespace HttpRepl.Model
{
public class CustomerRepository
{
private readonly IList<Customer> customers;
public CustomerRepository()
{
var doc = XDocument.Load("Customers.xml");
customers = new ObservableCollection<Customer>((from c in doc.Descendants("Customer")
select new Customer
{
CustomerId = GetValueOrDefault(c, "CustomerID"),
CompanyName = GetValueOrDefault(c, "CompanyName"),
ContactName = GetValueOrDefault(c, "ContactName"),
ContactTitle = GetValueOrDefault(c, "ContactTitle"),
Address = GetValueOrDefault(c, "Address"),
City = GetValueOrDefault(c, "City"),
Region = GetValueOrDefault(c, "Region"),
PostalCode = GetValueOrDefault(c, "PostalCode"),
Country = GetValueOrDefault(c, "Country"),
Phone = GetValueOrDefault(c, "Phone"),
Fax = GetValueOrDefault(c, "Fax")
}).ToList());
}
#region ICustomerRepository Members
public bool Add(Customer customer)
{
if (customers.FirstOrDefault(c => c.CustomerId == customer.CustomerId) == null)
{
customers.Add(customer);
return true;
}
return false;
}
public bool Remove(Customer customer)
{
if (customers.IndexOf(customer) >= 0)
{
customers.Remove(customer);
return true;
}
return false;
}
public bool Update(Customer customer)
{
var currentCustomer = GetCustomer(customer.CustomerId);
if (currentCustomer == null)
return false;
currentCustomer.CustomerId = customer.CustomerId;
currentCustomer.CompanyName = customer.CompanyName;
currentCustomer.ContactName = customer.ContactName;
currentCustomer.ContactTitle = customer.ContactTitle;
currentCustomer.Address = customer.Address;
currentCustomer.City = customer.City;
currentCustomer.Region = customer.Region;
currentCustomer.PostalCode = customer.PostalCode;
currentCustomer.Country = customer.Country;
currentCustomer.Phone = customer.Phone;
currentCustomer.Fax = customer.Fax;
return true;
}
public bool Commit()
{
try
{
var doc = new XDocument(new XDeclaration("1.0", "utf-8", "yes"));
var root = new XElement("Customers");
foreach (Customer customer in customers)
{
root.Add(new XElement("Customer",
new XElement("CustomerID", customer.CustomerId),
new XElement("CompanyName", customer.CompanyName),
new XElement("ContactName", customer.ContactName),
new XElement("ContactTitle", customer.ContactTitle),
new XElement("Address", customer.Address),
new XElement("City", customer.City),
new XElement("Region", customer.Region),
new XElement("PostalCode", customer.PostalCode),
new XElement("Country", customer.Country),
new XElement("Phone", customer.Phone),
new XElement("Fax", customer.Fax)
));
}
doc.Add(root);
doc.Save("Customers.xml");
return true;
}
catch (Exception)
{
return false;
}
}
public IEnumerable<Customer> GetAll() => customers;
public Customer GetCustomer(string id) => customers.FirstOrDefault(c => string.Equals(c.CustomerId, id, StringComparison.CurrentCultureIgnoreCase));
#endregion
private static string GetValueOrDefault(XContainer el, string propertyName)
{
return el.Element(propertyName) == null ? string.Empty : el.Element(propertyName).Value;
}
}
}
This code will use a file, named Customers.xml and will use it to serve the customers repository. With it, you will be able to get all customers, get, add, update or delete one customer. We will use it to serve our controller. The Customers.xml can be obtained at . You should add this file to the main folder and then add this code:
<ItemGroup >
<None Update="customers.xml" CopyToPublishDirectory="PreserveNewest" />
</ItemGroup>
This will ensure that the xml file is copied to the output folder when the project is built.
Then, in the Controllers folder, delete the WeatherForecastController and add a new file, CustomerController.cs and add this code:
using System.Collections.Generic;
using HttpRepl.Model;
using Microsoft.AspNetCore.Mvc;
namespace HttpRepl.Controllers
{
[ApiController]
[Route("[controller]")]
public class CustomerController : ControllerBase
{
[HttpGet]
public IEnumerable<Customer> Get()
{
var customerRepository = new CustomerRepository();
return customerRepository.GetAll();
}
[HttpGet("{id}")]
public IActionResult GetCustomer(string id)
{
var customerRepository = new CustomerRepository();
Customer customer = customerRepository.GetCustomer(id);
return customer != null ? Ok(customer) : NotFound();
}
[HttpPost]
public IActionResult Add([FromBody] Customer customer)
{
if (string.IsNullOrWhiteSpace(customer.CustomerId))
return BadRequest();
var customerRepository = new CustomerRepository();
if (customerRepository.Add(customer))
{
customerRepository.Commit();
return CreatedAtAction(nameof(Get), new { id = customer.CustomerId }, customer);
}
return Conflict();
}
[HttpPut]
public IActionResult Update([FromBody] Customer customer)
{
if (string.IsNullOrWhiteSpace(customer.CustomerId))
return BadRequest();
var customerRepository = new CustomerRepository();
var currentCustomer = customerRepository.GetCustomer(customer.CustomerId);
if (currentCustomer == null)
return NotFound();
if (customerRepository.Update(customer))
{
customerRepository.Commit();
return Ok(customer);
}
return NoContent();
}
[HttpDelete("{id}")]
public IActionResult Delete([FromRoute]string id)
{
if (string.IsNullOrWhiteSpace(id))
return BadRequest();
var customerRepository = new CustomerRepository();
var currentCustomer = customerRepository.GetCustomer(id);
if (currentCustomer == null)
return NotFound();
if (customerRepository.Remove(currentCustomer))
{
customerRepository.Commit();
return Ok();
}
return NoContent();
}
}
}
This controller will add actions to get all customers, get add, update or delete one customer. The project is ready to be run. You can run it with dotnet run
and, when you open a new browser window and type this address:
https://localhost:5001/swagger
You will get something like this:
You can test the service with the Swagger page (as you can see, it was generated automatically wen you compiled and ran the app), but there is still another tool: HttpRepl. This tool was added with .NET 5 and you can install it with the command:
dotnet tool install -g Microsoft.dotnet-httprepl
Once you install it, you can run it with
httprepl https://localhost:5001
When you run it, you will get the REPL prompt:
If you type the ui
command, a new browser window will open with the Swagger page. You can type ls
to list the available controllers and actions:
As you can see, it has a folder structure and you can test the service using the command line. For example, you can get all the customers with get customer
or get one customer with get customer/blonp
:
You can also “change directory” to the Customer “directory” with cd Customer
. In this case, you can query the customer with get blonp
:
If the body content is simple, you can use the -c parameter, like in:
post -c "{"customerid":"test"}"
This will add a new record with just the customer id:
If the content is more complicated, you must set the default editor, so you can edit the customer that will be inserted in the repository. You must do that with the command:
pref set editor.command.default "c:\\windows\\system32\\notepad.exe"
This will open notepad when you type a command that needs a body, so you can type the body that will be sent when the command is executed. If you type post
in the command line, notepad will open to edit the data. You can type this data:
{
"customerId": "ABCD",
"companyName": "Abcd Inc.",
"contactName": "A.B.C.Dinc",
"contactTitle": "Owner",
"address": "1234 - Acme St - Suite A",
"city": "Abcd",
"region": "AC",
"postalCode": "12345",
"country": "USA",
"phone": "(501) 555-1234"
}
When you save the file and close notepad, you will get something like this:
If you try to add the same record again, you will get an error 409, indicating that the record already exists in the database:
As you can see, the repository is doing the checks and sending the correct response to the REPL. You can use the same procedure to update or delete a customer. For the delete, you just have to pass the customer Id:
Now that we know all the commands we can do with the REPL, we can go one step further: using scripts. You can write a text file with the commands to use and run the script. Let’s say we want to exercise the entire API, by issuing all commands in a single run. We can create a script like this (we’ll name it TestApi.txt)
connect https://localhost:5001
ls
cd Customer
ls
get
get alfki
post --content "{"customerId": "ABCD","companyName": "Abcd Inc.","contactName": "A.B.C.Dinc"}"
get abcd
put --content "{"customerId": "ABCD","companyName": "ACME Inc"}"
delete abcd
get abcd
cd ..
ls
And then open HttpRepl and run the command
run testapi.txt
We’ll get this output:
As you can see, with this tool, you get a very easy way to exercise your API. It’s not something fancy as a dedicated program like Postman, but it’s easy to use and does its job.
The full code for the project is at https://github.com/bsonnino/HttpRepl