Technical lingo is a blessing and a curse. It helps us communicate within our community more effectively and precisely but has the downside of making it difficult for newcomers. Thus, it’s essential to clarify what terms mean within a given domain. In this post, we’ll explore what developers mean when using the name ** Model** within the context of ASP.NET Core and its different sub-frameworks like ASP.NET Core MVC, Razor Pages, Endpoints, and Blazor.

If you’re a beginner, you’ll learn everything you need to know about Models and work with the professionals in no time. Let’s get started.

What Is A Model?

In the .NET stack, the term Model refers to any object within our application representing a logical entity within our problem domain. The exact term of ** Model** originates from the software design pattern known as MVC or Model-view-controller.

It [the Model] is the application’s dynamic data structure, independent of the user interface. It directly manages the data, logic, and rules of the application. Wikipedia

As stated in the Wikipedia definition, the Model usually is responsible for state changes and business logic rules. Model definitions can range from plain old C# objects (POCOs) to types associated with data access frameworks such as Entity Framework.

The general rule of thumb for a Model is that it exists internally within the context of our application, and direct access by the user is limited by a user interface layer. User interface layers include but are not limited to HTTP API layers, HTML and HTTP calls, and native operating system interfaces.

Adding A Model To ASP.NET Core MVC

As mentioned previously, the term Model is integral to the MVC pattern. Starting a new ASP.NET Core MVC application, we are presented with a folder structure with a suggested location to place our model definitions.

|- Controllers
|- Models
|- Views

Within the Models folder, we can create a new class definition directly at the root or within a new nested folder.

Solution Explorer view of ASP.NET Core MVC app

Let’s add a new Person model. In this case, we aren’t tying it to a data access mechanism, but if you’re interested in doing so, please read my other Entity Framework related posts or ask questions below in the comments.

namespace WebApplication.Models
{
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}

Finally, we can use our new Person model within a Controller action. In the following example, we pass our newly created Model to the View method, which will allow us to utilize the Model to render the information to our clients.

public IActionResult Index()
{
    return View(new Person { 
        Id = 1, 
        Name = "Khalid" 
    });
}

What’s The Difference Between Models and ViewModels?

New ASP.NET Core developers may notice an ErrorViewModel in the Models folder of their newly created application. The immediate question might be:

What’s a ViewModel, and how is it different than a Model? –Curious Leaner

Great question! Both a Model and ViewModel are definitions within our application domain. The difference between the two is their intended purpose. As mentioned in a previous section, models can have logic and state management interfaces, and we can think of as exposing *“expensive”* operations. Operations may include calling a database, third-party service, or general I/O access.

On the other hand, ViewModels only expose data or logic that operates on existing properties within the instance. We may also hear ViewModels compared to Data Transfer Objects (DTOs), an object meant to transfer information from one location to another, usually a projection of existing data retrieved from a model. The ErrorViewModel is an excellent example of this approach.

public class ErrorViewModel
{
    public string RequestId { get; set; }

    public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

We can see that in this ViewModel, we have a property for RequestId and read-only property of ShowRequestId that encapsulates some basic logic.

There’s usually a clear distinction between a Model and ViewModel, but like most things in life, some codebases might stretch these definitions to solve the problem at hand. Understanding these definitions allows us to experiment and fine-tune models and viewmodels within our solutions.

Adding A Model To Razor Pages

Models get a little fuzzy in Razor Pages based applications. Starting a new Razor Pages application, we’ll notice the folder structure is different than our ASP.NET Core MVC application. We see we only have one Pages directory.

Solution Explorer view of ASP.NET Core Razor Pages app

Here we can decide our particular course of action. We can treat our Pages as models in themselves, create a new folder named Models as we had in our MVC-based application, or go a different approach entirely.

A good rule of thumb is still to create a Models folder and add our problem domain entities separate from our Razor Pages PageModel types. Also, we can logically treat our Razor Pages as ViewModels, with the exception that our PageModel might have logic to hydrate property values from our models.

To add a new Model to a Razor Pages application, we first need to create a Models folder, after which we can add our classes. This approach is necessary when dealing with data access libraries such as Entity Framework or Dapper.

In this example, we’ve added a Person model to our project and utilize it in our IndexPageModel. Note how our PageModel has a Name property, which we assign on every request to the page. Looking at the MVC example above, you might notice the similarity. In the Razor pages case, we can think of the VC part of MVC as handled by the PageModel.

public class IndexModel : PageModel
{
    private readonly ILogger<IndexModel> _logger;
    private readonly Person _person;
    
