官术网_书友最值得收藏!

The MVC pattern and ASP.NET MVC 4

The implementation of the MVC pattern in ASP.NET MVC 4 largely follows a convention-over-configuration paradigm. Simply stated, if you put something in the right place and/or name it the right way, it simply works. That is not to say that we cannot configure the framework to ignore or alter these conventions. It is the flexibility and adaptability of ASP.NET MVC 4 along with its adherence to web standards that has driven its rapid adoption.

Note

If you have no exposure to ASP.NET or ASP.NET MVC, please note that each of the components of the MVC pattern, as they relate to ASP.NET MVC 4, is often presented in a separate chapter of its own.

The following is a very condensed high-level overview of the MVC pattern in ASP.NET MVC 4.

Controllers in ASP.NET MVC

In ASP.NET MVC 4, controllers respond to HTTP requests and determine the action to take based upon the content of the incoming request.

Controllers in ASP.NET MVC 4 are located in a project folder named, appropriately, Controllers. The Internet Application project we selected for BrewHow contains two controllers in the Controllers folder: AccountController and HomeController.

If you examine the code in the HomeController.cs file, you will notice that the HomeController class extends the Controller class.

public class HomeController : Controller
{
    /* Class methods... */
}
Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

The Controller class and the ControllerBase class, from which the Controller class derives, contain methods and properties used to process and respond to HTTP requests. Almost all interaction that occurs while retrieving data from a request or returning data in response to a request will be through the methods and properties exposed by the Controller inheritance chain.

If you have no prior experience with ASP.NET MVC, you might assume that controllers in the project are identified by either placement in the Controllers folder or by extending the Controller class. In actuality, the runtime uses class name to identify controllers in the default MVC framework configuration; controllers must have a Controller suffix. For example, there might be a controller for recipes, and the class representing this controller would be named RecipeController.

Note

As we discussed briefly, every part of the ASP.NET MVC framework is extensible and configurable. You are free to provide some other convention to the framework that it can use to identify controllers.

Creating the Recipe controller

Let's create a new controller to return recipes to our users. In the Solution Explorer, right-click on the Controllers folder in the BrewHow project, select Add, and then select the Controller menu item. You may also press Ctrl + M, Ctrl + C.

In the Add Controller dialog, name the controller RecipeController, select the Empty MVC controller template from the Template drop-down menu, and then click on the Add button.

Congratulations! You have created your first controller in the BrewHow application. The code should look similar to the following:

public class RecipeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }
}

Notice that this code is very similar to the code in HomeController. The RecipeController inherits the Controller class and, like HomeController, contains an Index method that returns ActionResult. The class is also properly named with a Controller suffix, so the runtime will be able to identify it as a controller.

I'm sure you're ready to examine the output of this controller, but before we can do that we need to understand a little about how the controller is invoked in the first place.

Introduction to routing

At application startup, a series of formatted strings (similar to formatted strings you would use in String.Format) along with name, default values, constraints, and/or types are registered with the runtime. This combination of name, formatted string, values, constraints, and types is termed a route. The runtime uses routes to determine which controller and action should be invoked to handle a request. In our BrewHow project, routes are defined and registered with the runtime in the RouteConfig.cs file in the App_Start folder.

The default route for our app is registered with the runtime as shown below.

routes.MapRoute(
 name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new 
    { 
        controller = "Home", 
        action = "Index", 
        id = UrlParameter.Optional 
    });

The formatted string for our default route looks very similar to a URL you might type within the browser. The only notable difference is that each segment of the URL—the portion of the URL between the slashes (/)—is surrounded with curly braces ({}).

Our default route contains segments to identify a controller, an action (method), and an optional ID.

When our app receives a request, the runtime looks at the URL requested and attempts to map it to any formatted string that has been registered via the MapRoute method. If the incoming URL contains segments that can be mapped into the segments of the formatted string URL, the runtime determines that a match has been found.

Two segments, controller and action, have special meaning, and determine the controller to be instantiated and method (action) to be invoked on the controller. The other segments may be mapped to parameters on the method itself. All segments are placed in a route dictionary using the name of the segment within the curly braces as the key, and the mapped portion of the URL as the value.

To help clarify that with an example, assume a request is made to our app for the URL /do/something. The runtime would consult the route table, which is the collection of all routes registered by the application, and find a match on our Default route. It would then begin to map the segments of the URL request to the formatted string URL. The value do would be placed in the route dictionary under the key controller and the value of something would be under the key action. The runtime would then attempt to invoke the Something method of the DoController, appending the Controller suffix to the controller name.

