What’s In This Chapter?
- Features of ASP.NET MVC 6
- Routing
- Creating Controllers
- Creating Views
- Validating User Inputs
- Using Filters
- Working with HTML and Tag Helpers
- Creating Data-Driven Web Applications
- Implementing Authentication and Authorization
Wrox.com Code Downloads for This Chapter
The wrox.com code downloads for this chapter are found at http://www.wrox.com/go/ professionalcsharp6 on the Download Code tab. The code for this chapter is divided into the following major examples:
- MVC Sample App
- Menu Planner
Setting Up Services for ASP.NET MVC 6
Chapter 40, “ASP.NET Core,” showed you the foundation of ASP.NET MVC: ASP.NET Core 1.0 Chapter 40 shows you middleware and how dependency injection works with ASP.NET. This chapter makes use of dependency injection by injecting ASP.NET MVC services.
ASP.NET MVC is based on the MVC (Model-View-Controller) pattern. As shown in Figure 41.1, this standard pattern (a pattern documented in Design Patterns: Elements of Reusable Object-Oriented Software book by the Gang of Four [Addison-Wesley Professional, 1994]) defines a model that implements data entities and data access, a view that represents the information shown to the user, and a controller that makes use of the model and sends data to the view. The controller receives a request from the browser and returns a response. To build the response, the controller can make use of a model to provide some data, and a view to define the HTML that is returned.
Figure 41.1
With ASP.NET MVC, the controller and model are typically created with C# and .NET code that is run server-side. The view is HTML code with JavaScript and just a little C# code for accessing server-side information.
The big advantage of this separation in the MVC pattern is that you can use unit tests to easily test the functionality. The controller just contains methods with parameters and return values that can be covered easily with unit tests.
Let’s start setting up services for ASP.NET MVC 6. With ASP.NET Core 1.0 dependency injection is deeply integrated as you’ve seen in Chapter 40. You can create an ASP.NET MVC 6 project selecting the ASP.NET Core 1.0 Template Web Application. This template already includes NuGet packages required with ASP.NET MVC 6, and a directory structure that helps with organizing the application. However, here we’ll start with the Empty template (similar to Chapter 40), so you can see what’s all needed to build up an ASP.NET MVC 6 project, without the extra stuff you might not need with your project.
The first project created is named MVCSampleApp. To use ASP.NET MVC with the web application MVCSampleApp, you need to add the NuGet package Microsoft.AspNet.Mvc. With the package in place, you add the MVC services by invoking the extension method AddMvc within the ConfigureServices method (code file MVCSampleApp/Startup.cs):
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
// etc.
namespace MVCSampleApp
{
public class Startup
{
// etc.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// etc. }
// etc.
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseDefaultConfiguration(args)
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
The AddMvc extension method adds and configures several ASP.NET MVC core services, such as configuration features (IConfigureOptions with MvcOptions and RouteOptions); controller factories and controller activators (IControllerFactory, IControllerActivator); action method selectors, invocators, and constraint providers (IActionSelector, IActionInvokerFactory, IActionConstraintProvider); argument binders and model validators (IControllerActionArgumentBinder, IObjectModelValidator); and filter providers (IFilterProvider).
In addition to the core services it adds, the AddMvc method adds ASP.NET MVC services to support authorization, CORS, data annotations, views, the Razor view engine, and more.
Defining Routes
Chapter 40 explains how the Map extension method of the IapplicationBuilder defines a simple route. This chapter shows how the ASP.NET MVC routes are based on this mapping to offer a flexible routing mechanism for mapping URLs to controllers and action methods. The controller is selected based on a route. A simple way to create the default route is to invoke the method UseMvcWithDefaultRoute in the Startup class (code file MVCSampleApp/Startup.cs):
public void Configure(IApplicationBuilder app)
{
// etc.
app.UseIISPlatformHandler();
app.UseStaticFiles();
app.UseMvcWithDefaultRoute();
// etc.
}
NOTE The extension method UseStaticFiles is discussed in Chapter 40. This method requires adding the Microsoft.AspNet.StaticFiles NuGet package.
With this default route, the name of the controller type (without the Controller suffix) and the method name make up the route, such as http://server[:port]/controller/action. You can also use an optional parameter named id, like so: http://server[:port]/controller/action/id. The default name of the controller is Home; the default name of the action method is Index.
The following code snippet shows another way to specify the same default route. The UseMvc method can receive a parameter of type Action<IRouteBuilder>. This IRouteBuilder interface contains a list of routes that are mapped. You define routes using the MapRoute extension method:
app.UseMvc(routes =< with => routes.MapRoute(
name:"default",
template:"{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
));
This route definition is the same as the default one. The template parameter defines the URL; the ? with the id defines that this parameter is optional; thedefaults parameter defines the default values for the controller and action part of the URL.
Let’s have a look at this URL: http://localhost:[port]/UseAService/GetSampleStrings With this URL, UseAService maps to the name of the controller, because the Controller suffix is automatically added; the type name is UseAServiceController; and GetSampleStrings is the action, which represents a method in the UseAServiceController type.
Adding Routes
There are several reasons to add or change routes. For example, you can modify routes to use actions with the link, to define Home as the default controller, to add entries to the link, or to use multiple parameters.
You can define a route where the user can use links—such as http://<server>/About to address the About action method in the Home controller without passing a controller name—as shown in the following snippet. Notice that the controller name is left out from the URL. The controller keyword is mandatory with the route, but you can supply it with the defaults:
app.UseMvc(routes => routes.MapRoute(
name:"default",
template:"{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
));
Another scenario for changing the route is shown in the following code snippet. In this snippet, you are adding the variable language to the route. This variable is set to the section within the URL that follows the server name and is placed before the controller—for example, http://server/en/Home/About. You can use this to specify a language:
app.UseMvc(routes => routes.MapRoute(
name:"default",
template:"{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
).MapRoute(
name:"language",
template:"{language}/{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
);
If one route matches and the controller and action method are found, the route is taken; otherwise the next route is selected until one route matches.
Using Route Constraints
When you map the route, you can specify constraints. This way, URLs other than those defined by the constraint are not possible. The following constraint defines that the language parameter can be only en or de by using the regular expression (en)|(de). URLs such as http://<server>/en/Home/About or http://<server>/de/Home/About are valid:
app.UseMvc(routes => routes.MapRoute(
name:"language",
template:"{language}/{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"},
constraints: new {language = @"(en)|(de)"}
));
If a link should enable only numbers (for example, to access products with a product number), the regular expression \d+ matches any number of numerical digits, but it must match at least one:
app.UseMvc(routes => routes.MapRoute(
name:"products",
template:"{controller}/{action}/{productId?}",
defaults: new {controller ="Home", action ="Index"},
constraints: new {productId = @"\d+"}
));
Now you’ve seen how routing specifies the controller that is used and the action of the controller. The next section, “Creating Controllers,” covers the details of controllers.
Creating Controllers
A controller reacts to requests from the user and sends a response. As described in this section, a view is not required.
There are some conventions for using ASP.NET MVC. Conventions are preferred over configuration. With controllers you’ll also see some conventions. You can find controllers in the directory Controllers, and the name of the controller class must be suffixed with the name Controller.
Before creating the first controller, create the Controllers directory. Then you can create a controller by selecting this directory in Solution Explorer, select Add???New Item from the context menu, and select the MVC Controller Class item template. The HomeController is created for the route that is specified.
The generated code contains a HomeController class that derives from the base class Controller. This class also contains an Index method that corresponds to the Index action. When you request an action as defined by the route, a method within the controller is invoked (code file MVCSampleApp/Controllers/HomeController.cs):
public class HomeController : Controller
{
public IActionResult Index() => View();
}
Understanding Action Methods
A controller contains action methods. A simple action method is the Hello method from the following code snippet (code file MVCSampleApp/Controllers/HomeController.cs):
public string Hello() =>"Hello, ASP.NET MVC 6";
You can invoke the Hello action in the Home controller with the link http://localhost:5000/Home/Hello. Of course, the port number depends on your settings, and you can configure it with the web properties in the project settings. When you open this link from the browser, the controller returns just the string Hello, ASP.NET MVC 6; no HTML—just a string. The browser displays the string.
An action can return anything—for example, the bytes of an image, a video, XML or JSON data, or, of course, HTML. Views are of great help for returning HTML.
Using Parameters
You can declare action methods with parameters, as in the following code snippet (code file MVCSampleApp/Controllers/HomeController.cs):
public string Greeting(string name) =>
HtmlEncoder.Default.Encode($"Hello, {name}");
NOTE The HtmlEncoder requires the NuGet package System.Text.Encodings.Web.
With this declaration, the Greeting action method can be invoked to request this URL to pass a value with the name parameter in the URL:http://localhost:18770/Home/Greeting?name=Stephanie.
To use links that can be better remembered, you can use route information to specify the parameters. The Greeting2 action method specifies the parameter named id.
public string Greeting2(string id) =>
HtmlEncoder.Default.Encode($"Hello, {id}");
This matches the default route {controller}/{action}/{id?} where id is specified as an optional parameter. Now you can use this link, and the id parameter contains the string Matthias: http://localhost:5000/Home/Greeting2/Matthias.
You can also declare action methods with any number of parameters. For example, you can add the Add action method to the Home controller with two parameters, like so:
public int Add(int x, int y) => x + y;
You can invoke this action with the URL http://localhost:18770/Home/Add?x=4&y=5 to fill the x and y parameters.
With multiple parameters, you can also define a route to pass the values with a different link. The following code snippet shows an additional route defined in the route table to specify multiple parameters that fill the variables x and y (code file MVCSampleApp/Startup.cs):
app.UseMvc(routes =< routes.MapRoute(
name:"default",
template:"{controller}/{action}/{id?}",
defaults: new {controller ="Home", action ="Index"}
).MapRoute(
name:"multipleparameters",
template:"{controller}/{action}/{x}/{y}",
defaults: new {controller ="Home", action ="Add"},
constraints: new {x = @"\d", y = @"\d"}
));
Now you can invoke the same action as before using this URL:http://localhost:18770/Home/Add/7/2.
NOTE Later in this chapter, in the section “Passing Data to Views,” you seehow parameters of custom types can be used and how data from the client can map to properties.
Returning Data
So far, you have returned only string values from the controller. Usually, an object implementing the interface IActionResult is returned.
Following are several examples with the ResultController class. The first code snippet uses the ContentResult class to return simple text content. Instead of creating an instance of the ContentResult class and returning the instance, you can use methods from the base class Controller to return ActionResults. In the following example, the method Content is used to return text content. The Content method enables specifying the content, the MIME type, and encoding (code file MVCSampleApp/Controllers/ResultController.cs):
public IActionResult ContentDemo() =>
Content("Hello World","text/plain");
To return JSON-formatted data, you can use the Json method. The following sample code creates a Menu object:
public IActionResult JsonDemo()
{
var m = new Menu
{
Id = ,
Text ="Grilled sausage with sauerkraut and potatoes",
Price = 12.90,
Date = new DateTime(, , ),
Category ="Main"
};
return Json(m);
}
The Menu class is defined within the Models directory and defines a simple POCO
class with a few properties (code file MVCSampleApp/Models/Menu.cs):
public class Menu
{
public int Id {get; set;}
public string Text {get; set;}
public double Price {get; set;}
public DateTime Date {get; set;}
public string Category {get; set;}
}
The client sees this JSON data in the response body. JSON data can easily be consumed as a JavaScript object:
{"Id":,"Text":"Grilled sausage with sauerkraut and potatoes",
"Price":12.9,"Date":"2016-03-31T00:00:00","Category":"Main"}
Using the Redirect method of the Controller class, the client receives an HTTP redirect request. After receiving the redirect request, the browser requests the link it received. The Redirect method returns a RedirectResult (code file MVCSampleApp/Controllers/ResultController.cs):
public IActionResult RedirectDemo() =>
Redirect("http://www.cninnovation.com");
You can also build a redirect request to the client by specifying a redirect to another controller and action. RedirectToRoute returns a RedirectToRouteResult that enables specifying route names, controllers, actions, and parameters. This builds a link that is returned to the client with an HTTP redirect request:
public IActionResult RedirectRouteDemo() =>
RedirectToRoute(new {controller ="Home", action="Hello"});
The File method of the Controller base class defines different overloads that return different types. This method can return FileContentResult, FileStreamResult, and VirtualFileResult. The different return types depend on the parameters used—for example, a string for a VirtualFileResult, a Stream for a FileStreamResult, and a byte array for a FileContentResult.
The next code snippet returns an image. Create an Images folder and add a JPG file. For the next code snippet to work, create an Images folder in the wwwroot directory and add the file Matthias.jpg. The sample code returns a VirtualFileResult that specifies a filename with the first parameter. The second parameter specifies the contentType argument with the MIME type image/jpeg:
public IActionResult FileDemo() =>
File("~/images/Matthias.jpg","image/jpeg");
The next section shows how to return different ViewResult variants.
Working with the Controller Base Class and POCO Controllers
So far, all the controllers created have been derived from the base class Controller. ASP.NET MVC 6 also supports controllers—known as known as POCO (Plain Old CLR Objects) controllers—that do not derive from this base class. This way you can use your own base class to define your own type hierarchy with controllers.
What do you get out of the Controller base class? With this base class, the controller can directly access properties of the base class. The following table describes these properties and their functionality.
Property | Description |
ActionContext |
This property wraps some other properties. Here you can get information about the action descriptor, which contains the name of the action, controller, filters, and method information; the HttpContext , which is directly accessible from the Context property; the state of the model that is directly accessible from the ModelState property, and route information that is directly accessible from the RouteData property. |
Context |
This property returns the HttpContext . With this context you can access the ServiceProvider to access services registered with dependency injection ( ApplicationServices property), authentication and user information, request and response information that is also directly accessible from the Request and Response properties, and web sockets (if they are in use). |
BindingContext |
With this property you can access the binder that binds the received data to the parameters of the action method. Binding request information to custom types is discussed later in this chapter in the section “Submitting Data from the Client.” |
MetadataProvider |
You use a binder to bind parameters. The binder can make use of metadata that is associated with the model. Using the MetadataProvider property, you can access information about what providers are configured to deal with metadata information. |
ModelState |
The ModelState property lets you know whether model binding was successful or had errors. In case of errors, you can read the information about what properties resulted in errors. |
Request |
With this property you can access all information about the HTTP request: header and body information, the query string, form data, and cookies. The header information contains a User-Agent string that gives information about the browser and client platform. |
Response |
This property holds information that is returned to the client. Here, you can send cookies, change header information, and write directly to the body. Earlier in this chapter, in the section Startup, you’ve seen how a simple string can be returned to the client by using the Response property. |
Resolver |
The Resolver property returns the ServiceProvider where you can access the services that are registered for dependency injection. |
RouteData |
The RouteData property gives information about the complete route table that is registered in the startup code. |
ViewBag |
You use these properties to send information to the view. This is explained later in the section “Passing Data to Views.” |
ViewData | |
TempData |
This property is written to the user state that is shared between multiple requests (whereas data written to ViewBag and ViewData can be written to share information between views and controllers within a single request). By default, TempData writes information to the session state. |
User |
The User property returns information, including identity and claims, about an authenticated user. |
A POCO controller doesn’t have the Controller base class, but it’s still important to access such information. The following code snippet defines a POCO controller that derives from the object base class (you can use your own custom type as a base class). To create an ActionContext with the POCO class, you can create a property of this type. The POCOController class uses ActionContext as the name of this property, similar to the way the Controller class does. However, just having a property doesn’t set it automatically. You need to apply the ActionContext attribute. Using this attribute injects the actual ActionContext. The Context property directly accesses the HttpContext property from the ActionContext. The Context property is used from the UserAgentInfo action method to access and return the User-Agent header information from the request (code file MVCSampleApp/Controllers/POCOController.cs):
public class POCOController
{
public string Index() =>
"this is a POCO controller";
[ActionContext]
public ActionContext ActionContext {get; set;}
public HttpContext Context => ActionContext.HttpContext;
public ModelStateDictionary ModelState => ActionContext.ModelState;
public string UserAgentInfo()
{
if (Context.Request.Headers.ContainsKey("User-Agent"))
{
return Context.Request.Headers["User-Agent"];
}
return"No user-agent information";
}
}
Creating Views
The HTML code that is returned to the client is best specified with a view. For the samples in this section, the ViewsDemoController is created. The views are all defined within the Views folder. The views for the ViewsDemo controller need a ViewsDemo subdirectory. This is a convention for the views (code file MVCSampleApp/Controllers/ViewsDemoController.cs):
public ActionResult Index() => View();
NOTE Another place where views are searched is the Shared directory. You can put views that should be used by multiple controllers (and special partial views used by multiple views) into the Shared directory.
After creating the ViewsDemo directory within the Views directory, the view can be created using Add???New Item and selecting the MVC View Page item template. Because the action method has the name Index, the view file is named Index.cshtml.
The action method Index uses the View method without parameters, and thus the view engine searches for a view file with the same name as the action name in the ViewsDemo directory. The View method used in the controller has overloads that enable passing a different view name. In that case, the view engine looks for a view with the name passed to the View method.
A view contains HTML code mixed with a little server-side code, as shown in the following snippet (code file MVCSampleApp/Views/ViewsDemo/Index.cshtml):
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index</title>
</head>
<body>
<div>
</div>
</body>
</html>
Server-side code is written using the @ sign, which starts the Razor syntax, which is discussed later in this chapter. Before getting into the details of the Razor syntax, the next section shows how to pass data from a controller to a view.
Passing Data to Views
The controller and view run in the same process. The view is directly created from within the controller. This makes it easy to pass data from the controller to the view. To pass data, you can use a ViewDataDictionary. This dictionary stores keys as strings and enables object values. You can use the ViewDataDictionary with the ViewData property of the Controller class—for example, you can pass a string to the dictionary where the key value MyData is used: ViewData[“MyData”] =“Hello”. An easier syntax uses the ViewBag property. ViewBag is a dynamic type that enables assigning any property name to pass data to the view (code file MVCSampleApp/Controllers/SubmitDataController.cs):
public IActionResult PassingData()
{
ViewBag.MyData ="Hello from the controller";
return View();
}
NOTE Using dynamic types has the advantage that there is no direct dependency from the view to the controller. Dynamic types are explained in detail in Chapter 16, “Reflection, Metadata, and Dynamic Programming.”
From within the view, you can access the data passed from the controller in a similar way as in the controller. The base class of the view (WebViewPage) defines a ViewBag property (code file MVCSampleApp/Views/ViewsDemo/PassingData.cshtml):
<div>
<div>@ViewBag.MyData</div>
</div>
Understanding Razor Syntax
As discussed earlier when you were introduced to views, the view contains both HTML and server-side code. With ASP.NET MVC you can use Razor syntax to write C# code in the view. Razor uses the @ character as a transition character. Starting with @, C# code begins.
With Razor you need to differentiate statements that return a value and methods that don’t. A value that is returned can be used directly. For example, ViewBag.MyData returns a string. The string is put directly between the HTML div tags as shown here:
<div>@ViewBag.MyData</div>
When you’re invoking methods that return void, or specifying some other statements that don’t return a value, you need a Razor code block. The following code block defines a string variable:
@{
string name ="Angela";
}
You can now use the variable with the simple syntax; you just use the transition character @ to access the variable:
<div>@name</div>
With the Razor syntax, the engine automatically detects the end of the C# code when it finds an HTML element. There are some cases in which the end of the C# code cannot be detected automatically. You can resolve this by using parentheses as shown in the following example to mark a variable, and then the normal text continues:
<div>@(name), Stephanie</div>
Another way to start a Razor code block is with the foreach statement:
@foreach(var item in list)
{
<li>The item name is @item.</li>
}
NOTE Usually text content is automatically detected with Razor—for example, Razor detects an opening angle bracket or parentheses with a variable. There are a few cases in which this does not work. Here, you can explicitly use @: to define the start of text.
Creating Strongly Typed Views
Passing data to views, you’ve seen the ViewBag in action. There’s another way to pass data to a view—pass a model to the view. Using models allows you to create strongly typed views.
The ViewsDemoController is now extended with the action method PassingAModel. The following example creates a new list of Menu items, and this list is passed as the model to the View method of the Controller base class (code file MVCSampleApp/Controllers/ViewsDemoController.cs):
public IActionResult PassingAModel()
{
var menus = new List<Menu>
{
new Menu
{
Id=,
Text="Schweinsbraten mit Knödel und Sauerkraut",
Price=6.9,
Category="Main"
},
new Menu
{
Id=,
Text="Erdäpfelgulasch mit Tofu und Gebäck",
Price=6.9,
Category="Vegetarian"
},
new Menu
{
Id=,
Text="Tiroler Bauerngröst'l mit Spiegelei und Krautsalat",
Price=6.9,
Category="Main"
}
};
return View(menus);
}
When model information is passed from the action method to the view, you can create a strongly typed view. A strongly typed view is declared using the model keyword. The type of the model passed to the view must match the declaration of the model directive. In the following code snippet, the strongly typed view declares the type IEnumerable<Menu>, which matches the model type. Because the Menu class is defined within the namespace MVCSampleApp.Models, this namespace is opened with the using keyword.
The base class of the view that is created from the .cshtml file derives from the base class RazorPage. With a model in place, the base class is of type RazorPage<TModel>; with the following code snippet the base class is RazorPage<IEnumerable<Menu>>. This generic parameter in turn defines a Model property of type IEnumerable<Menu>. With the code snippet, the Model property of the base class is used to iterate through the Menu items with @foreach and displays a list item for every menu (code file MVCSampleApp/ViewsDemo/PassingAModel.cshtml):
@using MVCSampleApp.Models
@model IEnumerable<Menu>
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>PassingAModel</title>
</head>
<body>
<div>
<ul>
@foreach (var item in Model)
{
<li>@item.Text</li>
}
</ul>
</div>
</body>
</html>
You can pass any object as the model—whatever you need with the view. For example, when you’re editing a single Menu object, you’d use a model of type Menu. When you’re showing or editing a list, you can use IEnumerable<Menu>.
When you run the application showing the defined view, you see a list of menus in the browser, as shown in Figure 41.2.
Figure 41.2
Defining the Layout
Usually many pages of web applications share some of the same content—for example, copyright information, a logo, and a main navigation structure. Until now, all your views have contained complete HTML content, but there’s an easier way to managed the shared content. This is where layout pages come into play.
To define a layout, you set the Layout property of the view. For defining default properties for all views, you can create a view start page. You need to put this file into the Views folder, and you can create it using the item template MVC View Start Page. This creates the file _ViewStart.cshtml (code file MVCSampleApp/Views/_ViewStart.cshtml):
@{
Layout ="_Layout";
}
For all views that don’t need a layout, you can set the Layout property to null:
@{
Layout = null;
}
Using a Default Layout Page
You can create the default layout page using the item template MVC View Layout Page. You can create this page in the Shared folder so that it is available for all views from different controllers. The item template MVC View Layout Page creates the following code:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>
The layout page contains the HTML content that is common to all pages (for example, header, footer, and navigation) that use this layout page. You’ve already seen how views and controllers can communicate with the ViewBag. The same mechanism can be used with the layout page. You can define the value for ViewBag.Title within a content page; from the layout page, it is shown in the preceding code snippet within the HTML title element. The RenderBody method of the base class RazorPage renders the content of the content page and thus defines the position in which the content should be placed.
With the following code snippet, the generated layout page is updated to reference a style sheet and to add header, footer, and navigation sections to every page. environment, asp-controller, and asp-action are Tag Helpers that create HTML elements. Tag Helpers are discussed later in this chapter in the “Helpers” section (code file MVCSampleApp/Views/Shared/_Layout.cshtml):
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<environment names="Development">
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment names="Staging,Production">
<link rel="stylesheet" href="~/css/site.min.css"
asp-append-version="true" />
</environment>
<title>@ViewBag.Title - My ASP.NET Application</title>
</head>
<body>
<div class="container">
<header>
<h1>ASP.NET MVC Sample App</h1>
</header>
<nav>
<ul>
<li><a asp-controller="ViewsDemo" asp-action="LayoutSample">
Layout Sample</a></li>
<li><a asp-controller="ViewsDemo" asp-action="LayoutUsingSections">
Layout Using Sections</a></li>
</ul>
</nav>
<div>
@RenderBody()
</div>
<hr />
<footer>
<p>
<div>Sample Code for Professional C#</div>
© @DateTime.Now.Year - My ASP.NET Application
</p>
</footer>
</div>
</body>
</html>
A view is created for the action LayoutSample (code file MVCSampleApp/Views/ViewsDemo/LayoutSample.cshtml). This view doesn’t set the Layout property and thus uses the default layout. The following code snippet sets ViewBag.Title, which is used within the HTML title element in the layout:
@{
ViewBag.Title ="Layout Sample";
}
<h2>LayoutSample</h2>
<p>
This content is merged with the layout page
</p>
When you run the application now, the content from the layout and the view is merged, as shown in Figure 41.3.
Figure 41.3
Using Sections
Rendering the body and using the ViewBag is not the only way to exchange data between the layout and the view. With section areas you can define where the named content should be placed within a view. The following code snippet makes use of a section named PageNavigation. Such sections are required by default, and loading the view fails if the section is not defined. When the required parameter is set to false, the section becomes optional (code file MVCSampleApp/Views/Shared/_Layout.cshtml):
<!-- etc. -->
<div>
@RenderSection("PageNavigation", required: false)
</div>
<div>
@RenderBody()
</div>
<!-- etc. -->
Within the view page, the section keyword defines the section. The position where the section is placed is completely independent from the other content. The view doesn’t define the position within the page; this is defined by the layout (code file MVCSampleApp/Views/ViewsDemo/LayoutUsingSections.cshtml):
@{
ViewBag.Title ="Layout Using Sections";
}
<h2>Layout Using Sections</h2>
Main content here
@section PageNavigation
{
<div>Navigation defined from the view</div>
<ul>
<li>Nav1</li>
<li>Nav2</li>
</ul>
}
When you run the application, the content from the view and the layout are merged according to the positions defined by the layout, as shown in Figure 41.4.
Figure 41.4
NOTE Sections aren’t used only to place some content within the body of an HTML page; they are also useful for allowing the view to place something in the head—for example, metadata from the page.
Defining Content with Partial Views
Whereas layouts give an overall definition for multiple pages from the web application, you can use partial views to define content within views. A partial view doesn’t have a layout.
Other than that, partial views are similar to normal views. Partial views use the same base class as normal views, and they have a model.
Following is an example of partial views. Here you start with a model that contains properties for independent collections, events, and menus as defined by the class EventsAndMenusContext (code file MVCSampleApp/Models/EventsAndMenusContext.cs):
public class EventsAndMenusContext
{
private IEnumerable<Event> events = null;
public IEnumerable<Event> Events
{
get
{
return events ?? (events = new List<Event>()
{
new Event
{
Id=,
Text="Formula 1 G.P. Australia, Melbourne",
Day=new DateTime(, , )
},
new Event
{
Id=,
Text="Formula 1 G.P. China, Shanghai",
Day = new DateTime(, , )
},
new Event
{
Id=,
Text="Formula 1 G.P. Bahrain, Sakhir",
Day = new DateTime(, , )
},
new Event
{
Id=,
Text="Formula 1 G.P. Russia, Socchi",
Day = new DateTime(, , )
}
});
}
}
private List<Menu> menus = null;
public IEnumerable<Menu> Menus
{
get
{
return menus ?? (menus = new List<Menu>()
{
new Menu
{
Id=,
Text="Baby Back Barbecue Ribs",
Price=16.9,
Category="Main"
},
new Menu
{
Id=,
Text="Chicken and Brown Rice Piaf",
Price=12.9,
Category="Main"
},
new Menu
{
Id=,
Text="Chicken Miso Soup with Shiitake Mushrooms",
Price=6.9,
Category="Soup"
}
});
}
}
}
The context class is registered with the dependency injection startup code to have the type injected with the controller constructor (code file MVCSampleApp/Startup.cs):
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddScoped<EventsAndMenusContext>();
}
This model will now be used with partial view samples in the following sections, a partial view that is loaded from server-side code, as well as a view that is requested using JavaScript code on the client.
Using Partial Views from Server-Side Code
In the ViewsDemoController class, the constructor is modified to inject the EventsAndMenusContext type (code file MVCSampleApp/Controllers/ViewsDemoController.cs):
public class ViewsDemoController : Controller
{
private EventsAndMenusContext _context;
public ViewsDemoController(EventsAndMenusContext context)
{
_context = context;
}
// etc.
The action method UseAPartialView1 passes an instance of EventsAndMenus to the view (code file MVCSampleApp/Controllers/ViewsDemoController.cs):
public IActionResult UseAPartialView1() => View(_context);
The view page is defined to use the model of type EventsAndMenusContext. You can show a partial view by using the HTML Helper method Html.PartialAsync. This method returns a Task<HtmlString>. With the sample code that follows, the string is written as content of the div element using the Razor syntax. The first parameter of the PartialAsync method accepts the name of the partial view. With the second parameter, the PartialAsync method enables passing a model. If no model is passed, the partial view has access to the same model as the view. Here, the view uses the model of type EventsAndMenusContext, and the partial view just uses a part of it with the type IEnumerable<Event> (code file MVCSampleApp/Views/ViewsDemo/UseAPartialView1.cshtml):
@model MVCSampleApp.Models.EventsAndMenusContext
@{
ViewBag.Title ="Use a Partial View";
ViewBag.EventsTitle ="Live Events";
}
<h2>Use a Partial View</h2>
<div>this is the main view</div>
<div>
@await Html.PartialAsync("ShowEvents", Model.Events)
</div>
Instead of using an async method, you can use the synchronous variant Html.Partial. This is an extension method that returns an HtmlString.
Another way to render a partial view within the view is to use the HTML Helper method Html.RenderPartialAsync, which is defined to return a Task. This method directly writes the partial view content to the response stream. This way, you can use RenderPartialAsync within a Razor code block.
You create the partial view similar to the way you create a normal view. You have access to the model and also to the dictionary that is accessed by using the ViewBag property. A partial view receives a copy of the dictionary to receive the same dictionary data that can be used (code file MVCSampleApp/Views/ViewsDemo/ShowEvents.cshtml):
@using MVCSampleApp.Models
@model IEnumerable<Event>
<h2>
@ViewBag.EventsTitle
</h2>
<table>
@foreach (var item in Model)
{
<tr>
<td>@item.Day.ToShortDateString()</td>
<td>@item.Text</td>
</tr>
}
</table>
When you run the application, the view, partial view, and layout are rendered, as shown in Figure 41.5.
Figure 41.5
Returning Partial Views from the Controller
So far the partial view has been loaded directly without the interaction with a controller, but you can also use controllers to return a partial view.
In the following code snippet, two action methods are defined within the class ViewsDemoController. The first action method UsePartialView2 returns a normal view; the second action method ShowEvents returns a partial view using the base class method PartialView. The partial view ShowEvents was already created and used previously, and it is used here. With the method PartialView a model containing the event list is passed to the partial view (code file MVCSampleApp/Controllers/ViewDemoController.cs):
public ActionResult UseAPartialView2() => View();
public ActionResult ShowEvents()
{
ViewBag.EventsTitle ="Live Events";
return PartialView(_context.Events);
}
When the partial view is offered from the controller, the partial view can be called directly from client-side code. The following code snippet makes use of jQuery: An event handler is linked to the click event of a button. Inside the event handler, a GET request is made to the server with the jQuery load function to request /ViewsDemo/ShowEvents. This request returns a partial view, and the result from the partial view is placed within the div element named events (code file MVCSampleApp/Views/ViewsDemo/UseAPartialView2.cshtml):
@model MVCSampleApp.Models.EventsAndMenusContext
@{
ViewBag.Title ="Use a Partial View";
}
<script src="~/lib/jquery/dist/jquery.js"></script>
<script>
$(function () {
$("#getEvents").click(function () {
$("#events").load("/ViewsDemo/ShowEvents");
});
});
</script>
<h2>Use a Partial View</h2>
<div>this is the main view</div>
<button id="FileName_getEvents">Get Events</button>
<div id="FileName_events">
</div>
Working with View Components
ASP.NET MVC 6 offers a new alternative to partial views: view components. View components are very similar to partial views; the main difference is that view components are not related to a controller. This makes it easy to use them with multiple controllers. Examples of where view components are really useful are dynamic navigation of menus, a login panel, or sidebar content in a blog. These scenarios are useful independent of a single controller.
Like controllers and views, view components have two parts. With view components the controller functionality is taken over by a class that derives from ViewComponent (or a POCO class with the attribute ViewComponent). The user interface is defined similarly to a view, but the method to invoke the view component is different.
The following code snippet defines a view component that derives from the base class ViewComponent. This class makes use of the EventsAndMenusContext type that was earlier registered in the Startup class to be available with dependency injection. This works similarly to the controllers with constructor injection. The InvokeAsync method is defined to be called from the view that shows the view component. This method can have any number and type of parameters, as the method defined by the IViewComponentHelper interface defines a flexible number of parameters using the params keyword. Instead of using an async method implementation, you can synchronously implement this method returning
IViewComponentResult instead of Task<IViewComponentResult>. However, typically the async variant is the best to use—for example, for accessing a database. The view component needs to be stored in a ViewComponents directory. This directory itself can be placed anywhere within the project (code file MVCSampleApp/ViewComponents/EventListViewComponent.cs):
public class EventListViewComponent : ViewComponent
{
private readonly EventsAndMenusContext _context;
public EventListViewComponent(EventsAndMenusContext context)
{
_context = context;
}
public Task<IViewComponentResult> InvokeAsync(DateTime from, DateTime to)
{
return Task.FromResult<IViewComponentResult>(
View(EventsByDateRange(from, to)));
}
private IEnumerable<Event> EventsByDateRange(DateTime from, DateTime to)
{
return _context.Events.Where(e => e.Day >= from && e.Day <= to);
}
}
The user interface for the view component is defined within the following code snippet. The view for the view component can be created with the item template MVC View Page; it uses the same Razor syntax. Specifically, it must be put into the Components/[viewcomponent] folder—for example, Components/EventList. For the view component to be available with all the controls, you need to create the Components folder in the Shared folder for the views. When you’re using a view component only from one specific controller, you can put it into the views controller folder instead. What’s different with this view, though, is that it needs to be named default.cshtml. You can create other view names as well; you need to specify these views using a parameter for the View method returned from the InvokeAsync method (code file MVCSampleApp/Views/Shared/Components/EventList/default.cshtml):
@using MVCSampleApp.Models;
@model IEnumerable<Event>
<h3>Formula 1 Calendar</h3>
<ul>
@foreach (var ev in Model)
{
<li><div>@ev.Day.ToString("D")</div><div>@ev.Text</div></li>
}
</ul>
Now as the view component is completed, you can show it by invoking the InvokeAsync method. Component is a dynamically created property of the view that returns an object implementing IViewComponentHelper. IviewComponentHelper allows you to invoke synchronous or asynchronous methods such as Invoke, InvokeAsync, RenderInvoke, and RenderInvokeAsync. Of course you can only invoke these methods that are implemented by the view component, and only use the parameters accordingly (code file MVCSampleApp/Views/ViewsDemo/UseViewComponent.cshtml):
@{
ViewBag.Title ="View Components Sample";
}
<h2>@ViewBag.Title</h2>
<p>
@await Component.InvokeAsync("EventList", new DateTime(2016, 4, 10),
new DateTime(2016, 4, 24))
</p>
Running the application, you can see the view component rendered as shown in Figure 41.6.
Figure 41.6
Using Dependency Injection in Views
In case a service is needed directly from within a view, you can inject it using the inject keyword:
@using MVCSampleApp.Services
@inject ISampleService sampleService
<p>
@string.Join("*", sampleService.GetSampleStrings())
</p>
When you do this, it’s a good idea to register services using the AddScoped method. As previously mentioned, registering a service that way means it’s only instantiated once for one HTTP request. Using AddScoped, injecting the same service within a controller and the view, it is only instantiated once for a request.
Importing Namespaces with Multiple Views
All the previous samples for views have used the using keyword to open all the namespaces needed. Instead of opening the namespaces with every view, you can use the Visual Studio item template MVC View Imports Page to create a file (_ViewImports.cshml) that defines all using declarations (code file MVCSampleApp/Views/_ViewImports.cshtml):
@using MVCSampleApp.Models
@using MVCSampleApp.Services
With this file in place, there’s no need to add all the using keywords to all the views.
Submitting Data from the Client
Until now you used only HTTP GET requests from the client to retrieve HTMLcode from the server. What about sending form data from the client?
To submit form data, you create the view CreateMenu for the controller SubmitData. This view contains an HTML form element that defines what data should be sent to the server. The form method is declared as an HTTP POST request. The input elements that define the input fields all have names that correspond to the properties of the Menu type (code file MVCSampleApp/Views/SubmitData/CreateMenu.cshtml):
@{
ViewBag.Title ="Create Menu";
}
<h2>Create Menu</h2>
<form action="/SubmitData/CreateMenu" method="post">
<fieldset>
<legend>Menu</legend>
<div>Id:</div>
<input name="id" />
<div>Text:</div>
<input name="text" />
<div>Price:</div>
<input name="price" />
<div>Category:</div>
<input name="category" />
<div></div>
<button type="submit">Submit</button>
</fieldset>
</form>
Figure 41.7 shows the opened page within the browser.
Figure 41.7
Within the SubmitData controller, two CreateMenu action methods are created: one
for an HTTP GET request and another for an HTTP POST request. Because C# has
different methods with the same name, it’s required that the parameter numbers
or types are different. Of course, this requirement is the same with action
methods. Action methods also need to differ with the HTTP request method. By
default, the request method is GET; when you apply the attribute HttpPost, the
request method is POST. For reading HTTP POST data, you could use information
from the Request object . However, it’s much simpler to define the CreateMenu
method with parameters. The parameters are matched with the name of the form
fields (code file MVCSampleApp/Controllers/SubmitDataController.cs):
public IActionResult Index() => View();
public IActionResult CreateMenu() => View();
[HttpPost]
public IActionResult CreateMenu(int id, string text, double price,
string category)
{
var m = new Menu { Id = id, Text = text, Price = price };
ViewBag.Info =
$"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}";
return View("Index");
}
To display the result, just the value of the ViewBag.Info is shown (code file MVCSampleApp/Views/SubmitData/Index.cshtml):
@ViewBag.Info
Model Binder
Instead of using multiple parameters with the action method, you can also use a type that contains properties that match the incoming field names (code file MVCSampleApp/Controllers/SubmitDataController.cs):
[HttpPost]
public IActionResult CreateMenu2(Menu m)
{
ViewBag.Info =
$"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}";
return View("Index");
}
When the user submits the data with the form, a CreateMenu method is invoked that shows the Index view with the submitted menu data, as shown in Figure 41.8.
Figure 41.8
A model binder is responsible for transferring the data from the HTTP POST request. A model binder implements the interface IModelBinder. By default the FormCollectionModelBinder class is used to bind the input fields to the model. This binder supports primitive types, model classes (such as the Menu type), and collections implementing ICollection<T>, IList<T>, and IDictionary<TKey, TValue>.
In case not all the properties of the parameter type should be filled from the model binder, you can use the Bind attribute. With this attribute you can specify a list of property names that should be included with the binding.
You can also pass the input data to the model using an action method without parameters, as demonstrated by the next code snippet. Here, a new instance of the Menu class is created, and this instance is passed to the TryUpdateModelAsync method of the Controller base class. TryUpdateModelAsync returns false if the updated model is not in a valid state after the update:
[HttpPost]
public async Task<IActionResult> CreateMenu3Result()
{
var m = new Menu();
bool updated = await TryUpdateModelAsync<Menu>(m);
if (updated)
{
ViewBag.Info =
$"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}";
return View("Index");
}
else
{
return View("Error");
}
}
Annotations and Validation
You can add some annotations to the model type; the annotations are used when updating the data for validation. The namespace System.ComponentModel.DataAnnotations contains attribute types that can be used to specify some information for data on the client and can be used for validation.
The Menu type is changed with these added attributes (code file MVCSampleApp/Models/Menu.cs):
public class Menu
{
public int Id { get; set; }
[Required, StringLength()]
public string Text { get; set; }
[Display(Name="Price"), DisplayFormat(DataFormatString="{0:C}")]
public double Price { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[StringLength()]
public string Category { get; set; }
}
Possible attribute types you can use for validation are CompareAttribute to compare different properties, CreditCardAttribute to verify a valid credit card number, EmailAddressAttribute to verify an e-mail address, EnumDataTypeAttribute to compare the input to enumeration values, and PhoneAttribute to verify a phone number.
You can also use other attributes to get values for display and error messages—for example, DataTypeAttribute and DisplayFormatAttribute.
To use the validation attributes, you can verify the state of the model using ModelState.IsValid within an action method as shown here (code file MVCSampleApp/Controllers/SubmitDataController.cs):
[HttpPost]
public IActionResult CreateMenu4(Menu m)
{
if (ModelState.IsValid)
{
ViewBag.Info =
$"menu created: {m.Text}, Price: {m.Price}, category: {m.Category}";
}
else
{
ViewBag.Info ="not valid";
}
return View("Index");
}
If you use tool-generated model classes, you might think it’s hard to add attributes to properties. As the tool-generated classes are defined as partial classes, you can extend the class by adding properties and methods, by implementing additional interfaces, and by implementing partial methods that are used by the tool- generated classes. You cannot add attributes to existing properties and methods if you can’t change the source code of the type, but there’s help for such scenarios! Assume the Menu class is a tool-generated partial class. Then a new class with a different name (for example, MenuMetadata) can define the same properties as the entity class and add the annotations, as shown here:
public class MenuMetadata
{
public int Id { get; set; }
[Required, StringLength()]
public string Text { get; set; }
[Display(Name="Price"), DisplayFormat(DataFormatString="{0:C}")]
public double Price { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[StringLength()]
public string Category { get; set; }
}
The MenuMetadata class must be linked to the Menu class. With tool-generated partial classes, you can create another partial type in the same namespace to add the MetadataType attribute to the type definition that creates the connection:
[MetadataType(typeof(MenuMetadata))]
public partial class Menu
{
}
HTML Helper methods can also make use of annotations to add information to the client.
Working with HTML Helpers
HTML Helpers are helpers that create HTML code. You can use them directly within the view using Razor syntax.
Html is a property of the view base class RazorPage and is of type IHtmlHelper. HTML Helper methods are implemented as extension methods to extend the IHtmlHelper interface.
The class InputExtensions defines HTML Helper methods to create check boxes,password controls, radio buttons, and text box controls. The Action and RenderAction helpers are defined by the class ChildActionExtensions. Helper methods for display are defined by the class DisplayExtensions. Helper methods for HTML forms are defined by the class FormExtensions.
The following sections get into some examples using HTML Helpers.
Using Simple Helpers
The following code snippet uses the HTML Helper methods BeginForm, Label, and CheckBox. BeginForm starts a form element. There’s also an EndForm for ending the form element. The sample makes use of the IDisposable interface implemented by the MvcForm returned from the BeginForm method. On disposing of the MvcForm, EndForm is invoked. This way the BeginForm method can be surrounded by a using statement to end the form at the closing curly brackets. The method DisplayName directly returns the content from the argument; the method CheckBox is an input element with the type attribute set to checkbox (code file MVCSampleApp/Views/HelperMethods/SimpleHelper.cshtml):
@using (Html.BeginForm()) {
@Html.DisplayName("Check this (or not)")
@Html.CheckBox("check1")
}
The resulting HTML code is shown in the next code snippet. The CheckBox method creates two input elements with the same name; one is set to hidden. There’s a good reason for this behavior: If a check box has a value of false, the browser does not pass this information to the server with the forms content. Only check box values of selected check boxes are passed to the server. This HTML characteristic creates a problem with automatic binding to the parameters of action methods. A simple solution is performed by the CheckBox helper method. This method creates a hidden input element with the same name that is set to false. If the check box is not selected, the hidden input element is passed to the server, and the false value can be bound. If the check box is selected, two input elements with the same name are sent to the server. The first input element is set to true; the second one is set to false. With automatic binding, only the first input element is selected to bind:
<form action="/HelperMethods/SimpleHelper" method="post">
Check this (or not)
<input id="FileName_check1" name="check1" type="checkbox" value="true" />
<input name="check1" type="hidden" value="false" />
</form>
Using Model Data
You can use helper methods with model data. This example creates a Menu object. This type was declared earlier in this chapter within the Models directory and passes a sample menu as a model to the view (code file MVCSampleApp/Controllers/HTMLHelpersController.cs):
public IActionResult HelperWithMenu() => View(GetSampleMenu());
private Menu GetSampleMenu() =>
new Menu
{
Id = ,
Text ="Schweinsbraten mit Knödel und Sauerkraut",
Price = 6.9,
Date = new DateTime(, , ),
Category ="Main"
};
The view has the model defined to be of type Menu. The DisplayName HTML Helper returns the text from the parameter, as shown with the previous sample. The Display method uses an expression as the parameter where a property name can be passed in the string format. This way this property tries to find a property with this name and accesses the property accessor to return the value of the property (code file MVCSampleApp/Views/HTMLHelpers/HelperWithMenu.cshtml):
@model MVCSampleApp.Models.Menu
@{
ViewBag.Title ="HelperWithMenu";
}
<h2>Helper with Menu</h2>
@Html.DisplayName("Text:")
@Html.Display("Text")
<br />
@Html.DisplayName("Category:")
@Html.Display("Category")
With the resulting HTML code, you can see this as output from calling the DisplayName and Display methods:
Text:
Schweinsbraten mit Knödel und Sauerkraut
<br />
Category:
Main
NOTE Helper methods also offer strongly typed variants to access members of the model. See the “Using Strongly Typed Helpers” section for more information.
Defining HTML Attributes
Most HTML Helper methods have overloads in which you can pass any HTML attributes. For example, the following TextBox method creates an input element of type text. The first parameter defines the name; the second parameter defines the value that is set with the text box. The third parameter of the TextBox method is of type object that enables passing an anonymous type where every property is changed to an attribute of the HTML element. Here, the result of the input element has the required attribute set to required, the maxlength attribute to 15, and the class attribute to CSSDemo. Because class is a C# keyword, it cannot be directly set as a property. Instead it is prefixed with @ to generate the class attribute for CSS styling:
@Html.TextBox("text1","input text here",
new { required="required", maxlength=15, @class="CSSDemo" });
The resulting HTML output is shown here:
<input class="Test" id="FileName_text1" maxlength="15" name="text1"
required="required"
type="text" value="input text here" />
Creating Lists
For displaying lists, helper methods such as DropDownList and ListBox exist. These methods create the HTML select element.
Within the controller, first a dictionary is created that contains keys and values. The dictionary is then converted to a list of SelectListItem with the custom extension method ToSelectListItems. The DropDownList and ListBox methods make use of SelectListItem collections (code file MVCSampleApp/Controllers/HTMLHelpersController.cs):
public IActionResult HelperList()
{
var cars = new Dictionary<int, string>();
cars.Add(,"Red Bull Racing");
cars.Add(,"McLaren");
cars.Add(,"Mercedes");
cars.Add(,"Ferrari");
return View(cars.ToSelectListItems());
}
The custom extension method ToSelectListItems is defined within the class SelectListItemsExtensions that extends IDictionary<int, string>, the type from the cars collection. Within the implementation, a new SelectListItem object is returned for every item in the dictionary (code file MVCSampleApp/Extensions/SelectListItemsExtensions.cs):
public static class SelectListItemsExtensions
{
public static IEnumerable<SelectListItem> ToSelectListItems(
this IDictionary<int, string> dict, int selectedId)
{
return dict.Select(item =>
new SelectListItem
{
Selected = item.Key == selectedId,
Text = item.Value,
Value = item.Key.ToString()
});
}
}
With the view, the helper method DropDownList directly accesses the Model that is returned from the controller (code file MVCSampleApp/Views/HTMLHelpers/HelperList.cshtml):
@{
ViewBag.Title ="Helper List";
}
@model IEnumerable<SelectListItem>
<h2>Helper2</h2>
@Html.DropDownList("carslist", Model)
The resulting HTML creates a select element with option child elements as created from the SelectListItem and defines the selected item as returned from the controller:
<select id="FileName_carslist" name="carslist">
<option value="1">Red Bull Racing</option>
<option value="2">McLaren</option>
<option value="3">Mercedes</option>
<option selected="selected" value="4">Ferrari</option>
</select>
Using Strongly Typed Helpers
The HTML Helper methods offer strongly typed methods to access the model passed from the controller. These methods are all suffixed with the name For. For example, instead of the TextBox method, here the TextBoxFor method can be used.
The next sample again makes use of a controller that returns a single entity (code file MVCSampleApp/Controllers/HTMLHelpersController.cs):
public IActionResult StronglyTypedMenu() => View(GetSampleMenu());
The view uses the Menu type as a model; thus the methods DisplayNameFor and DisplayFor can directly access the Menu properties. By default, DisplayNameFor returns the name of the property (in this example, it’s the Text property), and DisplayFor returns the value of the property (code file MVCSampleApp/Views/HTMLHelpers/StronglyTypedMenu.cshtml):
@model MVCSampleApp.Models.Menu
@Html.DisplayNameFor(m => m.Text)
<br />
@Html.DisplayFor(m => m.Text)
Similarly, you can use Html.TextBoxFor(m => m.Text), which returns an input element that enables setting the Text property of the model. This method also makes use of the annotations added to the Text property of the Menu type. The Text property has the Required and MaxStringLength attributes added, which is why the data-val-length, data-val-length-max, and data-val-required attributes are returned from the TextBoxFor method:
<input data-val="true"
data-val-length="The field Text must be a string with a maximum length of
50."
data-val-length-max="50"
data-val-required="The Text field is required."
id="FileName_Text" name="Text"
type="text"
value="Schweinsbraten mit Knödel und Sauerkraut" />
Working with Editor Extensions
Instead of using at least one helper method for every property, helper methods from the class EditorExtensions offer an editor for all the properties of a type.
Using the same Menu model as before, with the method Html.EditorFor(m => m) the complete user interface (UI) for editing the menu is built. The result from this method invocation is shown in Figure 41.9.
Figure 41.9
Instead of using Html.EditorFor(m => m), you can use Html.EditorForModel. The method EditorForModel makes use of the model of the view without the need to specify it explicitly. EditorFor has more flexibility in using other data sources (for example, properties offered by the model), and EditorForModel needs fewer parameters to add.
Implementing Templates
A great way to extend the outcome from HTML Helpers is by using templates. A template is a simple view used—either implicitly or explicitly—by the HTML Helper methods. Templates are stored within special folders. Display templates are stored within the DisplayTemplates folder that is in the view folder (for example, Views/HelperMethods) or in a shared folder (Shared/DisplayTemplates). The shared folder is used by all views; the specific view folder is used only by views within this folder. Editor templates are stored in the folder EditorTemplates.
Now have a look at an example. With the Menu type, the Date property has the annotation DataType with a value of DataType.Date. When you specify this attribute, the DateTime type by default does not show as date and time; it shows only with the short date format (code file MVCSampleApp/Models/Menu.cs):
public class Menu
{
public int Id { get; set; }
[Required, StringLength()]
public string Text { get; set; }
[Display(Name="Price"), DisplayFormat(DataFormatString="{0:c}")]
public double Price { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[StringLength()]
public string Category { get; set; }
}
Now the template for the date is created. With this template, the Model is returned using a long date string format D, which is embedded within a div tag that has the CSS class markRed (code file MVCSampleApp/Views/HTMLHelpers/DisplayTemplates/Date.cshtml):
<div class="markRed">
@string.Format("{0:D}", Model)
</div>
The markRed CSS class is defined within the style sheet to set the color red (code file MVCSampleApp/wwwroot/styles/Site.css):
.markRed {
color: #f00;
}
Now a display HTML Helper such as DisplayForModel can be used to make use of the defined template. The model is of type Menu, so the DisplayForModel method displays all properties of the Menu type. For the Date it finds the template Date.cshtml, so this template is used to display the date in long date format with the CSS style (code file MVCSampleApp/Views/HTMLHelpers/Display.cshtml):
@model MVCSampleApp.Models.Menu
@{
ViewBag.Title ="Display";
}
<h2>@ViewBag.Title</h2>
@Html.DisplayForModel()
If a single type should have different presentations in the same view, you can use other names for the template file. Then you can use the attribute UIHint to specify the template name, or you can specify the template with the template parameter of the helper method.
Getting to Know Tag Helpers
ASP.NET MVC 6 offers a new technology that can be used instead of HTML Helpers: Tag Helpers. With Tag Helpers you don’t write C# code mixed with HTML; instead you use HTML attributes and elements that are resolved on the server. Nowadays many JavaScript libraries extend HTML with their own attributes (such as Angular), so it’s very convenient to be able to do use custom HTML attributes with server-side technology. Many of the ASP.NET MVC Tag Helpers have the prefix asp-, so you can easily see what’s resolved on the server. These attributes are not sent to the client but instead are resolved on the server to
generate HTML code.
Activating Tag Helpers
To use the ASP.NET MVC Tag Helpers, you need to activate the tags by calling addTagHelper. The first parameter defines the types to use (a * opens all Tag Helpers of the assembly); the second parameter defines the assembly of the Tag Helpers. With removeTagHelper, the Tag Helpers are deactivated again. Deactivating Tag Helpers might be important—for example, to not get into naming conflicts with scripting libraries. You’re most likely not getting into a
conflict using the built-in Tag Helpers with the asp- prefix, but conflicts can easily happen with other Tag Helpers that can have the same names as other Tag Helpers or HTML attributes used with scripting libraries.
To have the Tag Helpers available with all views, add the addTagHelper statement to the shared file _ViewImports.cshtml (code file MVCSampleApp/Views/_ViewImports.cshtml):
@addTagHelper *, Microsoft.AspNet.Mvc.TagHelpers
Using Anchor Tag Helpers
Let’s start with Tag Helpers that extend the anchor a element. The sample controller for the Tag Helpers is TagHelpersController. The Index action method returns a view for showing the anchor Tag Helpers (code file MVCSampleApp/Controllers/TagHelpersController.cs):
public class TagHelpersController : Controller
{
public IActionResult Index() => View();
// etc.
}
The anchor Tag Helper defines the asp-controller and asp-action attributes. With these, the controller and action methods are used to build up the URL for the anchor element. With the second and third examples, the controller is not needed because it’s the same controller the view is coming from (code file MVCSampleApp/Views/TagHelpers/Index.cshtml):
<a asp-controller="Home" asp-action="Index">Home</a>
<br />
<a asp-action="LabelHelper">Label Tag Helper</a>
<br />
<a asp-action="InputTypeHelper">Input Type Tag Helper</a>
The following snippet shows the resulting HTML code. The asp-controller and asp-action attributes generate an href attribute for the a element. With the first sample to access the Index action method in the Home controller, as both are defaults as defined by the route, an href to / is all that’s needed in the result. When you specify the asp-action LabelHelper, the href directs to /TagHelpers/LabelHelper, the action method LabelHelper in the current controller:
<a href="/">Home</a>
<br />
<a href="/TagHelpers/LabelHelper">Label Tag Helper</a>
<br />
<a href="/TagHelpers/InputTypeHelper">Input Type Tag Helper</a>
Using Label Tag Helpers
In the following code snippet, which demonstrates the features of the label Tag Helper, the action method LabelHelper passes a Menu object to the view (code file MVCSampleApp/Controllers/TagHelpersController.cs):
public IActionResult LabelHelper() => View(GetSampleMenu());
private Menu GetSampleMenu() =>
new Menu
{
Id = ,
Text ="Schweinsbraten mit Knödel und Sauerkraut",
Price = 6.9,
Date = new DateTime(, , ),
Category ="Main"
};
}
The Menu class has some data annotations applied to influence the outcome of the Tag Helpers. Have a look at the Display attribute for the Text property. It sets the Name property of the Display attribute to “Menu” (code file MVCSampleApp/Models/Menu.cs):
public class Menu
{
public int Id { get; set; }
[Required, StringLength()]
[Display(Name ="Menu")]
public string Text { get; set; } [Display(Name ="Price"), DisplayFormat(DataFormatString ="{0:C}")]
public double Price { get; set; }
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[StringLength()]
public string Category { get; set; }
}
The view makes use of asp-for attributes applied to label controls. The value that is used for this attribute is a property of the model of the view. With Visual Studio 2015, you can use IntelliSense for accessing the Text, Price, and Date properties (code file MVCSampleApp/Views/TagHelpers/LabelHelper.cshtml):
@model MVCSampleApp.Models.Menu
@{
ViewBag.Title ="Label Tag Helper";
}
<h2>@ViewBag.Title</h2>
<label asp-for="Text"></label>
<br/>
<label asp-for="Price"></label>
<br />
<label asp-for="Date"></label>
With the generated HTML code, you can see the for attribute, which references elements with the same name as the property names and the content that is either the name of the property or the value of the Display attribute. You can use this attribute also to localize values:
<label for="Text">Menu</label>
<br/>
<label for="Price">Price</label>
<br />
<label for="Date">Date</label>
Using Input Tag Helpers
An HTML label typically is associated with an input element. The following code snippet gives you a look at what’s generated using input elements with Tag Helpers:
<label asp-for="Text"></label>
<input asp-for="Text"/>
<br/>
<label asp-for="Price"></label>
<input asp-for="Price" />
<br />
<label asp-for="Date"></label>
<input asp-for="Date" />
Checking the result of the generated HTML code reveals that the input type Tag Helpers create a type attribute depending on the type of the property, and they also apply the DateType attribute. The property Price is of type double, which results in a number input type. Because the Date property has the DataType with a value of DataType.Date applied, the input type is a date. In addition to that you can see data-val-length, data-val-length-max, and data-val-required attributes that are created because of annotations:
<label for="Text">Menu</label>
<input type="text" data-val="true"
data-val-length=
"The field Menu must be a string with a maximum length of 50."
data-val-length-max="50"
data-val-required="The Menu field is required."
id="FileName_Text" name="Text"
value="Schweinsbraten mit Knödel und Sauerkraut" />
<br/>
<label for="Price">Price</label>
<input type="number" data-val="true"
data-val-required="The Price field is required."
id="FileName_Price" name="Price" value="6.9" />
<br />
<label for="Date">Date</label>
<input type="date" data-val="true"
data-val-required="The Date field is required."
id="FileName_Date" name="Date" value="10/5/2016" />
Modern browsers have a special look for HTML 5 input controls such as date control. The input date control of Microsoft Edge is shown in Figure 41.10.
Figure 41.10
Using a Form with Validation
For sending data to the server, the input fields need to be surrounded by a form. A Tag Helper for the form defines the action attribute by using asp-method and asp-controller. With input controls, you’ve seen that validation information is defined by these controls. The validation errors need to be displayed. For display, the validation message Tag Helper extends the span element with asp-validation-for (code file MVCSampleApp/Views/TagHelpers/FormHelper.cs):
<form method="post" asp-method="FormHelper">
<input asp-for="Id" hidden="hidden" />
<hr />
<label asp-for="Text"></label>
<div>
<input asp-for="Text" />
<span asp-validation-for="Text"></span>
</div>
<br />
<label asp-for="Price"></label>
<div>
<input asp-for="Price" />
<span asp-validation-for="Price"></span>
</div>
<br /> <label asp-for="Date"></label>
<div>
<input asp-for="Date" />
<span asp-validation-for="Date"></span>
</div>
<label asp-for="Category"></label>
<div>
<input asp-for="Category" />
<span asp-validation-for="Category"></span>
</div>
<input type="submit" value="Submit" />
</form>
The controller verifies whether the receive data is correct by checking the ModelState. In case it’s not correct, the same view is displayed again (code file MVCSampleApp/Controllers/TagHelpersController.cs):
public IActionResult FormHelper() => View(GetSampleMenu());
[HttpPost]
public IActionResult FormHelper(Menu m)
{
if (!ModelState.IsValid)
{
return View(m);
}
return View("ValidationHelperResult", m);
}
When you run the application, you can see error information like that shown in Figure 41.11.
Figure 41.11
Creating Custom Tag Helpers
Aside from using the predefined Tag Helpers, you can create a custom Tag Helper. The sample custom Tag Helper you build in this section extends the HTML table element to show a row for every item in a list and a column for every property.
The controller implements the method CustomHelper to return a list of Menu objects (code file MVCSampleApp/Controllers/TagHelpersController.cs):
public IActionResult CustomHelper() => View(GetSampleMenus());
private IList<Menu> GetSampleMenus() =>
new List<Menu>()
{
new Menu
{
Id = ,
Text ="Schweinsbraten mit Knödel und Sauerkraut",
Price = 8.5,
Date = new DateTime(, , ),
Category ="Main" },
new Menu
{
Id = ,
Text ="Erdäpfelgulasch mit Tofu und Gebäck",
Price = 8.5,
Date = new DateTime(, , ),
Category ="Vegetarian"
},
new Menu
{
Id = ,
Text ="Tiroler Bauerngröst'l mit Spiegelei und Krautsalat",
Price = 8.5,
Date = new DateTime(, , ),
Category ="Vegetarian"
}
};
Now step into the Tag Helper. The custom implementation needs these namespaces:
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
A custom Tag Helper derives from the base class TagHelper. The attribute TargetElement defines what HTML elements are extended by the Tag Helper. This Tag Helper extends the table element; thus the string “table” is passed to the constructor of the element. With the property Attributes, you can define a list of attributes that are assigned to the HTML element that are used by the Tag Helper. This Tag Helper makes use of the items attribute. You can use the Tag Helper with this syntax: <table items=“Model”></table>, where Model needs to be a list that can be iterated. In case you’re creating a Tag Helper that should be used with multiple HTML elements, you just need to apply the attribute TargetElement multiple times. To automatically assign the value of the items attribute to the Items property, the attribute HtmlAttributeName is assigned to this property (code file MVCSampleApp/Extensions/TableTagHelper.cs):
[TargetElement("table", Attributes = ItemsAttributeName)]
public class TableTagHelper : TagHelper
{
private const string ItemsAttributeName ="items";
[HtmlAttributeName(ItemsAttributeName)]
public IEnumerable<object> Items { get; set; }
// etc.
}
The heart of the Tag Helper is in the method Process. This method needs to create HTML code that is returned from the helper. With the parameters of the Process method you receive a TagHelperContext This context contains both the attributes of the HTML element where the Tag Helper is applied and all child elements. With the table element, rows and columns could already have been defined, and you could merge the result with the existing content. In the sample, this is ignored, and just the attributes are taken to put them in the result. The result needs to be written to the second parameter: the TagHelperOutput object. For creating HTML code, the TagBuilder type is used. The TagBuilder helps create HTML elements with attributes, and it deals with closing of elements. To add attributes to the TagBuilder, you use the method MergeAttributes. This method requires a dictionary of all attribute names and their values. This dictionary is created by using the LINQ extension method ToDictionary. With the Where method, all of the existing attributes—with the exception of the items attribute—of the table element are taken. The items attribute is used for defining items with the Tag Helper but is not needed later on by the client:
public override void Process(TagHelperContext context, TagHelperOutput output)
{
TagBuilder table = new TagBuilder("table");
table.GenerateId(context.UniqueId,"id");
var attributes = context.AllAttributes
.Where(a => a.Name != ItemsAttributeName).ToDictionary(a => a.Name);
table.MergeAttributes(attributes);
// etc.
}
NOTE In case you need to invoke asynchronous methods within the Tag Helper implementation, you can override the ProcessAsync method instead of the Process method.
NOTE LINQ is explained in Chapter 13, “Language Integrated Query.”
Next create the first row in the table. This row contains a tr element as a child of the table element, and it contains td elements for every property. To get all the property names, you invoke the First method to retrieve the first object of the collection. You access the properties of this instance using reflection, invoking the GetProperties method on the Type object, and writing the name of the property to the inner text of the th HTML element:
// etc.
var tr = new TagBuilder("tr");
var heading = Items.First();
PropertyInfo[] properties = heading.GetType().GetProperties();
foreach (var prop in properties)
{
var th = new TagBuilder("th");
th.InnerHtml.Append(prop.Name);
th.InnerHtml.AppendHtml(th);
}
table.InnerHtml.AppendHtml(tr);
// etc.
NOTE Reflection is explained in Chapter 16.
The final part of the Process method iterates through all items of the collection and creates more rows (tr) for every item. With every property, a td element is added, and the value of the property is written as inner text. Last, the inner HTML code of the created table element is written to the output:
foreach (var item in Items)
{
tr = new TagBuilder("tr");
foreach (var prop in properties)
{
var td = new TagBuilder("td");
td.InnerHtml.Append(prop.GetValue(item).ToString());
td.InnerHtml.AppendHtml(td);
}
table.InnerHtml.AppendHtml(tr);
}
output.Content.Append(table.InnerHtml);
After you’ve created the Tag Helper, creating the view becomes very simple. After you’ve defined the model, you reference the Tag Helper with addTagHelper passing the assembly name. The Tag Helper itself is instantiated when you define an HTML table with the attribute items (code file MVCSampleApp/Views/TagHelpers/CustomHelper.cshtml):
@model IEnumerable<Menu>
@addTagHelper"*, MVCSampleApp"
<table items="Model" class="sample"></table>
When you run the application, the table you see should look like the one shown in
Figure 41.12. After you’ve created the Tag Helper, it is really easy to use. All the
formatting that is defined using CSS still applies as all the attributes of the defined
HTML table are still in the resulting HTML output.
Figure 41.12
Implementing Action Filters
ASP.NET MVC is extensible in many areas. For example, you can implement a controller factory to search and instantiate a controller (interface IControllerFactory). Controllers implement the IController interface. Finding action methods in a controller is resolved by using the IActionInvoker interface. You can use attribute classes derived from ActionMethodSelectorAttribute to define the HTTP methods allowed. The model binder that maps the HTTP request to parameters can be customized by implementing the IModelBinder interface. The section “Model Binder” uses the FormCollectionModelBinder type. You can use different view engines that implement the interface IViewEngine. This chapter uses the Razor view engine. You can also customize by using HTML Helpers, Tag Helpers, and action filters. Most of the extension points are out of the scope of this book, but action filters are likely ones that you will implement or use, and thus these are covered here.
Action filters are called before and after an action is executed. They are assigned to controllers or action methods of controllers using attributes. Action filters are implemented by creating a class that derives from the base class ActionFilterAttribute. With this class, the base class members OnActionExecuting, OnActionExecuted, OnResultExecuting, and OnResultExecuted can be overridden. OnActionExecuting is called before the action method is invoked, and OnActionExecuted is called when the action method is completed. After that, before the result is returned, the method OnResultExecuting is invoked, and finally OnResultExecuted is invoked.
Within these methods, you can access the Request object to retrieve information of the caller. Using the Request object you can decide some actions depending on the browser, you can access routing information, you can change the view result dynamically, and so on. The code snippet accesses the variable language from routing information. To add this variable to the route, you can change the route as described earlier in this chapter in the section “Defining Routes.” By adding a language variable with the route information, you can access the value supplied with the URL using RouteData.Values as shown in the following code snippet. You can use the retrieved value to change the culture for the user:
public class LanguageAttribute : ActionFilterAttribute
{
private string _language = null;
public override void OnActionExecuting(ActionExecutingContext
filterContext)
{
_language = filterContext.RouteData.Values["language"] == null ?
null : filterContext.RouteData.Values["language"].ToString();
//…
}
public override void OnResultExecuting(ResultExecutingContext filterContext)
{
}
}
NOTE Globalization and localization, setting cultures, and other regional specifics are explained in Chapter 28, “Localization.”
With the created action filter attribute class, you can apply the attribute to a controller as shown in the following code snippet. Using the attribute with the class, the members of the attribute class are invoked with every action method. Instead, you can also apply the attribute to an action method, so the members are invoked only when the action method is called:
[Language]
public class HomeController : Controller
{
The ActionFilterAttribute implements several interfaces: IActionFilter,
IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IFilter, and
IOrderedFilter.
ASP.NET MVC includes some predefined action filters, such as a filter to require HTTPS, authorize callers, handle errors, or cache data.
Using the attribute Authorize is covered later in this chapter in the section “Authentication and Authorization.”
Creating a Data-Driven Application
Now that you’ve read about all the foundations of ASP.NET MVC, it’s time to look into a data-driven application that uses the ADO.NET Entity Framework. Here you can see features offered by ASP.NET MVC in combination with data access.
NOTE The ADO.NET Entity Framework is covered in detail in Chapter 38, “Entity Framework Core.”
The sample application MenuPlanner is used to maintain restaurant menu entries in a database. Only an authenticated account may perform maintenance of the database entries. Browsing menus should be possible for non-authenticated users.
This project is started by using the ASP.NET Core 1.0 Web Application template. For the authentication, you use the default selection of Individual User Accounts. This project template adds several folders for ASP.NET MVC and controllers, including a HomeController and AccountController. It also adds several script libraries.
Defining a Model
Start by defining a model within the Models directory. You create the model using the ADO.NET Entity Framework. The MenuCard type defines some properties and a relation to a list of menus (code file MenuPlanner/Models/MenuCard.cs):
public class MenuCard
{
public int Id { get; set; }
[MaxLength()]
public string Name { get; set; }
public bool Active { get; set; }
public int Order { get; set; }
public virtual List<Menu> Menus { get; set; }
}
The menu types that are referenced from the MenuCard are defined by the Menu class (code file MenuPlanner/Models/Menu.cs):
public class Menu
{
public int Id { get; set; }
public string Text { get; set; }
public decimal Price { get; set; }
public bool Active { get; set; }
public int Order { get; set; }
public string Type { get; set; }
public DateTime Day { get; set; }
public int MenuCardId { get; set; }
public virtual MenuCard MenuCard { get; set; }
}
The connection to the database, and the sets of both Menu and MenuCard types, are managed by MenuCardsContext. Using ModelBuilder, the context specifies that the Text property of the Menu type may not be null, and it has a maximum length of 50 (code file MenuPlanner/Models/MenuCardsContext.cs):
public class MenuCardsContext : DbContext
{
public DbSet<Menu> Menus { get; set; }
public DbSet<MenuCard> MenuCards { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Menu>().Property(p => p.Text)
.HasMaxLength().IsRequired();
base.OnModelCreating(modelBuilder);
}
}
The startup code for the web application defines MenuCardsContext to be used as data context, and reads the connection string from the configuration file (code file MenuPlanner/Startup.cs):
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
// Add Entity Framework services to the services container.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]))
.AddDbContext<MenuCardsContext>(options =>
options.UseSqlServer(
Configuration["Data:MenuCardConnection:ConnectionString"]));
// etc.
}
With the configuration file, the MenuCardConnection connection string is added.
This connection string references the SQL instance that comes with Visual Studio
2015. Of course, you can change this and also add a connection string to SQL
Azure (code file MenuPlanner/appsettings.json):
{
"Data": {
"DefaultConnection": {
"ConnectionString":"Server=(localdb)\\mssqllocaldb;
Database=aspnet5-MenuPlanner-4d3d9092-b53f---f360ef6b2aa8;
Trusted_Connection=True;MultipleActiveResultSets=true"
},
"MenuCardConnection": {
"ConnectionString":"Server=
(localdb)\\mssqllocaldb;Database=MenuCards; Trusted_Connection=True;MultipleActiveResultSets=true"
}
},
// etc.
}
Creating a Database
You can use Entity Framework commands to create the code to create the database. With a command-line prompt, you use the .NET Core Command Line (CLI) and the ef command to create code to create the database automatically. Using the command prompt, you must set the current folder to the directory where the project.json file is located:
>dotnet ef migrations add InitMenuCards --context MenuCardsContext
NOTE The dotnet tools are discussed in Chapter 1, “.NET Application Architectures”, and Chapter 17, “Visual Studio 2015.”
Because multiple data contexts (the MenuCardsContext and the ApplicationDbContext) are defined with the project, you need to specify the data context with the ––context option. The ef command creates a Migrations folder within the project structure and the InitMenuCards class with an Up method to create the database tables, and the Down method to delete the changes again (code file MenuPlanner/Migrations/[date]InitMenuCards.cs):
public partial class InitMenuCards : Migration
{
public override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name:"MenuCard",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy",
SqlServerValueGenerationStrategy.IdentityColumn),
Active = table.Column<bool>(nullable: false),
Name = table.Column<string>(nullable: true),
Order = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_MenuCard", x => x.Id);
});
migrationBuilder.CreateTable(
name:"Menu",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Active = table.Column<bool>(nullable: false),
Day = table.Column<DateTime>(nullable: false),
MenuCardId = table.Column<int>(nullable: false),
Order = table.Column<int>(nullable: false),
Price = table.Column<decimal>(nullable: false),
Text = table.Column<string>(nullable: false),
Type = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Menu", x => x.Id);
table.ForeignKey(
name:"FK_Menu_MenuCard_MenuCardId",
column: x => x.MenuCardId,
principalTable:"MenuCard",
principalColumn:"Id",
onDelete: RefeerentialAction.Cascade);
});
}
public override void Down(MigrationBuilder migration)
{
migration.DropTable("Menu");
migration.DropTable("MenuCard");
}
}
Now you just need some code to start the migration process, filling the database with initial sample data. The MenuCardDatabaseInitializer applies the migration process by invoking the extension method MigrateAsync on the DatabaseFacade object that is returned from the Database property. This in turn checks whether the database associated with the connection string already has the same version as the database specified with the migrations. If it doesn’t have the same version, required Up methods are invoked to get to the same version. In addition to that, a few MenuCard objects are created to have them stored in the database (code file MenuPlanner/Models/MenuCardDatabaseInitializer.cs):
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System.Threading.Tasks;
namespace MenuPlanner.Models
{
public class MenuCardDatabaseInitializer
{
private static bool _databaseChecked = false;
public MenuCardDatabaseInitializer(MenuCardsContext context)
{
_context = context;
}
private MenuCardsContext _context;
public async Task CreateAndSeedDatabaseAsync() {
if (!_databaseChecked)
{
_databaseChecked = true;
await _context.Database.MigrateAsync();
if (_context.MenuCards.Count() == )
{
_context.MenuCards.Add(
new MenuCard { Name ="Breakfast", Active = true, Order = });
_context.MenuCards.Add(
new MenuCard { Name ="Vegetarian", Active = true, Order = });
_context.MenuCards.Add(
new MenuCard { Name ="Steaks", Active = true, Order = });
}
await _context.SaveChangesAsync();
}
}
}
}
With the database and model in place, you can create a service.
Creating a Service
Before creating the service, you create the interface IMenuCardsService that defines all the methods that are needed by the service (code file MenuPlanner/Services/IMenuCardsService.cs):
using MenuPlanner.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MenuPlanner.Services
{
public interface IMenuCardsService
{
Task AddMenuAsync(Menu menu);
Task DeleteMenuAsync(int id);
Task<Menu> GetMenuByIdAsync(int id);
Task<IEnumerable<Menu>> GetMenusAsync();
Task<IEnumerable<MenuCard>> GetMenuCardsAsync();
Task UpdateMenuAsync(Menu menu);
}
}
The service class MenuCardsService implements the methods to return menus and menu cards, and it creates, updates, and deletes menus (code file MenuPlanner/Services/MenuCardsService.cs):
using MenuPlanner.Models;
using Microsoft.EntityFrameworkCore
using System.Collections.Generic; using System.Linq;
using System.Threading.Tasks;
namespace MenuPlanner.Services
{
public class MenuCardsService : IMenuCardsService
{
private MenuCardsContext _menuCardsContext;
public MenuCardsService(MenuCardsContext menuCardsContext)
{
_menuCardsContext = menuCardsContext;
}
public async Task<IEnumerable<Menu>> GetMenusAsync()
{
await EnsureDatabaseCreated();
var menus = _menuCardsContext.Menus.Include(m => m.MenuCard);
return await menus.ToArrayAsync();
}
public async Task<IEnumerable<MenuCard>> GetMenuCardsAsync()
{
await EnsureDatabaseCreated();
var menuCards = _menuCardsContext.MenuCards;
return await menuCards.ToArrayAsync();
}
public async Task<Menu> GetMenuByIdAsync(int id)
{
return await _menuCardsContext.Menus.SingleOrDefaultAsync(
m => m.Id == id);
}
public async Task AddMenuAsync(Menu menu)
{
_menuCardsContext.Menus.Add(menu);
await _menuCardsContext.SaveChangesAsync();
}
public async Task UpdateMenuAsync(Menu menu)
{
_menuCardsContext.Entry(menu).State = EntityState.Modified;
await _menuCardsContext.SaveChangesAsync();
}
public async Task DeleteMenuAsync(int id)
{
Menu menu = _menuCardsContext.Menus.Single(m => m.Id == id);
_menuCardsContext.Menus.Remove(menu);
await _menuCardsContext.SaveChangesAsync();
}
private async Task EnsureDatabaseCreated() {
var init = new MenuCardDatabaseInitializer(_menuCardsContext);
await init.CreateAndSeedDatabaseAsync();
}
}
}
To have the service available via dependency injection, the service is registered in the service collection using the AddScoped method (code file MenuPlanner/Startup.cs):
public void ConfigureServices(IServiceCollection services)
{
// etc.
services.AddScoped<IMenuCardsService, MenuCardsService>();
// etc.
}
Creating a Controller
ASP.NET MVC offers scaffolding to create controllers for directly accessing the database. You can do this by selecting the Controllers folder in Solution Explorer, and from the context menu select Add???Controller. The Add Scaffold dialog opens. From the Add Scaffold dialog, you can select MVC 6 Controller views, using Entity Framework. Clicking the Add button opens the Add Controller dialog shown in Figure 41.13. With this dialog, you can select the Menu model class and the Entity Framework data context MenuCardsContext, configure to generate views, and give the controller a name. Create the controller with the views to look at the generated code including the views.
Figure 41.13
The book sample doesn’t use the data context directly from the controller but puts a service in between. Doing it this way offers more flexibility. You can use the service from different controllers and also use the service from a service such as ASP.NET Web API.
NOTE ASP.NET Web API is discussed in Chapter 42.
With the following sample code, the ASP.NET MVC controller injects the menu card service via constructor injection (code file MenuPlanner/Controllers/MenuAdminController.cs):
public class MenuAdminController : Controller
{
private readonly IMenuCardsService _service;
public MenuAdminController(IMenuCardsService service)
{
_service = service;
}
// etc.
}
The Index method is the default method that is invoked when only the controller is referenced with the URL without passing an action method. Here, all Menu items from the database are created and passed to the Index view. The Details method returns the Details view passing the menu found from the service. Pay attention to the error handling. When no ID is passed to the Details method, an HTTP Bad Request (400 error response) is returned using the HttpBadRequest method from the base class. When the menu ID is not found in the database, an HTTP Not Found (404 error response) is returned via the HttpNotFound method:
public async Task<IActionResult> Index()
{
return View(await _service.GetMenusAsync());
}
public async Task<IActionResult> Details(int? id = )
{
if (id == null)
{
return HttpBadRequest();
}
Menu menu = await _service.GetMenuByIdAsync(id.Value);
if (menu == null)
{
return HttpNotFound();
}
return View(menu);
}
When the user creates a new menu, the first Create method is invoked after an HTTP GET request from the client. With this method, ViewBag information is passed to the view. This ViewBag contains information about the menu cards in a SelectList. The SelectList allows the user to select an item. Because the MenuCard collection is passed to the SelectList, the user can select a menu card with the newly created menu.
public async Task<IActionResult> Create()
{
IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
ViewBag.MenuCardId = new SelectList(cards,"Id","Name");
return View();
}
NOTE To use the SelectList type, you must add the NuGet package Microsoft.AspNet.Mvc.ViewFeatures to the project.
After the user fills out the form and submits the form with the new menu to the server, the second Create method is invoked from an HTTP POST request. This method uses model binding to pass the form data to the Menu object and adds the Menu object to the data context to write the newly created menu to the database:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(
[Bind("Id","MenuCardId","Text","Price","Active","Order","Type","Day")] Menu menu)
{
if (ModelState.IsValid)
{
await _service.AddMenuAsync(menu);
return RedirectToAction("Index");
}
IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
ViewBag.MenuCards = new SelectList(cards,"Id","Name");
return View(menu);
}
To edit a menu card, two action methods named Edit are defined—one for a GET request, and one for a POST request. The first Edit method returns a single menu item; the second one invokes the UpdateMenuAsync method of the service after the model binding is done successfully:
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return HttpBadRequest();
}
Menu menu = await _service.GetMenuByIdAsync(id.Value);
if (menu == null)
{
return HttpNotFound();
}
IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
ViewBag.MenuCards = new SelectList(cards,"Id","Name", menu.MenuCardId);
return View(menu);
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(
[Bind("Id","MenuCardId","Text","Price","Order","Type","Day")]
Menu menu)
{
if (ModelState.IsValid)
{
await _service.UpdateMenuAsync(menu);
return RedirectToAction("Index");
}
IEnumerable<MenuCard> cards = await _service.GetMenuCardsAsync();
ViewBag.MenuCards = new SelectList(cards,"Id","Name", menu.MenuCardId);
return View(menu);
}
The last part of the implementation of the controller includes the Delete methods. Because both methods have the same parameter—which is not possible with C# the second method has the name DeleteConfirmed. However, the second method can be accessed from the same URL Link as the first Delete method, but the second method is accessed with HTTP POST instead of GET using the ActionName attribute. This method invokes the DeleteMenuAsync method of the service:
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return HttpBadRequest();
}
Menu menu = await _service.GetMenuByIdAsync(id.Value);
if (menu == null)
{
return HttpNotFound();
}
return View(menu);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
Menu menu = await _service.GetMenuByIdAsync(id);
await _service.DeleteMenuAsync(menu.Id);
return RedirectToAction("Index");
}
Creating Views
Now it’s time to create views. The views are created within the folder Views/MenuAdmin. You can create the view by selecting the MenuAdmin folder in Solution Explorer and select Add???View from the context menu. This opens the Add View dialog as shown in Figure 41.14. With this dialog you can choose List, Details, Create, Edit, Delete templates, which arrange HTML elements accordingly. The Model class you select with this dialog defines the model that the view is based on.
Figure 41.14
The Index view, which defines an HTML table, has a Menu collection as its model. For the header elements of the table, the HTML element label with a Tag Helper asp-for is used to access property names for display. For displaying the items, the menu collection is iterated using @foreach, and every property value is accessed with a Tag Helper for the input element. A Tag Helper for the anchor element creates links for the Edit, Details, and Delete pages (code file MenuPlanner/Views/MenuAdmin/Index.cshtml):
@model IList<MenuPlanner.Models.Menu>
@{
ViewBag.Title ="Index";
}
<h2>@ViewBag.Title</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
@if (Model.Count() > 0)
{
<table>
<tr>
<th>
<label asp-for="@Model[0].MenuCard.Item"></label>
</th>
<th>
<label asp-for="@Model[0].Text"></label>
</th>
<th> <label asp-for="Model[0].Day"></label>
</th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
<input asp-for="@item.MenuCard.Name" readonly="readonly"
disabled="disabled" />
</td>
<td>
<input asp-for="@item.Text" readonly="readonly"
disabled="disabled" />
</td>
<td>
<input asp-for="@item.Day" asp-format="{0:yyyy-MM-dd}"
readonly="readonly" disabled="disabled" />
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a>
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</table>
}
In the MenuPlanner project, the second view for the MenuAdmin controller is the Create view. The HTML form uses the asp-action Tag Helper to reference the Create action method of the controller. It’s not necessary to reference the controller with the asp-controller helper, as the action method is in the same controller where the view was coming from. The form content is built up using the Tag Helpers for label and input elements. The asp-for helper for the label returns the name of the property; the asp-for helper for the input element returns the value (code file MenuPlanner/Views/MenuAdmin/Create.cshtml):
@model MenuPlanner.Models.Menu
@{
ViewBag.Title ="Create";
}
<h2>@ViewBag.Title</h2>
<form asp-action="Create" method="post">
<div class="form-horizontal">
<h4>Menu</h4>
<hr />
<div asp-validation-summary="ValidationSummary.All" style="color:blue"
id="FileName_validation_day" class="form-group">
<span style="color:red">Some error occurred</span>
</div>
<div class="form-group">
<label asp-for="@Model.MenuCardId" class="control-label col-md2">
</label>
<div class="col-md-10">
<select asp-for="@(Model.MenuCardId)"
asp-items="@((IEnumerable<SelectListItem>)ViewBag.MenuCards)"
size="2" class="form-control">
<option value="" selected="selected">Select a menu card</option>
</select>
</div>
</div>
<div class="form-group">
<label asp-for="Text" class="control-label col-md-2"></label>
<div class="col-md-10">
<input asp-for="Text" />
</div>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label col-md-2"></label>
<div class="col-md-10">
<input asp-for="Price" />
<span asp-validation-for="Price">Price of the menu</span>
</div>
</div>
<div class="form-group">
<label asp-for="Day" class="control-label col-md-2"></label>
<div class="col-md-10">
<input asp-for="Day" />
<span asp-validation-for="Day">Date of the menu</span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<a asp-action="Index">Back</a>
The other views are created similarly to the views shown here, so they are not covered in this book. Just get the views from the downloadable code.
You can now use the application to add and edit menus to existing menu cards.
Implementing Authentication and Authorization
Authentication and authorization are important aspects of web applications. If a website or parts of it should not be public, users must be authorized. For authentication of users, different options are available when creating an ASP.NET Web Application (see Figure 41.15: No Authentication, Individual User Accounts, and Work and School Accounts. The Windows Authentication selection is not available for ASP.NET Core 5.
Figure 41.15
With Work and School Accounts, you can select an Active Directory from the cloud to do the authentication.
Using Individual User Accounts, you can store user profiles within an SQL Server database. Users can register and log in, and they also can use existing accounts from Facebook, Twitter, Google, or Microsoft.
Storing and Retrieving User Information
For user management, user information needs to be added to a store. The type IdentityUser (namespace Microsoft.AspNet.Identity.EntityFramework) defines a name and lists roles, logins, and claims. The Visual Studio template that you’ve used to create the MenuPlanner application created some noticeable code to save the user: the class ApplicationUser that is part of the project derives from the base class IdentityUser (namespace Microsoft.AspNet.Identity.EntityFramework). The ApplicationUser is empty by default, but you can add information you need from the user, and the information will be stored in the database (code file MenuPlanner/Models/IdentityModels.cs):
public class ApplicationUser : IdentityUser
{
}
The connection to the database is made via the IdentityDbContext<TUser> type. This is a generic class that derives from DbContext and thus makes use of the Entity Framework. The IdentityDbContext<TUser> type defines properties Roles and Users of type IDbSet<TEntity>. The IDbSet<TEntity> type defines the mapping to the database tables. For convenience, the ApplicationDbContext is created to define the ApplicationUser type as the generic type for the IdentityDbContext class:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
Starting Up the Identity System
The connection to the database is registered with the dependency injection service collection in the startup code. Similar to the MenuCardsContext that you created earlier, the ApplicationDbContext is configured to use SQL Server with a connection string from the config file. The identity service itself is registered using the extension method AddIdentity. The AddIdentity method maps the type of the user and role classes that are used by the identity service. The class ApplicationUser is the previously mentioned class that derives from IdentityUser; IdentityRole is a string-based role class that derives from IdentityRole<string>. An overloaded method of the AddIdentity method allows you to configure the identity system with two-factor authentication; e-mail token providers; user options, such as requiring unique e-mails; or a regular expression that requires a username to match. AddIdentity returns an IdentityBuilder that allows additional configurations for the identity system, such as the entity framework context that is used (AddEntityFrameworkStores), and the token providers (AddDefaultTokenProviders). Other providers that can be added are for errors, password validators, role managers, user managers, and user validators (code file MenuPlanner/Startup.cs):
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration["Data:DefaultConnection:ConnectionString"]))
.AddDbContext<MenuCardsContext>(options =>
options.UseSqlServer(
Configuration["Data:MenuCardConnection:ConnectionString"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders(); services.Configure<FacebookAuthenticationOptions>(options =>
{
options.AppId = Configuration["Authentication:Facebook:AppId"];
options.AppSecret = Configuration["Authentication:Facebook:AppSecret"];
});
services.Configure<MicrosoftAccountAuthenticationOptions>(options =>
{
options.ClientId =
Configuration["Authentication:MicrosoftAccount:ClientId"];
options.ClientSecret =
Configuration["Authentication:MicrosoftAccount:ClientSecret"];
});
// etc.
}
Performing User Registration
Now let’s step into the generated code for registering and logging in the user. The heart of the functionality is within the AccountController class. The controller class has the Authorize attribute applied, which restricts all action methods to authenticated users. The constructor receives a user manager, sign-in manager, and database context via dependency injection. E-mail and SMS sender are used for two-factor authentication. In case you don’t implement the empty AuthMessageSender class that is part of the generated code, you can remove the injection for IEmailSender and ISmsSender (code file MenuPlanner/Controllers/AccountController.cs):
[Authorize]
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly ISmsSender _smsSender;
private readonly ApplicationDbContext _applicationDbContext;
private static bool _databaseChecked;
public AccountController(
UserManager<ApplicationUser> userManager,
SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender,
ISmsSender smsSender,
ApplicationDbContext applicationDbContext)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
_smsSender = smsSender;
_applicationDbContext = applicationDbContext;
}
To register a user, you define RegisterViewModel. This model defines what data the user needs to enter on registration. From the generated code, this model only requires e-mail, password, and confirmation password (which must be the same as the password). In case you would like to get more information from the user, you can add properties as needed (code file MenuPlanner/Models/AccountViewModels.cs):
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name ="Email")]
public string Email { get; set; }
[Required]
[StringLength(, ErrorMessage =
"The {0} must be at least {2} characters long.", MinimumLength = )]
[DataType(DataType.Password)]
[Display(Name ="Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name ="Confirm password")]
[Compare("Password", ErrorMessage =
"The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
User registration must be possible for non-authenticated users. That’s why the AllowAnonymous attribute is applied to the Register methods of the AccountController. This overrules the Authorize attribute for these methods. The HTTP POST variant of the Register method receives the RegisterViewModel object and writes an ApplicationUser to the database by calling the method _userManager.CreateAsync. After the user is created successfully, sign-in is done via _signInManager.SignInAsync (code file MenuPlanner/Controllers/AccountController.cs):
[HttpGet]
[AllowAnonymous]
public IActionResult Register()
{
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model)
{
EnsureDatabaseCreated(_applicationDbContext);
if (ModelState.IsValid)
{
var user = new ApplicationUser
{ UserName = model.Email,
Email = model.Email
};
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction(nameof(HomeController.Index),"Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Now the view (code file MenuPlanner/Views/Account/Register.cshtml) just needs information from the user. Figure 41.16 shows the dialog that asks the user for information.
Figure 41.16
Setting Up User Login
When the user registers, a login occurs directly after the successful registration is completed. The LoginViewModel model defines UserName, Password, and RememberMe properties—all the information the user is asked with the login. This model has some annotations used with HTML Helpers (code file MenuPlanner/Models/AccountViewModels.cs):
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name ="Remember me?")]
public bool RememberMe { get; set; }
}
To log in a user that is already registered, you need to call the Login method of the AccountController. After the user enters login information, the sign-in manager is used to validate login information with PasswordSignInAsync. If login is successful, the user is redirected to the original requested page. If login fails, the same view is returned to give the user one more option to enter the username and password correctly (code file MenuPlanner/Controllers/AccountController.cs):
[HttpGet]
[AllowAnonymous]
public IActionResult Login(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
return View();
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model,
string returnUrl = null)
{
EnsureDatabaseCreated(_applicationDbContext);
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
model.Email, model.Password, model.RememberMe, lockoutOnFailure:
false);
if (result.Succeeded)
{
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor) {
return RedirectToAction(nameof(SendCode),
new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
}
if (result.IsLockedOut)
{
return View("Lockout");
}
else
{
ModelState.AddModelError(string.Empty,"Invalid login attempt.");
return View(model);
}
}
return View(model);
}
Authenticating Users
With the authentication infrastructure in place, it’s easy to require user authentication by annotating the controller or action methods with the Authorize attribute. Applying this attribute to the class requires the role for every action method of the class. If there are different authorization requirements on different action methods, the Authorize attribute can also be applied to the action methods. With this attribute, it is verified if the caller is already authorized (by checking the authorization cookie). If the caller is not yet authorized, a 401 HTTP status code is returned with a redirect to the login action.
Applying the attribute Authorize without setting parameters requires users to be authenticated. To have more control you can define that only specific user roles are allowed to access the action methods by assigning roles to the Roles property as shown in the following code snippet:
[Authorize(Roles="Menu Admins")]
public class MenuAdminController : Controller
{
You can also access user information by using the User property of the Controller base class, which allows a more dynamic approval or deny of the user. For example, depending on parameter values passed, different roles are required.
NOTE You can read more information about user authentication and other information about security in Chapter 24, “Security.”
Summary
In this chapter, you explored the latest web technology to make use of the ASP.NET MVC 6 framework. You saw how this provides a robust structure for you to work with, which is ideal for large-scale applications that require proper unit testing. You saw how easy it is to provide advanced capabilities with minimum effort, and how the logical structure and separation of functionality that this framework provides makes code easy to understand and easy to maintain.
The next chapter continues with ASP.NET Core but discusses communication with services in the form of the ASP.NET Web API.