Chapter 4: The MVC Pattern using Razor

The Model View Controller (MVC) pattern is probably one of the most extensively adapted architectural patterns for displaying web user interfaces. Why? Because it matches the concept behind HTTP and the web almost to perfection, especially for a typical server-rendered web application. For page-oriented applications, Razor Pages can also be a contestant to this claim.

From the old ASP.NET MVC to ASP.NET Core, the MVC framework is cleaner, leaner, faster, and more flexible than ever before. Moreover, dependency injection is now built-in at the heart of ASP.NET, which helps leverage its power. We will be covering dependency injection in greater depth in Chapter 7, Deep Dive into Dependency Injection.

MVC is an opt-in feature now, like pretty much everything else. You can opt-in MVC, Razor Pages, or web APIs and configure them with only a few statements. The ASP.NET pipeline is based on a series of middleware that can be leveraged to handle cross-cutting concerns, such as authentication and routing.

This chapter is essential for any developer desiring to create ASP.NET Core 5 MVC applications. Those technologies and the patterns learned in this chapter are used throughout the book. We are building web APIs more than Razor, but the MVC framework remains the backbone of both.

In this chapter, we will cover the following topics:

  • The Model View Controller design pattern
  • MVC using Razor
  • The View Model design pattern

The Model View Controller design pattern

When using ASP.NET Core 5 MVC, there are two types of applications that developers can build.

  • The first use is to display a web user interface, using a classic client-server application model where the page is composed on the server. The result is then sent back to the client. To build this type of application, you can take advantage of Razor, which allows developers to mix C# and HTML to build rich user interfaces elegantly. From my perspective, Razor is the technology that made me embrace MVC in the first place when ASP.NET MVC 3 came out in 2011.
  • The second use of MVC is to build web APIs. In a web API, the presentation (or the view) becomes a data contract instead of a user interface. The contract is defined by the expected input and output, like any API. The most significant difference is that a web API is called over the wire and acts as a remote API. Essentially, inputs and outputs are serialized data structures, usually JSON or XML, mixed with HTTP verbs such as GET and POST. More on that in Chapter 5, The MVC Pattern for Web APIs.

One of the many nice facets of ASP.NET Core 5 MVC is that you can mix both techniques in the same application without any additional effort. For example, you could build a complete website and a full fledged web API in the same project.

MVC using Razor

Let's explore the first type of application, the classic server-rendered web user interface. In this kind of application using MVC, you divide the application into three distinct parts, where each has a single responsibility:

  • Model: The model represents a data structure, a representation of the domain that we are trying to model.
  • View: The view's responsibility is to present a model to a user, in our case, as a web user interface, so mainly HTML, CSS, and JavaScript.
  • Controller: The controller is the key component of MVC. It plays the coordinator role between a request from a user to its response. The code of a controller should remain minimal and should not include complex logic or manipulation. The controller's primary responsibility is to handle a request and dispatch a response. The controller is an HTTP bridge.

If we put all of that back together, the controller is the entry point of every request, the view composes the response (UI), and both share the model. The model is fetched or manipulated by the controller, and then sent to the view for rendering. The user then sees the requested resource in their browser.

The following diagram illustrates the MVC concept:

Figure 4.1 – MVC Workflow

We can interpret Figure 4.1 as the following:

  1. The user requests an HTTP resource (routed to an action of a controller).
  2. The controller reads or updates the model to be used by the view.
  3. The controller then dispatches the model to the view for rendering.
  4. The view uses the model to render the HTML page.
  5. That rendered page is sent to the user over HTTP.
  6. The user's browser displays the page, like any other web page; it is only HTML after all.

Next, we are looking into ASP.NET Core MVC itself, how the directories are organized by default, what controllers are, and how routing works. Then, we will dig into some code before improving on those default features using view models.

Directory structure

The default ASP.NET Core MVC directory structure is very explicit. There is a Controllers directory, a Models directory, and a Views directory. By convention, we create controllers in the Controllers directory, models in the Models directory, and views in the Views directory.

That said, the Views directory is a little different. To keep your project organized, each controller has its own subdirectory under Views. For example, the views for HomeController are found in the Views/Home directory.

