Bind route parameter & values from json body to the model - .net-core

I have a put API in .net core 3.1 Web API project.
URL: https://localhost:44319/api/user/4
Controller method:
[HttpPut("{id}")]
[ProducesResponseType(typeof(UpdateUserCommandResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<UpdateUserCommandResponse>> PutUser(UpdateUserCommand updateUserCommand)
{}
UpdateUserCommand model:
public class UpdateUserCommand : IRequest<UpdateUserCommandResponse>
{
[FromRoute(Name = "id")]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Is it possible to populate Id in model from the URL & rest of the fields from the json body? Right now, Id remain 0
I would also like to not include the attribute right in the model, since the same
--- Updated Code -----
[HttpPut("{id}")]
[ProducesResponseType(typeof(UpdateUserCommandResponse), StatusCodes.Status200OK)]
public async Task<ActionResult<UpdateUserCommandResponse>> PutUser(UpdateUserCommand updateUserCommand)
{
var response = await _mediator.Send(updateUserCommand);
return Ok(response);
}
public class UpdateUserCommand : IRequest<UpdateUserCommandResponse>
{
//[FromRoute]
public int Id { get; set; }
[FromForm]
public string FirstName { get; set; }
[FromForm]
}

It will be working properly without any attributes at all if you select Content-Type
application/x-www-form-urlencoded
//or
multipart/form-data
or if you still have some problem you can try this
public int Id { get; set; }
[FromForm]
public string FirstName { get; set; }
[FromForm]
public string LastName { get; set; }
if you select Content-Type
application/json
you will have to change your action
[HttpPut("{id}")]
... PutUser( int id, [FromBody] UpdateUserCommand updateUserCommand)
{}
and IMHO remove [ProducesResponseType(typeof(UpdateUserCommandResponse), StatusCodes.Status200OK)] . I can't see what do you need it for

Related

Looking for a way to change http request and add user id on body data

I am working on a web API where login is required in order to do any other action, so I am using identity with role-based authentication.
What I would like to achieve is to override the user id into the request body before the controller's action only in cases where the request type implements a certain interface.
For example, I have these view model
public class UserVM : IVMLogging {
public Guid? ID { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public bool IsActive { get; set; }
public Guid Role { get; set; }
public string LastModifiedBy { get; set; }
}
public class OptionVM : IVMLogging {
public Guid? ID { get; set; }
public string Name { get; set; }
public string LastModifiedBy { get; set; }
}
And this interface
public interface IVMLogging {
string LastModifiedBy { get; set; }
}
In both cases property LastModifiedBy is where I want store the user id (ignoring the value coming from client).
Is this possible to be done?
While you can use middleware to modify an inbound request on the way to the controller, the work to try to inspect, serialize, and deserialize the body type is probably more trouble than it's worth. Your controllers can modify the contents of the view model on return if there are any properties that you'd like to update, like attaching the user:
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[HttpPost]
public IActionResult Post(Foo foo)
{
foo.Qux = HttpContext.User.Identity.Name; // Use the identity name
foo.Qux = HttpContext.User.FindFirst("claimName").Value; // grab a claim value
// do whatever actions
}
...
Your controller will have access to the HTTP context and can grab the current identity or claims and add them where you want them before you process your viewmodel.
If this is something you find yourself doing regularly, you could add an extension for it for your interface:
public static class FooExtensions
{
public static IFoo SetFooUser(this IFoo foo, ClaimsPrincipal claimsPrincipal)
{
foo.Qux = claimsPrincipal.Identity.Name;
return foo;
}
}
Which can then be called more simply with foo.SetFooUser(HttpContext.User) in your controller as long as it implements the interface.

Passing Date in an API

I am working on .Net Core and want to post attributes in the api containing dates but I m not able to handle dates for an API. How to overcome with this error.
using System;
using System.Collections.Generic;
namespace Server.Dtos
{
public class ProjectSessionDto
{
public int Id { get; set; }
public string Activity { get; set; }
public string ResourcePerson { get; set; }
public DateTime? TentativeDate { get; set; }
public DateTime SubmissionDate { get; set; }
public int SemesterId { get; set; }
public string Program { get; set; }
public string Batch { get; set; }
}
}
Controller Code...
[HttpPost("addprojectsession")] //Since there would be 2 Post Methods. Login and Register
public async Task<IActionResult> AddProjectSession(ProjectSessionDto projectsession)
{
var semester = await _semester.GetSemesterWithProjectSession(projectsession.SemesterId);
semester.projectsessions.Add(_mapper.Map<ProjectSessionDto,ProjectSession>(projectsession));
await _semester.AddSemesterWithProject(semester);
return Ok();
}
}
There are two issues you need to fix :
In Postman ,change the values with double quotes:
"tentativeDate": "12/12/2019",
"SubmissionDate":"12/12/2019"
In controller , use [FromBody]to make asp.net core model binding wokring for reading value from request body :
public async Task<IActionResult> AddProjectSession([FromBody]ProjectSessionDto projectsession)

How to overload an Odata V2 controller method to accept multiple values as parameters?

I am new in ASP.NET MVC web development. I am just trying to overload a simple odata controller method for several days but failing again and again. I want to know the mystery behind this. Please help me...
This is my EducationInfo Model class...
public partial class EducationInfo
{
[Key]
public int EducationID { get; set; }
[ForeignKey("UserID")]
public int UserID { get; set; }
[Required]
public string Title { get; set; }
[Required]
[StringLength(50)]
public string EducationLevel { get; set; }
public string Department_Group { get; set; }
public string InstituteName { get; set; }
[Required]
public string Board_University_Name { get; set; }
[StringLength(30)]
public string Duration { get; set; }
public DateTime PassingYear { get; set; }
[StringLength(30)]
public string Result { get; set; }
public virtual UserInfo UserInfo { get; set; }
}
And here is one of my EducationInfoesController GET methods which accepts EducationID as parameter
// GET: odata/EducationInfoes(5)
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.EducationID == key));
}
I want to overload this method in a such way that it might take 2 parameters [e.g. GetEducationInfo(int UserID, string EducationLevel)] and return only a single result based on the combination of two parameters (UserID and EducationLevel).
I have already tried to overload this method by following code...
// GET: odata/EducationInfoes(5, "bachelor")
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key, string eLevel)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.UserID == key && educationInfo.EducationLevel == eLevel));
}
But when I'm sending GET requst to my WebService by this URL http://localhost:10194/odata/EducationInfoes(5, "Bachelor"), I'm getting this message:
No HTTP resource was found that matches the request URI 'http://localhost:10194/odata/EducationInfoes(5,"Bachelor")'.
If I change the default method to the following...
// GET: odata/EducationInfoes(5)
[EnableQuery]
public SingleResult<EducationInfo> GetEducationInfo([FromODataUri] int key)
{
return SingleResult.Create(db.EducationInfoes.Where(educationInfo => educationInfo.UserId== key));
}
and requesting using this url http://localhost:10194/odata/EducationInfoes(3) and getting this message
The action 'GetEducationInfo' on controller 'EducationInfoes' returned a SingleResult containing more than one element. SingleResult must have zero or one elements.
this message is returned because every single user has multiple Educational Information stored in EducationInfo table.
But I must have to get every EducationInfo result separately or as single result based on UserID and EducationLevel but not based on EducationID. Please help me...

