Out of all the new capabilities C# 9 brings, records are my favorite. With positional syntax, they are immutable by default, which makes working with data classes a snap. I love the possibility of maintaining mutable state in C# where appropriate, like for business logic, and maintaining immutability (and data equality!) with records.

And did you know that with ASP.NET Core 5, model binding and validation supports record types?

In the last post about OpenAPI support in ASP.NET Core 5, I used a sample project that worked with three very simple endpoints (or controllers): Bands, Movies, and People. Each model was in its own class, like this:

Band.cs:

using System.ComponentModel.DataAnnotations;

namespace HttpReplApi.Models
{
    public class Band
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }
    }
}

Movie.cs:

using System.ComponentModel.DataAnnotations;

namespace HttpReplApi.Models
{
    public class Movie
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }
        public int ReleaseYear { get; set; }

    }
}

Person.cs:

using System.ComponentModel.DataAnnotations;

namespace HttpReplApi.Models
{
    public class Person
    {
        public int Id { get; set; }

        [Required]
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

I can simplify these with using records instead. In the root, I’ll just create an ApiModels.cs file (I could have them in the controllers themselves, but that feels … messy):

using System.ComponentModel.DataAnnotations;

namespace HttpReplApi
{
    public record Band(int Id, [Required] string Name);
    public record Movie(int Id, [Required] string Name, int ReleaseYear);
    public record Person(int Id, [Required] string FirstName, string LastName);
}

After I change my SeedData class to use positional parameters, I am good to go—this took me about 90 seconds.

For some fun, if I grab a movie by ID, I can use the deconstruction support to get out my properties. (I’m using a discard since I’m not doing anything with the first argument, the Id.)

[HttpGet("{id}")]
public async Task<ActionResult<Movie>> GetById(int id)
{
    var movie = await _context.Movies.FindAsync(id);

    if (movie is null)
    {
        return NotFound();
    }

    var (_, name, year) = movie;
    _logger.LogInformation($"We have {name} from {year}");

    return movie;
}

Tags:

Updated:



Level up with The .NET Stacks Newsletter

If you enjoy my content, consider subscribing to The .NET Stacks, my weekly newsletter. It isn't a link blast! I go in-depth on news and trends, interview leaders in the community, and allow you to catch up with one resource.

    I don't do spam and will never share your address. Unsubscribe at any time.

    Leave a comment