The Views/Shared directory is a particular case. The views in that subdirectory are accessible by all other views and controllers alike; they are shared views. We usually create global views, such as layouts, menus, and other similar elements, in that directory.

Structure of a controller

The easiest way to create a controller is to create a class inheriting from Microsoft.AspNetCore.Mvc.Controller. By convention, the class's name is suffixed by Controller, and the class is created in the Controllers directory. This is not mandatory.

That base class adds all of the utility methods that you should need in order to return the appropriate view, like the View() method.

Once you have a controller class, you need to add actions. Actions are public methods that represent the operations that a user can perform.

More precisely, the following defines a controller:

  • A controller exposes one or more actions.
  • An action can take zero or more input parameters.
  • An action can return zero or one output value.
  • The action is what handles the actual request.
  • We can group cohesive actions under the same controller, thus creating a unit.

For example, the following represents the HomeController class containing a single Index action:

public class HomeController : Controller

{

    public IActionResult Index() => View();

}

The HomeController.Index action is called by default when sending a GET / request to the server. In that case, it returns the Home/Index.cshtml view, without further processing or any model manipulation. Let's now explore why.

Default routing

To know which controller should handle a specific HTTP request, ASP.NET Core 5 has a routing mechanism that allows developers to define one or more routes. A route is a URI template that maps to C# code. Those definitions are created in the Configure() method of the Startup class. By default, MVC defines the following pattern: "{controller=Home}/{action=Index}/{id?}".

The first fragment is about controllers, where the following applies:

  • {controller} maps the request to a {controller}Controller class. For example, Home would map to the HomeController class.
  • {controller=Home} means that the HomeController class is the default controller, which is used if no {controller} is supplied.

The second fragment is about actions:

  • {action} maps the request to a controller method (the action).
  • Like its controller counterpart, {action=Index} means that the Index method is the default action. For example, if we had a ProductsController class in our application, making a GET request to https://somedomain.tld/Products would make MVC invoke the ProductsController.Index() method. As a side note, TLD means top-level domain, such as .com, .net, and .org.

    Note

    In a CRUD controller (Create, Read, Update, Delete), Index is where you usually define your list.

The last fragment is about an optional id parameter:

  • {id} means that any value following the action name maps to the parameter named id of that action method.
  • The ? in {id?} means the parameter is optional.

Let's take a look at some examples to wrap up our study of the default routing template:

  • Calling /Some/Action would map to SomeController.Action()
  • Calling /Some would map to SomeController.Index()
  • Calling / would map to HomeController.Index()
  • Calling /Some/Action/123 would map to SomeController.Action(123)

    Endpoint routing

    From ASP.NET Core 2.2 onward, the team introduced a new routing system, named endpoint routing. If you are interested in advanced routing or in extending the current routing system, that would be an excellent starting point.

Project: MVC

Let's explore some code created using VS Code and the .NET CLI. The project aims at visiting certain MVC concepts.

Reminder

To generate a new MVC project, you can execute the dotnet new mvc command or the dotnet new web command, depending on how much boilerplate code you want in your project. In this case, I used dotnet new web and added the files myself to create a leaner project, but both are viable options.

The HomeController class defines the following actions. This should give you an overview of what we can do with ASP.NET Core 5 MVC:

  • Index
  • ActionWithoutResult
  • ActionWithoutResultV2Async
  • DownloadTheImageAsync
  • ActionWithSomeInput
  • ActionWithSomeInputAndAModel

Index

The Index() method is the default action and, in this case, the home page. It returns a view that contains some HTML. The view uses other actions to generate URLs. The view is defined in the Views/Home/Index.cshtml file.

The C# code to load the view is as simple as the following:

public IActionResult Index() => View();

In the preceding code snippet, the Controller.View() method tells ASP.NET Core to render the default view associated with the current action. By default, it loads Views/[controller]/[action].cshtml, where [controller] is the name of the controller without the Controller suffix, and [action] is the name of the action method.

The view itself looks like this:

@{

    ViewData["Title"] = "Home Page";

    var imageUri = Url.Action("ActionWithoutResultV2");

}

<p>Use the left menu to navigate through the examples.</p>

