Blog for Junior Developers C#/.NET

Thursday, November 07, 2024

If you are starting to learn programming in ASP.NET Core, you may encounter various problems at the beginning, but it is worth learning from mistakes, not only your own, and drawing conclusions from them. This is the best way to learn. In this article, I will present you with 8 mistakes that beginner programmers make when writing applications in ASP.NET Core. I hope that thanks to this material you will not repeat them in your applications.

8-common-aspdotnet-core-beginner-mistakes.jpg

Mistake 1: Too large actions in controllers


Your actions in controllers should be as small as possible, you should only validate the sent data, then call some method with logic, e.g. from some service or application layer, preferably using mediatr, and return the appropriate view with the model based on this data. Do not place any additional code here, do not create any logic, conditional statements or loops inside the controller action, it is not its responsibility.

Bad:

public IActionResult Contact()
{
    var model = _context.Params.ToList();

    if (model == null)
        return RedirectToAction("Contact");

    if (model.First().IsDisabled)
    {
        foreach (var item in model)
        {
            item.Date = _dateTimeService.Now;
        }
    }

    var availableTemplates = _context.Templates.ToList();

    ViewBag.Title = "Contact";

    var vm = new ContactVm
    {
        model = model,
        availableTemplates = availableTemplates
    };

    return View(vm);
}

Good:

public IActionResult Contact()
{
    return View(new SendContactEmailCommand());
}


Mistake 2: Logic in views


There should be no logic in views that we create in razor in MVC. If you want to retrieve some data, perform some other logic, the view should not do it. This logic should be performed earlier in the application layer and passed from the controller to the view using the model or view model. Because of the logic performed in the view layer, our application becomes less readable and the so-called spaghetti code is created.

Bad:

Client.cshtml

@model ClientDo

@{
    ViewData["Title"] = "Client";
}

@if (Model.Id == 0)
{
    <div>Adding a customer</div>
}
else
{
    <div>Editing customer data</div>
}

Good:

Client.cshtml

@model ClientVm

@{
    ViewData["Title"] = "Client";
}

<div>@Model.Title</div>


Mistake 3: No DTO objects


Remember not to return entire domain models to the user. A much better practice is to create DTO objects in such a case. The user does not need all the information about our model. Return only what is actually needed in the view or in the API. Thanks to DTO objects, you have more control over what will be returned from the action and you can prepare and adjust all the data accordingly.

Bad:

public class Client
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public DateTime RegistrationDate { get; set; }
    public bool IsDeleted { get; set; }
    public bool IsActive { get; set; }
    public List<Order> Orders { get; set; }
    public Address Address { get; set; }
}

public class GetClientQuery : IRequest<Client>
{
}

public async Task<IActionResult> Dashboard()
{
    return View(await Mediator.Send(new GetClientQuery()));
}

Good:

public class ClientDto
{
    public string FullName { get; set; }
    public DateTime DateOfBirth { get; set; }
    public bool IsActive { get; set; }
}

public class GetClientQuery : IRequest<ClientDto>
{
}

public async Task<IActionResult> Dashboard()
{
    return View(await Mediator.Send(new GetClientQuery()));
}


Mistake 4: Bad data passing to the view


If you want to pass more data than just the model from the controller to the view, you can do it in several ways. That is, you can pass the model directly, and additional data, e.g. via ViewBag or ViewData. However, this is not the best solution, it introduces some chaos in the code, so a much better solution in such a case would be to create a dedicated view model and pass all the data via the view model. They are ideal for such cases, which is why we should use it. We can easily refer to the view model in our view later.

Bad:

public class GetClientDashboardQuery : IRequest<ClientDto>
{
}

public async Task<IActionResult> Dashboard()
{
    ViewBag.Title = "Client dashboard";
    ViewBag.Now = _dateTimeService.Now;
    ViewBag.City = "Warsaw";
    ViewBag.Email = "kazik@codewithkazik.com";
    return View(await Mediator.Send(new GetClientDashboardQuery()));
}

Good:

public class GetClientDashboardQuery : IRequest<GetClientDashboardVm>
{
}