If you examine our Default route a little further, you will see the words within the curly braces correspond to the names of values passed via the defaults parameter of MapRoute. This is, in fact, what makes this a default route. If no controller has been identified by a route, then the runtime will use HomeController. If it can find no action, the Index method on the identified controller will be used. The welcome page we viewed in Chapter 2, Homebrew and You, was invoked because the requested URL of / (slash) mapped to our Default route, and hence, invoked the Index method of HomeController.

Note

Routing is a very complex topic. Incoming routes can be ignored, may contain specific values or constraints as part of their mapping, and may include optional parameters. And never forget, the order in which routes are registered is important, very important. Routing is so complex that entire tools have been built around debugging routing issues. This introduction covers only the information needed to understand the content of this book until we get to Chapter 7, Separating Functionality Using Routes and Areas.

Action methods

An action method (action for short) is a special method in an ASP.NET MVC controller that does the heavy lifting. The code within action methods handles business decisions based upon the data passed to it, interacts with the model on behalf of the request, and tells the runtime which view should be processed in the response to the client.

In the initial request to our application, we identified that the HomeController class was being invoked and instructed to execute the Index action when we examined how routing worked.

For our Recipe controller, this method is pretty straightforward.

public ActionResult Index()
{
    return View();
}

There is no model interaction and no business decision to be made within our action method. All that the action does is use the View method of the base Controller class to return the default view. The View method is one of several methods in the base Controller class that return an ActionResult.

ActionResults

An ActionResult is a special return type used by action methods. ActionResult allows you to render a view to the response, redirect the requestor to another resource (a controller, view, image, and others), translate data into JSON for AJAX method calls, and pretty much anything else you can imagine.

The base Controller class provides methods that return most of the ActionResult derived classes you need, so you should rarely need to instantiate a class derived from ActionResult. The following table illustrates the helper methods within the base Controller class that return an ActionResult:

Invoking the Recipe controller

Applying what we've learned about routing, accessing /Recipe should invoke the Index action method of our new RecipeController using the Default route. Launch the app in the browser, append /Recipe to the URL, and press Enter.

This is probably not the result you were expecting. The error exists because there is no view available for the Index action of RecipeController.

Views in ASP.NET MVC

In ASP.NET MVC and the MVC pattern in general, the view handles the presentation of the data as the result of a request sent to a controller. By default, views are stored in the Views folder.

Within the Views folder of the project there are folders corresponding to each controller. Note that in the Home folder there are views corresponding to each action method in the HomeController class. This is by design, and is one of the other ASP.NET MVC's convention-over-configuration features.

Whenever the runtime is looking for the view to correlate back to an action, it will first look for a view whose name matches the action being invoked. It will try and locate that view in a folder whose name matches the controller being invoked. For our initial request of /Home/Index, the runtime will look for the view Views/Home/Index.cshtml.

Note

There is a pretty substantial search order that the runtime goes through to determine the default view. You can see that search order if you examine the error screen presented when trying to invoke the RecipeController without first creating the view for the action.

Razor

Razor is the default View Engine, part of the runtime that parses, processes, and executes any code within a view, in ASP.NET MVC 4. The Razor View Engine has a very terse syntax designed to limit keystrokes and increase readability of your views.

Note that I said syntax and not language. Razor is intended to be easily incorporated into your existing skill set and allow you to focus on the current task. All you really need to know to use Razor successfully is the location of the @ character on your keyboard.

Note

The .cshtml is an extension that identifies a view as a Razor View Engine view written in C#. If the language in which the view is written is Visual Basic, the extension will be .vbhtml. If you elect to use the Web Form View Engine, your views will have a .aspx extension associated with them, but you will be unable to follow along with the samples in this book. All of the views used by the BrewHow app are parsed and processed by the Razor View Engine.

The @ character

The @ character is the key to understanding the Razor syntax. It is the symbol used to denote blocks of code, comments, expressions, and inline code. More precisely, the @ character instructs the View Engine to begin treating the content of a view as code that must be parsed, processed, and executed.

Code blocks

If you want to place a block of code within a Razor view, simply prefix the block of code using @.

@{
    string foo = "bar";
}

Code blocks are the portions of code between the curly braces ({}).

Expressions

Expressions are portions of code whose evaluation results in a value being returned. Expressions can be a method invocation.

@foo.Bar() 

They can also be used to retrieve the value of a property.

@user.FirstName.

Expressions can be embedded directly into the output HTML.