<h2>The result of ActionWithoutResultV2</h2>

<figure>

    <img src="@imageUri" alt="ASP.NET Core logo" />

    <figcaption>

        The result of <em>ActionWithoutResultV2</em> displayed using the following HTML markup:

        <code>&lt;img src="@imageUri" alt="ASP.NET Core logo" /></code>.

    </figcaption>

</figure>

<p>You can download the image by clicking <a href="@Url.Action("DownloadTheImage")">here</a>.</p>

We will talk about the download and image links later, but besides that, the view only contains basic HTML written using Razor.

Expression-bodied function members (C# 6)

In the previous example, we used an expression-bodied function member, which is a C# 6 feature. It allows a single statement method to be written without curly braces by using the arrow operator.

Before C# 6, public IActionResult Index() => View(); would have been written like this:

public IActionResult Index()

{

    return View();

}

The arrow operator can also be applied to read-only properties, as follows:

public int SomeProp => 123;

Instead of the previous, more explicit, method of rendering:

public int SomeProp

{

    get

    {

        return 123;

    }

}

ActionWithoutResult

The ActionWithoutResult() method does nothing. But if you navigate to /Home/ActionWithoutResult using a browser, it displays a blank page with an HTTP status code of 200 OK, which means the action was executed successfully.

This is not very UI-centric applications, but it is a case supported by ASP.NET Core 5:

public void ActionWithoutResult()

{

    var youCanSetABreakpointHere = "";

}

There is no view associated with this action. Next, we add some usefulness to this type of action.

ActionWithoutResultV2Async

As a more complex example, we could use this type of action to write to the HTTP response stream manually. We could load a file from disk, make some modifications in memory, and write that updated version to the output stream without saving it back to disk. For example, we could be updating an image that is dynamically rendered or pre-filling a PDF form before sending it to the user.

Here is an example:

public async Task ActionWithoutResultV2Async()

{

    var filePath = Path.Combine(

        Directory.GetCurrentDirectory(),

        "wwwroot/images/netcore-logo.png"

    );

    var bytes = System.IO.File.ReadAllBytes(filePath);

    //

    // Do some processing here

    //

    Response.ContentLength = bytes.Length;

    Response.ContentType = "image/png";

    await Response.Body.WriteAsync(bytes, 0, bytes.Length);

}

The preceding code loads an image and then manually sends it to the client using HTTP; no MVC magic is involved. Then we can load that image using the following markup in a Razor page or view:

<img src="@Url.Action("ActionWithoutResultV2")" />

There are two important details here that are easy to miss:

  1. From .NET Core 3.0 onward, we cannot do Response.Body.Write(…); we must use Response.Body.WriteAsync(…) instead (hence the asynchronous method).
  2. We must remove the Async suffix when calling the Url.Action(…) helper.

By using an action without a result, we could force the downloading of the image or do many other interesting things. I recommend studying HTTP if you are not already familiar with it. It should help you understand what can be done and how. Sometimes, you don't need the full power of MVC, and some use cases can be trivialized by using the lower-level APIs, such as the HttpResponse class available through the ControllerBase.Response property.

Routing (endpoint to delegate)

When you need that kind of action, you can also define your endpoint manually by mapping a URI to a delegate without the need to create a controller. You don't even need MVC.

A downside of this technique is that you need to do the model binding and everything else by hand as your delegate is not run inside the MVC pipeline, so no MVC magic.

The following example defines two GET endpoint; one with a route parameter and one without:

public class Startup

{

    public void ConfigureServices(IServiceCollection services) { }

    public void Configure(IApplicationBuilder app)

    {

        app.UseRouting();

        app.UseEndpoints(endpoints =>

        {

            endpoints.MapGet("/", async context =>

            {

                await context.Response.WriteAsync("Hello World!");

            });

            endpoints.MapGet("/echo/{content}", async context =>

            {

                var text = context.Request.Path.Value.Replace("/echo/", "");

                await context.Response.WriteAsync(text);

            });

        });

    }

}

This is a great feature worth studying, for small applications, demos, microservices, and more. I suggest creating a similar app to play with it if you are interested.

DownloadTheImageAsync

As a follow-up example, to download the image previously rendered by the ActionWithoutResultV2Async action, you only need to add a Content-Disposition header (that's HTTP, not MVC/.NET). The new method looks like this:

public async Task DownloadTheImageAsync()

{

    var filePath = Path.Combine(

        Directory.GetCurrentDirectory(),

        "wwwroot/images/netcore-logo.png"

    );

    var bytes = System.IO.File.ReadAllBytes(filePath);

    //

    // Do some processing here

    //

    Response.ContentLength = bytes.Length;

    Response.ContentType = "image/png";

    Response.Headers.Add("Content-Disposition", "attachment; filename="netcore.png"");

    await Response.Body.WriteAsync(bytes, 0, bytes.Length);

}

Now, if you call that action from a page, the browser prompts you to download the file instead of displaying it.

Here is an example from the home page:

<p>You can download the image by clicking <a href="@Url.Action("DownloadTheImage")">here</a>.</p>

As stated in the previous example, this uses basic elements of HTTP.

ActionWithSomeInput

The ActionWithSomeInput method has an id parameter and renders a simple view, with that number displayed on the page. The code of the action appears as follows:

public IActionResult ActionWithSomeInput(int id)

{

    var model = id;

    return View(model);

}

The code of the view is as follows:

@model int

@{

    ViewData["title"] = "Action with some input";

}

<h2>This action has an input</h2>

<p>The input was: @Model</p>

When following the link from the menu (/Home/ActionWithSomeInput/123), we can see the MVC pattern in action:

  1. MVC is routing the call to HomeController, requesting the ActionWithSomeInput action, and binding the value 123 to the id parameter.
  2. In ActionWithSomeInput, the id parameter is assigned to the model variable.

    This step serves only to be explicit and demonstrates that we are passing a model to the view. We could have passed the id parameter directly to the View method. For example, we could have simplified the action as follows: public IActionResult ActionWithSomeInput(int id) => View(id);.

  3. Then, the model variable is dispatched to the ActionWithSomeInput view for rendering, returning the result to the user.
  4. Then, MVC renders the view, ActionWithSomeInput.cshtml, by means of the following:

    a) It implicitly uses the default layout (Shared/_Layout.cshtml) set in ViewStart.cshtml.

    b) It uses the inputted model, typed using the @model int directive at the top of the view.

    c) It renders the value of the model in the page using the @Model property; notice the different casing of both m and M (see below).

    @Model and @model

    Model is a property from Microsoft.AspNetCore.Mvc.Razor.RazorPage<TModel> of type TModel. Each view in ASP.NET Core MVC inherits from RazorPage, which is where all of those "magic properties" come from. @model is a Razor directive allowing us to strongly type a view (in other words, the TModel type parameter). I do recommend strongly typing all of your views unless there is no way to do so, or if it's not using a model (such as Home/Index). In the background, your Razor files are compiled into C# classes, which explains how the @model directive becomes the value of the TModel generic parameter.