Asp Mvc model binding of array with content type application/x-www-form-urlencoded

Below is a POST url with content type application/x-www-form-urlencoded
POST
http://partner-site.com/api_implementation/hotel_availability
BODY
api_version=4
&hotels=[{"ta_id":97497,"partner_id":"229547","partner_url":"http://partner.com/deeplink/to/229547"},{"ta_id":97832,"partner_id":"id34234","partner_url":"http://partner.com/deeplink/to/id34234"}]
&start_date=2013-07-01
&end_date=2013-07-03
&num_adults=2
&num_rooms=1
&lang=en_US
&currency=USD
&user_country=US
&device_type=d
&query_key=6167a22d1f87d2028bf60a8e5e27afa7_191_1360299600000_2_2
CONTENT TYPE
application/x-www-form-urlencoded
And i have wrote the class that read like
public class HotelAvailabilityRequest
{
public int api_version { get; set; }
public List<HotelSummary> hotels { get; set; }
public string start_date { get; set; }
public string end_date { get; set; }
public int num_adults { get; set; }
public int num_rooms { get; set; }
public string lang { get; set; }
public string query_key { get; set; }
public string currency { get; set; }
public string user_country { get; set; }
public string device_type { get; set; }
}
public class HotelSummary
{
public int ta_id { get; set; }
public string partner_id { get; set; }
public string partner_url { get; set; }
}
When i use the HotelAvailabilityRequest in my ASP MVC method
public ActionResult Hotel_Availability(HotelAvailabilityRequest request)
{}
I'm getting other parameter like request.api_version, request.device_type except request.hotels
I'm getting request.hotels.Count() equal to zero.
How do i get the request.hotel to bind accordingly?
After a few round of testing, I resort to manually parsing and deserializing my query string. Since primitive types (int, string like request.api_version) are well captured by the default model binder, my focus is to resolve the complex object type (request.hotels) that is problem.
First, I get the full query string with
Request.InputStream.Position = 0;
var queryString = new StreamReader(Request.InputStream).ReadToEnd();
var queryStringCollection = HttpUtility.ParseQueryString(queryString);
Then, I deserialize the query string into the intended object list
var hotelStr = queryStringCollection.Get("hotels");
var requestHotels = (List<HotelSummary>) JsonConvert.DeserializeObject(hotelStr, typeof (List<HotelSummary>));
request.hotels = requestHotels;
Not the best solution but it works. Hope someone workout a better ModelBinder for ASP MVC that resolve complex object type at binding.

