left-icon

ASP.NET Core 2 Succinctly®
by Simone Chiaretta and Ugo Lattanzi

Previous
Chapter

of
A
A
A

CHAPTER 6

Beyond the Basics: Application Frameworks

Beyond the Basics: Application Frameworks


HTTP isn’t just for serving up webpages; it’s also for serving APIs that expose services and data over HTTP protocol. This chapter will gently introduce you to managing these scenarios using ASP.NET MVC, which hasn't changed much from the previous version.

You’ll manage controllers, views, APIs, and the newest and coolest tag helper, and you’ll play with the view components. But before proceeding, it’s important to know that the part related to MVC is just a small introduction. A complex framework like this would easily require its own book. This book targets ASP.NET.

Web API

A web API (application programming interface) is a set of subroutine definitions with the scope of managing data between clients and servers. Over the last few years, the trend has been to build APIs over HTTP protocol, allowing third-party apps to interact with a server with the help of the application protocol.

Probably the most well-known example is Facebook, which allows users to share content and posts, manage pages, and do more from any client, such as a mobile app or desktop. This is due to a set of APIs and HTTP, but how does it work?

The good thing about using HTTP protocol is that there are no major differences from classic web navigation, except for the result. In normal HTTP navigation, when we use a browser to call www.tostring.it, the server returns HTML. For APIs, it returns data using JSON format.

The result could also be XML or any other type of structured data. The point is that the result doesn't contain any information about the layout or interaction, like CSS and JavaScript.

With the previous version of ASP.NET, a specific library managed APIs, called Web API. This library doesn’t exist with the newest version, and you can handle API and classic requests using the same framework, because Web API has merged into the MVC framework. This is the primary difference between the old ASP.NET and the new.

When you think about what we were using in the previous version, it makes absolute sense. Most of the code between the two frameworks was similar. Think about the controller, attributes, and dependency injection—same code, different namespaces.

Installation

ASP.NET MVC Core is not different from what we saw in the previous chapters; you don't have to install it because it is included in Microsoft.AspNetCore.App.

Once it’s installed, register the service and configure it within your web application like this.

Code Listing 5-1

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Logging;

namespace Syncfusion.Asp.Net.Core.Succinctly.WebApi

{

     public class Startup

     {

        private IHostingEnvironment _env;

       

        public Startup(IHostingEnvironment env)

        {

               _env = env;

          }

       

          public void ConfigureServices(IServiceCollection services)

          {

               services.AddMvc();

          }

          public void Configure(IApplicationBuilder app)

          {

               if (_env.IsDevelopment())

               {

                    app.UseDeveloperExceptionPage();

               }

               app.UseMvcWithDefaultRoute();

          }

     }

}

Playing around with URLs and verbs

Because you are managing data and not HTML pages, HTTP verbs are key to understanding what the client needs. There is a kind of convention that almost all API implementations respect, which uses different HTTP verbs according to the type of action needed.

Suppose we have the request api/users.

Table 1: Handling the Request api/users

Resource Sample

Read
(GET Verb)

Insert
(POST Verb)

Update (PUT Verb)

Partially Update (PATCH Verb)

Delete (DELETE Verb)

Action

Gets a list of users

Creates a user

Update users with batch update

Batch-updates users only with the attributes present in the request

Errors or deletes all users, depending on what you want

Response

List of users

New user or redirect to the URL to get the single user

No payload; only HTTP status code

No payload; only HTTP status code

No payload; only HTTP status code

If we want to manage a single user, the request should be api/users/1, where 1 is the ID of the user we want to work with.

Table 2: Handling the request api/users/1

Resource Sample

Read
(GET Verb)

Insert
(POST Verb)

Update
(PUT Verb)

Partially Update
(PATCH Verb)

Delete
(DELETE Verb)

Action

Gets a single user

Return an error because the user already exists

Update the specified user

Partially updates the user only with the attributes present in the request

Deletes the specified user

Response

Single user

No payload; only HTTP status code

Updated user or redirect to the URL to get the single user

The updated user (complete object) or redirect to the URL to get the single user

No payload; only HTTP status code

