C
C#•10mo ago
TrustyTea

Minimal API : Model / DTO Help

Hello! I'm new here and I'm looking for some help on my project. 🙂 I'm working on building a website for my portfolio. I've got a Blazor frontend, and I'm using a minimal API for my backend. I have models for BlogPosts, Projects and Tags. I am also using DTOs for each. I decided to use DTOs to prevent cyclical relationships but I keep running into them. I'm quite new to this side of .NET and using Databases. I've been at this for a while and having a hard time getting it to work how I'd like it to. From my BlogEndpoints, I'd like my GET requests to get all my blogs from the database, which is what it does currently, but it shows me only the IDs of the Tags and Projects. I think showing just the ID for the Project is fine since I'll be able to use the id in a link using said ID. I'd like to have a List<Tag> Tags in each of my BlogPost so that I can just iterate through and do something like this on my Blazor end :
foreach(Tag tag in blogPost.Tags){
<TagItem Data=tag/>
}
foreach(Tag tag in blogPost.Tags){
<TagItem Data=tag/>
}
I'm sure there are better ways, but that's why I came here to ask for some advice. If I can figure out how to fix this on my Blog endpoint, I'll be able to fix it on my Project endpoint as well. Here are the Model and DTO for my BlogPost, as well as my GET and POST request for my endpoint
namespace BackEndAPI.DTOs
{
public class BlogPostDTO {
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Summary { get; set; }
public int? ProjectId { get; set; }
public List<int> TagIds { get; set; } = new List<int>();
}
}
namespace BackEndAPI.DTOs
{
public class BlogPostDTO {
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Summary { get; set; }
public int? ProjectId { get; set; }
public List<int> TagIds { get; set; } = new List<int>();
}
}
namespace BackEndAPI.Models
{
public class BlogPost {
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Summary { get; set; }
[Required]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;

public int? ProjectId { get; set; }
[JsonIgnore]
public Project? Project { get; set; }
[JsonIgnore]
public List<Tag> Tags { get; set; } = new();
}
}
namespace BackEndAPI.Models
{
public class BlogPost {
public int Id { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Body { get; set; }
[Required]
public string Summary { get; set; }
[Required]
public DateTime CreatedOn { get; set; } = DateTime.UtcNow;

public int? ProjectId { get; set; }
[JsonIgnore]
public Project? Project { get; set; }
[JsonIgnore]
public List<Tag> Tags { get; set; } = new();
}
}
private static async Task<IResult> GetBlogs(ApplicationDbContext db)
{
var blogPosts = await db.Blogs.Include(b => b.Tags).ToListAsync();
return Results.Ok(blogPosts);
}

private static async Task<IResult> GetBlogById(int id, ApplicationDbContext db)
{
var blogPost = await db.Blogs.SingleOrDefaultAsync(b => b.Id == id);
return Results.Ok(blogPost);
}

private static async Task<IResult> CreateBlog(BlogPostDTO dto, ApplicationDbContext db)
{
var tags = await db.Tags.Where(t => dto.TagIds.Contains(t.Id)).ToListAsync();
var project = await db.Projects.Where(p => dto.ProjectId == p.Id).ToListAsync();
var blogPost = new BlogPost
{
Title = dto.Title,
Body = dto.Body,
Summary = dto.Summary,
ProjectId = dto.ProjectId,
Project = project[0],
Tags = tags,
};

db.Blogs.Add(blogPost);
await db.SaveChangesAsync();
return Results.Created($"/blogs/{blogPost.Id}", blogPost);
}
private static async Task<IResult> GetBlogs(ApplicationDbContext db)
{
var blogPosts = await db.Blogs.Include(b => b.Tags).ToListAsync();
return Results.Ok(blogPosts);
}

private static async Task<IResult> GetBlogById(int id, ApplicationDbContext db)
{
var blogPost = await db.Blogs.SingleOrDefaultAsync(b => b.Id == id);
return Results.Ok(blogPost);
}

private static async Task<IResult> CreateBlog(BlogPostDTO dto, ApplicationDbContext db)
{
var tags = await db.Tags.Where(t => dto.TagIds.Contains(t.Id)).ToListAsync();
var project = await db.Projects.Where(p => dto.ProjectId == p.Id).ToListAsync();
var blogPost = new BlogPost
{
Title = dto.Title,
Body = dto.Body,
Summary = dto.Summary,
ProjectId = dto.ProjectId,
Project = project[0],
Tags = tags,
};

db.Blogs.Add(blogPost);
await db.SaveChangesAsync();
return Results.Created($"/blogs/{blogPost.Id}", blogPost);
}
Maybe DTOs aren't needed. Maybe I should do away with the List<Tag> Tags in my model and just use a List<int> TagIds instead and then ask the database what each Id corresponds to. I'm not sure which direction I should head. Any and all feedback and help would be greatly appreciated 🙂
20 Replies
Angius
Angius•10mo ago
Why do yout database models have [JsonIgnore] if you're using DTOs?
TrustyTea
TrustyTeaOP•10mo ago
I was running into cycles and thought that might help. It might have been left over from a few things I was trying and forgot to remove it after I changed up the Model & DTO.
Angius
Angius•10mo ago
You should be returning a DTO from your endpoints As it stands, none of them do that
Pobiega
Pobiega•10mo ago
the only DTO I see is the incomming DTO in CreateBlog, which isnt usually what we mean when we say "DTO" in C# land
Angius
Angius•10mo ago
var project = await db.Projects.Where(p => dto.ProjectId == p.Id).ToListAsync();
var project = await db.Projects.Where(p => dto.ProjectId == p.Id).ToListAsync();
also, you can use .FirstOrDefaultAsync() instead, to avoid the Project = project[0],
Pobiega
Pobiega•10mo ago
thats just a normal request object
TrustyTea
TrustyTeaOP•10mo ago
Ahh okay, maybe I'll spend a little more time to learn about DTO's then. I thought they were for incoming objects and not for outgoing but it does make sense. Thanks for the tip on the .FirstOrDefaultAsync() too!
Pobiega
Pobiega•10mo ago
its actually mostly for outgoing we use them
Angius
Angius•10mo ago
To avoid cycles you want to .Select() into a DTO, that allows you to omit the properties that would cause a cycle issue
TrustyTea
TrustyTeaOP•10mo ago
So I'd be creating a new DTO in the requests, and populating it with the data from the database? I had something like this before I went to what I have now:
private static async Task<IResult> GetBlogs(ApplicationDbContext db) {
var blogDtos = await db.Blogs
.Include(b => b.Tags)
.Select(b => new BlogDTO
{
Id = b.Id,
Title = b.Title,
Summary = b.Summary,
Body = b.Body,
CreatedOn = b.CreatedOn,
Tags = b.Tags.Select(t => new TagDTO { Id = t.Id, Name = t.Name }).ToList(),
AssociatedProjectId = b.AssociatedProjectId
})
.ToListAsync();
return Results.Ok(blogDtos);
}
private static async Task<IResult> GetBlogs(ApplicationDbContext db) {
var blogDtos = await db.Blogs
.Include(b => b.Tags)
.Select(b => new BlogDTO
{
Id = b.Id,
Title = b.Title,
Summary = b.Summary,
Body = b.Body,
CreatedOn = b.CreatedOn,
Tags = b.Tags.Select(t => new TagDTO { Id = t.Id, Name = t.Name }).ToList(),
AssociatedProjectId = b.AssociatedProjectId
})
.ToListAsync();
return Results.Ok(blogDtos);
}
I'll have to go back to this and figure it out now that I know to use DTOs as outgoing too. IIRC I wasn't getting the Tags in the response, just the IDs
Pobiega
Pobiega•10mo ago
yep that looks about right at a glance
Angius
Angius•10mo ago
But you're getting blogs... twice For some reason
Pobiega
Pobiega•10mo ago
oh yep :p change it to just return the blogDtos
TrustyTea
TrustyTeaOP•10mo ago
Ah oops that's a copy error :P, that's not actually there sorry
Angius
Angius•10mo ago
private static async Task<IResult> GetBlogs(ApplicationDbContext db) {
var blogDtos = await db.Blogs
- .Include(b => b.Tags)
.Select(b => new BlogDTO
{
Id = b.Id,
Title = b.Title,
Summary = b.Summary,
Body = b.Body,
CreatedOn = b.CreatedOn,
Tags = b.Tags.Select(t => new TagDTO { Id = t.Id, Name = t.Name }).ToList(),
AssociatedProjectId = b.AssociatedProjectId
})
.ToListAsync();
- var blogPosts = await db.Blogs.ToListAsync();
- return Results.Ok(blogPosts);
+ return Results.Ok(blogDtos);

}
private static async Task<IResult> GetBlogs(ApplicationDbContext db) {
var blogDtos = await db.Blogs
- .Include(b => b.Tags)
.Select(b => new BlogDTO
{
Id = b.Id,
Title = b.Title,
Summary = b.Summary,
Body = b.Body,
CreatedOn = b.CreatedOn,
Tags = b.Tags.Select(t => new TagDTO { Id = t.Id, Name = t.Name }).ToList(),
AssociatedProjectId = b.AssociatedProjectId
})
.ToListAsync();
- var blogPosts = await db.Blogs.ToListAsync();
- return Results.Ok(blogPosts);
+ return Results.Ok(blogDtos);

}
And no need for an include with a select
TrustyTea
TrustyTeaOP•10mo ago
Oh ok, is it because I have this line? Tags = b.Tags.Select(t => new TagDTO { Id = t.Id, Name = t.Name }).ToList(), Or the .Select() right below the .Include()?
Angius
Angius•10mo ago
In general But I guess more so because of the select into blogpost DTO You explicitly select some parts of the tags, so there's no need to include all of them in their entirety
TrustyTea
TrustyTeaOP•10mo ago
I see 🤔 , yeah that makes sense. I also need to practice and learn some more LINQ. Thanks for the help! Just knowing that I should be using DTOs as part of my responses is enough of a direction to get me started on it. If I have some more questions, am I ok to come back to this thread?
Pobiega
Pobiega•10mo ago
sure
TrustyTea
TrustyTeaOP•10mo ago
Sweet thank you! 😄
Want results from more Discord servers?
Add your server