ASP.NET web API casting http response to json array

My Code works fine when calling REST URL:
http://api.feedzilla.com/v1/categories.json
but when I call following URL I get error:
http://api.feedzilla.com/v1/categories/15/articles.json?count=36&since=2012-11-15&client_source=&order=relevance&title_only=0&
Error:
{"Cannot deserialize the current JSON object (e.g. {\"name\":\"value\"}) into type 'System.Collections.Generic.IEnumerable`1[Nitin.News.DAL.Resources.Article]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.\r\nTo fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.\r\nPath 'articles', line 1, position 12."}
My Code is as follows:
public class Article
{
public string publish_date { get; set; }
public string source { get; set; }
public string source_url { get; set; }
public string summary { get; set; }
public string title { get; set; }
public string url { get; set; }
}
public IEnumerable<Article> Get()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://api.feedzilla.com/v1/");
//Add an Accept header for JSON format.
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// call the REST method
HttpResponseMessage response = client.GetAsync("http://api.feedzilla.com/v1/categories/2/articles.json??count=36&since=2012-11-15&client_source=&order=relevance&title_only=0&").Result; // Blocking call!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
return response.Content.ReadAsAsync<IEnumerable<Article>>().Result;
//wont work
//string JSON =response.Content.ReadAsStringAsync().Result;
//return JsonConvert.DeserializeObject<IEnumerable<T>>(JSON);
}
else
{
throw new Exception(string.Format("Data access faild,{0} ({1}) method:{2}", (int)response.StatusCode, response.ReasonPhrase, MethodURL));
}
}
You need another level in your object hierachy... i.e. a root to contain the IEnumerable.
Runing the JSON into the tool at http://json2csharp.com/ generates the following proxy classes:
public class Enclosure
{
public int length { get; set; }
public string media_type { get; set; }
public string uri { get; set; }
}
public class Article
{
public string author { get; set; }
public string publish_date { get; set; }
public string source { get; set; }
public string source_url { get; set; }
public string summary { get; set; }
public string title { get; set; }
public string url { get; set; }
public List<Enclosure> enclosures { get; set; }
}
public class RootObject
{
public List<Article> articles { get; set; }
public string description { get; set; }
public string syndication_url { get; set; }
public string title { get; set; }
}
You just need to change your code to this then:
// Parse the response body. Blocking!
return response.Content.ReadAsAsync<RootObject>().Result.articles;
Obviously strip out any properties you dont need. Just thought I would show them all out of interest.

Resources