To summarize, the URL combined with the HTTP verb allows you to understand what has to be done:

  • /api is the prefix indicating that the request is an API request and not a classic web request.
  • users is the MVC controller.
  • verb identifies the action to execute.

Return data from an API

Now that everything is correctly configured, you can create your first MVC controller. Not to deviate too much from what you used in the previous version, let's create a folder called Controllers.

Although not mandatory, the best practice is to create a folder for all the API controllers to separate the API’s from the standard controllers.

Code Listing 5-2

using System.Linq;

using Microsoft.AspNetCore.Mvc;

namespace Syncfusion.Asp.Net.Core.Succinctly.WebApi.Controllers.APIs

{

     [Route("api/[controller]")]

     public class UsersController : Controller

     {

          [HttpGet]

          public User[] Get()

          {

               return new[]

               {

                    new User() {Id = 1, Firstname = "Ugo", Lastname = "Lattanzi", Twitter = "@imperugo"},

                    new User() {Id = 2, Firstname = "Simone", Lastname = "Chiaretta", Twitter = "@simonech"},

               };

          }

          [HttpGet("{id}")]

          public User Get(int id)

          {

               var users = new[]

               {

                    new User() {Id = 1, Firstname = "Ugo", Lastname = "Lattanzi", Twitter = "@imperugo"},

                    new User() {Id = 2, Firstname = "Simone", Lastname = "Chiaretta", Twitter = "@simonech"},

               };

               return users.FirstOrDefault(x => x.Id == id);

          }

     }

     public class User

     {

          public int Id { get; set; }

          public string Firstname { get; set; }

          public string Lastname { get; set; }

          public string Twitter { get; set; }

     }

}

As you can see, the controller is simple and the code is not so different from the previous version.

Let’s analyze this step by step.

[Route("api/[controller]")]

This says that the controller can manage all the requests with the api prefix in the URL. Basically, if the URL doesn't start with /api, the controller will never handle the request. (This is helpful for all the mixed applications that need to handle APIs and other contents like MVC stuff).

[HttpGet]

This attribute specifies the verb for the action it is decorating. You could use all the possible verbs: HttpGet, HttpPost, HttpPut, and so on.

Update data using APIs

You just saw how to return data as well as partially read input information from the query string. Unfortunately, this is not so useful when you have to send a lot of data from the client to the server because there is a limit related to the number of characters you can add to the URL. For this reason, you have to move to another verb.

Hypertext Transfer Protocol (HTTP/1.1) doesn't specify limits for the length of a query string, but limits are imposed by web browsers and server software. You can find more info here.

This approach is not so different from what you saw before. The only difference is the place where the data comes from: the body instead of the query string.

Code Listing 5-3

// Adding user

[HttpPost]

public IActionResult Update([FromBody] User user)

{

    var users = new List<User>();

    users.Add(user);

    return new CreatedResult($"/api/users/{user.Id}", user);

}

//Deleting user

[HttpDelete]

public IActionResult Delete([FromQuery] int id)

{

    var users = new List<User>

    {

        new User() {Id = 1, Firstname = "Ugo", Lastname = "Lattanzi", Twitter = "@imperugo"},

        new User() {Id = 2, Firstname = "Simone", Lastname = "Chiaretta", Twitter = "@simonech"},

    };

    var user = users.SingleOrDefault(x => x.Id == id);

    if (user != null)

    {

        users.Remove(user);

        return new EmptyResult();

    }

    return new NotFoundResult();

}

As you can see, the only differences are the following:

  • [FromBody]: This specifies that data comes from the payload of the HTTP request.
  • return new CreatedResult($"/api/users/{user.Id}", user);: This returns the created object and the URL where the client can get the user again. The status code is 201 (created).
  • return new EmptyResult();: Basically, this means status code 200 (OK). The response body is empty.
  • return new NotFoundResult();: This is the 404 message that is used when the requested client can’t be found.

Testing APIs

A browser can be used to test the API because APIs are being implemented over HTTP, but there are other applications that can help with this, too. One popular application is Postman, which can be downloaded for free here.

The following screenshots show how to test the code we used previously.

Retrieve users (/api/users using GET):

Using Postman to Test REST Endpoint-1

Figure 5-1: Using Postman to Test REST Endpoint-1

Retrieve one user (/api/users/1 using GET):