public async Task<IActionResult> Dashboard()
{
    return View(await Mediator.Send(new GetClientDashboardQuery()));
}


Mistake 5: No user data validation


You can never trust the user of your application and at every step you should check the data they entered. Always validate the data they enter. If the data does not meet the validation conditions, instead of throwing an exception or causing some errors, you should display an appropriate message.

Bad:

[HttpPost]
public async Task<IActionResult> Contact(SendContactEmailCommand command)
{
    await Mediator.Send(command);

    return RedirectToAction("Contact");
}

Good:

[HttpPost]
public async Task<IActionResult> Contact(SendContactEmailCommand command)
{
    if (!ModelState.IsValid)
        return View(command);

    await Mediator.Send(command);

    return RedirectToAction("Contact");
}


Mistake 6: Using the wrong controls on the view


This is a fairly common mistake, made mainly by beginners. If you want the user to enter a date, then instead of entering it on a TextBox, for example, let them select it on a dedicated control, in this case a DateTimePicker. In addition to making it easier for users to select a specific date on the calendar, you will also prevent various validation errors. Similarly, if you want the user to enter a true/false value, display a CheckBox or Switch, if they have to enter a number or amount, also display the appropriate control, TextBox is not always the right control for entering all data.


Mistake 7: Bad use of HttpClient


The best way in an ASP.NET Core application is to use HttpClient via a factory, i.e. use the IHttpClientFactory interface. If you create a new HttpClient object in the standard way without using a factory, this creates 2 problems. First, objects are not properly disposed of, and second, I can cause resource exhaustion, because each object actually creates a new socket and even using will not immediately free this socket. If you wanted to create an HttpClient object as a singleton or static, then in turn there may be problems with an outdated DNS, so the only good solution is to use IHttpClientFactory, thanks to which we will avoid these errors.

Bad:

HttpClient httpClient = new HttpClient();

using (HttpClient httpClient = new HttpClient())
{
}

Good:

HttpClientFactory

services.AddHttpClient<SomInterface, SomeClass>();

public SomeClass(HttpClient httpClient)
{
    _httpClient = httpClient;
}


Mistake 8: Waiting for unnecessary and long-lasting operations


If the user wants to perform some action, after which some long-lasting operation must be called, and additionally the result of its operation is not important to him, then you should perform such an operation in the background and do not make the user wait for the operation to finish. Thanks to this, his request will not hang for a few seconds, and the application will work smoothly. When can this be used? For example, when the user has performed some action, and additionally an email is sent to the administrator. In some libraries, the wait for sending an email can be about a second, so there is no need for the user to wait for the task to finish. Simply finish the request and send the email in the background. In ASP.NET Core, you can achieve this in several ways, e.g. by implementing a class inheriting from BackgroundService and adding it to the application as HostedService.

public interface IBackgroundWorkerQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
}

public class LongRunningService : BackgroundService
{
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        //code
    }
}

services.AddHostedService<LongRunningService>();
services.AddSingleton<IBackgroundWorkerQueue, BackgroundWorkerQueue>();

_backgroundWorkerQueue.QueueBackgroundWorkItem(async x =>
{
    await _email.SendAsync(title, admin, to);

});


ASP.NET Core School


If you are interested in topics related to creating professional web applications in ASP.NET Core, consider joining: ASP.NET Core School. This is an advanced, practical ASP.NET Core MVC + REST API training for C#/.NET Developers. The training consists of 14 extensive modules. In the training, we create a complete application in ASP.NET Core from the first lines of code to cloud implementation (more here).

Author of the article:
Kazimierz Szpin

KAZIMIERZ SZPIN
Software Developer C#/.NET, Freelancer. Specializes in ASP.NET Core, ASP.NET MVC, ASP.NET Web API, Blazor, WPF and Windows Forms.
Author of the blog CodeWithKazik.com

Previous article - Fluent Assertions in Unit Testing
Next article - 12 Best Practices in ASP.NET Core MVC
Dodaj komentarz
© Copyright 2024 CodeWithKazik.com. All rights reserved. Privacy policy.
Design by Code With Kazik and Modest Programmer.