WebAPI - Why is the HttpRequestMessage null for some HttpRequests - c#

I have a simple WebAPI controller. I've added the AutoMapper nuget package for mapping between a DataModel type and a corresponding Dto type like so:
namespace WebApi.Controllers
{
public class Contact
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ContactDto
{
public int ID { get; set; }
public string Name { get; set; }
}
public class ValuesController : ApiController
{
public ValuesController()
{
SetupMaps();
}
private void SetupMaps()
{
Mapper.CreateMap<ContactDto, Contact>();
Mapper.CreateMap<Contact, ContactDto>()
.AfterMap((c, d) =>
{
//Need to do some processing here
if (Request == null)
{
throw new ArgumentException("Request was null!");
}
}
);
}
public ContactDto Get(int id)
{
Contact c = new Contact { ID = id, Name = "test" };
ContactDto dto = Mapper.Map<Contact, ContactDto>(c);
return dto;
}
}
}
I'd like to run some logic after the mapping completes and need to use the HttpRequestMessage object in "AfterMap"
When I hit the ValuesController from Fiddler it returns the JSON representation of the Dto as expected.
The fun begins if I issue a bunch of simultaneous requests to simulate load and hit the endpoint;
Some requests succeed and some fail because the "Request" property of the HttpController is null!
Question is why is the Request null?
I have also tried using async controller methods and the behavior is identical:
private async Task<Contact> GetContact(int id)
{
Task<Contact> task = Task.Factory.StartNew<Contact>(
() => new Contact { ID = id, Name = "test" }
);
return await task;
}
public async Task<ContactDto> Get(int id)
{
Contact c = await GetContact(id);
ContactDto dto = Mapper.Map<Contact, ContactDto>(c);
return dto;
}
I've attached a screenshot of the Fiddler calls indicating some requests succeeding with a 200 and the debugger break in Visual studio when the calls fails when the HttpRequestMessage is null.
Any insights as to why is this happening?

I do not think you are supposed to use the Request property from the constructor. Controllers can be initialized before the context is available.
Try refactoring the AfterMap delegate into a separate method and calling it after the ContactDto dto = Mapper.Map<Contact, ContactDto>(c); call.

Related

JsonExtensionData attribute with default DotNetCore binder

I am trying to pass JSON with some dynamic fields to a controller action method in DotNetCore 3.1 Web API project. The class I am using when sending the payload looks something like this:
public class MyDynamicJsonPayload
{
public string Id { get; set; }
[JsonExtensionData]
public IDictionary<string, object> CustomProps { get; set; }
}
I can see that object serialized correctly with props added to the body of JSON. So I send it from one service to another:
using var response = await _httpClient.PostAsync($"/createpayload", new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"));
On the receiving end, however, when using same class in the controller action:
public async Task<ActionResult> CreatePayload([FromBody] MyDynamicJsonPayload payload)
{
var payload = JsonConvert.SerializeObject(payload);
return Ok(payload);
}
The object is parsed as something different where customProps is an actual field with JSON object containing my properties, plus instead of a simple value I get a JSON object {"valueKind":"string"} for string properties for example. I tried with both Newtonsoft.Json and System.Text.Json.Serialization nothing works as expected. Anyone has any ideas?
Thank you dbc for pointing me in the right direction, the problem was Newtownsoft vs System.Text.Json serialization/deserialization. I could not change the serializer in the Startup class because the service had many other methods and I didn't want to break existing contracts. However, I managed to write a custom model binder that did the trick:
public class NewtonsoftModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
throw new ArgumentNullException(nameof(bindingContext));
string body = string.Empty;
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
body = await reader.ReadToEndAsync();
}
var result = JsonConvert.DeserializeObject(body, bindingContext.ModelType);
bindingContext.Result = ModelBindingResult.Success(result);
}
}
And usage:
public async Task<ActionResult> CreatePayload([ModelBinder(typeof(NewtonsoftModelBinder))] MyDynamicJsonPayload payload)
{
// Process payload...
return Ok();
}
First of all welcome to the StackOverflow.
You can try this solution;
Fist create a class like you did but without dictionary;
public class History
{
public int Id { get; set; }
public string DeviceName { get; set; }
public int DeviceId { get; set; }
public string AssetName { get; set; }
}
After that please add this attribute to your controller class;
[Produces("application/json")]
Your method should be like this;
[Produces("application/json")]
public class ExampleController: Controller
{
[HttpGet]
public Task<IEnumerable<History>> Get()
{
List<History> historyList = new List<History>()
{
new History()
{
...
},
new History()
{
...
}
}
return historyList;
}
}