Using Postman to Test REST Endpoint-2

Figure 5-2: Using Postman to Test REST Endpoint-2

Create user (/api/users using POST):

Using Postman to Test REST Endpoint-3

Figure 5-3: Using Postman to Test REST Endpoint-3

Delete user (/api/users?id=2 using DELETE):

Using Postman to Test REST Endpoint-4

Figure 5-4: Using Postman to Test REST Endpoint-4

ASP.NET MVC Core

As mentioned in the previous section, ASP.NET MVC isn't different than Web API, so the code you are going to see here will be very similar, except for the result.

Because you already registered ASP.NET MVC for APIs, you can jump directly to the code. In the same folder (Controller) where you created UserController, you can create another controller with the scope of serving the main page of the website. In this case, call it HomeController.

Code Listing 5-4

using Microsoft.AspNetCore.Mvc;

namespace Syncfusion.Asp.Net.Core.Succinctly.Mvc.Controllers.MVC

{

    public class HomeController : Controller

    {

        [HttpGet]

        public IActionResult Index()

        {

            return View();

        }

    }

}

This code is similar to the API controller; the only differences are the missing route attribute on the controller and the View method used to return the action.

The first one is not needed because you don't have to override the default routing configuration (we will discuss this later in the chapter), and the second one indicates to the controller that it has to render a view, not JSON.

If you try to run this code, you'll get an error like this:

Unable to Find the MVC View

Figure 5-5: Unable to Find the MVC View

This happens because the MVC framework cannot find the view file, but how does it locate the file? The logic behind it is very simple: if you don't specify any information about the view, everything happens using conventions.

First, it is important to know that all the views are placed in the folder Views in the root of the project, as you can see in the following screenshot.

The Views Folder

Figure 5-6: The Views Folder

Inside, there must be a folder for each MVC controller you created. In this case, you have just one controller called HomeController, so you must create the folder Home inside Views.

Finally, it's time to create our views. Visual Studio 2017 makes it really simple to do.

The Views Folder for the Home Controller

Figure 5-7: The Views Folder for the Home Controller

Adding the View-1

Figure 5-8: Adding the View-1

Adding the View-2

Figure 5-9: Adding the View-2

Now, if you open the newest file, you’ll see that it is almost empty. It contains just a few strange sections that you’ll recognize because they have different colors and use @ in the beginning.

These parts are managed server-side and contain instructions on how to change the output to render the final HTML. Everything is possible thanks to Razor, the view engine embedded within ASP.NET MVC Core

A good introduction to Razor syntax is available here.

Because you have to render a webpage, you first have to create the right markup, so let's add the following code to our view.

Code Listing 5-5

<!DOCTYPE html>

<html lang="en">

<head>

     <meta charset="utf-8">

     <title>Hello World</title>

</head>

<body>

     <h1>Hello World</h1>

</body>

</html>

When you run the application again, the result should not be an error, but a simple HTML page.

Server-Side Page Rendered Using MVC

Figure 5-10: Server-Side Page Rendered Using MVC

This is definitely an improvement, but you’re returning just a static file; there isn’t any kind of transformation. But that isn’t needed to use MVC because you already have a specific middleware component to manage static files.

To make the view more elaborate and to use something from the server, you have to go back to the controller and send the data to the view.

Code Listing 5-6

using Microsoft.AspNetCore.Mvc;

using Syncfusion.Asp.Net.Core.Succinctly.Mvc.Models;

namespace Syncfusion.Asp.Net.Core.Succinctly.Mvc.Controllers.MVC

{

    public class HomeController : Controller

    {

        [HttpGet]

        public IActionResult Index()

        {

            var users = new[]

            {

                new User() {Id = 1, Firstname = "Ugo", Lastname = "Lattanzi", Twitter = "@imperugo"},

                new User() {Id = 2, Firstname = "Simone", Lastname = "Chiaretta", Twitter = "@simonech"},

            };

            return View(users);

        }

    }

}

Notice that we have reused the class User previously created for the API response. This code doesn't need explanation; you’re just sending an array of users as a model to a View method.

In our view, it’s time to get users from the controller and print the data to the page. The first thing to do is to specify what kind of model the view is using. In our case, it’s IEnumerable<User>.

