C
C#13mo ago
Yianni

EFCore: don't require primary key for POST

Howdy, forgive me if I don't have the terminology down yet, I just started following this guide recently: https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api?view=aspnetcore-8.0&preserve-view=true&tabs=visual-studio So I've created a Model:
c#
public class User
{
[Key]
public required string Guid { get; set; }

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

[Required]
public required string Tag { get; set; }

public DateTime LastUpdate { get; set; }
}
c#
public class User
{
[Key]
public required string Guid { get; set; }

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

[Required]
public required string Tag { get; set; }

public DateTime LastUpdate { get; set; }
}
And here's my code for the Controller. Note that I am assigning the primary key within this method.
c#
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
User newUser = new User()
{
Guid = Guid.NewGuid().ToString(),
Name = name,
Tag = tag,
LastUpdate = DateTime.Now
};

_context.Users.Add(newUser);
return CreatedAtAction(nameof(GetUsers), user);
}
c#
[HttpPost]
public async Task<ActionResult<User>> PostUser(User user)
{
User newUser = new User()
{
Guid = Guid.NewGuid().ToString(),
Name = name,
Tag = tag,
LastUpdate = DateTime.Now
};

_context.Users.Add(newUser);
return CreatedAtAction(nameof(GetUsers), user);
}
My problem is that I do not want the primary key (Guid) to be required in the request body to send the POST request. Is there any way around this? I just want my POST request body to look like the following example:
{
"Name": "Jimbo",
"Tag": 12345
}
{
"Name": "Jimbo",
"Tag": 12345
}
Omitting the Guid from this results in the following error: "JSON deserialization for type 'api.Models.User' was missing required properties, including the following: guid"
7 Replies
Tvde1
Tvde113mo ago
have users send a different class
class CreateUserRequest
{
public string Name { get; set; }
public int Tag { get; set; }
}

[HttpPost]
public async Task<ActionResult<User>> PostUser(CreateUserRequest userRequest)
{
User newUser = new User()
{
Guid = Guid.NewGuid().ToString(),
Name = userRequest.Name,
Tag = userRequest.Tag.ToString(),
LastUpdate = DateTime.Now
};

_context.Users.Add(newUser);
return CreatedAtAction(nameof(GetUsers), user);
}
class CreateUserRequest
{
public string Name { get; set; }
public int Tag { get; set; }
}

[HttpPost]
public async Task<ActionResult<User>> PostUser(CreateUserRequest userRequest)
{
User newUser = new User()
{
Guid = Guid.NewGuid().ToString(),
Name = userRequest.Name,
Tag = userRequest.Tag.ToString(),
LastUpdate = DateTime.Now
};

_context.Users.Add(newUser);
return CreatedAtAction(nameof(GetUsers), user);
}
Yianni
YianniOP13mo ago
Thanks @Tvde1 , I didn't realize how the models could be used, this solved my issue!
Tvde1
Tvde113mo ago
it's very good practicec to have separate models for input + output of your API, and your entity so that changing one doesn't mean you have to change the other
PixxelKick
PixxelKick13mo ago
I also like to be in the habit of making my "Read" models inherit from my "Write" models, with the extra fields on the "read" model representing the "readonly" values. That way if you "read" out a model to fill out a form, when they post it back the fields should implicitly map 1:1 cuz, well, its the same fields!
Tvde1
Tvde113mo ago
yes! I have some where:
class UserForModification
{
public string Tag { get; set; }
public string FavoriteColor { get; set; }
}

class UserForCreation : UserForModification
{
public string UserName { get; set; }
}

public class User : UserForCreation
{
public int Id { get; set; }
}
class UserForModification
{
public string Tag { get; set; }
public string FavoriteColor { get; set; }
}

class UserForCreation : UserForModification
{
public string UserName { get; set; }
}

public class User : UserForCreation
{
public int Id { get; set; }
}
Where the PUT update endpoint uses te modification model, the POST create endpoint uses the creation model, and they return the User model with all properties is works nicely with libraries like Mapperly, that make mapping easier/safer but that's optional
Yianni
YianniOP13mo ago
oh wow thats pretty smart
PixxelKick
PixxelKick13mo ago
I typically use a single "write" model, and the trick is I actually handle the id as a totally seperate variable, because when you follow proper RESTful design, the Id should come from the route instead of the body. IE
[HttpPut(/api/users/{userId})]
public IActionResult UpdateUser(string userId, userWriteModel writeModel)
{
using var txn = Db.Database.OpenTransaction();
if (!UserService.TryUpdate(Db, userId, writeModel, out var readModel)
{
txn.Rollback();
return NotFound();
}
txn.Commit();
return Ok(readModel);
[HttpPut(/api/users/{userId})]
public IActionResult UpdateUser(string userId, userWriteModel writeModel)
{
using var txn = Db.Database.OpenTransaction();
if (!UserService.TryUpdate(Db, userId, writeModel, out var readModel)
{
txn.Rollback();
return NotFound();
}
txn.Commit();
return Ok(readModel);
Thats the pattern I typically adhere to for all my methods And then you'll notice that you can have "Create" on a POST method without the Id, and the "Put" method does have the Id, and you can use the same model for both If you wanna see some expanded examples of this pattern (I dont have txns setup yet on it but it gives the general idea) I have this controller as an example here: https://github.com/SteffenBlake/Notes/blob/main/src/Notes.Website/Controllers/ProjectsController.cs
Want results from more Discord servers?
Add your server