Can you use ModelBinding with a GET request?

I'm curious if it's possible to bind a query string that is passed in with a GET request to a Model.
For example, if the GET url was https://localhost:1234/Users/Get?age=30&status=created
Would it be possible on the GET action to bind the query parameters to a Model like the following:
[HttpGet]
public async Task<JsonResult> Get(UserFilter filter)
{
var age = filter.age;
var status = filter.status;
}
public class UserFilter
{
public int age { get; set; }
public string status { get; set; }
}
I am currently using ASP.NET MVC and I have done quite a bit of searching but the only things I can find are related to ASP.NET Web API. They suggest using the [FromUri] attribute but that is not available in MVC.
I just tested the this, and it does work (at least in .net core 3.1)
[HttpGet("test")]
public IActionResult TestException([FromQuery]Test test)
{
return Ok();
}
public class Test
{
public int Id { get; set; }
public string Yes { get; set; }
}
You can can create an ActionFilterAttribute where you will parse the query parameters, and bind them to a model. And then you can decorate your controller method with that attribute.
For example
public class UserFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var controller = actionContext.ControllerContext.Controller as CustomApiController;
var queryParams = actionContext.Request.GetQueryNameValuePairs();
var ageParam = queryParams.SingleOrDefault(a => a.Key == "age");
var statusParam = queryParams.SingleOrDefault(a => a.Key == "status");
controller.UserFilter = new UserFilter {
Age = int.Parse(ageParam.Value),
Status = statusParam.Value
};
}
}
The CustomApiController (inherits from your current controller) and has a UserFilter property so you can keep the value there. You can also add null checks in case some of the query parameters are not sent with the request..
Finally you can decorate your controller method
[HttpGet]
[UserFilter]
public async Task<JsonResult> Get()
{
var age = UserFilter.age;
var status = UserFilter.status;
}

ASP.NET Core Web API & EF Core Models with Foreign Key relationship