Code Listing 5-7

@model IEnumerable<Syncfusion.Asp.Net.Core.Succinctly.Mvc.Models.User>

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="utf-8">

    <title>Hello World</title>

</head>

<body>

    <h1>Hello World</h1>

</body>

</html>

Now you have to iterate all the users from the model and show the data.

Code Listing 5-8

@model IEnumerable<Syncfusion.Asp.Net.Core.Succinctly.Mvc.Models.User>

<!DOCTYPE html>

<html lang="en">

<head>

     <meta charset="utf-8">

     <title>Hello World</title>

</head>

<body>

     <h1>Users</h1>

     <div>

          @foreach(var user in Model) {

               <hr />

               <p>Firstname: @user.Firstname</p>

               <p>Lastname: @user.Lastname</p>

               <p>Twitter: <a href="http://www.twitter.com/@user.Twitter"> @user.Twitter</a></p>

          }

     </div>

</body>

</html>

The output should be something like this:

Server-Side Page Rendered Using MVC for a List

Figure 5-11: Server-Side Page Rendered Using MVC for a List

Routing

You just saw a small introduction to ASP.NET MVC. Notice that HomeController, combined with the Index action, handles the root domain (http://localhost:5000 in our case). But why?

To get the answer, you have to go back to the MVC configuration where we wrote this code:

Code Listing 5-9

using Microsoft.AspNetCore.Builder;

using Microsoft.AspNetCore.Hosting;

using Microsoft.Extensions.DependencyInjection;

using Microsoft.Extensions.Logging;

namespace Syncfusion.Asp.Net.Core.Succinctly.Mvc

{

     public class Startup

     {

          public void ConfigureServices(IServiceCollection services)

          {

               services.AddMvc();

          }

          public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

          {

               loggerFactory.AddConsole();

               if (env.IsDevelopment())

               {

                    app.UseDeveloperExceptionPage();

               }

               app.UseMvcWithDefaultRoute();

          }

     }

}

The method app.UseMvcWithDefaultRoute() defines the default route; it is equivalent to writing the following:

Code Listing 5-10

app.UseMvc(routes =>

{

     routes.MapRoute(

          name: "default",

          template: "{controller=Home}/{action=Index}/{id?}");

});

This part of code says to split the URL into segments, where the first is the controller, the second is the action, and the third (optional) is the parameter. If they are all missing, use the action Index of HomeController.

In fact, if you run the application again and write the following URL, the output should be the same:

http://localhost:5000/home/index

In you want to manage a URL like http://localhost:5000/products, it’s enough to create a ProductController with an Index action.

View-specific features

After seeing the general features of ASP.NET Core MVC, it is time to see two new features that are specific to the view side of ASP.NET MVC applications:

  • Tag Helpers
  • View components

Tag Helpers are a new way of exposing server-side code that renders HTML elements. They bring the same features of HTML Razor helpers to the easier-to-use syntax of standard HTML elements.

View components can be seen as either a more powerful version of partial views, or a less convoluted way to develop child actions. Let's look in detail at both of these new features.

Tag Helpers

Compared to HTML Razor helpers, Tag Helpers look very much like standard HTML elements—no more switching context between HTML and Razor syntax.

Let's see an example to make things a bit clearer. If you want to make an editing form with ASP.NET MVC, you have to display a text box that takes the value from the view model, renders validation results, and so on.

Using the previous HTML Razor helpers, you would have written:

@Html.TextBoxFor(m=>m.FirstName)

But now with Tag Helpers, you can directly write <input asp-for="FirstName" /> without introducing the Razor syntax. Notice that it's just a normal HTML <input> tag enhanced with the special attribute asp-for.

It doesn't look like such a big change, but the advantage becomes clear when you add more attributes. One example of this is the class attribute.

This is how you add the class using the old HTML Razor syntax:

@Html.TextBoxFor(m=>m.FirstName, new { @class = "form-control" })

Using the Tag Helper, it's just this:

<input asp-for="FirstName" class="form-control" />.

Basically, you write the tag like you were writing a static HTML tag, with the addition of the special asp-for.

But this new syntax retains the support of Visual Studio IntelliSense. As soon as you start typing an HTML element that is somehow enhanced via Tag Helpers, you see in the IntelliSense menu that the tag is represented with an icon that is different from normal HTML tags.

Visual Studio IntelliSense for HTML Tags

Figure 5-12: Visual Studio IntelliSense for HTML Tags

If you then trigger the IntelliSense menu to see all the available attributes, only one has the same icon again, the asp-for attribute. Once you add this attribute and open the IntelliSense menu again, you’ll see all methods and properties of the page model.

Visual Studio IntelliSense for MVC Model

Figure 5-13: Visual Studio IntelliSense for MVC Model

Look at the two previous screenshots carefully. You'll notice that the <input> tag has changed color. The following figure highlights the difference.

Difference Between the Two Tags

Figure 5-14: Difference Between the Two Tags

In the first line, the element is brown with attributes in red. In the second, the element is purple with the special attribute also in purple (the normal attribute is still red). Visual Studio recognizes both normal HTML tags and attributes, and Tag Helpers. This avoids confusion when editing a view.

ASP.NET Core MVC comes with a lot of Tag Helpers; most of them are just re-implementing the same HTML Razor helpers used to edit forms, like the input, form, label, and select elements. But there are other Tag Helpers used to manage cache, render different HTML elements based on the environment, and manage script fallback or CSS files. You can see many of these Tag Helpers used in the View\Shared\_Layout.cshtml file in the default project template. A reduced version of the file (without the body) is available in the following code listing.

Code Listing 5-11

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>@ViewData["Title"] - ViewHelpers</title>

    <environment include="Development">

        <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />

    </environment>

    <environment exclude="Development">

        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.min.css"

              asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"

              asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"

              integrity="sha256-eSi1q2PG6J7g7ib17yAaWMcrr5GrtohYChqibrV7PBE="/>

    </environment>

    <link rel="stylesheet" href="~/css/site.css" />

</head>

<body>

[...]

    <environment include="Development">

        <script src="~/lib/jquery/dist/jquery.js"></script>

        <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>

    </environment>

    <environment exclude="Development">

        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"

                asp-fallback-src="~/lib/jquery/dist/jquery.min.js"

                asp-fallback-test="window.jQuery"

                crossorigin="anonymous"

                integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=">

        </script>

        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/js/bootstrap.bundle.min.js"

                asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"

                asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"

                crossorigin="anonymous"

                integrity="sha256-E/V4cWE4qvAeO5MOhjtGtqDzPndRO1LBk8lJ/PR7CA4=">

        </script>

    </environment>

    <script src="~/js/site.js" asp-append-version="true"></script>

    @RenderSection("Scripts", required: false)

</body>

</html>

Building custom Tag Helpers

Tag Helpers are easy to create, so it's worth seeing how to create your own. Writing a custom Tag Helper is especially useful when you want to output an HTML snippet that is long and repetitive, but changes very little from one instance to another, or when you want to somehow modify the content of an element.

As an example, you are going to create a Tag Helper that automatically creates a link by just specifying the URL.

Something that takes this:

Code Listing 5-12

<url>https://www.syncfusion.com/resources/techportal/ebooks</url>

And creates a working a tag, like this:

Code Listing 5-13

<a href="https://www.syncfusion.com/resources/techportal/ebooks">https://www.syn cfusion.com/resources/techportal/ebooks</a>

Start by creating a UrlTagHelper file inside the MVC project. A good convention is to put the file inside a TagHelpers folder.

A Tag Helper is a class that inherits from TagHelper and defines its behavior by overriding the method Process, or its asynchronous counterpart, ProcessAsync. These methods have two arguments:

  • context: Contains information on the current execution context (even if it is rarely used).
  • output: Contains a model of the original HTML tag and its content, and is the object that has to be modified by the Tag Helper.

To use a Tag Helper in the views, you have to tell both Visual Studio and the .NET Core framework where to find them. This is done by adding a reference in the _ViewImports.cshtml file.

Code Listing 5-14

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@addTagHelper "*, MvcSample"

To make sure all the basic steps are done, copy the following code into the UrlTagHelper file.

Code Listing 5-15

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace MvcSample.TagHelpers

{

    public class UrlTagHelper: TagHelper

    {

        public override void Process(TagHelperContext context, TagHelperOutput output)

        {

            output.TagName = "a";

        }

    }

}

This Tag Helper, at the moment, is useless. It only replaced the tag used when calling the helper from whatever it was (a URL in our example) to a tag, but it doesn't create an href attribute pointing to the specified URL. To do so, you have to read the content of the element and create a new attribute. Since the content could also be a Razor expression, the method to do so is an Async method, output.GetChildContentAsync(). You also have to change the method you implemented from Process to ProcessAsync.

The complete Tag Helper is shown in the following code listing.

Code Listing 5-16

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace MvcSample.TagHelpers

{

    public class UrlTagHelper: TagHelper

    {

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

        {

            output.TagName = "a";

            var content = await output.GetChildContentAsync();

            output.Attributes.SetAttribute("href", content.GetContent());

        }

    }

}

Tag Helpers can also have attributes. You can extend the URL Tag Helper to specify the target of the link. To add an attribute to a Tag Helper, add a property to the class. In the case of the target, you just need to add public string Target { get; set; } to the class. But having a string parameter is not a nice experience because IntelliSense doesn't show which values are allowed. You can define the property as an enum whose values are only the ones allowed for the target HTML attribute. Then, you have nice IntelliSense.

HTML Property Suggestion

Figure 5-15: HTML Property Suggestion

This is the code of the updated Tag Helper:

Code Listing 5-17

using Microsoft.AspNetCore.Razor.TagHelpers;

namespace MvcSample.TagHelpers

{

    public enum TargetEnum

    {

        None = 0,

        Blank,

        Parent,

        Self,

        Top

    }

    public class UrlTagHelper: TagHelper

    {

        public TargetEnum Target { get; set; }

        public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)

        {

            output.TagName = "a";

            var content = await output.GetChildContentAsync();

            output.Attributes.SetAttribute("href", content.GetContent());

            if (Target!=TargetEnum.None)

            {

                output.Attributes.SetAttribute("target", "_"+Target.ToString().ToLowerInvariant());

            }

        }

    }

}