ActionWithSomeInputAndAModel

As the last action method, ActionWithSomeInputAndAModel takes an integer parameter, named id, as input, but instead of sending it directly to the view, it sends a SomeModel instance. This is a better simulation of a real-life application, where objects are used to share and persist information, such as using a unique identifier (an id parameter) to load a record from the database, and then sending that data to the view for rendering (in this case, there is no database):

public IActionResult ActionWithSomeInputAndAModel(int id)

{

    var model = new SomeModel

    {

        SelectedId = id,

        Title = "This title was set in HomeController!"

    };

    return View(model);

}

When calling this action, the flow is the same as the previous one. The only exception is that the model is an object instead of an integer. The streamlined flow is as follows:

  1. The HTTP request is routed to the controller's action.
  2. The controller manipulates the model (in this case, creates it).
  3. The controller dispatches the model to the view.
  4. The view engine renders the page.
  5. The page is sent back to the client that requested it.

Here is the view displaying the Title and the SelectedId properties of the model:

@model MVC.Models.SomeModel

@{

    ViewData["title"] = Model.Title;

}

<h2>This action has an input and uses a model</h2>

<p>The input was: @Model.SelectedId</p>

From the preceding code, we can see that we are using the Model property to access the anonymous object that we passed to the View method in our action.

Conclusion

