Post Multiple Parameters Without DTO Class - c#

I have an action on my web project which calls to an API
[HttpPost]
public async Task<IActionResult> ExpireSurvey(int id)
{
var token = await HttpContext.GetTokenAsync("access_token");
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var path = "/api/forms/ExpireSurvey";
var url = Domain + path;
var data = JsonConvert.SerializeObject(id);
HttpContent httpContent = new StringContent(data, Encoding.UTF8, "application/json");
var response = await client.PutAsync(url, httpContent);
return Json(response);
}
}
In the API project this is received as follows:
[HttpPut]
public IActionResult ExpireSurvey([FromBody] int surveyId)
{
_repository.ExpireSurvey(surveyId, expiryDate);
return Ok();
}
This works fine - however, say I want to pass in an int id and a DateTime variable, how do I serialise and pass them both into the HttpContent? I can do it with a DTO object, but I don't want to be setting up DTO objects when there is only two fields.

You can use anonymous types like this
var x = new { id = 2, date = DateTime.Now };
var data = JsonConvert.SerializeObject(x);
When receiving the data, you can only have one [FromBody] parameter. So that doesn't work for receiving multiple parameters (unless you can put all but one into the URL). If you don't want to declare a DTO, you can use a dynamic object like this:
[HttpPost]
public void Post([FromBody] dynamic data)
{
Console.WriteLine(data.id);
Console.WriteLine(data.date);
}
Don't overdo using anonymous types and dynamic variables though. They're very convenient for working with JSON, but you lose all type checking which is one of the things that makes C# really nice to work with.

I think it would be helpful to recognize that ASP.NET Core is REST-based and REST fundamentally deals with the concept of resources. While not an unbreakable rule, the general idea is that you should have what you're calling DTOs here. In other words, you're not posting distinct and unrelated bits of data, but an object that represents something.
This becomes increasingly important if you start mixing in things like Swagger to generate documentation for your API. The objects you create become part of that documentation, giving consumers of your API a template for follow in the development of their apps.
Long and short, I'd say embrace the concept of resources/objects/DTOs/whatever. Model the data your API works with. It will help both you as a developer of the API and any consumers of your API.

You can pass multiple parameters in as URL as below example
Parameter name must be the same (case-insensitive), If names do not
match then values of the parameters will not be set.
[HttpPost]
[Route("{surveyId}/{expiryDate}")]
public IActionResult Post(int surveyId, DateTime expiryDate)
{
return Ok(new { surveyId, expiryDate });
}
Call URL
http://localhost:[port]/api/[controller]/1/3-29-2018

Based on the answers above, I got the following code working. Hope this helps someone! (thanks to others of course for getting me on the right track)
/// <summary>
/// Post api/dostuff/{id}
[HttpPost]
[Route("dostuff/{id}")]
public async Task<IActionResult> DoStuff([FromBody]Model model, int id)
{
// Both model and id are available for use!
}

You can replace the line
var data = JsonConvert.SerializeObject(id);
with
var data = new StringContent(JsonConvert.SerializeObject((surveyId, expiryDate)), Encoding.UTF8, "application/json");
The trick is that you use a Tuple object containig your parameters as a single parameter.
You should use the Tuple type on the the server side as well. It could look like:
[HttpPost]
public Task MyWebApiMethod([FromBody] (int SurveyId, DateTime ExpiryDate) parameters)
{
int surveyId = parameters.SurveyId;
DateTime expiryDate = parameters.ExpiryDate;
// Process your parameters ...
}
I do not have Visual Studio now and sorry if there are any compilation issues etc.

you can do it with a dictionary
Dictionary<int, object> dict = new Dictionary<int, object>();
dict["id"] = 1
dict["date"] = DateTime.Now;
JsonConvert.SerializeObject(dict);

Related

How do i call a Put-method from WEB API in separate project?