Now, <url target="Blank">http://example.com</url> is rendered as <a href="http://example.com" target="_blank">http://example.com</a>.

View components

View components are the next new, view-related feature. In a way, they are similar to partial views, but they are much more powerful, and are used to solve different problems.

A partial view is, as the name implies, a view. It is typically used to simplify complex views by splitting them into reusable parts. Partial views have access to the view model of the parent page and don't have complex logic.

On the other hand, view components don't have access to the page model; they only operate on the arguments that are passed to them, and they are composed by both view and class with the logic.

If you used a child action in previous versions of ASP.NET MVC, view components more or less solve the same problem in a more elegant way, as their execution doesn’t go through the whole ASP.NET MVC execution pipeline starting from the routing.

Typically, view controllers are used to render reusable pieces of pages that also include logic that might involve hitting a database—for example, sidebars, menus, and conditional login panels.

How to write a View component

As mentioned, a view component is made of two parts. The class containing the logic extends the ViewComponent class and must implement either the Invoke or the InvokeAsync method. This returns IViewComponentResult with the model that has to be passed to the view. Conventionally, all view components are located in a folder named ViewComponents in the root of the project.

The view is just like any other view. It receives the model passed by the component class that is accessed via the Model variable. The view for a view component has to be saved in the Views\Shared\Components\<component-name>\Default.cshtml file.

