C
C#ā€¢3y ago
ero

ā” Creating a backend in .NET

Hi! I have absolutely zero knowledge about web development, databases, or making any kinds of web requests. Basically, I'm a complete noob when it comes to literally anything web-related (and I know databases aren't necessarily web, but you get the idea). I would like to build a backend for a website idea that's already pretty big on paper. I have a lot of ideas, have a lot of thoughts about how things should work, I just can't create it. I was wondering if there are any very beginner friendly, modern tutorials on this stuff? Preferrably videos, as I have a hard time focussing on reading a lot. If I should go into any more detail, let me know.
770 Replies
Yawnder
Yawnderā€¢3y ago
@Ero Break your problem pieces by pieces, as you do with everything. Ignore the database aspect of it. Start with the API project template (the one that has a Weather controller endpoint) and see how you can modify it to understand. Something important for you to understand is the life cycle of an http request. How all the middleware are linked together. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
What kind of frontend are you making for this? If its a JS based one like React, Vue, Svelte or whatever they are called these days, your backend will likely be a pure web API where all requests and responses are JSON (if any content at all). This matters because ASP.NET can do a lot of different things, including generating and serving up HTML (MVC, Razor pages), so you'll want to focus your attention on the aspects of it that you need.
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
TS, JS, Vue
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
Great, then a bog standard ASP.NET Web API will be your best bet. Do you know how basic HTTP stuff works, with the request/responses etc?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i do not
Pobiega
Pobiegaā€¢3y ago
super simplified version: Every data exchange begins with the client submitting a request and the server sending a response back. You can only respond once to each request. Each request has a "method" or verb (GET, POST, PUT, DELETE, PATCH) that partially restricts what it can do but also indicates intent. You can have a webserver.com/hello endpoint respond only to GET, or GET and POST. with ASP.NET its easy to write a method that handles the get, and another method that handles the post. All requests and responses consist of several parts: the URI (path), the headers (a key value pair list) and the body (a stream, but often just JSON) when a request contains data, it can be as part of the URI, aka "query string": www.webserver.com/hello?query=floop <-- query is the parameter, floop is the value. or it can be part of the headers (they are just string-string key value pairs, so you can create them at will) or it can be the body (for apis, very common to accept a JSON object. ASP.NET has excellent support for this, you can make a record/class and just specify it as an input to your method) responses have 3 parts: the response code (indicates success or failure, and what kind of failure), headers, and optionally a body so your "endpoint" (what we call a unique path or route) can respond with 200 OK, or 404 Not Found, or 400 Bad Request, for example.
ero
eroOPā€¢3y ago
were these just some examples or is that all of them? i thought i'd seen SET and FETCH too at some point so, what exactly is a "header"? where does it come from? i understand it's a key-value pair, but it has to be stored somewhere, no? or like, input somewhere
Esa
Esaā€¢3y ago
It's part of the response object. a body contained inside a response object may have headers of its own in addition to the overall response object having some. Headers usually contain information such as the content-type (text/plain, application/json). These things also dictate what you can expect in return, as this is a string and "this is a string" are the result of different content-types (text/plain vs application/json) set in the Content-Type header. There may also be custom headers, some times these are prefixed by x- etc. Webservices don't usually support every http method for every endpoint, it's usually just a few if at all more than one. /api/customers/{customerId} indicates a search for a customer by a specific ID (the customerId variable) inside a collection of customers, so this one is a GET. If you remove the customerId variable it by convention becomes a POST (insert), and you'd be expected to provide the payload inside the body of your request. However that would require the webservice to have a route with a POST (as in, there needs to be an actual explicit endpoint for that path that accepts a POST httpmethod).
Pobiega
Pobiegaā€¢3y ago
It's all the ones you will likely use. There are others thou, such as HEAD, TRACE, OPTIONS, CONNECT Have not used any of them ever So yeah headers is just part of the protocol and the http client has some it adds by default, and same for the response ( added by the server) Here is a sample GET request I just googled
GET /hello.htm HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.tutorialspoint.com
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
GET /hello.htm HTTP/1.1
User-Agent: Mozilla/4.0 (compatible; MSIE5.01; Windows NT)
Host: www.tutorialspoint.com
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
you wont be making these yourself quite as raw as this, as most http clients do most of the job for you for example, if you are gonna use the fetch api you literally just need the method and the URL most of the time the three most common methods are GET, POST and PUT. GET is for querying data. Sending a GET to api/customers would, if supported, return you some kind of list of customers. GET to api/customers?category=3&startsWith=steve would do similar, but filter based on the query string parameters (assuming the backend was implemented as such, but this is convention) GET to api/customers/5 would get you data about customer number 5 POST adds to collections or submits general data. POST to api/customers would register a new customer based on the body data, and return either the whole customer or just the ID (as it was assigned by the backend). PUT is "create or replace at the given id", so you could PUT to api/customers/5 to edit(replace) that customer record. PATCH is for making smaller edits than a full on replace, but its tricky to implement right. A fairly common thing to use is "jsonpatch" which has a way to indicate what to replace with what, but dont think System.Text.Json supports it just yet.
ero
eroOPā€¢3y ago
in this fashion, would api/customers?id=5 be bad practice?
Pobiega
Pobiegaā€¢3y ago
It would not follow convention, but is technically valid The nice thing about using it as part of the URL is that you can keep going. Let's say that customers have users, you could show them at api/customers/5/users/63 etc
ero
eroOPā€¢3y ago
yeah, this'll likely be a struggle point for me choosing what to put into the url, and what to put into the query
Pobiega
Pobiegaā€¢3y ago
Welcome to the family šŸ™‚ I tend to use url parameters for IDs and "hard" identifiers, and query strings for "filtering" multiple choices url params are obviously not optional, while query strings usually are
ero
eroOPā€¢3y ago
i'll start setting everything up later today. i'm excited, but i'm scared i won't get far. i have a hard time learning through reading, and finding complete, modern, best practice examples is obviously impossible, because a lot of it is too personal
Pobiega
Pobiegaā€¢3y ago
Yup, very much so. Not to mention that "best pratice" changes every few weeks, šŸ˜„
ero
eroOPā€¢3y ago
truuue
Pobiega
Pobiegaā€¢3y ago
So now that we've got most of the theory out of the way, ASP is actually really nice to work with. It handles most of the stuff for you
ero
eroOPā€¢3y ago
i know that you use source gen for your stuff and it's like. yes. i want that. but i don't understand source generators for shit
Pobiega
Pobiegaā€¢3y ago
It has two modes thou, old-school controllers and the more modern (and highly divisive) minimal apis where endpoints are literally a single method, usually a lambda
ero
eroOPā€¢3y ago
i believe a minimal API is all i need as well? hm, maybe not
Pobiega
Pobiegaā€¢3y ago
I think with .net 7 you can do almost everything with either approach
ero
eroOPā€¢3y ago
the project is big. like thousands of users with a massive amount of DB data big
Pobiega
Pobiegaā€¢3y ago
minimal has better performance, but its not like controllers are terribly slow or anything it all comes down to code style preference
ero
eroOPā€¢3y ago
(i already know this, because it's gonna be a competitor to an existing site which basically went to shit under new management)
Pobiega
Pobiegaā€¢3y ago
with controllers, you tend to make stuff like...
[Route("api/customers")]
public class CustomersController : ControllerBase
{
[HttpGet]
// no route, so it matches the "root" route of the controller
public List<Customer> GetAll() { ... }

[HttpPost]
// no route, so it matches the "root" route of the controller
public CustomerDto AddCustomer(CreateCustomerDto customer) { ... }
}
[Route("api/customers")]
public class CustomersController : ControllerBase
{
[HttpGet]
// no route, so it matches the "root" route of the controller
public List<Customer> GetAll() { ... }

[HttpPost]
// no route, so it matches the "root" route of the controller
public CustomerDto AddCustomer(CreateCustomerDto customer) { ... }
}
I actually kind of like minimal apis, but its a little bit more work getting it set up in a nice way. Just throwing api methods together in program.cs gets messy real fast (once you have hundreds of endpoints for example) so you need to add your own abstraction to separate the mapping into different files with logical responsibilities and registration of dependencies There are ofc packages that do this already, such as FastEndpoints (which I almost love, but it demands that your models are where T : new() which rules out records), and writing your own is like... 10 minutes of effort
ero
eroOPā€¢3y ago
i mean, i don't mind that. what i just care about is doing it "properly". in such a way that makes it easy to use and especially to maintain and expand
Pobiega
Pobiegaā€¢3y ago
sure dont think there is a problem with that then if you make a boilerplate asp.net app with and without controllers you can compare them
ero
eroOPā€¢3y ago
and also to make it as extensive as possible. like i want a very rich and intuitive api the competitor site's api is absolutely abyssmal and has zero documentation
Pobiega
Pobiegaā€¢3y ago
hm, so the API is not just for your frontends use? like, you plan to actually support external users interacting with it directly? Then you will 100% want to read up on API versioning
ero
eroOPā€¢3y ago
yeah, it'll be a public api for fetching heaps of data don't know if you remember, but it's that leaderboards project. there will be hundreds and thousands of gaming leaderboards there will also be hundreds of "series" (a series contains multiple games (leaderboards))
Pobiega
Pobiegaā€¢3y ago
What we do at work, is we have two separate APIs more or less one for our own frontend, and one for external users
ero
eroOPā€¢3y ago
and each leaderboard contains hundreds of submissions with potentially multiple players
Pobiega
Pobiegaā€¢3y ago
the second is heavily versioned and protected against breaking changes, while the first one we can change as we want since we use command pattern internally, most of the external stuff uses the same commands as the internal one, just with a different layer of very strict DTO mappings applied
ero
eroOPā€¢3y ago
hm. so the entire thing (and i mean the entire thing) is gonna be open source is it safe to have the internal API open source?
Pobiega
Pobiegaā€¢3y ago
just make sure you have very good security on it šŸ˜› ie, validate absolutely everything everywhere open source means that hackers wont have to guess for exploits
ero
eroOPā€¢3y ago
right
Pobiega
Pobiegaā€¢3y ago
but it also means you might get issues/PRs to help fix free pentesting, so to speak šŸ˜„
ero
eroOPā€¢3y ago
i don't know whether the split between public and internal makes sense yet like if that's really necessary maybe i'll see as it moves on would you recommend going for the minimal api approach for now?
Pobiega
Pobiegaā€¢3y ago
Test it out, see how it feels. this video from nick explains the "abstraction" level I tend to use for minimal apis
Pobiega
Pobiegaā€¢3y ago
Nick Chapsas
YouTube
In defence of .NET Minimal APIs | Refactoring
Become a Patreon and get source code access: https://www.patreon.com/nickchapsas Check out my courses: https://nickchapsas.com Hello everybody I'm Nick and in this video I wanna address a lot of the critisism that Minimal APIs have been getting in the past few weeks as we are getting closer and closer to the release of .NET 6. In this video I w...
Pobiega
Pobiegaā€¢3y ago
I quite like FastEndpoints too thou, if only it would let you instantiate the response model yourself so records would be usable :((((
ero
eroOPā€¢3y ago
You can add an empty ctor to records, no?
Pobiega
Pobiegaā€¢3y ago
Absolutely but then you can't use the short form definition
ero
eroOPā€¢3y ago
mh, right so, where should i start?
Pobiega
Pobiegaā€¢3y ago
dotnet new webapi šŸ™‚
ero
eroOPā€¢3y ago
hah, fair (and pretty much exactly what i wanted to hear)
Pobiega
Pobiegaā€¢3y ago
It really is that easy to get started. Asp.net is very well made and extremely modular So you start out there with like 4 lines of code and add as you go
ero
eroOPā€¢3y ago
perhaps somewhat unrelated, but i care a lot about project structure let me run by you what i'd do
Pobiega
Pobiegaā€¢3y ago
Makes sense Project structure is important I think tebe recently made a tag for a project scaffold that looked nice. Was it... $scaffolding?
ero
eroOPā€¢3y ago
LeaderboardsGG.Backend
ā”œā”€ .github
ā”‚ ā”œā”€ ISSUE_TEMPLATES
ā”‚ ā”‚ ā””ā”€ ...
ā”‚ ā””ā”€ workflows
ā”‚ ā””ā”€ ...
ā”œā”€ assets
ā”‚ ā””ā”€ ...
ā”œā”€ src
ā”‚ ā”œā”€ LeaderboardsGG.Backend
ā”‚ ā”‚ ā”œā”€ Properties
ā”‚ ā”‚ ā”‚ ā”œā”€ appsettings.json
ā”‚ ā”‚ ā”‚ ā”œā”€ appsettings.Development.json
ā”‚ ā”‚ ā”‚ ā””ā”€ launchSettings.json
ā”‚ ā”‚ ā””ā”€ LeaderboardsGG.Backend.csproj
ā”‚ ā””ā”€ LeaderboardsGG.Backend.Tests
ā”‚ ā””ā”€ LeaderboardsGG.Backend.Tests.csproj
ā”œā”€ .editorconfig
ā”œā”€ .gitignore
ā””ā”€ LeaderboardsGG.Backend.sln
LeaderboardsGG.Backend
ā”œā”€ .github
ā”‚ ā”œā”€ ISSUE_TEMPLATES
ā”‚ ā”‚ ā””ā”€ ...
ā”‚ ā””ā”€ workflows
ā”‚ ā””ā”€ ...
ā”œā”€ assets
ā”‚ ā””ā”€ ...
ā”œā”€ src
ā”‚ ā”œā”€ LeaderboardsGG.Backend
ā”‚ ā”‚ ā”œā”€ Properties
ā”‚ ā”‚ ā”‚ ā”œā”€ appsettings.json
ā”‚ ā”‚ ā”‚ ā”œā”€ appsettings.Development.json
ā”‚ ā”‚ ā”‚ ā””ā”€ launchSettings.json
ā”‚ ā”‚ ā””ā”€ LeaderboardsGG.Backend.csproj
ā”‚ ā””ā”€ LeaderboardsGG.Backend.Tests
ā”‚ ā””ā”€ LeaderboardsGG.Backend.Tests.csproj
ā”œā”€ .editorconfig
ā”œā”€ .gitignore
ā””ā”€ LeaderboardsGG.Backend.sln
is it possible at all to put the appsettings.json into the Properties folder? i don't like having it in the root of the project
Pobiega
Pobiegaā€¢3y ago
Possible, but would require changing the default web host builder a bit Ie, not using the default :p
ero
eroOPā€¢3y ago
fine by me shouldn't be more than 2 lines right?
Pobiega
Pobiegaā€¢3y ago
Eh, probably closer to 10 But still doable
Accord
Accordā€¢3y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
ero
eroOPā€¢3y ago
@Pobiega so the minimal api stuff is pretty clean, but i really don't like how it's set up. it just feels kinda... weird to split everything apart like that. because of that, i'd like to look into source gen. do you think something like this would make sense?
[HttpRepo] // custom attribute to generate .MapGet, .MapPost, etc.
public partial class CustomerRepository
{
[HttpModel] // custom attribute to identify the repo's model
public record Customer(Guid Id, string FullName);

private readonly Dictionary<Guid, Customer> _customers = new();

[HttpGet("/customers")] // not used for anything besides identifying what to generate
public List<Customer> GetAll()
{
return _customers.Values.ToList();
}
}
[HttpRepo] // custom attribute to generate .MapGet, .MapPost, etc.
public partial class CustomerRepository
{
[HttpModel] // custom attribute to identify the repo's model
public record Customer(Guid Id, string FullName);

private readonly Dictionary<Guid, Customer> _customers = new();

[HttpGet("/customers")] // not used for anything besides identifying what to generate
public List<Customer> GetAll()
{
return _customers.Values.ToList();
}
}
Pobiega
Pobiegaā€¢3y ago
Could work for sure. if you just want to generate CRUD stuff for you, that'll do it Might get complicated when you try and add validation of inputs and stuff thou
ero
eroOPā€¢3y ago
the thing is, i don't know if i need anything more in the past, i've only experienced the entity + dto + controller + service way of making a web api which feels a lot more complete
Pobiega
Pobiegaā€¢3y ago
minimal apis only really replace the controller
ero
eroOPā€¢3y ago
and the entity? or the dto i guess?
Pobiega
Pobiegaā€¢3y ago
you still need those. and DTOs too
ero
eroOPā€¢3y ago
but isn't the entity the dto in minimal apis? like aren't they the same thing in this? Customer is both the entity and the dto, no?
Pobiega
Pobiegaā€¢3y ago
in this case yes but that comes with issues what if you want to add some calculated properties to the response object, but not to your database entity? or if you need to add some hidden fields to the entity that should not be shown to the user?
ero
eroOPā€¢3y ago
this could be handled by that HttpModel attribute too i guess
Pobiega
Pobiegaā€¢3y ago
or if you have multiple endpoints using the same entity with different information, like an admin vs a user endpoint
ero
eroOPā€¢3y ago
i feel like minimal apis just aren't it
Pobiega
Pobiegaā€¢3y ago
Controllers are still a thing so you can absolutely just use them if you prefer that
ero
eroOPā€¢3y ago
mh. is there like anything to get me started on this?
Pobiega
Pobiegaā€¢3y ago
the source gen stuff? or just the controllers?
ero
eroOPā€¢3y ago
just, everything else i don't know how to explain it. like i don't know anything about anything web dev related i don't even really know what entities and dtos and controllers and services are i don't know how they work together
Pobiega
Pobiegaā€¢3y ago
right
ero
eroOPā€¢3y ago
i don't know what they do
Pobiega
Pobiegaā€¢3y ago
well lets go though that then, might clear some things up
ero
eroOPā€¢3y ago
i don't know how to test things, how to run things to test them
Pobiega
Pobiegaā€¢3y ago
Entity is your core domain object. its the class that represents the true state of an item you care about its usually saved in your database
ero
eroOPā€¢3y ago
what's a domain object
Pobiega
Pobiegaā€¢3y ago
this project, you said its about game leaderboards right?
ero
eroOPā€¢3y ago
yup i mean i know what my entities are right like User, Leaderboard, Submission
Pobiega
Pobiegaā€¢3y ago
yeah exactly those are your entities. its the stuff you save in your database and your core logic work with
ero
eroOPā€¢3y ago
ok wait wait i wanna write code on the side so i can remember this and since i still care about project structure; do you think entities should be in their own namespace X.Entities?
Pobiega
Pobiegaā€¢3y ago
usually just x.Domain a classlib that contains your entities and little else.
ero
eroOPā€¢3y ago
really an entire project just for the entities
Pobiega
Pobiegaā€¢3y ago
its very common, yes. the reason being what if you want to reuse those entities for a cli tool, or a WPF admin app, or something but you can ofc just put it in a namespace somewhere
ero
eroOPā€¢3y ago
would you call them UserEntity or just User?
Pobiega
Pobiegaā€¢3y ago
User
ero
eroOPā€¢3y ago
would they be records or classes? i believe you've said you use records for everything
Pobiega
Pobiegaā€¢3y ago
depends on what database ORM you use EF for example doesnt like record entities if you use dapper, I think its fine
ero
eroOPā€¢3y ago
still? in net7?
Pobiega
Pobiegaā€¢3y ago
well, EFs primary thing is the change tracker if your entites are immutable... no changes to track :p
ero
eroOPā€¢3y ago
right class it is so, guid or ulong for ids?
Pobiega
Pobiegaā€¢3y ago
I like guids. prevents object enumeration, which is a security flaw
ero
eroOPā€¢3y ago
but i do want users to be enumerated i guess not the entities in particular
Pobiega
Pobiegaā€¢3y ago
in what way? ie, if you see the url: api/user/500 you can try to access api/user/501 too and if my code sucks, perhaps you get in or see some data you shouldnt or even NOT get a 404 which tells you there is a user with that ID
ero
eroOPā€¢3y ago
why's that a problem? i want anyone to be able to GET a user by their id of course the information they get back is limited and if someone wants to GET all users on the site, then go right ahead
Pobiega
Pobiegaā€¢3y ago
alright, if thats a desired trait then go ahead and use ulongs
ero
eroOPā€¢3y ago
so, about usernames. the original idea was to have it be [a-zA-Z0-9_'-]{2,32} but perhaps i don't want usernames like that maybe i want a system like discord
Pobiega
Pobiegaā€¢3y ago
Shouldn't be much of a problem.
ero
eroOPā€¢3y ago
also i've heard that ulong is a problem, but long less so is there any merit to that?
Pobiega
Pobiegaā€¢3y ago
Not sure why ulong would be a problem, but long/int are very common as ID types, even if you never really expect negative ids
ero
eroOPā€¢3y ago
also by this i mean that _'-, if used at all, must be surrounded by alphanum characters so __ or a- don't work for usernames
Pobiega
Pobiegaā€¢3y ago
right. Thats all up to you, not a problem either way
ero
eroOPā€¢3y ago
but also then the user page link will have to contain their id, which is ugly
Pobiega
Pobiegaā€¢3y ago
if you want a discord like system, there are two unique identifiers for a user right the ID, and the "name#number"
ero
eroOPā€¢3y ago
right
Pobiega
Pobiegaā€¢3y ago
I believe the discord API term for the id is the snowflake
ero
eroOPā€¢3y ago
yup
Pobiega
Pobiegaā€¢3y ago
yeah, not very pretty, but also primarily used when its important it cant be changed like in a ban list for a user endpoint, api/users/Ero#1111 would be fine
ero
eroOPā€¢3y ago
is that a valid url? didn't know if you could include the hash
Pobiega
Pobiegaā€¢3y ago
hm right, hash is used for html anchors so probably not
ero
eroOPā€¢3y ago
would it be very difficult to change from usernames to tags later on? i imagine so hm, unless
Pobiega
Pobiegaā€¢3y ago
like going from just ID + name, to ID + tag + name?
ero
eroOPā€¢3y ago
yeah
Pobiega
Pobiegaā€¢3y ago
probably not, as long as you used the ID for all relationships
ero
eroOPā€¢3y ago
right i'll go with normal usernames then for now
namespace Lbgg.Domain;

public sealed class User
{
[Required]
public required ulong Id { get; set; }

[Required]
[MinLength(2)]
[MaxLength(32)]
[RegularExpression(@"^[a-zA-Z0-9][a-zA-Z0-9_'-]*[a-zA-Z0-9]$")]
public required string Name { get; set; }
}
namespace Lbgg.Domain;

public sealed class User
{
[Required]
public required ulong Id { get; set; }

[Required]
[MinLength(2)]
[MaxLength(32)]
[RegularExpression(@"^[a-zA-Z0-9][a-zA-Z0-9_'-]*[a-zA-Z0-9]$")]
public required string Name { get; set; }
}
is this acceptable? should i not be using those attributes? also i just wanna thank you a bunch for doing this with me, i'd be absolutely lost otherwise
Pobiega
Pobiegaā€¢3y ago
I personally dislike attribute validation I prefer something like FluentValidation instead but thats preference my reason being that its not clear WHEN attribute validation takes place
ero
eroOPā€¢3y ago
that's understandable i'll leave the attributes out for now
public sealed class User
{
public required ulong Id { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
public required string Password { get; set; }

public string? About { get; set; }
}
public sealed class User
{
public required ulong Id { get; set; }
public required string Name { get; set; }
public required string Email { get; set; }
public required string Password { get; set; }

public string? About { get; set; }
}
so we're here now
Pobiega
Pobiegaā€¢3y ago
seems good!
ero
eroOPā€¢3y ago
things subject to change of course
Pobiega
Pobiegaā€¢3y ago
ofc
ero
eroOPā€¢3y ago
and you just have this in the root of the .Domain project? not like .Domain.Entities or something?
Pobiega
Pobiegaā€¢3y ago
probably something like the latter, as I tend to eventually add more things to Domain
ero
eroOPā€¢3y ago
sounds good what'd be my next step? i guess at this point i have to decide whether i want a minimal api or not, huh
Pobiega
Pobiegaā€¢3y ago
yup, next step is figuring out what endpoints you want so that means deciding on minimal vs controller
ero
eroOPā€¢3y ago
so, i want my actual user pages to be /u/{username} does that have anything to do with anything? or is that the frontend's job
Pobiega
Pobiegaā€¢3y ago
primarily frontend, but you'll need to consider the data the frontend has if thats the route you want, you'll need an API endpoint that takes in a username and returns a user object
ero
eroOPā€¢3y ago
this would make the switch from usernames to tags more difficult i think
Pobiega
Pobiegaā€¢3y ago
indeed it would or well would it?
ero
eroOPā€¢3y ago
well, not for me
Pobiega
Pobiegaā€¢3y ago
your endpoint would swap to be /u/tag
ero
eroOPā€¢3y ago
but it'd result in a lot of broken links like if someone has their user page linked somewhere
Pobiega
Pobiegaā€¢3y ago
right, true
ero
eroOPā€¢3y ago
argh, really struggling over this using the ID isn't particularly weird, lots of sites do it but i find it ugly and using the username directly is a lot more intuitive and leads to more readable links
Pobiega
Pobiegaā€¢3y ago
well, then go with tags from the start? if its a feature you know you'll want later on
ero
eroOPā€¢3y ago
i'd just gonna go with IDs in the url i think because people can change their tags too right
Pobiega
Pobiegaā€¢3y ago
šŸ‘
ero
eroOPā€¢3y ago
that's the whole point
Pobiega
Pobiegaā€¢3y ago
yep so you'd get broken links anyways
ero
eroOPā€¢3y ago
and if they change their tag, or their numbers, links broken again alright do you have any suggestions on what my next step could be?
Pobiega
Pobiegaā€¢3y ago
Controllers vs minimal apis. Controllers gives you a natural way to group similar endpoints together, but also comes with a little bloat in that every action (endpoint) will have all the dependencies resolved for it so even if only 1 out of 5 endpoints in your controller require the database connection, it will be resolved for all of them (since the controller is the item being resolved)
ero
eroOPā€¢3y ago
does any of this have to do with the speed of the requests? i care a lot about speed
Pobiega
Pobiegaā€¢3y ago
it does. last time I saw numbers, I think it was 11-15% faster with minimal apis
ero
eroOPā€¢3y ago
oh boy
Pobiega
Pobiegaā€¢3y ago
granted, you're still in the "handles 10000 requests per second" bracket without even trying
ero
eroOPā€¢3y ago
oh lol yeah i doubt that's ever gonna happen maybe at peak times i mean i like the source gen approach the one i came up with seemed nice and tidy to me
Pobiega
Pobiegaā€¢3y ago
sounds neat, just worried it might be hard to implement nicely
ero
eroOPā€¢3y ago
yeah...
Pobiega
Pobiegaā€¢3y ago
for example an UpdateUser endpoint might not take in the same DTO as a CreateUser
ero
eroOPā€¢3y ago
mh
Pobiega
Pobiegaā€¢3y ago
where do you want your business logic to live btw? You normally have two options: service classes or command pattern actually its a very similar decision as controller vs minimal api
ero
eroOPā€¢3y ago
i've been wanting to use commands ever since i saw someone (i believe it was you?) use source gen with them as well
Pobiega
Pobiegaā€¢3y ago
hmm Don't remember using source gen with them, but I know you can ie, use a generated dispatcher so there is not even startup cost to initializing it haha, found this while googling: https://github.com/Burgyn/MMLib.MediatR.Generators it generates HTTP endpoints based on your commands ah here it is: https://github.com/martinothamar/Mediator source generated command/message dĆ­spatcher
ero
eroOPā€¢3y ago
namespace Lbgg.Controllers.Users;

[HttpCommand]
public static partial class Create
{
public sealed partial record Command(
ulong Id,
string Name)
{
private static void AddValidation(AbstractValidator<Command> v)
{
v.RuleFor(u => u.Name).NotEmpty().MinimumLength(2).MaximumLength(32);
}
}

private static async Task CommandHandler(
Command command,
ApplicationDbContext dbContext,
ILogger logger)
{
if (await context.Users.AnyAsync(u => u.Id == command.Id))
throw new InvalidOperationException($"User '{command.Id}' already exists.");

var user = new UserEntity
{
UserName = command.UserName,
Id = command.Id,
};

context.Users.Add(user);
await context.SaveChangesAsync();

using (logger.AddProperties(("@NewUserInformation", command)))
logger.LogInformation("New user created");
}
}
namespace Lbgg.Controllers.Users;

[HttpCommand]
public static partial class Create
{
public sealed partial record Command(
ulong Id,
string Name)
{
private static void AddValidation(AbstractValidator<Command> v)
{
v.RuleFor(u => u.Name).NotEmpty().MinimumLength(2).MaximumLength(32);
}
}

private static async Task CommandHandler(
Command command,
ApplicationDbContext dbContext,
ILogger logger)
{
if (await context.Users.AnyAsync(u => u.Id == command.Id))
throw new InvalidOperationException($"User '{command.Id}' already exists.");

var user = new UserEntity
{
UserName = command.UserName,
Id = command.Id,
};

context.Users.Add(user);
await context.SaveChangesAsync();

using (logger.AddProperties(("@NewUserInformation", command)))
logger.LogInformation("New user created");
}
}
this is what someone has posted in the past i saw this and liked it a lot and then you have
public sealed partial class UserController
{
[HttpPost]
public async Task Create(Create.Command command)
{
return await _sender.Send(command);
}
}
public sealed partial class UserController
{
[HttpPost]
public async Task Create(Create.Command command)
{
return await _sender.Send(command);
}
}
Pobiega
Pobiegaā€¢3y ago
Hm, thats neat. And it would generate the Http endpoint I assume
ero
eroOPā€¢3y ago
or something so the Create.Command record would be my Create dto i guess
Pobiega
Pobiegaā€¢3y ago
exactly.
ero
eroOPā€¢3y ago
i don't really know how to approach that yet, even in the slightest so let's just try to write it without any source gen in mind yet
Pobiega
Pobiegaā€¢3y ago
alright
ero
eroOPā€¢3y ago
just the full thing minimal api, commands
Pobiega
Pobiegaā€¢3y ago
šŸ‘
ero
eroOPā€¢3y ago
so first about namespaces. how should i split things up, what do i put where Nick had Repositories, Models and EndpointDefinitions this seems like so much
Pobiega
Pobiegaā€¢3y ago
There are a bunch of different approaches for sure I like to group mine by ... "area", if that makes sense like, lets say I have a few user endpoints
ero
eroOPā€¢3y ago
besides me not even knowing what a repository is in this context
Pobiega
Pobiegaā€¢3y ago
I'd probably stick them in X.Web.Endpoints.Users and that would contain the endpoints themselves, the DTOs, etc possible as subnamespaces
ero
eroOPā€¢3y ago
so let's say, something like this?
namespace Lbgg.Endpoints.Dtos;

public sealed record CreateUser(
string Name,
string Email,
string Password);
namespace Lbgg.Endpoints.Dtos;

public sealed record CreateUser(
string Name,
string Email,
string Password);
Pobiega
Pobiegaā€¢3y ago
yup well I'd scope it down a bit more the CreateUser dto is only ever relevant within the CreateUser endpoint really
ero
eroOPā€¢3y ago
oh, that was supposed to be in Lbgg.Endpoints.Users.Dtos
Pobiega
Pobiegaā€¢3y ago
right then that is perfectly fine there is an interesting idea someone raise a while back, which is just straight up using your commands as DTOs its doable, but comes with the problem that changing your command at all will likely be a breaking API change
ero
eroOPā€¢3y ago
i wonder...
[HttpEndpointGenerator]
public static class UserEndpoints
{
// generates the dto, the controller, and whatever else is needed
public static async Task Create(string name, string email, string password)
{
// create user
}
}
[HttpEndpointGenerator]
public static class UserEndpoints
{
// generates the dto, the controller, and whatever else is needed
public static async Task Create(string name, string email, string password)
{
// create user
}
}
how fun would this be it just like generates
namespace Lbgg.Endpoints.Users;

file static class Create
{
record Command(string Name, string Email, string Password);

static async Task Execute(Command c) => UserEndpoints.Create(c.Name, c.Email, c.Password);
}
namespace Lbgg.Endpoints.Users;

file static class Create
{
record Command(string Name, string Email, string Password);

static async Task Execute(Command c) => UserEndpoints.Create(c.Name, c.Email, c.Password);
}
or something but i mean, i don't even know what you actually need to generate to make it work swagger integration is a pretty important to me too i want very good api documentation
Pobiega
Pobiegaā€¢3y ago
Not a problem, thats available with both minimal APIs and controllers
Pobiega
Pobiegaā€¢3y ago
I'll throw in another recommendation for https://fast-endpoints.com/
FastEndpoints
FastEndpoints
FastEndpoints is a developer friendly alternative to Minimal APIs & MVC for rapid REST API development.
Pobiega
Pobiegaā€¢3y ago
like I said, my only gripe with this is that it creates the response instance for you by default, which ruins records for me Should doublecheck if thats still a thing, doesnt seem like it is by a quick glance at the code
ero
eroOPā€¢3y ago
i mean, yeah, right, that's cool and all, but i don't relaly understand what any of that does or means. like i'm still very much lost on how to actually create an api. what services are, what controllers are, what endpoints really are i don't know how to explain it. like i feel like i'm in the middle of the ocean with no support everything is going on at once and i don't know where to go
Pobiega
Pobiegaā€¢3y ago
well lets just start out easy then an endpoint is just some piece of code that is registered to handle requests to a given URL for example, POST api/users could be the route for an endpoint that adds users
ero
eroOPā€¢3y ago
probably a language thing but the words chosen for these things are very weird to me requests endpoints so what i wanna do right now is just, get to the point where i can run my api, open it in swagger, and add stuff in my in-memory db i think once that all works, i can expand on that a bit more easily
Pobiega
Pobiegaā€¢3y ago
Sure Lets start out with controllers for now then, as thats the easiest way to move forward, imho I guess you have an asp.net webapi project made already?
ero
eroOPā€¢3y ago
yeah i deleted everything though like the stupid ass weather forecast stuff
Pobiega
Pobiegaā€¢3y ago
not a problem what does your program.cs look like?
ero
eroOPā€¢3y ago




man can't display empty blocks it's empty
Pobiega
Pobiegaā€¢3y ago
okay, lets add the basics back in then
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.Run();
this will add all controllers to your DI container, add swagger, then build the app (looks at configurations etc), then configure the HTTP pipeline by adding swagger if we are in development mode, adding HTTPS redirection, adding support for authorization and finally mapping your controllers this should compile and start up a fairly empty swagger UI on boot
ero
eroOPā€¢3y ago
lots of things there that i still don't know
Pobiega
Pobiegaā€¢3y ago
lets just leave it like this for now so we can get to the point where your API can recieve a request and do something with it
ero
eroOPā€¢3y ago
right
ero
eroOPā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
development cert for HTTPS
ero
eroOPā€¢3y ago
oh yeah i wanna use postgres
Pobiega
Pobiegaā€¢3y ago
so you dont get warnings that localhost doesnt have a valid trusted HTTPS cert
ero
eroOPā€¢3y ago
i guess that doesn't have anything to do with the web part actually
Pobiega
Pobiegaā€¢3y ago
nope, thats fine npgsql has excellent connectors for C# and EF, or dapper or whatever you wanna use
ero
eroOPā€¢3y ago
ok yeah that works
ero
eroOPā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
cool lets add our first controller make a new class anywhere in the web project, something like UsersController make it inherit ControllerBase, and decorate it with [ApiController] also, give it a [Route("api/users")] attribute you can ofc change that string to be whatever base URI you want for this controller finally, lets make a simple method:
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public User GetUser(ulong id)
{
return new User()
{
Id = id,
About = "not yet",
Email = "[email protected]",
Name = "Steve",
Password = "oh dear god"
};
}
}
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public User GetUser(ulong id)
{
return new User()
{
Id = id,
About = "not yet",
Email = "[email protected]",
Name = "Steve",
Password = "oh dear god"
};
}
}
now, this is obviously a stupid method, but it should work and whatever ID you give as a parameter should be present in the json response controllers are added to the DI container, so you can just add a constructor with whatever dependencies you want access to, and they will be injected when the controller is resolved for example, a database context
ero
eroOPā€¢3y ago
i don't really know what DI or DI containers are
Pobiega
Pobiegaā€¢3y ago
Hm, okay. DI is a common way to solve the IoC problem (inversion of control) like, we want access to the database inside our controller, so we can query a user from the database but we dont want the controller to be responsible for creating connections to the database directly via something like var db = new DatabaseConnection();
ero
eroOPā€¢3y ago
i guess i just don't really understand what we're injecting?
Pobiega
Pobiegaā€¢3y ago
instead, we say that the controller gets a constructor parameter for the database connection
ero
eroOPā€¢3y ago
what dependency are we injecting
Pobiega
Pobiegaā€¢3y ago
the database connection.
ero
eroOPā€¢3y ago
to where
Pobiega
Pobiegaā€¢3y ago
for once we have one to the controller, in this case.
ero
eroOPā€¢3y ago
where are we injecting it i'm so confused we're not doing anything though
Pobiega
Pobiegaā€¢3y ago
well currently we are not injecting anything either since the method just returns dummy data each time lets play around with this a bit more and worry about the database stuff later
ero
eroOPā€¢3y ago
alright swagger ain't too smart, huh?
Pobiega
Pobiegaā€¢3y ago
hm?
ero
eroOPā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
this is what I get.
ero
eroOPā€¢3y ago
should be uint64
Pobiega
Pobiegaā€¢3y ago
hm, yeah it should. curious that it didnt figure that out tbh
ero
eroOPā€¢3y ago
and yet it knows that -123 is invalid
ero
eroOPā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
Pobiega
Pobiegaā€¢3y ago
OpenAPI Specification - Version 3.0.3 | Swagger
The OpenAPI Specification defines a standard interface to RESTful APIs which allows both humans and computers to understand service capabilities without access to source code, documentation, or network traffic inspection.
Pobiega
Pobiegaā€¢3y ago
no unsigned types in that specification bit silly
ero
eroOPā€¢3y ago
i guess i might just go with long then?
Pobiega
Pobiegaā€¢3y ago
Sure, thats fine but hey, we have a working endpoint that responds as expected
ero
eroOPā€¢3y ago
yup
Pobiega
Pobiegaā€¢3y ago
whats the next step? making it actually remember something?
ero
eroOPā€¢3y ago
i suppose, yeah
Pobiega
Pobiegaā€¢3y ago
alright, the simplest "in memory database" is just a class with lists in it šŸ™‚ so we can make one of those for now
public class Database
{
public List<User> Users { get; } = new();
}
public class Database
{
public List<User> Users { get; } = new();
}
there we go, almost a database Now, we want to get this into our controller so we can start using it there but we just var database = new Database(); it wont work, as we'll get a new database on each request and lets not use static either, since we want this to simulate being a real database connection, which we cant have static
ero
eroOPā€¢3y ago
why not?
Pobiega
Pobiegaā€¢3y ago
what part? not being static?
ero
eroOPā€¢3y ago
yeah the static part not like i'm looking for reasons to use static i just wanna know why
Pobiega
Pobiegaā€¢3y ago
has to do with object lifetimes etc the connection itself is disposable I'm sort of assuming you will be using EF at some point for now but I think this holds true for dapper or similar too
ero
eroOPā€¢3y ago
EF was the idea yeah
Pobiega
Pobiegaā€¢3y ago
cool then we'll go with that so, to get access to the database, we need to inject it add a constructor to your controller:
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly Database _database;

public UsersController(Database database)
{
_database = database;
}
[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
private readonly Database _database;

public UsersController(Database database)
{
_database = database;
}
it takes in a database and just saves the reference in a readonly field. this is very standard now if we run this, we get an error because we never told the DI container what a Database is so back to program.cs we go and... builder.Services.AddSingleton<Database>(); there are 3 methods of note on Services here (which is an IServiceCollection, btw) AddSingleton, AddScoped and AddTransient singelton just means "there will only ever be one instance of this" scoped means "there will only ever be one instance of this, PER SCOPE" (in ASP.NET thats usually request scope, meaning a single http request transient means "you'll get a new one each time"
ero
eroOPā€¢3y ago
alright
Pobiega
Pobiegaā€¢3y ago
so since we want our database to live as long as the application itself, we say its singleton now we can use it in our controller
ero
eroOPā€¢3y ago
oh sick
Pobiega
Pobiegaā€¢3y ago
so a logical rewrite of our get user method would be...
[HttpGet("{id}")]
public ActionResult<User> GetUser(long id)
{
var user = _database.Users.FirstOrDefault(x => x.Id == id);

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

return user;
}
[HttpGet("{id}")]
public ActionResult<User> GetUser(long id)
{
var user = _database.Users.FirstOrDefault(x => x.Id == id);

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

return user;
}
we look in the database. nothing found? well we want to return a 404 NOT FOUND http response controllers have helper methods for that. But we need to change the return type a bit ActionResult<T> lets us be strictly typed but return whatever http response codes we want
ero
eroOPā€¢3y ago
makes sense
Pobiega
Pobiegaā€¢3y ago
okay cool. lets try making the "CreateUser" endpoint now wanna give it a go?
ero
eroOPā€¢3y ago
sure thing
public record CreateUserDto(
string Name,
string Email,
string Password);


public ActionResult CreateUser(CreateUserDto userToCreate)
{
if (_database.Users.Any(u => u.Name == userToCreate.Name))
{
return Conflict();
}

_database.Users.Add(new()
{
Id = /* ? */,
Name = userToCreate.Name,
Email = userToCreate.Email,
Password = userToCreate.Password
});

return Success(); // The name 'Success' does not exist in the current context
}
public record CreateUserDto(
string Name,
string Email,
string Password);


public ActionResult CreateUser(CreateUserDto userToCreate)
{
if (_database.Users.Any(u => u.Name == userToCreate.Name))
{
return Conflict();
}

_database.Users.Add(new()
{
Id = /* ? */,
Name = userToCreate.Name,
Email = userToCreate.Email,
Password = userToCreate.Password
});

return Success(); // The name 'Success' does not exist in the current context
}
hm
Pobiega
Pobiegaā€¢3y ago
yeah, pretty much! normally we'd get the next available ID from the database itself, but our memory collection doesnt support that so we can add a simple tracker if we want, but idk maybe thats overkill šŸ˜›
ero
eroOPā€¢3y ago
but what do i return? there's no Success() and no ActionResult.Success oh it's Ok() why are they even methods pisses me off
Pobiega
Pobiegaā€¢3y ago
Well, because they often take arguments
ero
eroOPā€¢3y ago
ah, right is there a clear "next step"?
Pobiega
Pobiegaā€¢3y ago
I'd say introduce command pattern so instead of the controllers doing actual logic, they'd only handle HTTP stuff next step is probably adding an actual database context via EF, so you can start persisting stuff Would highly recommend reading up/watching some videos on dependency injection and EF
ero
eroOPā€¢3y ago
let's do this first and i'll worry about the rest after
Pobiega
Pobiegaā€¢3y ago
Okay. https://github.com/martinothamar/Mediator seems good, so you can use that if you don't want to go through the hassle of writing your own dispatcher
ero
eroOPā€¢3y ago
i do wanna go through the hassle for now so i at least understand
Pobiega
Pobiegaā€¢3y ago
Okay, so the simplest version of the command pattern with DI is to register your handlers with AddScoped (most will need to be scoped, because the database context will be scoped), and simply inject them into your controller pros: no dispatcher, yay cons: a controller might use many handlers, but perhaps only one at a time
ero
eroOPā€¢3y ago
so i really didn't understand a lot of that what's a dispatcher what's a handler in this context
Pobiega
Pobiegaā€¢3y ago
Handler = command handler a command is just a POCO, a stupid no-logic C# class/record that only guarantees that its "valid" (ie, if it needs an ID that an ID was provided) a dispatcher lets you use the fancy calls, like...
var command = new CreateUserCommand(...);
var result = _dispatcher.Run(command);
var command = new CreateUserCommand(...);
var result = _dispatcher.Run(command);
and result here would be your User, or whatever the handler for CreateUser responds with the type inference here is based on the fact that the command is generic over its result type, btw so a dispatch-less alterantive is
var command = new CreateUserCommand(...);
var result = _createUserHandler.Handle(command);
var command = new CreateUserCommand(...);
var result = _createUserHandler.Handle(command);
but as said, that means instead of just injecting the dispatcher, you'll need to know what handlers you need
ero
eroOPā€¢3y ago
i was wondering more about the actual implementation, not just how it'll be used in the end
Pobiega
Pobiegaā€¢3y ago
Well, they are non trivial and I really CBA making one from scratch right now :p But the idea is that you assembly scan for your handlers at startup (or sourcegen) And build a lookup of command type to handler type
ero
eroOPā€¢3y ago
ah, that was part of the nick video yeah he does assembly scanning there
Pobiega
Pobiegaā€¢3y ago
You can then resolve the handlers based on the command type
ero
eroOPā€¢3y ago
surprisingly less lines than i expected
Pobiega
Pobiegaā€¢3y ago
Yeah the reflection stuff isnt too bad. In general the very basics isn't too much code in general, but it adds up
ero
eroOPā€¢3y ago
need to wait for patrick to go back to bed to ask my source gen question i guess
Patrick
Patrickā€¢3y ago
you realise im asking those questions to make you realise it's backward
ero
eroOPā€¢3y ago
won't leave me alone
Patrick
Patrickā€¢3y ago
because making remarks about someone behind their back... is great?
Patrick
Patrickā€¢3y ago
if you can answer the question it'll help you build a better backend, considering im reading this now and you've never done it before
ero
eroOPā€¢3y ago
i can't answer the question i still don't even really know what a controller or a service or mediatr or whatever are i just know that what i have been doing so far is fucking annoying
Patrick
Patrickā€¢3y ago
so your protest to me asking questions has actually uncovered you don't know what you're doing here's a simple scenario: write a controller for what you want to do and leave it there
ero
eroOPā€¢3y ago
i don't know what i want to do there is so much this site needs to do i don't know where to start
Patrick
Patrickā€¢3y ago
im not sure if this has become a question of how to structure your API or how to start a project im now supposing the latter
ero
eroOPā€¢3y ago
both
Patrick
Patrickā€¢3y ago
ok so what is it
ero
eroOPā€¢3y ago
what's what
Patrick
Patrickā€¢3y ago
your project
ero
eroOPā€¢3y ago
gaming leaderboards for score and time
Patrick
Patrickā€¢3y ago
ok and are you going to let people add/manage those?
ero
eroOPā€¢3y ago
indeed
Patrick
Patrickā€¢3y ago
ok so we already have 2 domain objects, Leaderboard and User are you going to store users yourself or use a third party i.e. OAuth/etc
ero
eroOPā€¢3y ago
i don't know what that means
Patrick
Patrickā€¢3y ago
are you going to have a sign up page where users put in their email and a password, or are you going to shell out authentication to Discord/GitHub/Facebook/etc
ero
eroOPā€¢3y ago
there's also like dozens of objects more both...? i guess?
Patrick
Patrickā€¢3y ago
sure, im starting from scratch to look at requirements, because you said you don't know where to start ok so immediately you're going to need to build from the ground up a user controller which can handle the sign up so a request to the API would look like POST /api/user which adds a new user, so we'll have a UserController.
ero
eroOPā€¢3y ago
like i'd like to make sign up as easy as possible and would like to support at least google and github
Patrick
Patrickā€¢3y ago
is this an MVC site?
ero
eroOPā€¢3y ago
but also give the option not to use either of course it's not a site at all just the api frontend is TS and Vue
Patrick
Patrickā€¢3y ago
ok well you'll need a controller to handle manual sign up
[ApiController]
public class UserController : ControllerBase
{
private readonly EntityContext _db;

public UserController(EntityContext db) => _db = db;

[HttpPost]
public async Task<IActionResult> Add([FromBody] UserDto user)
{
var userEntity = new User
{
Username = user.Username,
Password = HashPassword(user.Password)
};

_db.Add(userEntity);
await _db.SaveChangesAsync();
return Ok();
}
}
[ApiController]
public class UserController : ControllerBase
{
private readonly EntityContext _db;

public UserController(EntityContext db) => _db = db;

[HttpPost]
public async Task<IActionResult> Add([FromBody] UserDto user)
{
var userEntity = new User
{
Username = user.Username,
Password = HashPassword(user.Password)
};

_db.Add(userEntity);
await _db.SaveChangesAsync();
return Ok();
}
}
the controller just handles the request and orchestrates, in this case we're just putting the business logic of how to sign up a user into the controller
ero
eroOPā€¢3y ago
i don't exactly... get the point of telling me this? i know that
Patrick
Patrickā€¢3y ago
because you don't know what a controller is
ero
eroOPā€¢3y ago
giving me the code doesn't tell me what a controller is
Patrick
Patrickā€¢3y ago
the controller just handles the request and orchestrates, in this case we're just putting the business logic of how to sign up a user into the controller
ero
eroOPā€¢3y ago
the user controller controls things that have to do with the user cool
Patrick
Patrickā€¢3y ago
ASP.NET Core is a phone book, a controller is just a name for a class that is a page in that phone book, every public method in that controller is a phone number someone can call.
ero
eroOPā€¢3y ago
hm
Patrick
Patrickā€¢3y ago
i can't do much with "hm", i suppose you're not interested in me helping which is all you need to say. my final advice is; since you haven't done this before you don't need to over engineer it. Scalability of users rarely depends on class structure or frameworks you use šŸ‘
ero
eroOPā€¢3y ago
i don't want to publish an incomplete product i want it to be perfect the moment it goes online
Patrick
Patrickā€¢3y ago
won't happen, that's not how software is built
ero
eroOPā€¢3y ago
it's supposed to be a competitor to another website. what's the point in releasing something that isn't already better
Patrick
Patrickā€¢3y ago
and your competitor is out there growing and improving, while you're here pondering class structure
ero
eroOPā€¢3y ago
i want it to be easy to read, easy to understand, easy to maintain, and easy to contribute to
Patrick
Patrickā€¢3y ago
competing in the software space is about MVPs
ero
eroOPā€¢3y ago
while also being fast, and feature complete
Patrick
Patrickā€¢3y ago
here's what's going to happen - you build your software today on the requirements/priorities you think are important. You release the software. You're going to run into problems. Your users are going to request features, your priorities change. Suddenly the beautiful architecture you have isn't proven, falls apart and you need to reprioritise. this is why software engineers exist; no one builds or ships perfect products
Timtier
Timtierā€¢3y ago
I do understand the desire to get a "perfect" or "complete" solution. Though I often have had projects grind to a halt because of it so I would be hesitant to start small and improve iteratively instead I follow "Done is better than perfect" as I otherwise get stuck with an overengineered solution; nicely designed but potentially overkill for what the customer needs I'd recommend to first and foremost look into; how will what you create have the competitive edge over other leaderbord services/sites that you are competing against? And put your eggs in that basket. Just start simple to realize the basic functionality for that, gather feedback and iterate on that. Often, simplest code is best anyhow You can still go with the flow of your source generator and project layout, but will you need it for the key functionality your API needs? I still have to a bunch prev messages so apologies if I make any assumptions
ero
eroOPā€¢3y ago
i mean again, i just don't know where to start like it's not that i don't know how to do it that's part of it but i also just don't know how to even begin there's so much i need to do, and so much i don't know, it's like impossible to do anything and i can't even lay it all out! because i don't know how to best put things together, or what's even possible
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
just any of the dozens which are .net 7 minimal api tutorial playlists right
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
Yeah very few changes in asp.net from 6 to 7
ero
eroOPā€¢3y ago
i don't even really know like... what to do first? like do i create users first? series? leaderboards? categories? submissions? posts? or do i not create any models at all first and worry about the database instead setting up postgres and whatever
Pobiega
Pobiegaā€¢3y ago
Could start with sqlite or an in-memory EF database while you learn the ropes no reason to go ham with migrations and stuff before you know what you are doing tbh And doing github + google + manual users all at once will take some tinkering to get going, authentication can be a lot of work
ero
eroOPā€¢3y ago
ugh i don't know what to do :/
Pobiega
Pobiegaā€¢3y ago
Start out small.
Timtier
Timtierā€¢3y ago
And if you dont know how to start; go by your data structure and start with the objects that do not depend on the others
ero
eroOPā€¢3y ago
i can't think of a single thing that doesn't depend on something else
Timtier
Timtierā€¢3y ago
What is a serie and what does it do?
ero
eroOPā€¢3y ago
a series is a collection of 1 or more leaderboards it has 1 or more moderating users with one of those users being the "owner" so to speak (the moderator with the most rights)
Timtier
Timtierā€¢3y ago
I see
ero
eroOPā€¢3y ago
a series also has a forum, a user which requested the series in the first place (this is the user who is made "owner" initially. the owner can change later), and a user which accepted the series (a site admin) or rejected it, as the case may be
Timtier
Timtierā€¢3y ago
And a form haa said posts, a leaderboard has submissions?
ero
eroOPā€¢3y ago
a forum is a collection of 0 or more posts, where a post is something like a linked list (child posts == comments) a post can be locked (or not) and stickied (or not) maybe i'll make a distinction between a ForumPost and a normal Post a leaderboard is a collection of 0 or more Categories where there'll have to be a distinction between level categories and game categories, maybe they'll certainly be displayed separately a category has a lot of settings, so does a leaderboard and a category is basically a collection of submissions also a category must be either go by time, or by score INumericalMetric, if you will
Timtier
Timtierā€¢3y ago
So in this case, the different type of users might be the only entity that doesn't have any direct dependencies? So we could start with the different user types
ero
eroOPā€¢3y ago
"different types"?
Timtier
Timtierā€¢3y ago
Previously you described an owner and a site admin
ero
eroOPā€¢3y ago
why should those be different? can't i just give them like a Role enum? or use claims or whatever they're called
Pobiega
Pobiegaā€¢3y ago
Yup, either claims or roles would work here. roles likely being the easiest.
ero
eroOPā€¢3y ago
but also a User can be the Owner of a Series, a Leaderboard, or a (Forum)Post the "owner" wasn't about the website
Pobiega
Pobiegaā€¢3y ago
well, isnt that more that the Series etc has an Owner prop?
ero
eroOPā€¢3y ago
i suppose
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
maybe it's pretentious, but i think this is far from simple
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Timtier
Timtierā€¢3y ago
Even so, nothing wrong with trying
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Timtier
Timtierā€¢3y ago
So going back to the previous models discussed Ero, let's say I'd want to create a Series on your frontend, what would I need to fill in?
ero
eroOPā€¢3y ago
(singular of series is series) i mean come on i even put it in parens to show that i don't care about it that much
Timtier
Timtierā€¢3y ago
I know I know šŸ˜‚
ero
eroOPā€¢3y ago
you have to provide the name of the series, you have to provide which leaderboards go into it (a minimum of 2), and you have to be the Owner of at least one of those leaderboards such a series request opens a "post" where a site admin and the requesting user can discuss things if needed (kinda like a pull request)
Timtier
Timtierā€¢3y ago
I see. I assume those leaderboards are pre-existing leaderboards or do you create them as part of the creation of a series?
ero
eroOPā€¢3y ago
they have to be pre-existing
Timtier
Timtierā€¢3y ago
Alright So based on this, if I were to mock what I would send to the backend, I would send something along the lines of (probably simplified):
{
"name" : "Name of series",
"leaderboards": [1, 2, 3]
}
{
"name" : "Name of series",
"leaderboards": [1, 2, 3]
}
The name being input that I fill in
Pobiega
Pobiegaā€¢3y ago
You'll likely want to model that as some kind of "SeriesRequest" entity, that can be created if conditions are fulfilled (owner of at least one included leaderboard etc), that then has its own lifecycle. Only once accepted does it actually "turn into" a new series.
ero
eroOPā€¢3y ago
i mean you wouldn't send anything CreateSeries would not be AllowAnonymous
Timtier
Timtierā€¢3y ago
Right, but assuming I am a logged in user, this is what I would send. I will get back to that part
Pobiega
Pobiegaā€¢3y ago
CreateSeriesRequest, would users not be able to do that?
ero
eroOPā€¢3y ago
i mean there's another layer of abstraction by the frontend i'd guess
Pobiega
Pobiegaā€¢3y ago
AcceptSeriesRequest obviously requires some form of admin rights even so, your backend can never fully trust the frontend and should validate everything itself too
ero
eroOPā€¢3y ago
of course
Timtier
Timtierā€¢3y ago
Still, lets stay with the request for "creating" the series before the rest of the flow Start simple and all that
ero
eroOPā€¢3y ago
but yeah, i guess the frontend would send the name of the series, the leaderboard IDs, and the ID of the user who created the request
Timtier
Timtierā€¢3y ago
Yep, though the ID matters a bit on your implementation of authorization and how you'd pass that
Pobiega
Pobiegaā€¢3y ago
ID of the user is implied by being the user issuing the call to the backend, no?
ero
eroOPā€¢3y ago
i don't know how frontend works
Timtier
Timtierā€¢3y ago
But for the sake of simplicity, lets stay away from the user part
ero
eroOPā€¢3y ago
it shouldn't be the user issuing the call, right? like that doesn't sound right
Pobiega
Pobiegaā€¢3y ago
via the frontend, it is
Timtier
Timtierā€¢3y ago
It does, let me see if I can clarify it
ero
eroOPā€¢3y ago
my frontend sends me data
Pobiega
Pobiegaā€¢3y ago
frontend is an application running on the users computer
ero
eroOPā€¢3y ago
and the frontend should also check whether the request can be made at all
Pobiega
Pobiegaā€¢3y ago
thats fine but its possible to get around those šŸ™‚
ero
eroOPā€¢3y ago
like a request with 1 leaderboard is invalid
Pobiega
Pobiegaā€¢3y ago
sure
ero
eroOPā€¢3y ago
and a request where the user isn't an owner of any of the boards is also invalid
Timtier
Timtierā€¢3y ago
Yep
ero
eroOPā€¢3y ago
so the "make request" button should simply not be clickable
Pobiega
Pobiegaā€¢3y ago
this is stuff you'd slap in a FluentValidation validator or something, imho on the DTO
Timtier
Timtierā€¢3y ago
Long story short, the backend will be the source of truth for that.
ero
eroOPā€¢3y ago
i should not even be able to receive a request which is not valid
Timtier
Timtierā€¢3y ago
Why not?
Pobiega
Pobiegaā€¢3y ago
Yeah. Your frontend wont allow users to click the button to issue the request if not, but even so, people can and will try and send invalid requests via devtools
ero
eroOPā€¢3y ago
because i say so because if you cannot click the button, you cannot make a request
Pobiega
Pobiegaā€¢3y ago
wrong
Timtier
Timtierā€¢3y ago
That's the fun part šŸ˜„
Pobiega
Pobiegaā€¢3y ago
devtools/insomnia/curl says you can šŸ˜›
Timtier
Timtierā€¢3y ago
If I can use your API, I can, without using your front-end Which is spooky
Pobiega
Pobiegaā€¢3y ago
yeah. This is why we say "never trust the frontend"
Timtier
Timtierā€¢3y ago
But which is why you at the minimum add your validation in the backend system Your frontend can also have validation, that's fine
Pobiega
Pobiegaā€¢3y ago
99.9% of all requests will be via your frontend and not manipulated, but there will be some that are entirely bogus or contain malicious input
Timtier
Timtierā€¢3y ago
But to go back to the first part. So you send the name and leaderboard IDs that you wish to use to create a series
ero
eroOPā€¢3y ago
if i don't send the user ID, how would i validate anything?
Timtier
Timtierā€¢3y ago
Usually, you would not just send an ID, because that can be faked easily. You would send something within your request, like a token or something similar, that the backend can recognize and make use of.
ero
eroOPā€¢3y ago
api tokens, right...
Timtier
Timtierā€¢3y ago
Since you want to support Google users and such, this would be an OAuth token or something similar. Anyhow, don't worry about that part yet But once it reaches your backend, somewhere in your backend you will have to perform the checks you described, starting with; - Does this request have a token? - Do I recognize this token as a User of my system?
ero
eroOPā€¢3y ago
right
Timtier
Timtierā€¢3y ago
This might be some built-in functionality of ASP.NET, it might be some library or package, doesn't quite matter. Or well, not at this point of the discussion šŸ˜„ So if the request does not have a token or is not a recognized user, you would usually return a response along the lines of Unauthorized Status Code 401, which basically means; you tried to do something to my backend, but I do not allow you to
Timtier
Timtierā€¢3y ago
Unrelated shoutout to https://http.cat/ šŸ˜„
HTTP Status Cats API
HTTP Cats
API for HTTP Cats
ero
eroOPā€¢3y ago
no token would be bad request, no? wrong token would be unauthorized
Timtier
Timtierā€¢3y ago
Hmmm. I would argue both are unauthorized The wrong auth is the same as using no auth You can use whatever you want but I generally keep BadRequest for; you're allowed to make a request, but you filled in something wrong. For example; a leader board you do not own
ero
eroOPā€¢3y ago
that's unauthorized to me lol like "hey, you don't have rights to do anything here" as in, you're not authorized to unauthorized
Timtier
Timtierā€¢3y ago
I get why you'd think that would be unauthorized, though usually after you pass the authorization as a user, people use BadRequest to say "Hi, I know who you are but you're not allowed to do this" Anyhow, you can use either, your implementation But lets say a user sends this request to create a series, goes past the check which verifies their token. They'll end up in the "actual" implementation of the endpoint
ero
eroOPā€¢3y ago
but then why is passing the wrong token to a series creation request unauthorized?
Timtier
Timtierā€¢3y ago
Because you're not authorized to perform the request. Performing meaning that the server will try to process it at all
ero
eroOPā€¢3y ago
thoroughly confused
Timtier
Timtierā€¢3y ago
At this point it doesn't care what the contents of the series creation request are or if they're correct The endpoint I chose as example is a bit annoying because of the ownership part of leaderboards. So to maybe simplify it a bit; what about if a user wants to make a post on a forum Completely different request of course The request towards the backend will probably contain something along the lines of; their post text and the ID of the forum they are posting on Similarly, it gets to the backend and checks (assuming part of your design here); hey, this endpoint requires a user, as I do not allow anonymous people to post. So it checks if you have a token and if it is a token of a known user. So in this example, lets assume yes for both; it will enter the endpoint logic. Endpoint logic being whatever you put in the Controller method or minimal API route.
Pobiega
Pobiegaā€¢3y ago
Regarding Authorization, you normally do that before entering the actual code that handles your request itself, ie the controller action or "endpoint" code. ASP.NET will look at the attributes you've assigned on the controller and action and on the users claims/roles etc and see if they are allowed to even access the route - only then does your code take over and can look at the specifics, such as "is the leaderboard specified one that you own?" business authorization takes place after API level authorization for example, at work our apis are quite large and in several places contain lines like SecurityAssert.StaffUserCanAccessPatient(dto.PatientId) just because you are a valid staff user with a care assignment doesnt mean you can access this patient.
ero
eroOPā€¢3y ago
i mean, that seems obvious yeah
Pobiega
Pobiegaā€¢3y ago
yup. its just that ASP can block a lot of requests from even reaching that point, because their token is invalid or expired or doesnt contain the right roles meaning that for us to do the expensive stuff (database queries usually), we know you are quite likely to be a real user and not doing funky stuff
Timtier
Timtierā€¢3y ago
So once a request enters your Create Series endpoints, the following would probably happen; First, if you add validation, the "stupid" validation comes first; is the name not empty, are all leaderboard ids above 0, etc. If all that's fine, you would get to the more "expensive" logic which would be the actual validation; do I own one of these leaderboards? Ofc this check entirely depends on your design but lets say you have some LeaderboardService which abstracts some logic away, it could be something like.
foreach(var leaderboardId in requests.leaderboard)
{
var leaderboard = await _leaderboardService.GetLeaderboardByIdAsync(leaderboardId);

if (leaderboard.Owner.Id == UserId)
{
// You own at least one, you set a bool here or something.
}
}
foreach(var leaderboardId in requests.leaderboard)
{
var leaderboard = await _leaderboardService.GetLeaderboardByIdAsync(leaderboardId);

if (leaderboard.Owner.Id == UserId)
{
// You own at least one, you set a bool here or something.
}
}
Pobiega
Pobiegaā€¢3y ago
and break the loop šŸ˜„
Timtier
Timtierā€¢3y ago
Depends on the checks, its a dumb poc example Otherwise some sort of _leaderboardsService.GetUserLeaderBoards(UserId);, bla bla and then go through that collection to see if it contains at least one of the request leaderboard ids ^ but i digress If it matches your own rule of "I must own at least one" then you pass the request values to the logic that actually creates the series Which might at this point just be a dumb database insert, who knows How the rest of your application does its' logic depends entirely on how you design it You can take the approach of Pobiega and put it in the controller, like here: https://discord.com/channels/143867839282020352/1045068329502646372/1046411827317321819
MODiX
MODiXā€¢3y ago
Pobiega#2671
[HttpGet("{id}")]
public ActionResult<User> GetUser(long id)
{
var user = _database.Users.FirstOrDefault(x => x.Id == id);

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

return user;
}
[HttpGet("{id}")]
public ActionResult<User> GetUser(long id)
{
var user = _database.Users.FirstOrDefault(x => x.Id == id);

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

return user;
}
Quoted by
<@!205052896599867392> from #Creating a backend in .NET (click here)
React with āŒ to remove this embed.
Timtier
Timtierā€¢3y ago
Or a different way entirely I have my own gripes with that structure, though it is very quick to get something up and running with it
ero
eroOPā€¢3y ago
see and that's why i want source gen right? having to make the services and validation all separately is like
Pobiega
Pobiegaā€¢3y ago
Oh yeah I don't do app logic in controllers. ĀÆ\_(惄)_/ĀÆ
Timtier
Timtierā€¢3y ago
Services sounds a bit scary but eh
Pobiega
Pobiegaā€¢3y ago
I don't think you can generate all those parts, since they are not just copypaste
Timtier
Timtierā€¢3y ago
That's just your logic, moved to a different class your controllers are generally stupid They verify requests, pass it along, possibly catch unexpected results, and give a response
Pobiega
Pobiegaā€¢3y ago
command+handler/service + fluentvalidation + functional result (Result<T>) is a very good start.
Timtier
Timtierā€¢3y ago
Even command/handler/CQRS might be a bit overkill here But if you want reusability, source generator is not really the solution here It's an overly complex solution for the what, 10 models you are making at the most?
ero
eroOPā€¢3y ago
hard to believe it'll only be 10
Timtier
Timtierā€¢3y ago
Right, but even if you double it, still managable šŸ˜›
Patrick
Patrickā€¢3y ago
Respectfully you need to actually start building something. All the architecture and frameworks here are over engineered when you canā€™t build a simple API Thereā€™s a reason why MS documentation for ASP.NET doesnā€™t include services, CQRS or other convoluted measures for architecture.
Timtier
Timtierā€¢3y ago
I'd also say indeed; just start implementing one endpoint in one controller; as simple as possible Once that works, you can refactor it to be more "flexible". But at that point you have something working you can fall back to
ero
eroOPā€¢3y ago
so uhh. how do i start?
Patrick
Patrickā€¢3y ago
Open up your IDE of choice and start a new ASP.NET Core API project Then make your first controller and work on one action Use the swagger UI to interact with your API
ero
eroOPā€¢3y ago
Alright yeah pobiega and i already did that earlier
Pobiega
Pobiegaā€¢3y ago
You could go on with the usercontroller we made maybe start experimenting with authentication too, as it seems to be very important to your application but start with just doing it locally, dont involve github/google auth for now
ero
eroOPā€¢3y ago
i also need like 2FA, and 3FA for site staff from a project architecture point of view, where would you put the database? like in what namespace?
Pobiega
Pobiegaā€¢3y ago
add 2fa/3fa later database stuff usually goes in its own project, often called Infrastructure or Data, but you can just make that a namespace if you want ofc
ero
eroOPā€¢3y ago
hm, i see
Pobiega
Pobiegaā€¢3y ago
the core class there is your DbContext child
ero
eroOPā€¢3y ago
that much i've gathered what's that? classlib?
Pobiega
Pobiegaā€¢3y ago
yap everything is a lib, except the web api itself
ero
eroOPā€¢3y ago
i mean????
Pobiega
Pobiegaā€¢3y ago
and the whole splitting in multiple projects is not a strict requirement either, its just common practice
ero
eroOPā€¢3y ago
its output type is also Library?
Pobiega
Pobiegaā€¢3y ago
yeah why not?
ero
eroOPā€¢3y ago
so the api is also a lib
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
oh, not the api
ero
eroOPā€¢3y ago
yeah that's a mega no for me
Pobiega
Pobiegaā€¢3y ago
the api is a console app or whatever
ero
eroOPā€¢3y ago
the 1 project thing is it?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
yup
ero
eroOPā€¢3y ago
but wouldn't the csproj need to specify <OutputType>Exe</> for that? also, i'm torn on what to call the solutions
Pobiega
Pobiegaā€¢3y ago
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>

</Project>
this is what I have in the one I've been using as we "coded along" and it works fine šŸ˜›
ero
eroOPā€¢3y ago
i guess it's the Sdk.Web part that changes it
Pobiega
Pobiegaā€¢3y ago
likely, yes
ero
eroOPā€¢3y ago
if you do just dotnet new console, you have that tag so the site is leaderboards.gg, so i just went with LeaderboardsGG.Backend for the solution and project names
Pobiega
Pobiegaā€¢3y ago
thats fine.
ero
eroOPā€¢3y ago
but i changed the namespace to Lbgg should that be LBGG, LbGg?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
does to me
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
iirc conventions would say Lbgg or LbGg two letter acronyms is the only thing that goes allcaps
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
even API gets turned into Api when you follow conventions
Timtier
Timtierā€¢3y ago
LeaderboardsGG / Lbgg both are fine so ehhhh
Pobiega
Pobiegaā€¢3y ago
yup
Timtier
Timtierā€¢3y ago
Id prefer the first one
ero
eroOPā€¢3y ago
so should i change the project names too?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
I prefer to keep project name and namespace synced up
ero
eroOPā€¢3y ago
does my dbcontext go into some namespace too?
Pobiega
Pobiegaā€¢3y ago
usually "top level" of your data project/namespace
Timtier
Timtierā€¢3y ago
Basically every class you make will be in a namespace. Just further namespaces are applied depending on which folders you have
ero
eroOPā€¢3y ago
oh lol no i know that
Timtier
Timtierā€¢3y ago
So if you use those namespaces you're good šŸ˜„
ero
eroOPā€¢3y ago
my infrastructure project needs to know about my domain project, right? so it knows about my entities?
Pobiega
Pobiegaā€¢3y ago
yup
ero
eroOPā€¢3y ago
namespace Lbgg.Infrastructure;

public class LbggDbContext : DbContext
{
public LbggDbContext(DbContextOptions<LbggDbContext> options)
: base(options) { }

public required DbSet<User> Users { get; set; }
}
namespace Lbgg.Infrastructure;

public class LbggDbContext : DbContext
{
public LbggDbContext(DbContextOptions<LbggDbContext> options)
: base(options) { }

public required DbSet<User> Users { get; set; }
}
[HttpPost]
public async Task<IActionResult> Create(CreateUserDto dto)
{
if (await _db.Users.AnyAsync(u => u.Username == dto.Username))
{
return BadRequest();
}

await _db.Users.AddAsync(new()
{
Username = dto.Username,
Email = dto.Email,
Password = dto.Password
});

return Ok();
}
[HttpPost]
public async Task<IActionResult> Create(CreateUserDto dto)
{
if (await _db.Users.AnyAsync(u => u.Username == dto.Username))
{
return BadRequest();
}

await _db.Users.AddAsync(new()
{
Username = dto.Username,
Email = dto.Email,
Password = dto.Password
});

return Ok();
}
Patrick
Patrickā€¢3y ago
You need savechanges but sure
ero
eroOPā€¢3y ago
i've been told not to use AddAsync?
Patrick
Patrickā€¢3y ago
Yes donā€™t use it avoid it if you can
ero
eroOPā€¢3y ago
why is that?
Patrick
Patrickā€¢3y ago
Because itā€™s a specialised method to allow for an operation to occur during the Adding process. In almost all cases all youā€™re doing is adding to a collection in memory which doesnā€™t need to be an async operation. Needless overhead and complication.
ero
eroOPā€¢3y ago
so i know that sometimes you also return the created user entity should you really do that? it contains the password after all that sounds pretty bad to return
Patrick
Patrickā€¢3y ago
You shouldnā€™t ever send any entity over the wire Dumping your entity model back to consumers of an API exposes primary and foreign keys, it shows your schema and tightly couples usage of your API with your database
ero
eroOPā€¢3y ago
i meant like return Ok(createdUser);
Patrick
Patrickā€¢3y ago
If createduser is an instance of your entity then my point stands
ero
eroOPā€¢3y ago
mh
Patrick
Patrickā€¢3y ago
If createduser is a DTO then fine
ero
eroOPā€¢3y ago
i mean, what does "show my schema" mean? the entire backend is open source
Patrick
Patrickā€¢3y ago
Exposes your database design, columns
ero
eroOPā€¢3y ago
yeah it's. it's open source
Patrick
Patrickā€¢3y ago
Sure thatā€™s irrelevant
ero
eroOPā€¢3y ago
i mean, doesn't that expose my database design more than anything lol
Patrick
Patrickā€¢3y ago
Yes thatā€™s by virtue of being open sourceā€¦ a couple hours ago you were concerned with building the perfect solution now you want to expose database entities over the wire and circumvent best practice Which is it?
ero
eroOPā€¢3y ago
i don't want to do anything i asked if you should do that
Patrick
Patrickā€¢3y ago
Right and I said no
ero
eroOPā€¢3y ago
i was there
Patrick
Patrickā€¢3y ago
Good luck.
ero
eroOPā€¢3y ago
ok...?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
what's that got anything to do with what i said lol
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i mean i already do...? i don't understand what's going on
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
because i've seen people do that
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
could i instead return something like a UserViewDto?
qqdev
qqdevā€¢3y ago
Hi! Just joined What is your entity called?
ero
eroOPā€¢3y ago
User!
qqdev
qqdevā€¢3y ago
You usually just append Dto
ero
eroOPā€¢3y ago
should i?
qqdev
qqdevā€¢3y ago
Kinda depends. Some people always create dto classes I don't
ero
eroOPā€¢3y ago
i don't know if that makes sense here
qqdev
qqdevā€¢3y ago
I would do it if you are a beginner Don't think about it too much
ero
eroOPā€¢3y ago
because i have a CreateUserDto. it's what's passed into the UsersController.CreateUser endpoint(?) then i would maybe have a GeneralUserViewDto which is perhaps passed when a user page is loaded
qqdev
qqdevā€¢3y ago
I see! How would that differ from a UserDto?
ero
eroOPā€¢3y ago
maybe a MinimalUserViewDto for clicking a user's profile picture from a forum post which perhaps yields a pop up like on discord
qqdev
qqdevā€¢3y ago
Where is that View infix coming from?
Anton
Antonā€¢3y ago
dto = view already AutoMapper is great btw, without it the dto mapping code will become unmaintainable, especially if you need to map nested things
qqdev
qqdevā€¢3y ago
that's not true got big dtos at work and it is easy to maintain without AutoMapper Just implement ToDto() methods and you are good to go Or use AutoMapper if you don't want to deal with that and can bear another dependency
ero
eroOPā€¢3y ago
i just don't see how i can use a singular dto for everything
qqdev
qqdevā€¢3y ago
you don't have to do whatever makes sense
ero
eroOPā€¢3y ago
especially because my CreateUserDto contains a Password property it just doesn't make sense to me
qqdev
qqdevā€¢3y ago
it's fine to use multiple ones then just try to keep it slim as much as possible
Anton
Antonā€¢3y ago
Not exactly. If you use EF Core and want to project the database entities, just ToDto is not enough, because EF Core needs expressions, so you'd have to repeat your mapping code for every request, or deal with lower lever expressions. This becomes bad if you have nested things, because then the amount of copypasta multiplies. Either copy pasta, or dealing with combining expression trees, which is no fun
qqdev
qqdevā€¢3y ago
i don't use ef core so I can't tell what do dtos have to do with the database btw?
Anton
Antonā€¢3y ago
It's even rougher for you then. If you want efficient queries, you'd have to select only the things you need in your sql queries
qqdev
qqdevā€¢3y ago
it should be a dto <-> entity relationship you get the entity from the db and convert it to a dto and vice versa
Anton
Antonā€¢3y ago
No, that's the incorrect way of doing it Doing that you fetch all data of that entity when you only need a subset The right way is to .Select into a dto, if you use efcore if you just write raw queries, that would mean select into dictionaries or anonymous objects or whatever, and then mapping into the target type
Timtier
Timtierā€¢3y ago
If we're being a bit more strict, it should be Database Model <---> Domain Model <---> DTO Model
qqdev
qqdevā€¢3y ago
Do you have an example? @AntonC To get familiar with the ef core approach
Saber
Saberā€¢3y ago
either select out the properties like a normal select statement into the dto, or use an expression like
public static Expression<Func<Entity, Dto>> Projection => x => new Dto { ... };
public static Expression<Func<Entity, Dto>> Projection => x => new Dto { ... };
Anton
Antonā€¢3y ago
With automapper, you do dbContext.Users.ProjectTo<MinimalUserDto>(), which would be equivalent to
dbContext.Users.Select(u => new MinimalUserDto
{
Name = u.Name,
Avatar = u.Avatar,
// no other properties, User has more props than MinimalUser
});
dbContext.Users.Select(u => new MinimalUserDto
{
Name = u.Name,
Avatar = u.Avatar,
// no other properties, User has more props than MinimalUser
});
qqdev
qqdevā€¢3y ago
I see
Anton
Antonā€¢3y ago
Which generates the following SQL
select Name, Avatar
from Users
select Name, Avatar
from Users
So it doesn't fetch the whole user
qqdev
qqdevā€¢3y ago
So you could just implement ToDto() methods I guess? And use it on each property you actually need
Anton
Antonā€¢3y ago
It just being a method is not enough, that thing you see in the argument of select is an Expression<Func<User, MinimalUserDto>>, it's not just a func It builds an actual expression tree
ero
eroOPā€¢3y ago
my entities don't evne know about my dtos
Anton
Antonā€¢3y ago
If you were to use a normal extension method instead, it would query the whole user and then do the mapping after the fact
ero
eroOPā€¢3y ago
they're in different projects entirely
Anton
Antonā€¢3y ago
or it would just be an error, I don't remember
qqdev
qqdevā€¢3y ago
dbContext.Users.Select(u => new MinimalUserDto
{
Name = u.Name,
Avatar = u.Avatar,
Permissions = u.Permissions.ToDto(),
// no other properties, User has more props than MinimalUser
});
dbContext.Users.Select(u => new MinimalUserDto
{
Name = u.Name,
Avatar = u.Avatar,
Permissions = u.Permissions.ToDto(),
// no other properties, User has more props than MinimalUser
});
What about that?
Anton
Antonā€¢3y ago
This would query all permissions, then map the permissions after the fact Or it's not even allowed possibly
Patrick
Patrickā€¢3y ago
You canā€™t just run arbitrary C# in a database engine
Anton
Antonā€¢3y ago
I don't remember
qqdev
qqdevā€¢3y ago
I see, thanks
Patrick
Patrickā€¢3y ago
Your projection has to be something that can be translated.
Anton
Antonā€¢3y ago
So you need to either build the expression tree manually, or copy-paste
Patrick
Patrickā€¢3y ago
Also stay away from automapper.
qqdev
qqdevā€¢3y ago
ty, didn't touch ef core that much. always sticked to more bare-metal stuff which does not have that "quirks"
Anton
Antonā€¢3y ago
AutoMapper does this for you, it builds the right expression tree It might be worse for perf, but it's way more maintainable like orders of magnitude
Timtier
Timtierā€¢3y ago
Until you get into more complex mappings and then it becomes more of an annoyance šŸ˜›
Patrick
Patrickā€¢3y ago
Less performant and less maintainable
qqdev
qqdevā€¢3y ago
i am not a big fan of magic āœØ what happens if property names don't match?
Anton
Antonā€¢3y ago
AutoMapper is plenty configurable
qqdev
qqdevā€¢3y ago
how does it know that property names are supposed to match?
Anton
Antonā€¢3y ago
It's convention-based. It has a bunch of predefined rules
Patrick
Patrickā€¢3y ago
You end up writing the same code people try to avoid
Anton
Antonā€¢3y ago
But you can add more fine grained things if you need to
Patrick
Patrickā€¢3y ago
Itā€™s quite funny to read configurations for automapper
Anton
Antonā€¢3y ago
imo automapper is less about not writing that mapping code than not writing it more than once. without manually combining expression trees, you'd have to copy paste some code multiple times if you have many dtos, especially with shared bits
ero
eroOPā€¢3y ago
actually, pobiega did a similar thing here, returning the entire database entity
qqdev
qqdevā€¢3y ago
Stack Overflow
Can I reuse code for selecting a custom DTO object for a child prop...
When querying using Entity Framework Core, I am using expressions to convert to DTO objects, which works well for the object, and any child collections. A simplified example: Model: public class Mo...
qqdev
qqdevā€¢3y ago
I just saw that you could turn that ToDto() methods into expressions which totally makes sense That's probably what I would do. But everyone can figure out what they like the most meowheart
ero
eroOPā€¢3y ago
so after converting my database to an actual DbContext, i'm getting
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Lbgg.Infrastructure.LbggDbContext Lifetime: Singleton ImplementationType: Lbgg.Infrastructure.LbggDbContext': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[Lbgg.Infrastructure.LbggDbContext]' while attempting to activate 'Lbgg.Infrastructure.LbggDbContext'.)'
System.AggregateException: 'Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Lbgg.Infrastructure.LbggDbContext Lifetime: Singleton ImplementationType: Lbgg.Infrastructure.LbggDbContext': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[Lbgg.Infrastructure.LbggDbContext]' while attempting to activate 'Lbgg.Infrastructure.LbggDbContext'.)'
qqdev
qqdevā€¢3y ago
That's a dependency injection exception It is trying to create a LbggDbContext instance which requires DbContextOptions<T> which are not registered Did you add a AddDbContext(...) call?
ero
eroOPā€¢3y ago
i haven't so i have an Id for my users which i'd like to be auto-incrementing first off, is that sensible, and if so, how do i do it?
Timtier
Timtierā€¢3y ago
That is sensible and secondly, it depends on the database type you're using
Timtier
Timtierā€¢3y ago
Timtier
Timtierā€¢3y ago
So if you ensure you define your Key in your database model or context. You might also need to apply a migration (google the concepts as I can't explain them in a short time :x )
ero
eroOPā€¢3y ago
System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
System.InvalidOperationException: No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.
this happens when i try to call my CreateUser endpoint
public class LbggDbContext : DbContext
{
public LbggDbContext(DbContextOptions<LbggDbContext> options)
: base(options) { }

public required DbSet<User> Users { get; set; }
}
public class LbggDbContext : DbContext
{
public LbggDbContext(DbContextOptions<LbggDbContext> options)
: base(options) { }

public required DbSet<User> Users { get; set; }
}
hm
qqdev
qqdevā€¢3y ago
.
ero
eroOPā€¢3y ago
yes it was the required keyword which makes total sense naw wait nevermind builder.Services.AddDbContext<LbggDbContext>(); is there anything else i need to do?
qqdev
qqdevā€¢3y ago
I think you have to setup the options iirc Just look it up on the web. I gtg, see ya!
Patrick
Patrickā€¢3y ago
builder.Services.AddDbContext<LbggDbContext>(d => d.UseSqlServer(...));
builder.Services.AddDbContext<LbggDbContext>(d => d.UseSqlServer(...));
or whatever provider you're using
Anton
Antonā€¢3y ago
you'll need the connection string to the database, which usually comes from the configuration for development there's a cool extension method for app, EnsureDatabaseCreate<context> useful for setting up the db on new machines
ero
eroOPā€¢3y ago
alright, getting back into it a little (at work, so can't do all that much) just using an in memory database for testing right now
builder.Services.AddDbContext<LbggDbContext>(opt => opt.UseInMemoryDatabase("Lbgg"));
builder.Services.AddDbContext<LbggDbContext>(opt => opt.UseInMemoryDatabase("Lbgg"));
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i'll worry about it later, yeah what i did for the auto-incrementing ID for now is this in my user entity:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public long Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public long Id { get; set; }
and this in my DbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Property(u => u.Id)
.ValueGeneratedOnAdd();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Property(u => u.Id)
.ValueGeneratedOnAdd();
}
is that right?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
oh?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
huh other thing. is there any way to do enums properly in swagger?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
that's annoying
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i mean it just shows the enum as an array of numbers
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
can't it's for pronouns
Esa
Esaā€¢3y ago
Funky thing about enums is they are just spicy ints šŸ‘€
ero
eroOPā€¢3y ago
which is a flag enum yeah i'm aware lol
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
yeah that doesn't exist in my version
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Esa
Esaā€¢3y ago
Just a quick question if you don't mind me asking Ero, you seem knowledgeable about a lot of things that relates to memory, performance etc - but i'm a little surprised backend is unfamiliar to you. What do you usually work with since you know your way around C# but not specifically backend tasks?
ero
eroOPā€¢3y ago
apparently it's obsolete and was removed
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
that's what i went with yeah
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i mean i don't really see another way
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i mean i'm not gonna use random ints that i forget what they mean... process memory, specifically reading and writing remote memory, injecting, the like so anyway. i've decided to make the namespaces Api.Users.DataTransfer.In and Api.Users.DataTransfer.Out. this obviously doesn't seem right, i've certainly never seen anyone else do it the In one contains for example the CreateUserDto which is the object passed into my controller by whoever is calling the endpoint the Out namespace for example contains GeneralUserDto, or MinimalUserDto since i don't super like this solution, are there any better suggestions?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Esa
Esaā€¢3y ago
re. structure, I usually have this structure:
Controllers
Models
(all dtos in here etc - you could also place them in their respective folders, for example Controllers.Users.Models if you wanted to)
Users
(my UserController exists here along with its interface and any other files that relates to only that controller)
// any other controllers I offer will have their own folder similar to Users
Integration
Logging
Spotify
Domain
(all domain files for my spotify integration would be here)
Spotify.cs
SpotifyAuthorizationHandler.cs
// etc
Service
// and in here I'd put my own service logic
Controllers
Models
(all dtos in here etc - you could also place them in their respective folders, for example Controllers.Users.Models if you wanted to)
Users
(my UserController exists here along with its interface and any other files that relates to only that controller)
// any other controllers I offer will have their own folder similar to Users
Integration
Logging
Spotify
Domain
(all domain files for my spotify integration would be here)
Spotify.cs
SpotifyAuthorizationHandler.cs
// etc
Service
// and in here I'd put my own service logic
Just posting this as an alternative seeing as you asked for suggestions
Anton
Antonā€¢3y ago
yes Ā AddEnumsWithValuesFixFilters in the package Ā Ā Ā Ā <PackageReferenceĀ Include="Unchase.Swashbuckle.AspNetCore.Extensions"Ā Version="2.6.12"Ā /> I think this effectively copies the enum declarations so both names and their int values
ero
eroOPā€¢3y ago
i'm not really sure what to do next. i also have a big concern about how to store user data surely i can't just have a string Password property and what about Google or GitHub logins? don't those also need to be stored as users? or are those separate entities?
Anton
Antonā€¢3y ago
auth is a pretty big topic, I'm figuring it out myself atm
Patrick
Patrickā€¢3y ago
you would still have your own user saved, for your own data
Anton
Antonā€¢3y ago
YouTube
ASP.NET Core Authentication and Authorization Tutorials
asp.net core authentication and authorization tutorials. Covering asp.net core cookie authentication and authorization, jwt token authentication and authoriz...
Anton
Antonā€¢3y ago
check out these vids
Patrick
Patrickā€¢3y ago
but they wouldn't have a password and instead have an identifier and way to store it in your database the passwords you store should never be plaintext and be hashed/salted BCrypt and several other popular libraries exist to make that painless.
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
does it make sense to have a sort of base entity? something like this maybe?
internal abstract class EntityBase
{
public Guid Id { get; set; }

public Instant CreatedAt { get; set; }
public Instant? RemovedAt { get; set; }
}
internal abstract class EntityBase
{
public Guid Id { get; set; }

public Instant CreatedAt { get; set; }
public Instant? RemovedAt { get; set; }
}
Instant is from NodaTime?
Pobiega
Pobiegaā€¢3y ago
Thats fairly normal, especially if your app has "all entities" fields except ID.
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
We have it at work, with very similar CreatedAt/UpdatedAt etc
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i see... i mean i don't know, i feel conflicted about that interfaces to me most of the time are like "this is what i expose" and i don't really want to "expose" setting anything
Pobiega
Pobiegaā€¢3y ago
Depends on if you need composability in your entities I suppose.
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
If everything uses softdelete and CreatedAt/UpdatedAt timestamps, you dont need it.
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
I like the composable interfaces a lot.
ero
eroOPā€¢3y ago
also is the bool IsDeleted really necessary if the DeletedAt instant is nullable? i don't really think it makes sense to make it non-nullable
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
Instant? UpdatedAt as well, imo
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i guess that one you can set to the same time as CreatedAt
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
normally if you care about when something happened but its optional, letting the timestamp be nullable is a good option and if you check this a lot, you can just add a helper property for the bool value public bool IsDeleted => DeletedAt is not null;
ero
eroOPā€¢3y ago
i don't expect to be checking it a lot not yet anyway like one use i can think of is checking whether a username is available after a user chose to delete their account like wait 30 days after or something
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Pobiega
Pobiegaā€¢3y ago
hm, not sure tbh. I would explicitly ignore it with the entity configuration for sure
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
whatever that means do i need to set CreatedAt and UpdatedAt and all that manually? does that go in some method in my dbcontext class?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
the reason i wanted to use a base class is so i don't have to implement the things like ID and the Instance on every single one of my entities every time cause i mean, that's just unnecessary code i feel like
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i mean vs does too, it's just why explicitly have those properties in the entities
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i can't think of a situation where i wouldn't want something to be soft deletable i think i would want to be able to track each and every single thing my site staff did
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
and be able to easily undo it
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Anton
Antonā€¢3y ago
using base classes as mixins isn't flexible, but I guess it's better than boilerplate
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Accord
Accordā€¢3y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
ero
eroOPā€¢3y ago
i'm not super sure on what my next steps are. i have a "working" user controller (literally only posts and gets by id), but it's obviously far from done i was thinking... setting up a proper database? i was thinking postgres or, perhaps work on encrypting passwords?
Kouhai
Kouhaiā€¢3y ago
If you're using EF, swapping your db drivers shouldn't require many modifications
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
people have recommended BCrypt?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i don't really understand why you would hash a password?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
don't you need to decrypt it to check for a valid login?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Kouhai
Kouhaiā€¢3y ago
Encrypting passwords would mean the person with the access to the key can decrypt all passwords
ero
eroOPā€¢3y ago
hm so, what about bcrypt? is it like, the "industry standard"?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
i see
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Anton
Antonā€¢3y ago
you can use microsoft identity since you're already using ef core
ero
eroOPā€¢3y ago
what's that?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Anton
Antonā€¢3y ago
it dsows everything for you basically
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
no i mean ef core, what's that sorry, trolling
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
thought it was funny
Accord
Accordā€¢3y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
ero
eroOPā€¢3y ago
How would one implement a "edit history" feature, say for posts? I'll be honest, i haven't worked on anything since i last posted here, just a bit busy and also not feeling like working on something i don't understand lol
Patrick
Patrickā€¢3y ago
another table that retains versions of posts
class Post
{
int Id
string Content
ICollection<PostVersion> Versions
}

class PostVersion
{
int Id
int PostId
Post Post
DateTimeOffset CreatedDateTime
string Content
}
class Post
{
int Id
string Content
ICollection<PostVersion> Versions
}

class PostVersion
{
int Id
int PostId
Post Post
DateTimeOffset CreatedDateTime
string Content
}
ero
eroOPā€¢3y ago
Hm, so I'd add the post's current information into a new instance of a PostVersion, add that to that post's Versions, and then overwrite it with the new information?
Patrick
Patrickā€¢3y ago
yes
ero
eroOPā€¢3y ago
And i guess i could do that in overwrite SaveChangesAsync?
Patrick
Patrickā€¢3y ago
no, that would just be part of your service
ero
eroOPā€¢3y ago
Mh
Patrick
Patrickā€¢3y ago
there's no overriding of ef behaviour
ero
eroOPā€¢3y ago
What? Sure there is In my DbContext?
Patrick
Patrickā€¢3y ago
no, if you're overriding save changes for some business logic it's generally incorrect
ero
eroOPā€¢3y ago
What if i want to change the entity's UpdatedAt time?
Patrick
Patrickā€¢3y ago
i do that in my services ĀÆ\_(惄)_/ĀÆ
ero
eroOPā€¢3y ago
You guys fight it out i guess lol
Patrick
Patrickā€¢3y ago
yeah i dont like that - every entity is beholden to that logic what if i change a property on an entity via the system and it shouldn't update the last updated column
ero
eroOPā€¢3y ago
It should, at least in my opinion
Patrick
Patrickā€¢3y ago
code like this is hidden from developers and is why EF gets a bad wrap obscuring what happens when i call SaveChanges by injecting business logic, without a developer knowing, is less maintainable, readable and leads to debugging sessions asking wtf is going on
ero
eroOPā€¢3y ago
Lol i had to Google that.
Bad wrap is considered wrong and is best saved for referring to wraps and tortillas.
ā€“ Merriam-Webster Found that funny
Patrick
Patrickā€¢3y ago
it's autocorrect it's bad rap, not wrap
ero
eroOPā€¢3y ago
I believe it, don't worry It's rep From reputation
Patrick
Patrickā€¢3y ago
no bad rap is an idiom i mean bad rap
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
i dont know what that interface does
ero
eroOPā€¢3y ago
Ah, you're right
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
think about the semantics of it, "savechanges" but it actually sets some properties code like this is only made possible because EF allows you to override save change behaviour, if you didn't have EF how would you implement it with Dapper?
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
yes
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
that's an entirely different concept multi tenanted apps are not on the same level as "im calling savechanges" initialising a db connection and having that go to the right tenant in a multi tenanted application is pretty standard nor is it hidden code, because the initialisation is expected to... well... initialise global query filters are as evil as this - let me just add a global filter real quick and have every query in my system affected by this, unless i opt out you don't get global query filters in sql, so why should i let some magic dictate that for me again - this is EF's doing and habits like that kill maintainability
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
sure, it's just a code smell
ero
eroOPā€¢3y ago
I'm gonna agree with Duke i think, i prefer not forgetting to update all that stuff, especially since all my entities will be softdeletable and lots will be updateable and versionable
Patrick
Patrickā€¢3y ago
easy to fall into the trap when you're new to this šŸ™‚
ero
eroOPā€¢3y ago
I just don't see the issue really
Patrick
Patrickā€¢3y ago
obscuring what happens when i call SaveChanges by injecting business logic, without a developer knowing, is less maintainable, readable and leads to debugging sessions asking wtf is going on
i won't bother to mention the perf hit
ero
eroOPā€¢3y ago
I don't know, i just disagree? Save changes means applying all changes to the db. That implicitly means updating the UpdatedAt time to me
Patrick
Patrickā€¢3y ago
to you being the key the argument against this is you forget you can also forget to add the interface then what?
ero
eroOPā€¢3y ago
I'm not sure how you mean Forget it where?
Patrick
Patrickā€¢3y ago
forget the interface
ero
eroOPā€¢3y ago
Yes, where?
Patrick
Patrickā€¢3y ago
your class
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢3y ago
How would I forget it? It's the very thing that makes something versionable Exactly
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
Patrick
Patrickā€¢3y ago
so an interface dictates business behaviour? the mere presence injects behaviour that you can't see anywhere in the entity or service or anywhere discernible other than your database layer because it's the argument against this you're human and forget things everyone does
ero
eroOPā€¢3y ago
This is incredibly doubtful, I'm gonna be real If this is the only way I've ever worked like in my project, it's unlikely I'll change how i work on a whim
Patrick
Patrickā€¢3y ago
not impossible, then
Unknown User
Unknown Userā€¢3y ago
Message Not Public
Sign In & Join Server To View
FusedQyou
FusedQyouā€¢3y ago
Damn, my man is creating a whole project in this thread
Accord
Accordā€¢3y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
ero
eroOPā€¢2y ago
@Pobiega could you look over this and offer some advice?
[AllowAnonymous]
[HttpPost("register")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> RegisterUser(RegisterUserRequest request)
{
if (await _db.Users.AnyAsync(u => u.Email == request.Email))
{
return Conflict("A user with this email address already exists.");
}

if (await _db.Users.AnyAsync(u => u.Username == request.Username))
{
return Conflict("A user with this username already exists.");
}

User registeredUser = new()
{
Username = request.Username,
Email = request.Email,
Password = BCryptNet.EnhancedHashPassword(request.Password)
};

_db.Users.Add(registeredUser);
await _db.SaveChangesAsync();

// TODO: Create response type.
RegisterUserResponse response = new(
);

return CreatedAtRoute($"{registeredUser.Id}", response);
}
[AllowAnonymous]
[HttpPost("register")]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task<IActionResult> RegisterUser(RegisterUserRequest request)
{
if (await _db.Users.AnyAsync(u => u.Email == request.Email))
{
return Conflict("A user with this email address already exists.");
}

if (await _db.Users.AnyAsync(u => u.Username == request.Username))
{
return Conflict("A user with this username already exists.");
}

User registeredUser = new()
{
Username = request.Username,
Email = request.Email,
Password = BCryptNet.EnhancedHashPassword(request.Password)
};

_db.Users.Add(registeredUser);
await _db.SaveChangesAsync();

// TODO: Create response type.
RegisterUserResponse response = new(
);

return CreatedAtRoute($"{registeredUser.Id}", response);
}
the thing here is that see people use services for things like those checks (whether a user with the given email or username already exists) so for example they have _userService.GetByUsername() and _userService.GetByEmail() and i'm not sure whether that's really required, if there's a better way i feel like doing 2 queries, once for email and once for username, is just unnecessary also is CreatedAtRoute correct here? should i return something else? i see CreatedAtAction being used, with a GetUserById endpoint in the controller
Unknown User
Unknown Userā€¢2y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢2y ago
Necessary comment
Pobiega
Pobiegaā€¢2y ago
So, this is fine, but having application logic in the controller directly is controversial, but it's a valid choice Doing one query could work, but might give a worse error message back, which may or may not be desired
ero
eroOPā€¢2y ago
What's your suggestion?
Pobiega
Pobiegaā€¢2y ago
Probably passing the entire request object into either a command handler or a service class method, and copy-pasting the entire method minus the HTTP specific bits (conflict, createdatroute) into there that would be the "other way" to do things. If this project will only ever be a web api, that isnt strictly needed and for shorter methods like this, this is tbh fine
ero
eroOPā€¢2y ago
which one do you think is more, i guess modern? more maintainable, more easily expandible?
Pobiega
Pobiegaā€¢2y ago
Imho command handlers, but as said, this is controversial and everyone has their own opinions you'll probably need something like a Result<T> type if you go that way, so that the handler can indicate what went wrong (was the email/username taken, was your password not long enough, etc) and let hte controller return the correct http response as long as you keep your controllers focused and specific, its fine to inline code in them. It becomes a problem when you have 500+ lines of code controllers that know everything about your app
ero
eroOPā€¢2y ago
where in your solution would you say you'd put those?
Pobiega
Pobiegaā€¢2y ago
the handlers? in your application logic/business logic project, usually grouped by some kind of feature. Like, a UserRegistrationHandler would live in the App.Business.Users namespace and if you are fine with having multiple types in a single file, I'd probably put my command in the same file as the handler as they are extremely closely related
ero
eroOPā€¢2y ago
so i have an App.Api.Users namespace, which has my UsersController. would you still say the handler goes into a App.Business.Users namespace?
Pobiega
Pobiegaā€¢2y ago
If you have an App.Business project, yes if you dont, then its fine to have it in your App.Api
ero
eroOPā€¢2y ago
mh, i have an App.Shared project?
Pobiega
Pobiegaā€¢2y ago
isnt that more for DTOs and contract classes?
ero
eroOPā€¢2y ago
which has my request and response records
Pobiega
Pobiegaā€¢2y ago
right, so thats not the place for a handler
ero
eroOPā€¢2y ago
oh so the "command" isn't the request
Pobiega
Pobiegaā€¢2y ago
it should be where your core logic is, wherever that might be well, it can be there are certainly cases where you can literally use a request as your command object
ero
eroOPā€¢2y ago
hm
Pobiega
Pobiegaā€¢2y ago
and just have a handler use that. sorta depends on what level of validation you do and where
ero
eroOPā€¢2y ago
so considering this
Pobiega
Pobiegaā€¢2y ago
then probably in your API
ero
eroOPā€¢2y ago
i feel like i need to change this a bit i'm find with adding an App.Business project, no harm there i'm just still a bit confused on how things are split up mainly Infrastructure and Domain
Pobiega
Pobiegaā€¢2y ago
thats understandable, there are thousands of ways to do this šŸ˜›
ero
eroOPā€¢2y ago
the main concerns apply make it easy for contributors to find their way around and make contributions easily
Pobiega
Pobiegaā€¢2y ago
well, domain is the core things that your program revolves around - users, categories, time series, individual submissions etc
ero
eroOPā€¢2y ago
make the code concise, maintainable, and performant "time series"?
Pobiega
Pobiegaā€¢2y ago
infrastructure will contain your EF specific stuff related to those core things, like your configuration classes, your dbcontext, etc
ero
eroOPā€¢2y ago
just an example of an entity?
Pobiega
Pobiegaā€¢2y ago
well you were making a speedrun website? yeah
ero
eroOPā€¢2y ago
right yeah not just speedrunning, score "runs" too but yeah
Pobiega
Pobiegaā€¢2y ago
yeah it was just an example of an entity
ero
eroOPā€¢2y ago
alright good so the command handlers just genuinely don't go in either domain or infra this is a lot
Pobiega
Pobiegaā€¢2y ago
not "traditionally" no, they are what contains most of your application logic some people have "had it" with this entireprise splitting of everything, and there are merits to that approach too
ero
eroOPā€¢2y ago
that's what the project is doing currently, and i'm not a huge fan of it... it's just very cluttered would you say validation goes into App.Business then too? https://github.com/leaderboardsgg/leaderboard-backend here's the current setup, by the way. feel free to absolutely criticize every little thing some of us know absolutely no db stuff, others are new to c#. so it's a bit of a mess which is why i'm planning this refactor get myself familiar with db things, the c# part is easy for me
Pobiega
Pobiegaā€¢2y ago
Sure, any explicit validation. You get some for free by ASP.NET, like respecting required and stuff
ero
eroOPā€¢2y ago
would you say making up a custom identification type is worth it? the site needs an id for each individual submission, which can become a lot. we're thinking of using something like discord's snowflakes; a 64 bit number where part of it consists of a timestamp and another of an incrementing number
Pobiega
Pobiegaā€¢2y ago
ĀÆ\_(惄)_/ĀÆ if you use long for IDs, they grow very large without needing snowflake style IDs but if you dont want enumerability, then go for a guid or a snowflake
ero
eroOPā€¢2y ago
imo the issue with guid is accessing a page via it? like !e $"https://lb.gg/user/{Guid.NewGuid()}"
MODiX
MODiXā€¢2y ago
Ero#1111
REPL Error
An error occurred while sending a request to the REPL service. This may be due to a StackOverflowException or exceeding the 30 second timeout. Details: Connection refused (repl:31337)
Tried to execute
ero
eroOPā€¢2y ago
aw really i mean you get the idea
Pobiega
Pobiegaā€¢2y ago
yeah urls get nasty with guids in them
ero
eroOPā€¢2y ago
and auto-incrementing sort of enables this "race" to get a low ID somewhere
Pobiega
Pobiegaā€¢2y ago
and enumerability so yeah, if you dont like that, go for a snowflake
ero
eroOPā€¢2y ago
is enumerability a bad thing?
Pobiega
Pobiegaā€¢2y ago
can be like, if my user profile page is lb.gg/user/6123, what stops me from scraping all the users?
ero
eroOPā€¢2y ago
the rate limit of the website! or something
Pobiega
Pobiegaā€¢2y ago
true obviously more of a problem if there is some valuable information on that page, like linkedin etc
ero
eroOPā€¢2y ago
i guess that's a valid concern though
Pobiega
Pobiegaā€¢2y ago
only "downside" will be that you wont be able to have your database generate IDs, but thats easy enough to work around
ero
eroOPā€¢2y ago
could i have guids as the internal storage, but get them via some other converted value? guids do store their bytes after all
Pobiega
Pobiegaā€¢2y ago
ĀÆ\_(惄)_/ĀÆ never tried easily solved by having the public entity ctor assign a snowflake, and have a protected one for EF to use
ero
eroOPā€¢2y ago
i don't even know how to begin doing that lmao
Pobiega
Pobiegaā€¢2y ago
just have your "first" constructor be a protected one and have an argument for every property EF will use that to create its entities and when you make new users or whatever with new User() you use the public ctor, that creates a snowflake as part of object initialization as for generating the snowflake, thats on you, i have no idea how they work šŸ™‚
ero
eroOPā€¢2y ago
how would i tell EF that my custom snowflake type is the primary key? just slap that attribute on it?
Pobiega
Pobiegaā€¢2y ago
call the property Id šŸ™‚
ero
eroOPā€¢2y ago
ah, right it does do that
Pobiega
Pobiegaā€¢2y ago
or attribute, or set it with your EntityConfiguration<T> class, or in the OnModelCreate (dont do this)
ero
eroOPā€¢2y ago
should things like migrations be pushed to the source repo?
Unknown User
Unknown Userā€¢2y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢2y ago
they don't
Unknown User
Unknown Userā€¢2y ago
Message Not Public
Sign In & Join Server To View
ero
eroOPā€¢2y ago
why should other people besides us get a working copy of the db lol
Pobiega
Pobiegaā€¢2y ago
Migrations are... a tradeoff. They generate a database snapshot for each migration, so in a long-living application with hundreds of migration, a very significant part of your compile time will be those snapshots however, once this thing goes live you dont want to have to restart the DB from scratch every time there is an update
Accord
Accordā€¢2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
ero
eroOPā€¢2y ago
do you mind It does this way too quickly man
Patrick
Patrickā€¢2y ago
24 hours isnā€™t way too quickly
ero
eroOPā€¢2y ago
disagree i guess? what an unnecessary comment
Patrick
Patrickā€¢2y ago
Iā€™m glad you consider a comment telling you 24 hours isnā€™t too quickly as unnecessary in the face of a post that has well exceeded the general use case of the help forum we have on this channel as unnecessary. Consider moving this thread elsewhere if you believe the tools that we have in place that fit the general use of the forum as invasive.
ero
eroOPā€¢2y ago
suggest a place to move the thread to
Patrick
Patrickā€¢2y ago
Looks like code review to me, now.
Jester
Jesterā€¢2y ago
nearly 1500 messages <:PES_OMGNO:681925594312736811>
Accord
Accordā€¢2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server