I have built a Web API that is connected to a database for persons. I am now trying to call this Web API from a separate MVC-application which is supposed to have full CRUD. So far i have managed to do so with the Get and Post-methods to create a new person and see a list of the persons currently in the database.
When trying to do a similar call for the Put-method, i get the following error:
This is how my method UpdatePerson is written in my API-application:
[HttpPut]
[Route("{id:guid}")]
public async Task<IActionResult> UpdatePerson([FromRoute] Guid id, UpdatePersonRequest updatePersonRequest)
{
var person = await dbContext.Persons.FindAsync(id);
if (person != null)
{
person.Name = updatePersonRequest.Name;
person.Email = updatePersonRequest.Email;
person.Phone = updatePersonRequest.Phone;
person.Address = updatePersonRequest.Address;
await dbContext.SaveChangesAsync();
return Ok(person);
}
And this is how i am trying to consume the API in my separate MVC-project:
[HttpGet]
public IActionResult Edit()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Edit(PersonViewModel pvm)
{
HttpClient client = new();
StringContent sContent = new StringContent(JsonConvert.SerializeObject(pvm), Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PutAsync("https://localhost:7281/api/Persons/", sContent);
response.EnsureSuccessStatusCode();
if (response.IsSuccessStatusCode)
{
return RedirectToAction("Get");
}
else
{
return NotFound();
}
}
Everything is working fine when i try to update the database through the API-app so i am not really sure what is wrong with my request. I hope that someone here can spot the issue right away or at least help me out as i am quite a beginner with WEB APIs.
I have mostly tried changing the URL in my MVC-project but the issue remains.
Are you sure you are receiving the request? It seems that your URI is
"https://localhost:7281/api/Persons/"
and your API is expecting
"https://localhost:7281/api/Persons/{id}" -> where {id} should be the guid
you need to append the guid in the URI
Looks like the request doesn't receive the correct the correct parameters, because the URI that appears in your picture seems a generic method.

JSON response getting truncated with rather small array

On the controller method, before returning the value, result array is filled correctly. However, generated JSON response is truncated from start. This is on asp net core 2.1. I have one middleware block but doesn't get called while returning response, only in requests.
I already tried ReferenceLoopHandling option, didn't work, I am working on quite small array anyways.
[HttpGet("GetAll")]
public IEnumerable<Job> GetAll()
{
var result = _dbManager.GetAllJobs();
return result;
}
Response should be fully constructed Json array, however I am getting this:
[{
You can map like that easily.
[HttpGet("GetAll")]
public async Task<IActionResult> GetAll()
{
var result = _dbManager.GetAllJobs();
return Ok(result.Select(x=> new JobDto(){ Id = x.Id, JobName = x.Name ....}));
}

What are the best practices in adding custom header fields for a .net web api call on Swagger?

I can see a lot of ways to do it online but most of them are messy, for me I was using these two ways
Using scopes, I did one for mobile and another one for the website
var webScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<WebAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
var mobileScope = apiDescription.ActionDescriptor.GetFilterPipeline()
.Select(filterInfo => filterInfo.Instance)
.OfType<MobileAuthorize>()
.SelectMany(attr => attr.Roles.Split(','))
.Distinct();
And it worked because I had two different ways in authorizing the api calls, as you can see I had a Mobile Authorize and a Web Authorize so my api calls would look something like this:
[HttpGet]
[Route("something")]
[WebAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<Dto> Getsomething()
{
return await unitOfWork.GetService<ISomething>().GetSomething();
}
Issues I face when using scopes is that all calls that have web authorize will share the same headers so for the special calls I used another way to add custom headers.
Using apiDescription.RelativePath, and I will check it if the relative path is equal to the api call I want to add that custom header, example:
[HttpPost]
[Route("rename")]
[InHouseAuthorize(Code = PermissionCode, Type =PermissionType)]
public async Task<HttpResponseMessage> RenameDevice()
{
HttpRequestMessage request = Request ?? new HttpRequestMessage();
String deviceName = request.Headers.GetValues("deviceName").FirstOrDefault();
String deviceGuid = request.Headers.GetValues("deviceGuid").FirstOrDefault();
await unitOfWork.GetService<IDeviceService>().RenameDevice(deviceGuid, deviceName);
await unitOfWork.Commit();
return new HttpResponseMessage(HttpStatusCode.OK);
}
And then I would add to the AddRequiredHeaderParameter.cs the following
if (apiDescription.RelativePath.Contains("device/rename"))
{
operation.parameters.Add(new Parameter
{
name = "deviceGuid",
#in = "header",
description = "Add the Device Guid",
type = "string",
required = false
});
operation.parameters.Add(new Parameter
{
name = "DeviceName",
#in = "header",
description = "Add the Device Name",
type = "string",
required = false
});
}
At first this was convenient and good enough fix but things are turning ugly as I'm adding a lot of calls that need custom headers and if the same URL have a Get and Post then it will even get uglier.
I am searching for the best way to deal with this issue.
It's possible to use attribute [FromHeader] for web methods parameters (or properties in a Model class) which should be sent in custom headers. Something like this:
[HttpGet]
public ActionResult Products([FromHeader(Name = "User-Identity")]string userIdentity)
For me it looks like the easiest solution. At least it works fine for ASP.NET Core 2.1 and Swashbuckle.AspNetCore 2.5.0.

How to force ASP.NET Web API to return JSON or XML data based on my input?

I try to get the output XML or JSON data based on my input. I used the below WEB API code but not able to exact output.
public string Get(int id)
{
if (GlobalConfiguration.Configuration.Formatters.XmlFormatter == null)
{
GlobalConfiguration.Configuration.Formatters.Add(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
}
if (GlobalConfiguration.Configuration.Formatters.JsonFormatter == null)
{
GlobalConfiguration.Configuration.Formatters.Add(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
}
if (id == 1)
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.JsonFormatter);
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
}
else
{
GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
GlobalConfiguration.Configuration.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;
}
return "value";
}
Add the below code app_start event in global.asax file. In API Url add the query string:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings.Add(
new QueryStringMapping("type", "json", new MediaTypeHeaderValue("application/json")));
GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(
new QueryStringMapping("type", "xml", new MediaTypeHeaderValue("application/xml")));
e.g.:
for xml : http://localhost:49533/api/?type=xml
for json: http://localhost:49533/api/?type=json
What you are trying to do will not work in a multi-threaded environment. You cannot add to and remove from the formatters collection on a per-request basis. Here is a better way of accomplishing what you want.
public HttpResponseMessage Get(int id)
{
Foo foo = new Foo();
var content = new ObjectContent<Foo>(foo,
((id == 1) ? Configuration.Formatters.XmlFormatter :
Configuration.Formatters.JsonFormatter));
return new HttpResponseMessage()
{
Content = content
};
}
Looked into this a bit more, and found your answer in another post:
public HttpResponseMessage Get(int id)
{
string content = "value";
if (id == 1)
{
return Request.CreateResponse<string>(HttpStatusCode.OK, content, Configuration.Formatters.JsonFormatter);
}
return Request.CreateResponse<string>(HttpStatusCode.OK, content, Configuration.Formatters.XmlFormatter);
}
It also works to force the accept headers. Great option if you aren't always returning HttpResponseMessage's. I.e
Request.Headers.Add("Accept", "text/json");
return Request.CreateResponse(HttpStatusCode.OK, yourobject);
or
Request.Headers.Add("Accept", "application/xml");
return new Rss20FeedFormatter(feed);
If your request specifies the mime type, for example application/json, then web api will format the response appropriately.
If you are attempting to debug your web api manually, use a tool like Fiddler 2 to specify the type.
This article describes the concept.
QueryStringMapping` is nice solution but I need a default value for type.
for xml : localhost:49533/api/?type=xml
for json: localhost:49533/api/
I solve that situation like that:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.SupportedMediaTypes.Clear();
var jSettings = new JsonSerializerSettings();
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings = jSettings;
GlobalConfiguration.Configuration.Formatters.XmlFormatter.MediaTypeMappings.Add(new QueryStringMapping("xml", "true", new MediaTypeHeaderValue("application/xml")));
While the accepted answer by vijayjan15 seems the best way to go for your specific situation (that is, using the MediaTypeMappings), you could alternatively have two different methods, one that returns XML and one that returns JSON. To do that, you can instantiate a controller-specific HttpConfiguration (to avoid modifying the one in GlobalConfiguration.Configuration):
public MyReturnType GetMyTypeAsXml() {
Configuration = new HttpConfiguration();
Configuration.Formatters.Clear();
Configuration.Formatters.Add(new XmlMediaTypeFormatter());
return new MyReturnType();
}
public MyReturnType GetMyTypeAsJson() {
Configuration = new HttpConfiguration();
Configuration.Formatters.Clear();
Configuration.Formatters.Add(new JsonMediaTypeFormatter());
return new MyReturnType();
}
I'm not sure how much overhead there is in spinning up a new instance of HttpConfiguration (I suspect not a lot), but the new instance comes with the Formatters collection filled by default, which is why you have to clear it right after instantiating it. Note that it if you don't use Configuration = new HttpConfiguration(), and instead modify Configuration directly, it modifies the GlobalConfiguration.Configuration property (so, it would impact all your other WebApi methods - bad!).

How to call a Post api with multiple parameters

How can i call a Post method with multiple parameters using HttpClient?
I am using the following code with a single parameter:
var paymentServicePostClient = new HttpClient();
paymentServicePostClient.BaseAddress =
new Uri(ConfigurationManager.AppSettings["PaymentServiceUri"]);
PaymentReceipt payData = SetPostParameter(card);
var paymentServiceResponse =
paymentServicePostClient.PostAsJsonAsync("api/billpayment/", payData).Result;
I need to add another parameter userid. How can i send the parameter along with the 'postData'?
WebApi POST method prototype:
public int Post(PaymentReceipt paymentReceipt,string userid)
Simply use a view model on your Web Api controller that contains both properties. So instead of:
public HttpresponseMessage Post(PaymentReceipt model, int userid)
{
...
}
use:
public HttpresponseMessage Post(PaymentReceiptViewModel model)
{
...
}
where the PaymentReceiptViewModel will obviously contain the userid property. Then you will be able to call the method normally:
var model = new PaymentReceiptViewModel()
model.PayData = ...
model.UserId = ...
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment/", model)
.Result;
UserId should be in query string:
var paymentServiceResponse = paymentServicePostClient
.PostAsJsonAsync("api/billpayment?userId=" + userId.ToString(), payData)
.Result;
In my case my existing ViewModels don't line up very nicely with the data I want to post to my WebAPI. So, instead of creating an entire new set of model classes, I posted an anonymous type, and had my Controller accept a dynamic.
var paymentServiceResponse = paymentServicePostClient.PostAsJsonAsync("api/billpayment/", new { payData, userid }).Result;
public int Post([FromBody]dynamic model)
{
PaymentReceipt paymentReceipt = (PaymentReceipt)model.paymentReceipt;
string userid = (string)model.userid;
...
}
(I'd be curious to hear some feedback on this approach. It's definitely a lot less code.)

Categories