As an example, let's build a view component that shows the sidebar of a blog. The component class just calls an external repository to fetch the list of links.

Code Listing 5-18

using System.Threading.Tasks;

using Microsoft.AspNetCore.Mvc;

using MvcSample.Services;

namespace MvcSample.ViewComponents

{

    public class SideBarViewComponent: ViewComponent

    {

        private readonly ILinkRepository db;

        public SideBarViewComponent(ILinkRepository repository)

        {

            db = repository;

        }

        public async Task<IViewComponentResult> InvokeAsync(int max=10)

        {

            var items = await db.GetLinks().Take(max);

            return View(items);

        }

    }

}

Notice that it receives the dependency in the constructor, as shown for controllers in ASP.NET Core MVC. In this case, since the operation of retrieving the links goes to a database, you implement the Async version of the component.

The next step is implementing the view. Nothing special to mention here, just a simple view that renders a list of links.

Code Listing 5-19

@model IEnumerable<MvcSample.Model.Link>

<h3>Blog Roll</h3>

<ul>

    @foreach (var link in Model)

    {

        <li><a href="@link.Url">@link.Title</a></li>

    }

</ul>

The important thing to remember is where this view is located. Following convention, it must be saved as Views\Shared\Components\Sidebar\Default.cshtml.

Now it’s time to include the component in a view. This is done by simply calling the view component using the Razor syntax.

