Creating the Project
- Open Visual Studio. Select
Create a new project
. - Select
C#
for the language filter. Search forasp.net core
templates. - Select the
ASP.NET Core Web Application
template. ClickNext
. - Name your Project and select an appropriate Location for saving your project.
- Uncheck the
Place solution and project in the same directory
box. - Click
Create
. - Select
Empty
project template. Other defaults should already be set to.NET Core
,ASP.NET Core 3.1
, andNo Authentication
. - Click
Create
.
Adding the Unit Testing Project
We will use xUnit to test our project.
- Right click your Solution and select
Add -> New Project
. - Select
C#
for the Language filter. Search forxUnit
templates. - Select the
xUnit Test Project (.NET Core)
. ClickNext
. - Name the project exactly the same as your first project, but add
.Tests
to the end of the name. (Why? See below.) - Click
Create
. - Right click on the Test Projects Dependencies and
Add Reference
from the.Tests
project to your MVC project (So the test code can use the app code).
Why does the name need to match? When you're creating classes in C#, each class is placed inside a namespace. Classes can automatically "see" other classes in the same namespaces. They can also see class defined in parent namespaces. When we need to use code from other namespaces we use the
using
statement at the top of file. This means that if our test code classes are in theFoo.Tests
namespace they can automatically see classes defined in theFoo
namespace. If you put your tests in theBar.Tests
namespace, however, you would have to addusing Foo;
at the top of every test class you create.
Running the App
Hit F5
to run the application. You should see "Hello World" printed out on a web page.
Our web server is running at the http://localhost:xxxx/
url. Right now, it's just hard coded to return that Hello World string.
Let's make it use MVC and set up the basics.
Configuring our MVC Project
ASP.NET Core apps use a Startup
class which may include ConfigureServices()
and Configure()
methods.
Let's open the Startup.cs
file and take a look...
First, add a Startup
constructor that initializes a Configuration
interface object using Constructor Dependency Injection. Don't worry if you don't understand this now...there will be more on Dependency Injection later.
Edit your Startup class to include the following Constructor:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration {get; }
// The ConfigureServices() and Configure() methods continue here...
}
Does IConfiguration
have an error? Fix it by adding in the using Microsoft.Extensions.Configuration;
using statement.
ConfigureServices() Method
The ConfigureServices()
method is an optional method used to set configuration options for the application's services. A service is a reusable component that provides functionality. Services are registered in this method, which makes them available within the application and in the Configure()
method.
So let's add the MVC Service to our app.
Why? The MVC Service is what our application will use to do things like routing, dispatching HTTP requests to our controllers / actions, and all the Razor templating functionality.
Edit your ConfigureServices()
method to include the following:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
}
Configure() Method
The Configure()
method is used to specify how the application responds to requests from the browser, otherwise known as HTTP Requests.
Next, let's reconfigure our application so that it actually uses the MVC service. Update your Configure
method so that it looks like this:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
We've made two important changes here.
First, we enabled "Static Files" support. That will allow us to host things like .css and .js files on our server.
Finally, we've enabled MVC and defined some route information. We'll get into routes more later, but for now just know that routes are what associate a url to a method on a controller.
Examples:
http://localhost:xxxx/Home/Index
=> HomeController.Index()
http://localhost:xxxx/Foo/Create
=> FooController.Create()
Hit F5
to run the app again, and bask in the beautiful 404
error message. This is progress, ugly as it may be.
Adding a Controller
We're getting a 404
error message because we don't have a Controller and a View defined.
Take another peek at the route we defined:
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
// ^default ^default ^? means it isn't required
By default we're looking for a HomeController
with an Index
action.
In your Tests
project, create a new test:
HomeControllerTests.cs
public class HomeControllerTests
{
[Fact]
public void Index_Returns_Hello_World()
{
var underTest = new HomeController();
var result = underTest.Index();
Assert.Equal("Hello World!", result);
}
}
Lets take care of the error that was created.
HomeController is not defined
The type or namespace name 'HomeController' could not be found (are you missing a using directive or an assembly reference?)
Sure enough, we're missing a HomeController
class.
First we'll create a Controllers
namespace inside our MVC project by right clicking on the project and selecting Add -> New Folder
. Name the folder "Controllers".
Next let's add the controller class inside our new namespace. Right click the Controllers
folder and select Add -> Class...
. Name the class HomeController
.
NOTE:
All Controllers must be suffixed with the word Controller, and they must be in the Controllers
namespace!
Update your class so that it has the Index method and it derives from MVC's base Controller
class. You will also need to add the using Microsoft.AspNetCore.Mvc;
using statement.
using System;
using Microsoft.AspNetCore.Mvc;
namespace TestCoreApp.Controllers
{ // ^ Use your project name, not mine!
public class HomeController : Controller
{
public string Index()
{
throw new NotImplementedException();
}
}
}
Back in your Test Project there is still an error on the HomeController
class. But this time the suggested fixes are different. Add a using statement to your HomeControllerTests
class that makes your Controllers
namespace available. For example, using TestCoreApp.Controllers;
, but make sure the project name matches yours. The HomeController
class should now be recognized.
Test fails, Make it pass
Run your test (CTRL R, A) and watch it fail, and then we'll make it pass by replacing the body of your method.
(NOTE: If your test project won't run, try Rebuilding your Solution first.)
We call this Index
method an action. Actions are just methods on Controllers. Modify your project's Index
method to look like this:
public class HomeController : Controller
{
public string Index()
{
return "Hello World!";
}
}
Run the app again, and check out the result.
At this point, we have a functional MVC app, but notice that we are only returning a string
.
Wouldn't it be awesome if we could return some HTML instead of our boring string?
Returning a View
Similar to how our Controllers all live inside a folder named Controllers
, all of our views must live inside a Views folder.
Within the Views folder, we will create a folder for each of our Controllers to house the views used by that Controller.
Right click on your web project and add a folder called Views
. Make sure this folder lives in your project, not in the Controllers
folder.
Within the Views
folder, create a folder called Home
that will hold the views for our HomeController
.
Let's add our Home View. Right click on the Views\Home
folder and select Add View
.
- Name the View
Index
. - Deselect the "Use a layout page" checkbox.
After a moment, you should have an Index.cshtml
file that looks like this:
@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body></body>
</html>
Let's add a bit of custom HTML to the body:
<body>
<h1>This is html!</h1>
<p>Here's a paragraph.</p>
</body>
Run the app again and view the home page.
Well, that was a let down. We created this HTML, but our controller is still just returning a string
. We need to write the code that tells it to use our fancy view.
Returning a View from a Controller
Let's modify our initial test so that it expects our Controller to return a ViewResult
.
[Fact]
public void Index_Returns_A_View()
{
var underTest = new HomeController();
var result = underTest.Index();
Assert.IsType<ViewResult>(result);
}
If you have an error on ViewResult
its because your test project doesn't have any knowledge of the ViewResult class, which is part of the AspNetCore.Mvc namespace. Add the using Microsoft.AspNetCore.Mvc;
statement to fix the error.
Run the test and watch it fail. The test expected the result to be a ViewResult
type, but instead it returned a string.
Now, let's make the test pass. Open up the HomeController
and modify the Index
Action (method) so that it looks like so:
public ViewResult Index()
{
return View();
}
We modified the return type to be a ViewResult
, and we're no longer returning a string
literal...we're running the View()
method and returning the result.
The View
method looks for a .cshtml file in the following file folder location by default: Views\{Controller}\{Action Name}.cshtml
Since we are in the HomeController
, and more specifically the Index
Action, it will look for the following file: Views\Home\Index.cshtml
. That's right where we created the view.
Run the unit test again and it should pass.
Run the app and you should see your HTML page being rendered.
Success!
YOU DID IT!!! You have successfully created an ASP.NET MVC Core Web Application with a Home Controller and View.