Now you can see all in one place how to get your .NET Core Web API up and running and accepting posted data.
Introduction
This is the second and concluding part of the two-part article series.
You can read Part 1 at .NET Core Web API: The Least You Need To Know (part 1 of 2)[^]
In this article, we will cover:
- Creating basic HTML page to use to submit Post to
WeatherForecastController
- Altering the dotnet Web API to return static pages (serve HTML)
- Basic JavaScript which will contain the code that creates the
XmlHttpRequest
(XHR) - Configuring the XHR to Post with data in the body and appropriate headers
In the First Article
In the first article, we created the basic Web API template project and learned how to post to it using PostMan^.
Now, we take all that we learned there and apply it to creating a web page that can post JSON to our Web API.
Let's jump right in and create our HTML page that we will use to post to our web controller.
Add SubFolder and index.htm
The first thing I do is add a new subfolder to my project named wwwroot. I am adding that folder name because it is the name of the folder where I deploy my files on my hosted web site where I can deploy my Web API.
You will also see that the default server that dotnet runs will automatically know about wwwroot and serve the index.htm file directly from that directory.
Next, I add a new file named index.htm and add our basic HTML that will provide us with a button we can click to submit our post to the Web API.
Here's the entire HTML file. It is very simple and keep in mind that this entire example uses vanilla JavaScript (no need for external JavaScript libraries), so all of the code is very simple.
<!DOCTYPE html>
<html lang="en">
<head>
<title>XHR Poster</title>
<meta charset="utf-8">
<script src="main.js"></script>
<style>
body{padding: 5px 15px 5px 15px;}
</style>
</head>
<body>
<label for="tempC">Temp (C)</label>
<input id="tempC" type="number" value="20">
<button onclick="postData()">Post TempC</button><br>
<label for="tempF">Temp (F)</label>
<input id="tempF" type="text">
</body>
</html>
That page renders the following in the browser:
Four Take-Aways From HTML
Since it is self-explanatory, I won't explain all of the HTML but I will point out four important things to notice:
- main.js which is loaded at the top contains all of our JavaScript code that provides our needed functionality.
- There is a
number
input which allows the user to set the TempC
value which is posted to the Web API (represents the value in Celsius). - The text input which displays the output value of
TemperatureF
which is returned by the Web API (represents the value in Fahrenheit). - The functionality of the page is fired by clicking the button which calls the JavaScript method
postData()
which is found in the main.js.
If you're following along, go ahead and add a main.js file to the /wwwroot folder now. Here's a snapshot of what all of the files look like in my Visual Studio Code (VSC) IDE (Integrated Development Environment).
If you look closely, you will see that the /wwwroot
directory contains the two files (index.htm and main.js).
dotnet run: Start Your Web Server
Let's run dotnet so it'll compile the Web API again and start up the web servers for us so we can attempt to get it to serve the index.htm file. It'll cause an error, but it'll be instructive.
To start the Web API, go to your console window (all of this is detailed in Part 1 of this series) and type the command:
$>dotnet run
When you do that, the server will start on the default host and port (localhost:5001) so we can attempt to load the index.htm.
Client Code and Server Code (Web API)
Keep in mind that now our project contains both client code (index.htm and main.js) and Server Code (written in C#). However, the dotnet engine basically ignores our HTML and JavaScript. It knows that it doesn't need to compile that code and it only needs to work with the C# code it finds. The other thing that dotnet does do however, is start a nice web server for you.
Try To Load index.htm.
Try this URL and see if index.htm loads: https://localhost:5001/index.htm
Here's what you'll see. It is a completely blank page.
Your first thought might be that you need to change the URL to include the subdirectory /wwwroot
like https://localhost:5001/wwwroot/index.htm.
I thought maybe that was the problem when I attempted to work through this.
You also may think there is at least some skeleton HTML in there, but I assure you there is not. When you hit that URL (which is non-existent to your Web API), the server responds with nothing.
If you are attempting this for the first time, it can be really confusing because you may think something else is wrong. It's a very difficult problem to search for too, because there is no error value or error message.
The problem is that the Web API is not set up to serve static pages like index.htm. Let's change that.
Change Web API to Serve Static Pages
By default, a Web API project will not serve a static page such as index.htm. That probably makes sense because this is a Web API we are creating. However, if you want to be able to test your Web API and not get into all kinds of difficulties with CORS (Cross-Origin Resource Shares), then it is nice to be able to know how to change the Web API and it is very easy.
Change the Startup.cs
To do this, we simply need to open up the Startup.cs and add a new service to start running on the dotnet app.
When you open up the Startup.cs and look inside the Configure()
method, you are going to see that a number of services are already loaded up. Before we change the code, it will look something like the following:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
You can see that the project template has already added a number of services to start running on the app object already. Those services provide certain functionality like routing (UseRouting()
) and HttpsRedirection()
, etc.
We just need to add one line of code which will add the static page service.
Let's add one line right after the current line that has:
app.UseAuthorization();
Our new line of code will be:
app.UseStaticFiles();
It looks like this in VSC:
Once you make that change, go back to your console and stop the Web API (CTRL-C) and then start it again via $> dotnet run
.
Now, we can try the URL again and the page will be served properly:
https://localhost:5001/index.htm^
That's better!
Now, we are set up to try out some JavaScript code. But now, we need to get our JavaScript just right with our XmlHttpRequest
(XHR). We'll do all of that work in the main.js file.
Configuring the XHR In JavaScript
We need to do four things:
- Instantiate the built-in (JavaScript / Browser provided XHR object).
- Add an event listener to the XHR object for when the Asynchronous request completes (load event).
- Add an event listener to the XHR object that will run if an error occurs.
- Add a variable to hold the url we will post to.
Here are the four lines of code that I've placed at the top of main.js:
var xhr = new XMLHttpRequest();
xhr.addEventListener("load", transferComplete);
xhr.addEventListener("error", transferFailed);
var url = "http://localhost:5001/WeatherForecast/"
When the XHR object load
event occurs, it will call a method named transferComplete()
that we will define further down in the JavaScript code.
If the XHR object error
event occurs, then it will call a method named transferFailed()
that will be defined further down in our JavaScript code.
The User Starts the Process By Clicking the Button
I will show you those two methods in a moment, but first, let's take a look at the code that is fired when the user clicks the [TempC
] button.
Remember, we wired up the button in the HTML so that onclick="postData()"
. That method is defined in our main.js JavaScript.
Here's the entire method from VSC with line numbers so we can discuss what is happening.
postData() Method: Line-by-Line
Since we've already instantiated the xhr
object and set the url, we now just need to call the xhr
object's open()
method.
We provide two parameters to the open()
method:
- the HTTP action (
Get
) as a string
- the URL that we are posting to
yyyy-mm-dd Date Format
On line 8, we set a local variable up to hold a date string
. Notice that this date is in the format of yyyy-mm-dd.
If you use another format, the Web API may not be able to transform the string
into a date on the server side and it may fail.
Line 9
We call the browser method document.querySelector()
to get a reference to the number input control and get its current value. That way, the user can dynamically change the value to submit different values to the Web API. We store the current value in a local variable for later use.
Line 10 is just a console.log()
to the browser console so we can see what is happening.
Line 11: JSON Object
On line 11, we create a new variable named weather
and we initialize it with JSON values. Each of the names in the JSON will become a property on the local weather
object. I do this because it makes it far easier to post JSON to the Web API. Of course, each property name in the weather
object matches the property names in our WeatherForecast
domain object (defined in C# in the Web API -- see Part 1 of this article for more information).
Line 12: Explicitly Convert Value to Number
One line 12, I add a new property named TemperatureC
to the weather object that I've already created. JavaScript is a dynamic language and allows this type of manipulation on its objects. You can dynamically add a new property simply by referencing it on the object and setting it to a value. I add this property name (TemperatureC
) because that is the expected name on the domain object on the Web API side.
Notice that I also explicitly coerce the local variable tempC
value (which is a string
) into a JavaScript Number
type (which is a Double
-- numeric high precision). That's because I found that since the Web API is expecting an Int32
for TemperatureC
that it seems to fail if the value comes in as a String
(there is no automatic conversion to the Int32
from the JSON object's string
).
Line 13 is another console.log()
just to check the format of the Date
property.
Set the Request Header
On line 14, we have to set the Content-Type
request header on our XHR object or the Web API will fail with a 415 error just as it did when we posted to the URL using PostMan (see Part 1).
XHR Send
Finally on line 15, we call the XHR send()
method which will actually post to the Web API url.
However, we want to post the JSON string
that represents our weather
object and to do that, we call a built-in browser method called JSON.stringify()
which will turn the weather
object into the appropriate JSON string.
But, this is just the request that gets the Web API WeatherForecastController
default Post
method to fire. We need to do some work, when the request completes and our JavaScript code is alerted that the load
event has fired. That's where the event handler that we bound earlier comes into play.
transferCompleted() Line-by-line
Here's the extremely simple transferCompleted()
method that we defined and wired up to run when the XHR object's load
event fires.
The first two lines just output to the browser console so we can see what is coming back from the Web API. We know that the default Post()
method of the Web API returns the Fahrenheit temperature that represents the Celsius temperature that we posted to the method (See Part 1 for more).
XHR Response Property
The xhr
object's response property holds the data that is returned by the Web API. In our case, that is just an integer value that represents the Fahrenheit temperature. Normally, you'd return an entire JSON object that represents something more complex than this, but for our tests, we are just returning the integer value.
On line 21, we simply use the document.querySelector()
method to get a reference to the tempF
text control and we set its value to the value returned by the Web API (xhr.response
).
Process Overview
That's it! Now when the user selects a value in the number control and clicks the button, the Web API is called and it returns the associated Fahrenheit temperature and displays it in the tempF
text box.
It all works because we set up our XHR
object properly with the:
- correct URL
- proper
Content-Type
header - correct JSON (representing a
WeatherForecast
domain object)
If you get any one of those incorrect, you will see a very basic error in your browser console letting you know that something went wrong.
Grab the Code and Try It
I've posted the final code at the top. Grab the code and try it for yourself. Now you can model your Web API and JavaScript requests on this and get yourself started with this technology and the information is all compiled in one place.
History
- 20th May, 2020: Article published