We could talk about MVC for the remainder of the book, but we would be missing the point. This chapter and the next aim at covering as many possibilities as possible to help you understand the MVC pattern and ASP.NET Core, but nothing too in-depth, just an overview.

We covered different types of actions and the basic routing mechanism, which should be enough to continue. We use ASP.NET Core 5 throughout the book, so don't worry, we will cover many other aspects, in different contexts. Next, we improve the MVC pattern a bit.

View Model design pattern

The View Model pattern is used when building server-rendered web applications using Razor and can be applied to other technologies. Typically, you access data from a data source and then render a view based on that data. That is where the view model comes into play. Instead of sending the raw data directly to the view, you copy the required data to another class that carries only the required information to render that view, nothing more.

Using this technique, you can even compose a complex view model based on multiple data models, add filtering, sorting, and much more, without altering your data or domain models. Those features are presentation-centric and, as such, the view model responsibility is to meet the view's requirements in terms of presentation of the information, which is in line with the Single Responsibility Principle, which we explored in Chapter 3, Architectural Principles.

We could even see the last example, ActionWithSomeInputAndAModel, as a crude implementation of the View Model pattern. You get an int as input and output a model for the view, a view model.

Goal

The goal of the View Model pattern is to create a model, specific to a view, decoupling the other parts of the software from the view. As a rule of thumb, you want each view to be strongly typed with its own view model class to make sure that views are not coupled with each other, thereby causing possible maintenance issues in the long run.

View models allow developers to gather data in a particular format and send it to the view in another format that's better suited for the rendering of that specific view. That improves the application's testability, which in turn should increase the overall code quality and stability.

Design

Here is a revised MVC workflow that supports view models:

Figure 4.2 – MVC workflow with view models

We can interpret Figure 4.2 as the following:

  1. The user requests an HTTP resource (routed to an action of a controller).
  2. The controller reads or updates the model.
  3. The controller creates the view model (or uses a view model created elsewhere).
  4. The controller dispatches that data structure to the view for rendering.
  5. The view uses the view model to render the HTML page.
  6. That rendered page is sent to the user over HTTP.
  7. The browser displays the page like any other web page.

Have you noticed that the model is now decoupled from the view?

Project: View models (a list of students)

Context: We have to build a list of students. Each list item must display a student's name and the number of classes that student is registered in.

Our graphic designers came up with the following Bootstrap 3 list with badges:

Figure 4.3 – Students list with their number of classes

Figure 4.3 – Students list with their number of classes

To keep things simple and to create our prototype, we load in-memory data through the StudentService class.

It is important to remember that the view model must only contain the required information to display the view. The view model classes, located in StudentListViewModels.cs, looks like the following:

public class StudentListViewModel

{

    public IEnumerable<StudentListItemViewModel> Students { get; set; }

}

public class StudentListItemViewModel

{

    public int Id { get; set; }

    public string Name { get; set; }

    public int ClassCount { get; set; }

}

That is one of the scenarios where keeping more than one class in the same file makes sense, hence the plural filename. That said, if you can't stand having multiple classes in a single file, feel free to split them up in your project.

Note

In a larger application, we could create subdirectories or use namespaces to keep our view model classes name unique and organized, for example, /Models/Students/ListViewModels.cs.

Another alternative is to create view models in a /ViewModels/ directory instead of using the default /Models/ one.

We could also create the view model classes as nested classes under their controller. For example, a StudentsController class could have a nested ListViewModel class, callable like this: StudentsController.ListViewModel.

These are all valid options. Once again, do as you prefer and what suits your project best.

The StudentController class is the key element and has an Index action that handles GET requests. For each request, it fetches the students, creates the view model, and then dispatches it to the view. Here is the controller code:

public class StudentsController : Controller

{

    private readonly StudentService _studentService = new StudentService();

    public async Task<IActionResult> Index()