    public string Name { get; set; }

    public IndexModel(ILogger<IndexModel> logger, Person person)
    {
        _logger = logger;
        _person = person;
    }

    public void OnGet()
    {
        Name = _person.Name;
    }
}

Razor Pages creates a situation where we can accomplish a lot, if not everything, within a single page. Approaches relying solely on PageModel can be both beneficial and problematic. Benefits include centralized logic, a smaller codebase, and straightforward implementations. Problems include tight-coupling, code duplication, and potential code bloat. Again, we must assess the problem we’re solving and take into account future goals.

Adding A Model To ASP.NET Core Endpoints

ASP.NET Core has seen an evolution to its routing infrastructure, mainly being separated from ASP.NET Core MVC and functioning independently of any framework. In the latest versions of ASP.NET Core, we can map endpoints directly into the routing infrastructure. This approach has trade-offs, with the idea of more specialized HTTP endpoints that favor performance over the flexibility and robustness of MVC, which can add operational overhead.

Like Razor Pages, we are free to choose our approach to add Models to our solution. Still, a good rule of thumb is to create a Models folder and add our problem domain entities there. Let’s work with the Person model again, but now within an endpoint registered using MapGet.

Remember to register any models retrieved through RequestServices into ASP.NET Core’s dependency injection container in the ConfigureServices method found in Startup.

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/", async (context) =>
    {
        var person = context.RequestServices.GetService<Person>();
        
        await context.Response.WriteAsJsonAsync(new
        {
            greetings = $"Hello, {person.Name}"
        });
    });
});

Here, we may be tempted to return our Person model by passing it to the WriteAsJsonAsync. Remember, Models generally have internal logic associated with them, and returning them directly inadvertently leak sensitive information. It’s better to lean on DTOs and be deliberate with what we send to our clients.

Adding A Model To Blazor Server-Side

The Blazor server app development model is similar to that of Razor Pages. Instead of dealing with PageModel types, we now deal with Blazor components. These files are easy to distinguish as they have the .razor extension within our solution.

Unlike Razor Pages, Blazor applications start with a Data folder, where we can add our Models. The default Blazor template starts with two types in the Data folder: WeatherForecast and WeatherForecastService.

public class WeatherForecast
{
    public DateTime Date { get; set; }
    public int TemperatureC { get; set; }
    public int TemperatureF => 32 + (int) (TemperatureC / 0.5556);
    public string Summary { get; set; }
}

public class WeatherForecastService
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    public Task<WeatherForecast[]> GetForecastAsync(DateTime startDate)
    {
        var rng = new Random();
        return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = startDate.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        }).ToArray());
    }
}

Within our FetchData.razor file, we utilize these models within our @code block. Below we can see ASP.NET Core injecting the WeatherForecastService Model into our Blazor component. I’ve excluded the HTML for brevity.

@page "/fetchdata"

@using WebApplication2.Data
@inject WeatherForecastService ForecastService

// HTML goes here

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.Now);
    }
}

As with Razor Pages, our components act as a combination of VC in the MVC pattern. We can accomplish many things within the @code block but should likely keep most of our rules and logic within the Model. As always, reflecting on our problems will help us determine the best course to take when working with Blazor.

Conclusion

Developers want not when choosing an approach with ASP.NET Core. We have many options, from robust frameworks like ASP.NET Core MVC, Razor Pages, and Blazor, to straightforward performance-driven strategies like Endpoints. We should remember that Models are more of a concept than a hard-set rule. This post outlined common approaches to add models, but code is flexible, and we can reevaluate our choices based on our needs. In truth, there’s no “wrong” place to put models, but it helps to understand where we might want to add them as our application scope grows.

I hope you enjoyed this post, and if you’re a beginner learning ASP.NET Core for the first time, please feel free to follow me on Twitter @buhakmeh and ask questions. I’m here to help folks on their journey.