Code Listing 5-20

@await Component.InvokeAsync("SideBar", new {max = 5})

This is a bit convoluted, especially the need to create an anonymous class just for passing the arguments. But there is also another way of calling a view component as if it were a Tag Helper. All view components are also registered as Tag Helpers with the prefix vc.

Code Listing 5-21

<vc:side-bar max="5"></vc:side-bar>

Apart from being easier to write, this also implements IntelliSense in the HTML editor.

IntelliSense for Tag Helpers

Figure 5-16: IntelliSense for Tag Helpers

Razor Pages

If you have to create dynamic pages but you don't want to use MVC, or you are looking to use a different approach, ASP.NET Core 2.x introduces a new way to create dynamic markup. It is called Razor Pages, and it uses the powerful Razor engine, without the part related to the model and controller that you already saw in the MVC section.

Since it is built on top of MVC, the first thing to be sure of is that MVC is registered and configured into your application exactly in the same way you do for a classic model-view-controller application:

Code Listing 5-22

public class Startup

{

    public void ConfigureServices(IServiceCollection services)

    {

        services.AddMvc();

    }

    public void Configure(IApplicationBuilder app)

    {

        app.UseMvc();

    }

}

Now the application is almost ready to host some Razor Pages, but in order to do so, you have to follow some conventions.

The first one is where the pages are located. You have to put them into a folder called Pages in the root of your application. To help with this, if you are using Visual Studio, you have a helpful context menu when you right-click on the folder.

Follow these steps to create your first Razor Page:

“Add Razor Page” Menu Item

Figure 5-17: “Add Razor Page” Menu Item

Add Scaffold Page Dialog

Figure 5-18: Add Scaffold Page Dialog

Add Razor Page Dialog

Figure 5-19: Add Razor Page Dialog

After going through these steps, an empty page is created in the Pages folder. If you now run the project, you can see your page in the browser. The Index page shows up by default; otherwise, you have to write the name of your page without the extension in the URL http://localhost:5000/myRazorPage.

Before going on and creating something more useful than a blank page, go back to the Solution Explorer and take a look at the file called Index.cshtml.cs.

It is somewhat reminiscent of the code-behind used with Web Forms, but it is not the same. In fact, if you open the .cs file, you don't have all the things you had with Web Forms (Page_Load and so on). This .cshtml.cs file contains the PageModel class.

../markdown/fw/assets/razor-pages/004.png

5-20: PageModel class

If we have to make a comparison, we could say that this file is like a combo between the MVC controller and its model. If you read the MVC part, you surely remember the logic behind the controller, where you create an array of users and return that model to the view. With a Razor Page, it’s not much different.

Code Listing 5-23

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace Syncfusion.Asp.Net.Core.Succinctly.WebAppStartup.Pages

{

    public class IndexModel: PageModel

    {

        public User[] Users {get;set;}

        

        public void OnGet()

        {

            Users = new[]

            {

                new User() {Id = 1, Firstname = "Ugo", Lastname = "Lattanzi", Twitter = "@imperugo"},

                new User() {Id = 2, Firstname = "Simone", Lastname = "Chiaretta", Twitter = "@simonech"},

            };;

        }

    }

}

Now go back a few pages and compare it to the code in Code Listing 5-6; you will notice a few changes. The code is inside an OnGet method, while with MVC it is implemented in the Index action method of the HomeController. Also, the Index method had the attribute [HttpGet] to indicate which action has to be executed for an HTTP GET verb. As you can imagine, with Razor Pages, the name of the method serves the same function as the attribute used with ASP.NET MVC. The OnGet method is executed for GET requests. If you have to handle POST, the method would be OnPost, and so on for all other verbs.

