WebApi different DTO for Get and Post - c#

Is it ok to have different DTO's for GET and POST actions ?
The reason for this is there is usually a vast difference between these two data models.
For example:
My POST would look like this:
/// <summary>
/// Add new user
/// </summary>
/// <param name="item">User data</param>
/// <returns>Newly added user id</returns>
[HttpPost]
public IHttpActionResult Post([FromBody] UserDto item)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var model = _mapper.Map<User>(item);
int itemid = _usersRepository.Insert(model);
return Ok(itemid);
}
public class UserDto
{
private string _password;
[Required]
[StringLength(100, ErrorMessage = "Name {0} must consist of at least {2} letters.", MinimumLength = 6)]
public string Name { get; set; }
[Required]
public string ExternalName { get; set; }
[Required]
public bool Active { get; set; }
[Required]
public string Password
{
get { return _password; }
set { _password = value.Hash(); }
}
}
and my GET would look like this:
/// <summary>
/// Get all users
/// </summary>
/// <returns>Users list</returns>
[HttpGet]
[ResponseType(typeof(List<UserInfoDto>))]
public IHttpActionResult Get()
{
IList<UserInfoDto> values = _usersRepository.SelectAll();
if (values == null || !values.Any())
return Ok();
return Json(new { collection = values });
}
public class UserInfoDto
{
public int Id { get; set; }
public string Name { get; set; }
public string ExternalName { get; set; }
public bool Active { get; set; }
}
The reason I'd do this is that I don't need Id when POSTing a resource but Password is necessary. It is reversed when using GET.
Is this the correct approach when using WebApi (Creating different Dtos for responses and requests)? Or is there some other way to do this ?

Is it ok to have different DTO's for GET and POST actions ?
There is nothing wrong with having different dto's for different actions.
If the api is being used by 3rd parties you would want to make sure that it is well documented.
Is this the correct approach when using WebApi?
Whether this is correct approach or not is a matter of opinion. Expose or accept the necessary information for the system to perform as intended.
Each action could use its own unique dto. Doesn't mean that it should. You want to make sure you are not leaking more information than is necessary.

Related

Unauthorized Message,Status 200 OK

I am doing the backend according to the given architecture given by the company I recently started working.
I am new to C# and now I'm trying to do some post/get/put methods for some api-s.
There is a problem which I couldn't solve it.
Postman says:
{
"code": 1,
"message": "Unauthorize"
}
But the status code is 200 OK.
UserController.cs
[Route("v1/users")]
[Produces("application/json")]
public class UserController : BaseController
{
/// <summary>
/// Get list of users (Authorize)
/// </summary>
/// <returns>
/// </returns>
[ProducesResponseType(typeof(BaseResponseModel<List<UserResource>>), 200)]
[HttpGet]
public async Task<IActionResult> Get()
{
var user = await _userService.GetUserResourcesAsync();
return Success(user);
}
}
This doesn't make any sense, or am I so dumb to realise the problem?
I have a login method and I can login, I get the success code, then I do this:
enter image description here
Header
IProductService.cs
public interface IProductService
{
Task<ProductDto> GetAsync(int id);
}
ProductService.cs
public async Task<ProductDto> GetAsync(int id)
{
var product = await _context.Product.SingleOrDefaultAsync(p => p.Id == id);
return _mapper.Map<ProductDto>(product);
}
ProductDto.cs
public class ProductDto
{
public int Id { get; set; }
public CategoryDto CategoryId { get; set; }
public string Title { get; set; }
public bool AllowEdit { get; set; }
public string ItemCode { get; set; }
public string CustomerCode { get; set; }
}
Product.cs
[Table("Products")]
public class Product : DomainModel<int>
{
[Required]
public int ProductCategoryId { get; set; }
[ForeignKey("ProductCategoryId")]
public virtual ProductCategory ProductCategory { get; set; }
[Required, StringLength(256)]
public string Title { get; set; }
[Required, DefaultValue(false)]
public bool AllowEdit { get; set; }
[StringLength(50)]
public string ItemCode { get; set; }
[StringLength(50)]
public string CustomerCode { get; set; }
}
Using the ProducesResponseTypeAttribute, the attributed API actually defines what should be response code for specified types. See the definition
of at ProducesResponseTypeAttribute.
How it works
Take following example which shows that the API throws the 404 error if the object is null:
public IActionResult GetById(string id)
{
var post = <<Your logic here>>;
if (post == null)
{
return NotFound();
}
return Json(post);
}
Now the same method can be changed to following, which would return 404 by using the ProducesResponseType instead of the code is being written in your API logic.
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public IActionResult GetPostById(string id)
{
var post = <<Your logic here>>;
return Json(post);
}
In cases, it might be good to also define a more explicit response type for successful calls. To do so, add a ProducesResponseTypeAttribute for status code with type. ( return type as parameter, which makes the Type property of Produces redundant).
This is valuable, if you want to return different things from one and the same method, for example, the following returns two different types depending on the returned status code:
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(Model), (int)HttpStatusCode.OK)]
public IActionResult GetById(string id)
What is your problem
Now if you see your code which defines this attribute as [ProducesResponseType(typeof(BaseResponseModel<List<UserResource>>), 200)]. And the code to fetch user :
var user = await _userService.GetUserResourcesAsync();
returns BaseResponseModel<T>. The BaseResponseModel should contain the Code and Message property. So here, the response returned by API is type of BaseResponseModel<T>, so API would return 200 HTTP Status as defined by attribute.
How to Fix
Either you return a different object in case of Unauthorize exception and attach the ProducesResponseType specific to that type OR handle the unathorized logic based at Authorize attribute.

