So, i'm trying to pass multiple parameters from fiddler to my web api, using FormDataCollection.ReadAsNameValueCollection().
Problem is everytime is send my data, formData comes back as null. I'm not sure what I'm doing wrong here. I've tried decorating formData with a [FromBody] attribute. Also registered JsonMediaTypeFormatter() in the global.asax class.
Any help would be much appreciated.
Please see code below:
[HttpPost]
public HttpResponseMessage PostAccount([FromBody]FormDataCollection formData)
{
if (formData != null)
{
var nValueCol = formData.ReadAsNameValueCollection();
var account = new Account()
{
Email = nValueCol["email"],
Password = nValueCol["password"],
AgreedToTerms = Convert.ToBoolean(nValueCol["agreesToTerms"]),
//LocationAccountCreated = DbGeography.FromText(nValueCol["userLocation"])
};
var userProfile = new UserProfile()
{
FirstName = nValueCol["firstName"],
LastName = nValueCol["lastName"],
DateOfBirth = Convert.ToDateTime(nValueCol["dateOfBirth"])
};
var newAcc = _accountService.CreateAccount(account.Email, userProfile.FirstName, userProfile.LastName,
userProfile.DateOfBirth, account.Email, account.AgreedToTerms,
account.LocationAccountCreated);
var response = Request.CreateResponse(HttpStatusCode.Created);
return response;
}
else
return Request.CreateResponse(HttpStatusCode.NotAcceptable);
}
Sample request:
FormDataCollection is normally associated with application/x-www-form-urlencoded media type.
Your screen shot shows you are trying to send json data. If you don't have a concrete data type for your data and you want to send it as Json you can use an IDictionary<string,string> which will be mapped by the model binder successfully.
You action will look something like...
[HttpPost]
public HttpResponseMessage PostAccount([FromBody]IDictionary<string, string> formData) {
if (formData != null) {
var nValueCol = formData;
//...other code removed for brevity, but can basically stay the same
var response = Request.CreateResponse(HttpStatusCode.Created);
return response;
} else
return Request.CreateResponse(HttpStatusCode.NotAcceptable);
}
Based on your code and the information from your fiddler screen shot, a TestController was created, a request was tested with fiddler like...
POST http://localhost:35979/api/account/create HTTP/1.1
User-Agent: Fiddler
Host: localhost:35979
Content-Type: application/json
Content-Length: 76
{"email":"myemail#email.com",
"firstname":"myFName",
"lastName":"myLName"}
...and the formData was populate with the 3 fields and their data.
Related
So I have to make a method in asp.net for an API that accepts 2 files via PUT (1 json and 1 xml for processing data, not saving- because I have to, OK? :) ), sending the request via fiddler..
Right now I'm sending the request like this on fiddler (PUT METHOD):
Content-Type: multipart/form-data
Authorization: XXXXX
Host: XXXX
Request body:
<#INCLUDE *C:\Users\ZZZZ.json*#>
<#INCLUDE *C:\Users\XXXX.xml*#>
Here's what I've tried inside the controller so far:
[HttpPut, Route("api/Student/{studentId}/Classes/{classId}")]
public async Task<string> Put(int studentId, int classId)
{
var file = HttpContext.Current.Request.Files.Count > 0 ?
HttpContext.Current.Request.Files[0] : null;
Stream fileContent = await this.Request.Content.ReadAsStreamAsync();
MediaTypeHeaderValue contentTypeHeader = this.Request.Content.Headers.ContentType;
if (fileContent != null)
return "ok";
return "not ok";
}
So far the file is not being uploaded nor appears within the request (everything's null). I've also tried the "Request" variable and HttpContext.
Tried the exact same thing but with a POST Method (including the boundaries) and the same happens.
What would you do in order to make this work? I really have to send a json object and another in xml, I really can't change languages or send everything in json ('cause that I could make it work)...
PS: The files don't have a defined structure, it has to be dynamic
PS2 : How would you then attempt to read those files without actually saving them?
You don't have to use a stream to read the file content. You can just try using the HttpPostedFile.
[HttpPut, Route("api/student/{studentId}/classes/{classId}")]
public async Task<string> Put(int studentId, int classId)
{
if (HttpContext.Current.Request.Files.Count == 0)
throw new HttpResponseException(new HttpResponseMessage()
{
ReasonPhrase = "Files are required",
StatusCode = HttpStatusCode.BadRequest
});
foreach (string file in HttpContext.Current.Request.Files)
{
var postedFile = HttpContext.Current.Request.Files[file];
if (!(postedFile.ContentType == "application/json" || postedFile.ContentType == "application/xml"))
{
throw new System.Web.Http.HttpResponseException(new HttpResponseMessage()
{
ReasonPhrase = "Wrong content type",
StatusCode = HttpStatusCode.BadRequest
});
}
}
return "OK";
}
POSTMAN
My POSTMAN:
enter image description here
Fiddler
OK, there is lots going on here so I will try and keep my question and examples as simple as I can. With that in mind, please ask if you need any additional information or clarification on anything.
The code
I have a Web API 2 project which has a number of controllers and actions. The particular action I am having problems with is defined in the ContactController as follows:
[HttpPost]
public MyModel GetSomething(System.Nullable<System.Guid> uid)
{
return GetMyModel(uid);
}
In case it matters, my routing is setup as follows:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = RouteParameter.Optional }
);
Now I have another project that is required to call the above action. For calling the Web API I am using HttpClient. Note that I have lots of other actions calls which are working correctly, so this isn't a connectivity issue.
The code I am using to call the Web API method is as follows:
using (HttpClient client = GetClient())
{
var obj = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("uid", someGuid.ToString()) };
var response = client.PostAsync(path, new FormUrlEncodedContent(obj)).Result;
return response.Content.ReadAsAsync<T>().Result;
}
In this instance, path is basically:
localhost:12345/api/contact/getsomething
The problem
The PostAsync call Result (i.e. response in the above code) gives this message:
{StatusCode: 404, ReasonPhrase: 'Not Found', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Pragma: no-cache
X-SourceFiles: =?UTF-8?B?QzpcRGV2ZWxvcG1lbnRcUHJvamVjdHNcTGltYVxMaW1hIHYzXERFVlxMaW1hRGF0YVNlcnZpY2VcYXBpXHVzZXJhY2Nlc3NcZ2V0bW9kdWxlc2FjY2Vzcw==?=
Cache-Control: no-cache
Date: Fri, 18 May 2018 10:25:49 GMT
Server: Microsoft-IIS/10.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 222
Content-Type: application/json; charset=utf-8
Expires: -1
}}
If I put a breakpoint inside the aciton then it doesn't fire. However, what I find strange is that when I call it, Visual Studio (2018) tells me that the specific action has a "failed request" on that specific action. So clearly it must know which method I am trying to call?
At this point I am running out of ideas on how to debug further. What am I doing wrong here?
in this case you can use the same endpoint as for getting and posting.
so you probably need:
[HttpGet]
public IActionResult Get(System.Nullable<System.Guid> uid)
{
return GetMyModel(uid); //make sure you got it, oterhwise return a NotFound()
}
[HttpPost]
public IActionResult Post(InputModel model)
{
_service.doMagicStuff();
return Ok();
}
cheers!
not sure but error may be because of you are passing keyvalue pair
var obj = new List<KeyValuePair<string, string>> { new KeyValuePair<string, string>("uid", someGuid.ToString()) };
var response = client.PostAsync(path, new FormUrlEncodedContent(obj)).Result;
instead of guild only i.e. only string value expected by function , so it will be
var response = client.PostAsync(path, new FormUrlEncodedContent(someGuid.ToString())).Result;
method should be
[HttpPost]
public MyModel GetSomething(string uid)
{
return GetMyModel(Guid.Parse( uid));
}
You are sending the guid with the FormUrlEncodedContent but the requests content type is application/json.
I recommend you to send it as a json like this
using (HttpClient client = GetClient())
{
var obj = new { uid = someGuid.ToString()) };
var json = JsonConvert.SerializeObject(obj);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var result = client.PostAsync(path, content).Result;
return response.Content.ReadAsAsync<T>().Result;
}
Then in the api controller, use the FromBody attribute to declare that the parameter is read from the request body
[HttpPost]
public MyModel GetSomething([FromBody]RequestModel model)
{
return GetMyModel(model.uid);
}
public class RequestModel
{
public System.Nullable<System.Guid> uid { get; set; }
}
Also, if you only have one Post method in the contact controller the url localhost:12345/api/contact will be enough
I have an AJAX form which post a form-data to a local API url: /api/document. It contains a file and a custom Id. We simply want to take the exact received Request and forward it to a remote API at example.com:8000/document/upload.
Is there a simple way of achieve this "forward" (or proxy?) of the Request to a remote API using Asp.NET Core?
Below we had the idea to simply use Web API Http client to get the request and then resend it (by doing so we want to be able to for example append a private api key from the backend), but it seems not to work properly, the PostAsync doesn't accept the Request.
Raw request sent by Ajax
POST http://localhost:62640/api/document HTTP/1.1
Host: localhost:62640
Connection: keep-alive
Content-Length: 77424
Accept: application/json
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryn1BS5IFplQcUklyt
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.8,fr;q=0.6
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="fileToUpload"; filename="test-document.pdf"
Content-Type: application/pdf
...
------WebKitFormBoundaryn1BS5IFplQcUklyt
Content-Disposition: form-data; name="id"
someid
------WebKitFormBoundaryn1BS5IFplQcUklyt--
Backend Code
Our .NET Core backend has a simple "forward to another API" purpose.
public class DocumentUploadResult
{
public int errorCode;
public string docId;
}
[Route("api/[controller]")]
public class DocumentController : Controller
{
// POST api/document
[HttpPost]
public async Task<DocumentUploadResult> Post()
{
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
HttpResponseMessage response = await client.PostAsync("/document/upload", Request.Form);
if (response.IsSuccessStatusCode)
{
retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
}
return retValue;
}
}
We have a GET request (not reproduced here) which works just fine. As it doesn't have to fetch data from locally POSTed data.
My question
How to simply pass the incoming local HttpPost request and forwarding it to the remote API?
I searched A LOT on stackoverflow or on the web but all are old resources talking about forwarding Request.Content to the remote.
But on Asp.NET Core 1.0, we don't have access to Content. We only are able to retrieve Request.Form (nor Request.Body) which is then not accepted as an argument of PostAsync method:
Cannot convert from Microsoft.AspNetCore.Http.IformCollection to
System.Net.Http.HttpContent
I had the idea to directly pass the request to the postAsync:
Cannot convert from Microsoft.AspNetCore.Http.HttpRequest to
System.Net.Http.HttpContent
I don't know how to rebuild expected HttpContent from the local request I receive.
Expected response
For information, When we post a valid form-data with the custom Id and the uploaded file, the remote (example.com) API response is:
{
"errorCode": 0
"docId": "585846a1afe8ad12e46a4e60"
}
Ok first create a view model to hold form information. Since file upload is involved, include IFormFile in the model.
public class FormData {
public int id { get; set; }
public IFormFile fileToUpload { get; set; }
}
The model binder should pick up the types and populate the model with the incoming data.
Update controller action to accept the model and proxy the data forward by copying content to new request.
[Route("api/[controller]")]
public class DocumentController : Controller {
// POST api/document
[HttpPost]
public async Task<IActionResult> Post(FormData formData) {
if(formData != null && ModelState.IsValid) {
client.BaseAddress = new Uri("http://example.com:8000");
client.DefaultRequestHeaders.Accept.Clear();
var multiContent = new MultipartFormDataContent();
var file = formData.fileToUpload;
if(file != null) {
var fileStreamContent = new StreamContent(file.OpenReadStream());
multiContent.Add(fileStreamContent, "fileToUpload", file.FileName);
}
multiContent.Add(new StringContent(formData.id.ToString()), "id");
var response = await client.PostAsync("/document/upload", multiContent);
if (response.IsSuccessStatusCode) {
var retValue = await response.Content.ReadAsAsync<DocumentUploadResult>();
return Ok(reyValue);
}
}
//if we get this far something Failed.
return BadRequest();
}
}
You can include the necessary exception handlers as needed but this is a minimal example of how to pass the form data forward.
Is it possible to send [FromBody] POST data to a controller using client.GetAsync() (or PostAsync/SendAsync?
I had to set up a base controller that all api calls will go through.
My ajax calls all go to this SecureApi controller, and they send the original path as a parameter to that they can be re-routed to the correct controller. Something like:
$.ajax({
url: "./api/SecureApi/?path=/api/OtherApi/SomeRoute",
data: {
param1: 1,
param2: 2
}
});
So my base controller looks something like:
public class SecurityApiController : ApiController
{
[HttpPost]
public HttpResponseMessage SecureApi([FromBody]object data, string path)
{
// do some stuff
// get uri
var applicationPath = Request.RequestUri.Scheme + "://" + Request.GetRequestContext().VirtualPathRoot.Replace("/", String.Empty);
Uri routeUri = new Uri(applicationPath + path);
// then redirect to correct controller
var config = new HttpConfiguration();
var server = new HttpServer(config);
var client = new HttpClient(server);
// how can I send [FromBody]object data here?
var response = client.GetAsync(routeUri).Result; // or PostAsync/SendAsync?
return response;
}
}
The other controller looks like:
public class OtherApiController : ApiController
{
[HttpPost]
public HttpResponseMessage OtherApi([FromBody]OtherData data)
{
// do stuff
}
}
Unfortunately I can't change OtherApi, so I HAVE to send the [FromBody] POST data in the same way (in the POST body).
Is that possible?
EDIT:
Per #Philippe's response below, I'm using PostAsJsonAsync and it seems to want to work, but I'm getting a 401 Unauthorized result. More info:
I went with the correct(?) ASYNC/AWAIT route...
public async Task<HttpResponseMessage> SecureApi([FromBody]Dictionary<string, dynamic> data, string path)
{
...
var client = new HttpClient();
var response = await client.PostAsJsonAsync(routePath, data);
return response;
}
And the Other controller has:
[Authorize(Roles = "Admin")] // I do have the "Admin" role
[Route("Save")]
[HttpPost]
public SaveResultBase Save([FromBody]Dictionary<string, dynamic> data)
{
...
}
But this controller is never hit (no breakpoints are hit there) and it returns a 401 Unauthorized response.
I guess that I have to add my user credentials to the client headers before calling PostAsJsonAsync. Can't find any way to do that though.
The method GetAsync of HttpClient will send a HTTP GET request so it would only be possible to have [FromUri] arguments. Because [FromBody] argument are by definition POST data, you will want to use PostAsJsonAsync/ PostAsXmlAsync/PostAsync. The difference between all of them is how the data is serialized.
var response = client.PostAsJsonAsync(routeUri, data).Result;
That being said, if you have security in mind, it would be rather easy for anyone to call the "right api" directly. Moreover you will increase latency by generating two HTTP requests.
You should take a look at this guide on MSDN. I believe that an authentication filter is probably what you are looking for.
I've got the following code running in a Windows Store application that is supposed to call one of my WebApis:
using (var client = new HttpClient())
{
var parms = new Dictionary<string, string>
{
{"vinNumber", vinNumber},
{"pictureType", pictureType},
{"blobUri", blobUri}
};
HttpResponseMessage response;
using (HttpContent content = new FormUrlEncodedContent(parms))
{
const string url = "http://URL/api/vinbloburis";
response = await client.PostAsync(url, content);
}
return response.StatusCode.ToString();
}
The WebApi code looks like this:
[HttpPost]
public HttpResponseMessage Post(string vinNumber, string pictureType, string blobUri)
{
var vinEntity = new VinEntity
{
PartitionKey = vinNumber,
RowKey = pictureType,
BlobUri = blobUri
};
_vinRepository.InsertOrUpdate(vinEntity);
return new HttpResponseMessage { Content = new StringContent("Success"), StatusCode = HttpStatusCode.OK };
}
Using Fiddler, I've observed the following ... here's what the request looks like:
POST http://URL/api/vinbloburis HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: URL
Content-Length: 113
Expect: 100-continue
vinNumber=vinNumber&pictureType=top&blobUri=https%3A%2F%2Fmystorage.blob.core.windows.net%2Fimages%2Fimage.png
But the response is an error with little/no information:
{"Message":"An error has occurred."}
I've even tried locally with the debugger attached to the WebApis and my Post method never catches.
Does anyone see something I've missed here? I feel like I'm looking right at it but not seeing it. I should add that I am able to call an HttpGet method while passing a parameter through the querystring. Right now the problem is only with this HttpPost.
Thanks!
UPDATE: Based on some good comments from folks I'm adding some more details.
I have the default routes configured for WebApis ...
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
Consequently, I think /URL/api/vinbloburis should work. Additionally, as alluded above, I currently have this working with an HttpGet. Here's what's working in the Windows Store app to call the HttpGet WebApi ...
using (var client = new HttpClient())
{
using (var response = await client.GetAsync(uri))
{
if (response.IsSuccessStatusCode)
{
var sasUrl = await response.Content.ReadAsStringAsync();
sasUrl = sasUrl.Trim('"');
return sasUrl;
}
}
}
... and it's calling the following WebApi ...
[HttpGet]
public HttpResponseMessage Get(string blobName)
{
const string containerName = "images";
const string containerPolicyName = "policyname";
_helper.SetContainerPolicy(containerName, containerPolicyName);
string sas = _helper.GenerateSharedAccessSignature(blobName, containerName, containerPolicyName);
return new HttpResponseMessage
{
Content = new StringContent(sas),
StatusCode = HttpStatusCode.OK
};
}
I hope the additional information helps!
I copied your code, as is and I was getting a 404 error.
I changed the signature to
public HttpResponseMessage Post(FormDataCollection data)
and it worked.
You can also just do this,
public HttpResponseMessage Post(VinEntity vinEntity)
and the model binder will do the mapping work for you.
Rick Strahl has a post on the issue here http://www.west-wind.com/weblog/posts/2012/Sep/11/Passing-multiple-simple-POST-Values-to-ASPNET-Web-API
Have you turned on Internet Networking in your manifest?
It may be a Cross Domain Ajax Security issue. See JSONP info here. => http://json-p.org/