I'm developing a basic Web API project for education purposes, and I am having trouble with my EF Model relationships. I have 2 models. Message and MessageBoard.
public class Message
{
public long Id { get; set; }
public string Text { get; set; }
public string User { get; set; }
public DateTime PostedDate { get; set; }
public long MessageBoardId { get; set; }
[ForeignKey("MessageBoardId")]
public MessageBoard MessageBoard { get; set; }
}
public class MessageBoard
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public ICollection<Message> Messages { get; set; }
}
I've setup my DBContext and created a migration to configure the database. I generated two Web API controllers using EF Scaffolding. The migration appears to correctly detect the relationship between the two models:
modelBuilder.Entity("Asp.net_Core_Web_Api.Models.Message", b =>
{
b.HasOne("Asp.net_Core_Web_Api.Models.MessageBoard", "MessageBoard")
.WithMany("Messages")
.HasForeignKey("MessageBoardId")
.OnDelete(DeleteBehavior.Cascade);
});
But, when I create a MessageBoard and then create a Message with the ID of the MessageBoard, they don't appear to link correctly. In PostMan, I am doing the following:
1) Post a new MessageBoard
POST - https://localhost:44384/api/MessageBoards/
Body - Raw - Json
{
"Name":"Test Board",
"Description":"A Message board for testing purposes."
}
Returns
{
"id": 4,
"name": "Test Board",
"description": "A Message board for testing purposes.",
"messages": null
}
2) Post a new Message
POST - https://localhost:44384/api/Messages
Body - Raw - JSON
{
"Text":"Posting my first message!",
"User":"Jesse",
"PostedDate":"1/1/2019",
"MessageBoardId":4
}
Returns
{
"id": 2,
"text": "Posting my first message!",
"user": "Jesse",
"postedDate": "2019-01-01T00:00:00",
"messageBoardId": 4,
"messageBoard": null
}
I would expect that the messageBoard would not be null, and it would instead return the JSON for the messageBoard that was previously created. If I change to a GET method, it is also null. Why is it null?
EDIT: Here are my controllers. I removed actions except for GET and POST.
[Route("api/[controller]")]
[ApiController]
public class MessageBoardsController : ControllerBase
{
private readonly MessageBoardContext _context;
public MessageBoardsController(MessageBoardContext context)
{
_context = context;
}
// GET: api/MessageBoards/5
[HttpGet("{id}")]
public async Task<ActionResult<MessageBoard>> GetMessageBoard(long id)
{
var messageBoard = await _context.MessageBoards.FindAsync(id);
if (messageBoard == null)
{
return NotFound();
}
return messageBoard;
}
// POST: api/MessageBoards
[HttpPost]
public async Task<ActionResult<MessageBoard>> PostMessageBoard(MessageBoard messageBoard)
{
_context.MessageBoards.Add(messageBoard);
await _context.SaveChangesAsync();
return CreatedAtAction("GetMessageBoard", new { id = messageBoard.Id }, messageBoard);
}
}
[Route("api/[controller]")]
[ApiController]
public class MessagesController : ControllerBase
{
private readonly MessageBoardContext _context;
public MessagesController(MessageBoardContext context)
{
_context = context;
}
// GET: api/Messages/5
[HttpGet("{id}")]
public async Task<ActionResult<Message>> GetMessage(long id)
{
var message = await _context.Messages.FindAsync(id);
if (message == null)
{
return NotFound();
}
return message;
}
// POST: api/Messages
[HttpPost]
public async Task<ActionResult<Message>> PostMessage(Message message)
{
_context.Messages.Add(message);
await _context.SaveChangesAsync();
return CreatedAtAction("GetMessage", new { id = message.Id }, message);
}
}
You need to load related data
So for example, for your MessageBoard GET - // GET: api/MessageBoards/5
Change from:
var messageBoard = await _context.MessageBoards.FindAsync(id);
To
var messageBoard = await _context.MessageBoards
.Include(i=>i.Messages)
.FirstOrDefaultAsync(i => i.Id == id);
I would expect that the messageBoard would not be null, and it would instead return the JSON for the messageBoard that was previously created. If I change to a GET method, it is also null. Why is it null?
This is because you are returning the newly created message, here only MessageBoadId is exists, not MessageBoad object. So you have to load the related MessageBoad from database using Include for newly created message.
Your PostMessage method should be as follows:
// POST: api/Messages
[HttpPost]
public async Task<ActionResult<Message>> PostMessage(Message message)
{
_context.Messages.Add(message);
await _context.SaveChangesAsync();
var message = await _context.Messages
.Include(i=>i.MessageBoard)
.FirstOrDefaultAsync(i => i.Id == message.Id);
return Json(message);
}
You've given the the application output and what the database looks like, but not the middle bit on how it saves/retrieves the data.
Without knowing what's going on in the middle, my best stab in the dark is that you've neither set your lazy loading correctly nor used Include to include the MessageBoard entity.
More info here on what they are.

How to get and post common data to each request made to the web api