So, the name of the method changes, as well as the way to pass the model to the view. In MVC you create a ViewModel object and return it with the View method, while with a Razor Page you just assign it to a property of the PageModel.

The Razor markup is exactly the same because the only difference between the two frameworks is how the view is instantiated.

Code Listing 5-24

@page

@model Pages.IndexModel

@{

    Layout = null;

}

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="utf-8">

    <title>Hello World</title>

</head>

<body>

    <h1>Users</h1>

    <div>

        @foreach (var user in Model.Users)

        {

            <hr />

            <p>Firstname: @user.Firstname</p>

            <p>Lastname: @user.Lastname</p>

            <p>Twitter: <a href="http://www.twitter.com/@user.Twitter"> @user.Twitter</a></p>

        }

    </div>

</body>

</html>

The routing is based on the physical location of the file inside the Pages folder, so it’s slightly different from how it works in ASP.NET MVC.

Table 3: Sample Files and their URLs

File name and path

Matching URL

/Pages/Index.cshtml

/ or /Index

/Pages/Contact.cshtml

/Contact

/Pages/Products/Index.cshtml

/Products or /Products/Index

/Pages/Info/Contact.cshtml

/Info/Contact

We can summarize by saying that the folder Pages never appears in the URL, and the structure of the URL is made by the structure of folders and files created inside the Pages folder.

This is only an introduction to Razor Pages, just like it was for ASP.NET MVC. If you want to go more in depth into the topic, you can do so with the official documentation.

Single-page applications

Single-page applications (SPAs) are a very hot topic, and it is now possible to create both Angular-based and React-based applications directly from within Visual Studio, and also from the dotnet CLI.

With Visual Studio you have the option of choosing the template inside the New ASP.NET Core Web Application window as shown in Figure 5-21.

New ASP.NET Core Web Application

Figure 5-21: New ASP.NET Core Web Application

With the dotnet CLI, you can create an SPA by choosing one of the SPA templates:

  • angular
  • react
  • reactredux

Whichever method of creation you choose, you get an application that uses the newly introduced feature called JavaScriptServices.

JavaScriptServices

JavaScriptServices is a set of features that helps use client-side technologies within ASP.NET Core applications. For example, it can be used, among other things, to run JavaScript on the server interoperating with Node.js, to automatically build client-side assets using Webpack, and to simplify the usage of various SPA frameworks (like Angular, React, and more).

The Angular application template

By choosing the Angular template you get a sample application with a few pages, one of which connects to a Web API. Figure 5-22 shows the structure of the application.

SPA with Angular and ASP.NET Core

Figure 5-22: SPA with Angular and ASP.NET Core

The ClientApp folder contains the whole Angular application, created using the Angular CLI. This way, you can work on Angular as if you were using a normal client-only Angular application, even without using Visual Studio. And if you are used to building Angular applications using the CLI, you can still do it.

The Controllers folder contains the controllers, like any other Web API application.

The Pages folder contains the few server-side pages that might be needed, like, in the sample, the Error page. They are just Razor pages because such a simple page doesn’t require the full view pages structure to be created.

With the structure set up like this, now you can start easily developing your SPA, as the ASP.NET part is just like any other ASP.NET Web API.

SignalR

Sitting next to SPAs, there is another class of applications, called “real-time applications,” where data is pushed from the server to the browser without any operation performed by the user.

This kind of interaction is possible thanks to a feature of HTML5, implemented nowadays in all modern browsers, called a WebSocket, which allows a full duplex connection between server and client.

ASP.NET Core has a framework that helps you build these kinds of applications in an easy and reliable way. This framework is called SignalR Core. Unfortunately, real-time applications are pretty complicated, and even a simple introduction would fill half of this book. However, you can find more information on SignalR in the official ASP.NET Core documentation.

Conclusion

ASP.NET Core comes with a rewritten MVC framework, improved from the previous version and aimed at being the unified programming model for any kind of web-based interaction.

You’ve seen the cool new features introduced for simplifying the development of views. This is the last chapter that explains coding. The next two chapters are more about tooling, demonstrating how to deploy apps and how to develop on Mac without Visual Studio.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.