I am generating a URI as follows (this code is simplified and falsified):
Uri baseUri = "http://localhost/MyApp/Account/Login";
Uri fullUri = GetFullUri(baseUri, user);
GetFullUri looks like this (this is in a .NET 2 assembly):
public Uri GetFullUri(Uri baseUri, User user)
{
string token = GetTokenFromUser(user); //Implementation not important.
//Create a new URI based on the base URI, adding a query string.
return new Uri(baseUri, string.Format("?Token={0}", token));
}
Calling GetFullUri from a .NET 4 assembly, the result is correct, fullUri looks like:
http://localhost/MyApp/Account/Login?Token=ABC123
Then I called the same exact code from a .NET 2 assembly and the result is incorrect, fullUri looks like:
http://localhost/MyApp/Account/?Token=ABC123
Notice how the .NET 2 result is missing the 4th and final segment, "Login"? What's the deal with that?
Looks like a bug which was fixed in .NET 4.0. Try using the UriBuilder, which works in both:
public Uri GetFullUri(Uri baseUri, User user)
{
string token = GetTokenFromUser(user); //Implementation not important.
var builder = new UriBuilder(baseUri);
builder.Query = string.Format("Token={0}", token);
return builder.Uri;
}
Related
I am calling a .NET WebApi2 endpoint from a dotnet core webapi. When I debug into the .NET WebApi2 POST endpoint, my value is always null. Is this not possible to do?
When I call the GET endpoint with an ID, the ID is passed with no issues.
I have used both Postman and Fiddler to debug. Whenever I pass my JSON object from Postman to the .NET WebApi2 POST endpoint, my value is populated.
Beyond frustrated as this seems pretty simple. :,(
Updated to include code
dotnet core web api (calling from Postman)
[HttpPost]
public async Task PostAsync([FromBody] string value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new JObject();
jsonObject.Add("text", "Rich");
var response = await client.PostAsJsonAsync("http://localhost:54732/api/Rich", jsonObject);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
.NET WebApi2 (JObject is always null)
// POST: api/Rich
public void Post(JObject value)
{
}
This boils down to using JObject basically. For your older Web Api action, JObject works merely because you're posting JSON, and JObject is a dynamic. However, that is an entirely incorrect approach. You should be binding to a concrete class that represents the JSON being posted. That said, you may or may not be able to change anything there, and its not technically the source of your current issue.
The actual source is that you're attempting to send a JObject, which is not doing what you think it is. Again, JObject is a dynamic. It has accessors to parse and access the underlying JSON, but it does not actually expose the members of that JSON object directly. As a result, if you attempt to serialize it, you won't get anything usable from it. Passing it to PostAsJsonAsync causes it to be serialized.
What you actually need is something like:
var jsonObject = new { text = "Rich" };
Then, what you're passing to PostAsJsonAsync will be an anonymous object with actual members that can be serialized.
My "REAL" issue turned out to be Transfer-Encoding: chunked was being sent in the request header.
Here is my corrected code (dotnet core web api):
public async Task PostAsync([FromBody] JObject value)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new
MediaTypeWithQualityHeaderValue("application/json"));
var jsonObject = new { variable1 = "Rich" };
var json = JsonConvert.SerializeObject(jsonObject);
var content = new StringContent(json, Encoding.UTF8, "application/json");
content.Headers.ContentLength = json.Length;
var response = await client.PostAsync("http://localhost:54732/api/Rich", content);
var responseResult = response.Content.ReadAsStringAsync().Result;
}
Here is my .NET WebApi2 code:
public IHttpActionResult Post([FromBody]RichTest value)
{
return Ok(value.variable1 + " done");
}
public class RichTest
{
public string variable1 { get; set; }
}
When I set the content.Headers.ContentLength, the Transfer-Encoding: chunked is removed. Now my code is working!!
I am still curious why the original PostAsJsonAsync does not work...
I want to setup an endpoint for testing webhooks from third parties. Their documentation is uniformly poor and there is no way ahead of time to tell exactly what I will be getting. What I've done is setup an ApiController that will just take a request and add a row to a table with what they are sending. This lets me at least verify they are calling the webhook, and to see the data so I can program to it.
// ANY api/webook/*
[Route("{*path}")]
public ActionResult Any(string path)
{
string method = Request.Method;
string name = "path";
string apiUrl = Request.Path;
string apiQuery = Request.QueryString.ToString();
string apiHeaders = JsonConvert.SerializeObject(Request.Headers);
string apiBody = null;
using (StreamReader reader = new StreamReader(Request.Body))
{
apiBody = reader.ReadToEnd();
}
Add(method, name, apiUrl, apiQuery, apiHeaders, apiBody);
return new JsonResult(new { }, JsonSettings.Default);
}
This works great, except for this new webhook I am usign that posts as form data so some middleware is reading the body and it ends up null in my code. Is there any way to disable the model processing so I can get at the request body?
You could actually use model binding to your advantage and skip all that stream reading, using the FromBody attribute. Try this:
[Route("{*path}")]
[HttpPost]
public ActionResult Any(string path, [FromBody] string apiBody)
From my MVC application, I am trying to make a POST request to these sample end-points (actions) in an API controller named MembershipController:
[HttpPost]
public string GetFoo([FromBody]string foo)
{
return string.Concat("This is foo: ", foo);
}
[HttpPost]
public string GetBar([FromBody]int bar)
{
return string.Concat("This is bar: ", bar.ToString());
}
[HttpPost]
public IUser CreateNew([FromBody]NewUserAccountInfo newUserAccountInfo)
{
return new User();
}
Here's the client code:
var num = new WebAPIClient().PostAsXmlAsync<int, string>("api/membership/GetBar", 4).Result;
And here's the code for my WebAPIClient class:
public class WebAPIClient
{
private string _baseUri = null;
public WebAPIClient()
{
// TO DO: Make this configurable
_baseUri = "http://localhost:54488/";
}
public async Task<R> PostAsXmlAsync<T, R>(string uri, T value)
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri(_baseUri);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
var requestUri = new Uri(client.BaseAddress, uri);
var response = await client.PostAsXmlAsync<T>(requestUri, value);
response.EnsureSuccessStatusCode();
var taskOfR = await response.Content.ReadAsAsync<R>();
return taskOfR;
}
}
}
I have the following default route defined for the Web API:
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
UPDATE
My code breaks into the debugger until the time the PostAsXmlAsync method on the System.Net.HttpClient code is called. However, no request shows up in Fiddler.
However, if I try to compose a POST request in Fiddler or try to fire a GET request via the browser to one of the API end-points, the POST request composed via Fiddler tells me that I am not sending any data and that I must. The browser sent GET request rightly tells me that the action does not support a GET request.
It just seems like the System.Net.HttpClient class is not sending the POST request properly.
One of the most usual problems is that you don't use the appropriate attribute.
Take into account that there are attributes for ASP.NET MVC and ASP.NET Web API with the same name, but which live in different namespaces:
For Web API you must use the one in System.Web.Http
For MVC, the one in System.Web.MVc
This is a very very usual error, and it affects to allkind of things that exist for both MVC and Web API. So you must be very careful when using something which can exists in bith worlds (for example filters, attributes, or dependency injection registration).
I experienced a similar problem (may not be same one though). In my case, I hadn't given name attribute to the input element. I only figured that out when fiddler showed no post data being sent to the server (just like your case)
<input id="test" name="xyz" type="text" />
Adding the name attribute in the input tag fixed my problem.
However, there is one more thing to note. WebAPI does not put form data into parameters directly. Either you have to create an object with those properties and put that object in the parameter of the post controller. Or you could put no parameters at all like this:
[Route("name/add")]
public async Task Post()
{
if (!Request.Content.IsMimeMultipartContent())
{
return;
}
var provider = PostHelper.GetMultipartProvider();
var result = await Request.Content.ReadAsMultipartAsync(provider);
var clientId = result.FormData["xyz"];
...
Try changing the FromBody to FromUri.
If the parameter is a "simple" type, Web API tries to get the value from the URI. Simple types include the .NET primitive types (int, bool, double, and so forth), plus TimeSpan, DateTime, Guid, decimal, and string, plus any type with a type converter that can convert from a string.
For complex types, Web API tries to read the value from the message body, using a media-type formatter.
Remove FromBody at all and don't make any restrictions in passing parameters (it can be passed at this time either in uri, query string or form submissions (which is kinda a similar to query strings)
[HttpPost]
public string GetFoo(string foo){...}
It will be implicitly parsed and passed.
I have implemented a custom media formatter and it works great when the client specifically requests "csv" format.
I have tested my api controller with this code:
HttpClient client = new HttpClient();
// Add the Accept header
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv"));
However, when I open the same URL from a web browser it returns JSON not CSV. This is probably due to standard ASP.NET WebAPI configuration that sets JSON as the default media formatter unless otherwise specified by the caller. I want this default behavior on every other web service I have but NOT on this single operation that returns CSV. I want the default media handler to be the CSV handler that I implemented. How do I configure the Controller's endpoint such that it returns CSV by default and only returns JSON/XML if requested by the client?
Which version of Web API are you using?
If you are using 5.0 version, you could use the new IHttpActionResult based logic like below:
public IHttpActionResult Get()
{
MyData someData = new MyData();
// creating a new list here as I would like CSVFormatter to come first. This way the DefaultContentNegotiator
// will behave as before where it can consider CSVFormatter to be the default one.
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
return new NegotiatedContentResult<MyData>(HttpStatusCode.OK, someData,
Configuration.Services.GetContentNegotiator(), Request, respFormatters);
}
If you are using 4.0 version of Web API, then you could the following:
public HttpResponseMessage Get()
{
MyData someData = new MyData();
HttpResponseMessage response = new HttpResponseMessage();
List<MediaTypeFormatter> respFormatters = new List<MediaTypeFormatter>();
respFormatters.Add(new MyCsvFormatter());
respFormatters.AddRange(Configuration.Formatters);
IContentNegotiator negotiator = Configuration.Services.GetContentNegotiator();
ContentNegotiationResult negotiationResult = negotiator.Negotiate(typeof(MyData), Request, respFormatters);
if (negotiationResult.Formatter == null)
{
response.StatusCode = HttpStatusCode.NotAcceptable;
return response;
}
response.Content = new ObjectContent<MyData>(someData, negotiationResult.Formatter, negotiationResult.MediaType);
return response;
}
Is there any way to retrieve username from this
http://username#myapplication.com
type of address in ASP.NET MVC (optionally in ASP.NET) from current request?
You're looking for the Uri class:
var uri = new Uri(someString, UriKind.Absolute);
var user = uri.UserInfo;
If that's the guaranteed string you might try something like:
string myString = "http://username#myapplication.com";
if(myString.Contains("#"))
{
return myString.Split('#')[0].Replace("http://","");
}