I have created a simple webapi service with few get/post methods, these methods are having some input parameters that client is passing while making call to it, other than these parameter I have some common parameters that has to pass in each request made to the web api, currently I added in every web api method as input parameter that is passing by client along with other input parameters. I am looking for a way where I don'n need to add these common parameters on every webapi method, I want to get these common parameters commonly under webapi.
This is my sample api controller
public class MessageController : ApiController
{
//companyID is a common parameter that is required to pass every web api method
public IHttpActionResult GetMessage(string messageCode, int companyID)
{
Message msg = null;
MesssageManager msgManager = null;
try
{
if(string.IsNullOrEmpty(messageCode))
{
throw new Exception("Plase pass the messageCode in order to get the message.");
}
msgManager = new MesssageManager();
List<Message> messages = msgManager.GetMessages(companyID);
msg = messages.FirstOrDefault(o => o.Code.Equals(messageCode, StringComparison.InvariantCultureIgnoreCase));
return Ok(msg);
}
catch (Exception)
{
throw;
}
finally
{
msgManager = null;
}
}
public IHttpActionResult GetWarningMessage(string warningCode, int companyID)
{
//doing actual stuff to get the data
}
public IHttpActionResult GetMthod1(string param1, int companyID)
{
//doing actual stuff to get the data
}
public IHttpActionResult GetMthod2(string param1, int companyID)
{
//doing actual stuff to get the data
}
[HttpPost]
public IHttpActionResult SaveMessage(string message, int companyID)
{
//doing actual stuff to get the data
}
}
In above controller "companyID" is a common parameter that has to pass in each request.
Please suggest me implementation in web api to get the common parameters, and how to pass it from client using HttpClient.
If the companyID is some kind of indentification/authentication parameter you could add the companyId to the request headers. Implement an authenticationfilter and grab the companyId from the headers. However, you still need some kind of short term persisting mechanism (session, cache, scoped DI container etc.) where the authentication filter would store the parameter and the controller method would get the parameter from.
At the end you need to pass the parameter from the client to the server each time it is required. You need to figure out if it's less hassle to put it into the headers or pass it as a parameter to the method. If the companyId varies from request to request I'd add it to each method. If the companyId is "static" for at least a duration of a session then I'd put it into the headers and would try to make sure, that the client automatically adds the appropriate companyId to the request headers (i.e. like you would handle user tokens).
Please refer below line
https://www.codeproject.com/Tips/574576/How-to-implement-a-custom-IPrincipal-in-ASP-NET-MV
We can add additional attribute in CustomPrincipalSerializedModel like below
public interface ICustomPrincipal : System.Security.Principal.IPrincipal
{
string FirstName { get; set; }
string LastName { get; set; }
int CompanyId { get;set; }
}
public class CustomPrincipal : ICustomPrincipal
{
public IIdentity Identity { get; private set; }
public CustomPrincipal(string username)
{
this.Identity = new GenericIdentity(username);
}
public bool IsInRole(string role)
{
return Identity != null && Identity.IsAuthenticated &&
!string.IsNullOrWhiteSpace(role) && Roles.IsUserInRole(Identity.Name, role);
}
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName { get { return FirstName + " " + LastName; } }
public int CompanyId { get;set; }
}
public class CustomPrincipalSerializedModel
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyId { get;set; }
}
https://www.codeproject.com/Tips/574576/How-to-implement-a-custom-IPrincipal-in-ASP-NET-MV
We can get from header or cookies or use Custom identity principals

Posting an XML to WebAPI and return a result

So I have been struggling to get this work for some time now.
Let me explain the requirement.
I have to write a WebAPI that will accept an XML, do some look up, and return a response.
I am new to this so sought some help. The suggestion was to create a custom object that represents the incoming XML
and use that object as the param for the method exposed by the WebAPI.
The result will be a class that's not a problem.
Here are the steps done.
Created an empty WebAPI project.
Added a class that represents the incoming XML.
Incoming XML:
<InComingStudent>
<StudentID>10</StudentID>
<Batch>56</Batch>
</InComingStudent>
The class:
public class InComingStudent
{
public string StudentID { get; set; }
public string Batch { get; set; }
}
Return object of this type:
public class StudentResult
{
public string StudentID { get; set; }
public string Batch { get; set; }
public string Score { get; set; }
}
Added a Students controller with this method:
public class StudentsController : ApiController
{
[HttpPost]
public StudentResult StudentStatus(InComingStudent inComingStudent)
{
ProcessStudent po = new ProcessStudent();
StudentResult studentResult = po.ProcessStudent();
return StudentResult;
}
}
Ran the service. It opened in a new browser with 404. That's ok as I don't have a starting page.
Wrote a console app to test:
private static async void PostToStudentService()
{
using (var client = new HttpClient())
{
var studentToPost = new InComingStudent() { StudentID = "847", Batch="56"};
client.BaseAddress = new Uri("http://localhost:53247/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
HttpResponseMessage response = await client.PostAsXmlAsync("api/Students/StudentStatus", studentToPost);
if (response.IsSuccessStatusCode)
{
// Print the response
}
}
}
MediaTypeWithQualityHeaderValue is set to application/xml.
So far so good.
When the service is running and I run the console app, the response is 404 "Not Found".
What am I missing?
Any help is greatly appreciated.
Regards.
Have you tried the [FromBody] Attribute?
public class StudentsController : ApiController
{
[HttpPost]
public StudentResult StudentStatus([FromBody]InComingStudent inComingStudent)
{
ProcessStudent po = new ProcessStudent();
StudentResult studentResult = po.ProcessStudent();
return StudentResult;
}
}
And as a suggestion, I would use Attribute-Routing.
public class StudentsController : ApiController
{
[HttpPost, Route("api/stutents/studentsstatus")]
public StudentResult StudentStatus([FromBody]InComingStudent inComingStudent)
{
// ...
}
}

Categories