<h1>Hello, @user.FirstName</h1>

By default, the result of any Razor expression evaluation is HTML encoded. This means that any special characters having meaning to the client are escaped as part of the output processing. This functionality also serves as a security mechanism, preventing accidental (or malicious) additions of executable code being sent to a requestor.

If you need to expose the raw value of a property or method, you may do so using the Raw HTML helper extension method.

@Html.Raw(beVeryCarefulDoingThis)
Note

HTML encoding helps prevent Cross-Site Scripting (XSS) attacks by instructing the browser to render the output as data. It is critical that you encode any and all data you return to the client. There are several other attacks possible on websites, which are well beyond the scope of this book, and I urge you to develop defensive coding practices and become knowledgeable about the potential threats your application may face.

Inline code

As mentioned earlier, the Razor View Engine is smart enough to infer when an actionable piece of code has terminated and processing should stop. This is exceptionally handy when placing inline code in your views.

@if (userIsAuthenticated) {
    <span>Hello, @username</span>
} else {
    <a href='#'>Please login</a>
}

If the content you want to display to the user within inline code has no wrapping tag such as span or div, you can wrap the content in <text> tags.

@if (jobIsDone) {
    <text>profit!</text>
}

Note that merely mentioning the placement of code directly within your view may result in discussions similar to those about the location of your opening curly brace. If you are a practicing member of the Church of Inline Code it may be best that you follow a "Don't Ask, Don't Tell" policy.

Comments

If you are wise and comment your code, Razor supports it.

@* Your comment goes here. *@

Comments, like anything else, need to be maintained. If you modify your code, modify your comments.

There are several other things that can be done with Razor such as creating delegates, but we will address them as we encounter them in our application.

Shared views

When we examined the views of HomeController, we learned that the views for a controller are placed in a folder that shares the same name. Views are only readily available to the controller owning that folder. If we want to make a view easily accessible to more than one controller, we need to mark that view as shared by placing the view in the Shared folder underneath the Views folder.

If the runtime cannot find the appropriate view in the executing controller's view folder, the next place it will look is the Shared folder. Our current app has three views located in the Shared folder.

_LoginPartial.cshtml is a partial view that contains our login control. This control is embedded in several of our views and displays the login form for users who are unauthenticated. Its consumption by several of the views merits it a position in the Shared folder.

The view Error.cshtml is the global error page. It is returned by action methods when something unforeseen has happened. Since any action can return this view, it has been placed in the Shared folder.

The third view, _Layout.cshtml, is a special type of shared view known as a layout.

Layouts

A layout is a template for the content of your view. Layouts typically contain scripts, navigation, headers, footers, or other elements you deem necessary on more than one page of your site. If you have worked with ASP.NET in the past, you are likely familiar with master pages. A layout is the master page of the ASP.NET MVC world.

You can specify the layout for a page using the Layout property of the view.

@{
    Layout = "~/Views/Shared/_Layout.cshtml"
}

When a view is loaded, the layout is executed and the content of the view is placed where the call to @RenderBody() is.

The _ViewStart file

