在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
What’s In This Chapter?
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:
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 = 3, Text ="Grilled sausage with sauerkraut and potatoes", Price = 12.90, Date = new DateTime(2016, 3, 31), 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":3,"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.
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=1, Text="Schweinsbraten mit Knödel und Sauerkraut", Price=6.9, Category="Main" }, new Menu { Id=2, Text="Erdäpfelgulasch mit Tofu und Gebäck", Price=6.9, Category="Vegetarian" }, new Menu { Id=3, 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= |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论