Disallow user to perform an update on a field or member of a class and return 405 Method Not Allowed : HTTP

I am working on an api which serves creating,updating,deleting of user settings for an application. My users are of two types
admin user
common user
I have a field public bool ReadOnly { get; set; } which says whether a common user is allowed to change the setting or not.
Now the question is in which layer i need to validate this and throw the 405 response to the client. Please suggest.
private readonly SettingsRepository _SettingsRepository;
[HttpPut("{userid}/settings/{settingName}")]
public IActionResult Put(string userid, [FromBody]Setting setting)
{
var result = _SettingsRepository.Update(userid, setting);
if (result == true)
{
return Ok(201);
}
else
{
return BadRequest();
}
}
//Updates the existing setting for a user having userid
public bool Update(string userid, Setting setting)
{
bool flag = false;
if (userid == null || setting == null)
{
return flag;
}
var existing = Profiles.profiles.Where(p => p.UserID.ToLower() == userid.ToLower() && p.Settings.Any(s => s.Name.ToLower() == setting.Name.ToLower())).SelectMany(res => res.Settings).ToList();
if (existing.Count() > 0)
{
existing.ForEach(e =>
{
e.Name = setting.Name;
e.Value = setting.Value;
e.Type = setting.Type;
e.Valid = setting.Valid;
e.ReadOnly = setting.ReadOnly;
e.ModifiedOn = DateTime.UtcNow;
e.Encrypted = setting.Encrypted;
e.Enabled = setting.Enabled;
e.CreatedOn = setting.CreatedOn;
e.Description = setting.Description;
});
FileSerDe.SerializeSettings<IList<Profile>>(Profiles.profiles, System.IO.Directory.GetCurrentDirectory() + "\\" + "seed.txt");
flag = true;
}
return flag;
}
//Profile Entity
public class Profile
{
public string UserID { get; set; }
public string UserName { get; set; }
public List<Setting> Settings { get; set; }
}
//Setting Entity
public class Setting
{
public string Name { get; set; }
public object Value { get; set; }
public string Type { get; set; }
public bool Encrypted { get; set; }
public bool ReadOnly { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ModifiedOn { get; set; }
public bool Valid { get; set; }
public bool Enabled { get; set; }
public string Description { get; set; }
}
It looks business logic is in repository. so you can put security measure in repository. do that first thing in repository & throw exception on failed.
this will centralize your business logic to single place.
405 Method not allowed would be used when the HTTP method (e.g. GET or PUT) is specifically not allowed to be used with a given URL, and would apply to all users. For something which is permissions-within your application and related to a specific user it would be more accurate to a send a 403 Forbidden response.
As for the layers, clearly the API Action method is the only one which can return the actual HTTP error code, but since the information that tells you whether the user has permission is held in the database, you could arrange it so that the data layer tells the API layer what the appropriate response should be, perhaps by throwing an exception or by setting a flag on an output parameter to the database method. This would require you to pass information about the current user to the database layer, though, some people think that's unnecessary overhead, unless it's required anyway to record audit data etc.
The alternative is that you could get the API layer to retrieve the relevant data from the database before attempting to run the Update, and make a decision based on that retrieved data, entirely within the API action method. It's really a design decision that is up to you and what suits your application structure. Either way is possible, and, arguably, valid.

HttpClient GetAsync and ReadAsStringAsync needed to deserialize just part of a complex JSON response

I am trying to deserialize just part of my JSON response when I call my function and then return it as a viewmodel but I can't seem to access an inner part of the JSON when I do this. The function in question is this,
// GetUserInfoTest method gets the currently authenticated user's information from the Web API
public IdentityUserInfoViewModel GetUserInfo()
{
using (var client = new WebClient().CreateClientWithToken(_token))
{
var response = client.GetAsync("http://localhost:61941/api/Account/User").Result;
var formattedResponse = response.Content.ReadAsStringAsync().Result;
return JsonConvert.DeserializeObject<IdentityUserInfoViewModel>(formattedResponse, jsonSettings);
}
}
I am able to setup an HttpClient with a token of an already authenticated user, and now I just need to get information regarding them by making a call to my API. Here is the viewmodel I am trying to fit the JSON into,
// Custom view model for an identity user
/// <summary>Custom view model to represent an identity user and employee information</summary>
public class IdentityUserInfoViewModel
{
/// <summary>The Id of the Identity User</summary>
public string Id { get; set; }
/// <summary>The Username of the Identity User</summary>
public string UserName { get; set; }
/// <summary>The Email of the Identity User</summary>
public string Email { get; set; }
/// <summary>Active status of the user</summary>
public bool Active { get; set; }
/// <summary>The Roles associated with the Identity User</summary>
public List<string> Roles { get; set; }
}
And the sample response,
{
"Success":true,
"Message":null,
"Result":{
"Id":"BDE6C932-AC53-49F3-9821-3B6DAB864931",
"UserName":"user.test",
"Email":"user.test#testcompany.com",
"Active":true,
"Roles":[
]
}
}
As you can see here, I just want to get the Result JSON and deserialize it into the IdentityUserInfoViewModel but I just can't seem to figure out how to go about doing it. It feels like something simple that I'll be kicking myself in the butt about later but can't seem to grasp what it is. Any ideas?
The data to deserialize into IdentityUserInfoViewModel is actually contained in the "Result" property of your posted JSON. Therefore you need to deserialize into some kind of container object like this:
public class Foo
{
public bool Success { get; set; }
public string Message { get; set; }
public IdentityUserInfoViewModel Result { get; set; }
}
Then your can deserialize into that and access the resulting object's Result property:
var o = JsonConvert.DeserializeObject<Foo>(formattedResponse);
var result = o.Result; // This is your IdentityUserInfoViewModel
You can make the response container generic, so it can contain any kind of result:
public class ResultContainer<T>
{
public bool Success { get; set; }
public string Message { get; set; }
public T Result { get; set; }
}
And then:
var container = JsonConvert.DeserializeObject<ResultContainer<IdentityUserInfoViewModel>>(formattedResponse);
var result = container.Result; // This is your IdentityUserInfoViewModel
Maybe I can help you by showing how I use the Deserializer for JSON:
public async Task GetJsonAsync()
{
HttpClient client = new();
HttpResponseMessage response = await client.GetAsync("put in your https");
if (response.IsSuccessStatusCode)
{
rootobjects = JsonSerializer
.Deserialize<ObservableCollection<Rootobject>>(await response.Content.ReadAsStringAsync(),
new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
}`

MVC ViewModel, using a VM with some properies marked required, depending if its a GET or POST

I've often found myself fixing validations for the modelstate manually, due to the inconsistency of some fields that are required in a view model during post and get.
Supposing I've got this View Model:
public class RestaurantMenuName_ViewModel
{
public Int64 RestaurantMenuNameId { get; set; }
public Int64 OwnerId{ get; set; }
public string MenuNameCategory { get; set; }
public string CategoryDescription { get; set; }
public bool IsFormSaved { get; set; }
}
During a GET request the controller/Action requires the validation of just the fields, RestaurantMenuNameId and OwnerId. When calling the Action RestaurantMenuName, the query string values are RestaurantMenuNameId and OwnerId. Modelstate validation will be done on:
RestaurantMenuNameId
OwnerId
During a POST request the controller/Action will require the modelstate validation of the fields:
RestaurantMenuNameId
OwnerId
MenuNameCategory
CategoryDescription
This is the inconsistency issue I'm talking about, a solution could be using a ViewModel for Get requests and one for Post, but this could be real a time waster and error prone. Using ViewBag is out of discussion.
Question:
Is there a way to tell MVC that we want some fields [required] for GET and other for POST?
The following is a Pseudo-code of what I'm talking about:
public class RestaurantMenuName_ViewModel
{
[Required: in GET, POST] //<--Pseudo code
public Int64 RestaurantMenuNameId { get; set; }
[Required: in GET, POST] //<--Pseudo code
public Int64 OwnerId { get; set; }
[Required: in POST] //<--Pseudo code
public string MenuNameCategory { get; set; }
[Required: in POST] //<--Pseudo code
public string CategoryDescription { get; set; }
public bool IsFormSaved { get; set; }
}
It's not a very good practice (and confusing in your case) to pass complex objects when you need only few properties. It will be better to pass only the required ids as primitives.
If the case is special and you really need the complex objects, it will be better to create two different view models for every request and decorate the required properties accordingly.
However, you can create your own require validation attribute which will validate properties dependening on the current request.
public class MyRequiredAttribute : ValidationAttribute
{
private string httpVerb;
public MyRequiredAttribute(string httpVerb)
{
this.httpVerb = httpVerb;
}
public override bool IsValid(object value)
{
if(HttpContext.Current.Request.HttpMethod == this.httpVerb)
{
return value != null;
}
return true;
}
}
// Usage
public class MyViewModel
{
[MyRequired("GET")]
public string A { get; set; }
[MyRequired("POST")]
public string B { get; set; }
}
Note: you can use an enumeration to avoid some difficulties (ex. upper case, lower case, misspelling etc.) and also you can override the FormatErrorMessage method to change the default error message and format it properly.

Can't serialize an object

I defined a model like this
public class Planilla
{
[Key]
public int IDPlanilla { get; set; }
[Required(ErrorMessage = "*")]
[Display(Name = "Dirección de Negocio")]
public int IDDireccionDeNegocio { get; set; }
[Required (ErrorMessage = "*")]
public string Nombre { get; set; }
[Display(Name = "Descripción")]
public string Descripcion { get; set; }
public bool Activo { get; set; }
[ScriptIgnore]
public virtual DireccionDeNegocio DireccionDeNegocio { get; set; }
}
And I have a method in my controller that returns the first element of this model
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(query);
}
My problem is when I invoke this method from client side throws an error that say's
circular reference is detected trying to serialize
System.Data.Entity.DynamicProxies.Planilla_7F7D4D6D9AD7AEDCC59865F32D5D02B4023989FC7178D7698895D2CA59F26FEE
Debugging my code I realized that the object returned by the execution
of the methodFirstit's a
{System.Data.Entity.DynamicProxies.Planilla_7F7D4D6D9AD7AEDCC59865F32D5D02B4023989FC7178D7698895D2CA59F26FEE}
instead a Model of my namespace like
Example.Models.DireccionDeNegocio`.
Why am I doing wrong?? Because I tried with other models and work's well
Use view models, that's the only advice I can give you. Never pass domain models to your views. It's as simple as that. And if you respect this simple rule and fundamental rule in ASP.NET MVC applications you will never have problems. So for example if you need only the id and the description in your view:
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(new
{
Id = query.IDPlanilla,
Description = query.Description
});
}
Notice that in this case the anonymous object serves as view model. But if you really wanted to do things properly you would write your view model:
public class PlanillaViewModel
{
public int Id { get; set; }
public string Description { get; set; }
}
and then:
[HttpPost]
public ActionResult GetElements(string IDCampana)
{
Planilla query = db.Planillas.First();
return Json(new PlanillaViewModel
{
Id = query.IDPlanilla,
Description = query.Description
});
}
By the way Ayende wrote a nice series of blog posts about this.
System.Data.Entity.DynamicProxies.* is the Entity Framework proxy namespace. Your DbContext creates your entities as such to support lazy loading and change tracking. This isn't your problem. The problem likely lies in a circular association.

Categories

Resources