If you have several views using the same layout, specifying the layout in each and every view may get a little repetitive. To keep things DRY (Don't Repeat Yourself), you can specify the default layout for all views of the site in _ViewStart.cshtml.

_ViewStart.cshtml is located at the root of the Views folder. It is used to store code common to all views within the app, not just to specify the layout. When each view is loaded, _ViewStart.cshtml is invoked. It acts like a base class for the views and any code within it becomes part of the constructor for every view in the site.

Note

You will note that both _LoginPartial.cshtml and _Layout.cshtml start with an underscore. The underscore is another convention indicating that the views are not to be requested directly.

Partial views

Partial views allow you to design portions of your view as reusable components. These are very similar to controls in ASP.NET Web Forms development.

Partial views may be returned directly from a controller using the PartialView method, or directly returning a PartialReviewResult return type. They may also be embedded directly into a view using the RenderPartial, Partial, RenderAction, or Action HTML helper methods.

HTML helpers

Much of the code you will see in our views has methods prefixed with Html. These methods are HtmlHelper extension methods (HTML helpers for short) intended to provide you a quick way to perform some action or embed some information into the output of your view. Typically, these methods have a return type of string. HTML helpers assist in standardizing the rendering of content presented in views such as forms or links.

Html.RenderPartial and Html.Partial

The RenderPartial HTML helper processes the partial view and writes the result directly to the response stream. The result is as if the code of the partial view were placed directly into the calling view.

@Html.RenderPartial("_MyPartialView", someData)

The Partial HTML helper does the same processing of the view as the RenderPartial helper, but the output of the processing is returned as a string. The _LoginPartial.cshtml in our project is rendered within the _Layout.cshtml layout using the Partial HTML helper.

@Html.Partial("_LoginPartial")

If you are returning a large amount of data in a partial view, RenderPartial will be slightly more efficient than Partial due to its direct interaction with the response stream.

Html.RenderAction and Html.Action

The RenderAction HTML helper differs from RenderPartial, in that it actually invokes a controller's action method and embeds that content within the view. This functionality is extremely useful as it provides a way to execute business logic or other code specific to a view in the controller responsible for returning the view to the requestor.

Let's say we decided we wanted to show a tag cloud of the most viewed styles of beer on our BrewHow site on every page.

You have two options: you could make every action of the site retrieve from the database or cache the current values to display in the tag, or you could have a controller action solely responsible for retrieving the values for the tag cloud and invoke that action in every view using the RenderAction or Action method.

The differences between RenderAction and Action are the same as those between RenderPartial and Partial. RenderAction will write the output of the processing directly to the response stream. Action will return the output of the processing as a string.

Display templates

Display templates are type-specific partial views. They exist in a folder named DisplayTemplates. This folder may exist inside a controller's View folder if the display template is specific to a controller, or it may exist under the Shared folder if it is to be used by the entire application.

Each of the display templates must be named for its type. A display template for a Beer class that could be used everywhere in the application would be named Beer.cshtml and placed in ~/Shared/Views/DisplayTemplates.

We can also create display templates for base types like strings or integers.

Display templates may be added to a view using either Display, DisplayFor, or DisplayForModel HTML helper methods.

Html.Display

The Display HTML helper takes a single string parameter. The string represents the property name of the current model or a value in the ViewData dictionary to be rendered.

Assume that the model passed to our view is defined as follows:

public class Recipe
{
    public string RecipeName { get; set; }
    /* Other properties here… */
}

If we want to invoke a display template for the RecipeName property, we put the following code into our view:

@Html.Display("RecipeName")

The runtime will try and locate a display template for the string type and use any identified template to render the RecipeName property.

Html.DisplayFor

DisplayFor is the expression-based version of Display. It takes as its argument the current model for the view and processes the display template for the specified property on the model.

@Html.DisplayFor(model => model.RecipeName)

We will examine how the view knows the model type when we populate our views later in this chapter.

Html.DisplayForModel

If we want to invoke the display template chain for an entire model, we simply use the DisplayForModel helper method.

@Html.DisplayForModel()
Editor templates

Editor templates are the read/write version of display templates. Their implementation and usage are identical to display templates.

Editor templates are stored in a folder named EditorTemplates. This folder may exist in the same location as the DisplayTemplates folder.

Editor templates, like display templates, are invoked using one of three HTML helper methods: Editor, EditorFor, or EditorForModel.

We will utilize display and editor templates as we refine our BrewHow app.

Creating our Recipe view

Having taken the long way around, we can get back to the task at hand. We now know we need to create a view in the ~/Views/Recipe folder named Index.cshtml if we want the /Recipe URL to succeed. We could manually create the folder structure in the solution (the Recipe folder does not currently exist), and then create a new partial view, but why not allow Visual Studio to do the heavy lifting for us.

Open the RecipeController.cs file in Solution Explorer and right-click anywhere within the Index action method. You will see a menu item labeled Add View….

Clicking on Add View… or pressing Ctrl + M, Ctrl + V will display the Add View dialog. Accept the default values by simply clicking on the Add button.

If you return to Solution Explorer, you will now see the view directory structure and the Index.cshtml view for RecipeController.

Launch the app by pressing Ctrl + F5 and append /Recipe to the URL in the browser's address bar as before. You should now see a page very similar to the following screenshot:

Making Recipe default

We should set the /Recipe controller's Index action to be the default action for the app, given our app is all about sharing recipes.

Open the RouteConfig.cs file in the App_Start folder and modify the values for the defaults parameter to default to the RecipeController. Remember that the runtime will append Controller to the controller name when looking for the class to handle a request.

routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
 defaults: new 
 { 
 controller = "Recipe", 
 action = "Index", 
 id = UrlParameter.Optional 
 });

Press Ctrl + F5 to build and launch the app.

Our RecipeController's Index action and view now handle presenting the default landing page to the user. The only thing left is to modify our layout so users are redirected to /Recipe/Index instead of /Home/Index when clicking on the Home navigation link or the logo text itself.