    {

        // Get data from the datastore

        var students = await _studentService.ReadAllAsync();

        // Create the View Model, based on the data

        var viewModel = new StudentListViewModel

        {

            Students = students.Select(student => new StudentListItemViewModel

            {

                Id = student.Id,

                Name = student.Name,

                ClassCount = student.Classes.Count()

            })

        };

        // Return the View

        return View(viewModel);

    }

}

The view renders the students as a Bootstrap list-group, using a badge to display the ClassCount property, as defined by our initial specifications.

@model StudentListViewModel

@{

    ViewData["Title"] = "Students";}

<h2>Students list</h2>

<ul class="list-group">

    @foreach (var item in Model.Students)

    {

        <li class="list-group-item">

            <span class="badge">@Html.DisplayFor(modelItem => item.ClassCount)</span>

            @ Html.DisplayFor(modelItem => item.Name)

        </li>

    }

</ul>

With the preceding few lines of code and the View Model pattern, we decoupled the Student model from the view by using an intermediate class, StudentListViewModel, which is composed of a list of StudentListItemViewModel. Moreover, we limited the amount of information passed to the view by replacing the Student.Classes property with the StudentListItemViewModel.ClassCount property, which only contains the required information to render the view (the number of classes a student is in).

Project: View models (a student form)

Context: Now that we have a list, some clever person at the company thought that it would be a good idea to be able to create and update students; makes sense, right?

To achieve that, we have to do the following:

  1. Create a GET action named Create.
  2. Create a POST action that handles the student creation.
  3. Add a Create view.
  4. Add a view model named CreateStudentViewModel.
  5. Repeat steps 1 to 4 for the Edit view.
  6. Add some navigation links to the list.

After some thinking, we figured that it would be way better to reuse the same form for both views. For simplicity's sake, our student model is minimal, but, in a real-world application, it would most likely not be the case. So, extracting that form should help us with maintenance in the long run, if both forms are the same.

Note

In another case, if both forms are different, I'd suggest you keep them separated to avoid breaking one when updating the other.

From a technical standpoint, this requires the following:

  • A partial view shared by both views.
  • A StudentFormViewModel class shared by both Create and Edit view models.

From here, to build our view models, we have two options:

  • Inheritance
  • Composition

As with everything in the software engineering world, both options have their advantages and drawbacks. Composition is the most flexible technique and is the most widely used throughout the book. When facing a dilemma, I recommend composition over inheritance. That said, inheritance can also be a valid option, which is why I decided to demo both techniques. We will first implement the Create view using inheritance, and then the Edit view using composition. Let's take a look at the differences, starting with the view model classes.

The CreateStudentViewModel class inherits from StudentFormViewModel (inheritance):

// StudentFormViewModels.cs

public class CreateStudentViewModel : StudentFormViewModel { }

The EditStudentViewModel class has a property of the StudentFormViewModel type instead (composition):

public class EditStudentViewModel

{

    public int Id { get; set; }

    public IEnumerable<string> Classes { get; set; }

    public StudentFormViewModel Form { get; set; }

}

The StudentFormViewModel class represents the form itself that we share between the Create and Edit views:

public class StudentFormViewModel

{

    public string Name { get; set; }

}

Next, let's have a look at the StudentsController GET actions:

public IActionResult Create()

{

    return View();

}

public async Task<IActionResult> Edit(int id)

{

    var student = await _studentService.ReadOneAsync(id);

    var viewModel = new EditStudentViewModel

    {

        Id = student.Id,

        Form = new StudentFormViewModel

        {

            Name = student.Name,

        },

        Classes = student.Classes.Select(x => x.Name)

    };

    return View(viewModel);

}

In the preceding code, the Create action returns an empty view model. The Edit action loads a student from the database and copy the needed information in its view model, before sending that view model to the edit view. Next, let's take a look at the views, starting by the partial view.

A partial view is a smaller view that can be rendered as part of other views. By default, Razor passes the view's Model property to a partial view. In this case, since CreateStudentViewModel inherits from StudentFormViewModel, the model is directly sent to _CreateOrEdit as a StudentFormViewModel (polymorphism);

// Create.cshtml

@model CreateStudentViewModel

...

<partial name="_CreateOrEdit" />

...

We will be exploring partial views more in Chapter 17, ASP.NET Core User Interfaces.

Reminder

Polymorphism is one of the core concepts behind object-oriented programming. It represents the ability of an object of a given type to be considered an instance of another type. For example, all subclasses of ClassA (say ClassB and ClassC) can be used as ClassA and themselves. So, ClassB is a ClassB and a ClassA.

For the Edit view, since EditStudentViewModel does not inherit from StudentFormViewModel but has a property of that type named Form instead, we need to specify that as a result of using the for attribute, as follows:

// Edit.cshtml

@model EditStudentViewModel

...

<partial name="_CreateOrEdit" for="Form" />

...

The for attribute lets us specify the model to pass to the partial view; in our case, the EditStudentViewModel.Form property.

Next, is the content of the partial view:

// _CreateOrEdit.cshtml

@model StudentFormViewModel

<div class="form-group">

    <label asp-for="Name" class="control-label"></label>

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

    <span asp-validation-for="Name" class="text-

     danger"></span>

</div>

As you can see in the preceding code, regardless of the option chosen, it does not change the partial view itself, just the consumers (the Create and Edit views and the structure of the view models in our case). The consumers must adapt themselves to the model defined by _CreateOrEdit partial view. If there is a mismatch, an error is thrown at runtime. This means that you have the flexibility to use either or both techniques in your application. Choose what best fits your needs.

In this precise use case, it is important to note that both the Create and Edit pages are tightly coupled over the shared part of the form. It can be very convenient as long as both forms are identical. For most data-oriented user interfaces using CRUD operations, I like to reuse forms like this as it helps save time. For simple view models, I would go for inheritance since we don't have to write the for attribute in that case. For more complex or modular view models containing many properties that need a partial view, I would start by exploring composition to make sure all partial views use a for attribute (linearity). The goal here was to demo the possibilities so you can use a technique or the other in a different scenario.

Composition versus inheritance

For that matter, I'd say that inheritance is more natural at first, but it can be harder to maintain in the long term. Composition is the most flexible of the two techniques. While composition leads to a more reusable and flexible design, it also creates complexity, especially when abstractions come into play. Composition helps follow the SOLID principles.

When using inheritance, you must make sure that the sub-class is-a parent-class. Do not inherit an unrelated class to reuse some elements of it or some implementations details. We talk more about composition in subsequent chapters.

Conclusion

If you are still uncertain or perplexed about this pattern, we use view models and other similar concepts throughout the book. That said, when you choose a pattern over another, it is essential to review the requirements of that specific project or feature as it dictates whether your choice is rational.

For example, if the following applies to your project:

  • It is a simple data-driven user interface, tightly coupled with a database.
  • It has no logic.
  • It is not going to evolve.

In that case, the use of view models may only add development time to your project, while you could have Visual Studio almost scaffold it all for you instead. Nothing stops you from using a view model or two when you need it, such as when creating a dashboard or some more complex views.

For more complex projects, I recommend using view models by default. If you are still uncertain or perplexed about this pattern, we are exploring multiple ways to build applications as a whole later in the book, which should help you.

Summary

In this chapter, we explored one part of ASP.NET Core 5 MVC, which allows us to create rich web user interfaces with Razor and C#.

We saw how to decouple the model from the presentation, using view models. View models are classes specially crafted around a view or a partial view. For example, rather than passing a data model to a view, and letting the view do some calculations, you instead do the calculation on the server side and pass just the results to the view. This way, the view only has one responsibility: displaying the user interface, the page.

Finally, we elaborated on the fact that it is imperative to reduce the tight coupling of our components in our systems, which follow the SOLID principles.

In the next few chapters, we will explore the web API counterpart to the MVC and View Models patterns. We will also look at our first Gang of Four (GoF) design patterns and deep dive into ASP.NET Core 5 dependency injection. All of that will push us further down the path of designing better applications.

Questions

Let's take a look at a few practice questions:

  1. What is the role of the controller in the MVC pattern?
  2. What Razor directive indicates the type of model that a view accepts?
  3. With how many views should a view model be associated?
  4. Can a view model add flexibility to a system?
  5. Can a view model add robustness to a system?

Further reading

Routing in ASP.NET Core: https://net5.link/YHVJ

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.64.128