<p class="site-title">
    @Html.ActionLink("your logo here", 
        "Index", 
 "Recipe")
</p>
<!-- Other stuff happens -->
<li>
    @Html.ActionLink("Home", 
        "Index", 
 "Recipe")
</li>

Returning a model to the view

We have successfully processed the view and returned it to the client. Now we need to return some data from RecipeController's Index action. This data represents the model portion of the MVC pattern, and ASP.NET MVC supports several ways to return to the view any data gathered by the controller.

Using ViewData

The ViewData property of the ControllerBase class (the parent of the Controller class) is a "magic strings" mechanism for passing data from a controller to a view. It is more or less a dictionary. Almost all interaction with it occurs through a key/value syntax courtesy of its IDictionary<string,object> interface implementation.

ViewData["MyMagicProperty"] = "Magic String Win!"

Any value put into the ViewData dictionary is retrievable in the view using the ViewData property of the WebViewPage base class.

@ViewData["MyMagicProperty"]

All other methods of sending a model to a view from within the Controller and ControllerBase classes are wrappers and abstractions of the ViewData dictionary.

While you may (or may not) view magic strings as an appropriate mechanism for data exchange, all other methods of returning data to a view leverage ViewData internally.

Using ViewBag

ViewBag is a wrapper around the ViewData dictionary enabling support for dynamic properties and types on the ViewData dictionary.

If we examine the HomeController class's Index action, we'll see it's using the ViewBag property to send a message back to the view.

public ActionResult Index()
{
 ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";

    return View();
}

Message is not a declared member of the ViewBag property. The ViewBag property's support for dynamic typing, also referred to as duck typing, allows assignment of values to properties not declared on the object. The mere act of assigning values to these properties adds them to the object.

The ViewBag data can be accessed from within the view using the ViewBag property of the WebViewPage base class. The Index view for HomeController has the following code:

<hgroup class="title">
    <h1> @ViewBag.Title. </h1>
    <h2> @ViewBag.Message </h2>
</hgroup>

This is displayed to the user as part of the content of the page.

Any use of ViewBag should be well documented. Due to their dynamic nature, the compiler has no way to validate references to dynamic types. Attempts to access properties or methods that do not exist, or that are defined with different types or arguments will not be caught at compile time. Instead, these errors will be captured at runtime and will cause your app to crash.

Using TempData

If you need to persist data for the view between redirects or sequential requests, then you may want to consider using TempData. TempData is another wrapper around the ViewData dictionary, but data placed in TempData is intentionally short-lived; anything stored in TempData will persist for exactly two requests: the current request and the next subsequent request for the same user.

Placing data in the TempData dictionary should be done sparingly as the implementation can result in unexpected behavior.

Assume that a user of our app has two tabs open to compare two recipes. A request originates from one of the tabs inserting data into TempData for consumption. A second request to the app is then triggered from the another open tab. The second request will force the values put in TempData by the first request to expire whether or not the intended recipient has retrieved and operated on the values.

The TempData dictionary is accessible through the TempData properties of ControllerBase and WebViewPage.

TempData["UserNotification"] = "Hope you get this message."

@TempData["UserNotification"]

The three methods presented thus far provide means to submit loosely coupled data to a view from a controller. While you certainly could send a full model to the view through these mechanisms, the framework provides a strongly typed method for moving data between the controller and view.

Strongly typed models

If you want to pass a strongly typed model from a controller to a view, you can do this in one of two ways. You can do this directly by setting the value of the Model property on the ViewData dictionary (OK, so it's a little more than a dictionary), or you can pass the model to the view through one of the ActionResult methods on the Controller class.

Setting the property directly can be done as follows:

ViewData.Model = myModel;

You will likely not set the value directly very often, but will instead use one of the ActionResult methods.

return View(myModel);

This is much more concise and if you examine the code in the Controller class you will see it's actually setting ViewData.Model on your behalf.

protected internal virtual ViewResult View
    (string viewName, string masterName, object model)         
{             
    if (model != null)             
    {                 
        ViewData.Model = model;             
    }              
    /* Other code removed */
} 
Tip

Take the initiative

The code above is taken directly from the RTM version of ASP.NET MVC 4. Microsoft's entire web stack is available under an open source license at aspnetwebstack.microsoft.com. You are urged to take the time and explore the code to learn what is actually being done by the framework.

For the view to operate on this model in a strongly typed fashion, it must be informed of the type of model to expect. In Razor views, this is done with the @model keyword.

@model BrewHow.Web.Model.MyModel
Returning a Recipe list

Let's put into practice everything we've learned. When a user of our app lands on the recipe page, we will present them with a list of recipes.

Creating the model

We need to define a model to represent a recipe within our app. For now, that model will contain four properties: name, style, original gravity, and final gravity.

Note

The gravity of a liquid is a measurement of its density compared to water, with water having a gravity of 1.0. Measuring the gravity of beer is done to determine how much sugar exists as a percentage of the liquid. Two measurements are taken when brewing beer. The original gravity is taken to measure the amount of sugar in the wort before yeast is added. The final gravity is taken to measure the gravity of the liquid at the end of fermentation. The difference between the two measurements can be used to determine how much of the sugar within the unfermented wort was converted into alcohol during fermentation. The formula for determining the amount of alcohol by volume is 132.715*(original gravity - final gravity).

In Solution Explorer, right-click on the Models folder, select Add, and then click on Class….

Select Class as the type of item to add in the Add New Item dialog if it is not already selected. Name the class Recipe, and click on Add to create the class.

Open the new Recipe.cs file, and replace the Recipe class definition with the following:

public class Recipe
{
    public string Name { get; set; }
    public string Style { get; set; }
    public float OriginalGravity { get; set; }
    public float FinalGravity { get; set; }
}

Save and close the file.

Returning the model

Open the RecipeController class, and replace the Index action with the following code:

public ActionResult Index()
{
    var recipeListModel = new List<Recipe>
    {
        new Recipe { Name = "Sweaty Brown Ale", Style = "Brown Ale", OriginalGravity = 1.05f, FinalGravity = 1.01f },
        new Recipe { Name = "Festive Milk Stout", Style="Sweet/Milk Stout", OriginalGravity = 1.058f, FinalGravity = 1.015f },
        new Recipe { Name = "Andy's Heffy", Style = "Heffeweisen", OriginalGravity = 1.045f, FinalGravity = 1.012f }
    }
    return View(recipeListModel);
}

The new Index action is pretty straightforward. The new code creates a populated list and assigns it to the recipeListModel variable. The populated list is sent to the Index view as a strongly typed model through the View method (remember, the view has the same name as the action unless otherwise specified).

return View(recipeListModel);

For the code to compile, you will need to add a using statement to the top of the file to reference the new Recipe model defined in BrewHow.Models.

using BrewHow.Models;
Displaying the model

The final step is to inform the view of the incoming model and provide the view the means to display the model.

Open up the Index.cshtml view in the ~/Views/Recipe folder and add the following line at the top of the view.

@model IEnumerable<BrewHow.Models.Recipe>

This declares the model we are passing to the view is an enumeration of recipes. It is now strongly typed.

Place your cursor at the bottom of the Index.cshtml view and paste the following code. You won't be judged for flinching at the use of tables even though they are being used appropriately to display tabular data. We are just trying to get the model to display right now.

<table>
    <tr >
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Style)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.OriginalGravity)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.FinalGravity)
        </th>
    </tr >

@foreach (var item in Model) {
    <tr >
        <td>
           @Html.DisplayFor(modelItem => item.Name)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.Style)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.OriginalGravity)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.FinalGravity)
        </td>
    </tr >
}

</table>

The markup you pasted into the view lays out a table to list each entry in the model collection passed to the view. Do take note of how little the Razor syntax interferes with the markup of the view.

The first row of the table contains the headers for the recipe list. During the processing of the view within the foreach loop, the view engine iterates over each item in the collection passed to it and outputs a row for the table using the display template for the type. Since we have not defined display templates, the values of the properties will simply be output as strings.

You have successfully created a model, populated the model in the controller, passed the populated model from the controller to the view, and displayed the populated model to the user. Press Ctrl + F5 on the keyboard to start the app and admire your work.

主站蜘蛛池模板: 红河县| 渝中区| 社会| 台北县| 上饶市| 平阴县| 岑巩县| 高邑县| 鄂托克前旗| 芦山县| 高邮市| 龙泉市| 津南区| 枣强县| 翁牛特旗| 林州市| 遂昌县| 永清县| 中阳县| 县级市| 广饶县| 柏乡县| 营口市| 鄂尔多斯市| 潞西市| 穆棱市| 陈巴尔虎旗| 外汇| 双峰县| 康乐县| 建阳市| 大化| 虹口区| 金门县| 民权县| 贵定县| 渭源县| 达拉特旗| 邵阳县| 柳州市| 林周县|