Request URI in MediaTypeFormatter - c#

I'm trying to output different formats of a type depending on the URL of the request. Up to Preview5 I did the following to get the URI in the MediaTypeFormatters OnWriteToStream-Method:
var requestUri = OperationContext.Current
.IncomingMessageHeaders
.To;
But with Preview6 the OperationContext.Current property is always null. Probably because the formatter gets executed on a different thread. So what is the correct way to get the URI in the MediaTypeFormatter? Or is there an alternative to the MediaTypeFormatter which has the request as argument?
Thank you in advance.
Regards
...
Joachim

You can use an UriFormatExtensionMessageChannel / OperationHandler as shown here.

For the sake of completeness I settled with the following solution
public class RazorHtmlHandler : HttpOperationHandler<HttpResponseMessage, HttpResponseMessage>
{
public static readonly String OUTPUT_PARAMETER_NAME = "response";
public static readonly MediaTypeWithQualityHeaderValue HTML_MEDIA_TYPE = new MediaTypeWithQualityHeaderValue("text/html");
public const String DEFAULT_TEMPLATE_NAME = "index.cshtml";
public const String DEFAULT_TEMPLATE_EXTENSION = ".cshtml";
public const String DEFAULT_RAZOR_NAME = "_RazorHtmlProcessor_Template";
public RazorHtmlHandler() : base(OUTPUT_PARAMETER_NAME)
{ }
protected override HttpResponseMessage OnHandle(HttpResponseMessage response)
{
var request = response.RequestMessage;
var accept = request.Headers.Accept;
if (!accept.Contains(HTML_MEDIA_TYPE))
return response;
var buffer = new StringBuilder();
var currentContent = response.Content as ObjectContent;
try
{
var template = LoadTemplateForResponse(request.RequestUri, currentContent);
var value = ReadValueFormObjectContent(currentContent);
buffer.Append(InvokeRazorParse(template, value));
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
response.Content = new StringContent(buffer.ToString(),
Encoding.UTF8,
HTML_MEDIA_TYPE.MediaType);
return response;
}
...
}

We also encountered this problem with a MediaTypeFormatter for our Web API but we solved it by simply using HttpContext.Current.Request.Url instead of going through OperationContext.Current.

Related

Why do two (almost) similar methods(using c#) of uploading a file to Slack cause different outcomes?

so this question baffels me. I'll post quite abit of code to explain this one. First, I have and "old" version of code(c#), which I used to post messages and files to Slack. And this code works fine for me! The method of interest is the following:
public class PostMessage
{
private string _token = "xoxp-MyToken";
public string token { get { return _token; } }
public string channel { get; set; }
public string text { get; set; }
public MultipartFormDataContent UploadFile()
{
var requestContent = new MultipartFormDataContent();
var fileContent = new StreamContent(GetFile.ReadFile());
requestContent.Add(new StringContent(token), "token");
requestContent.Add(new StringContent(channel), "channels");
requestContent.Add(fileContent, "file", Path.GetFileName(GetFile.path));
return requestContent;
}
public static class GetFile
{
public static string path = #"C:\Users\f.held\Desktop\Held-Docs\Download.jpg";
public static FileStream ReadFile()
{
FileInfo fileInfo = new FileInfo(path);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
return fs;
}
}
Here is my client:
public class SlackClient
{
public Uri _method { get; set; }
private readonly HttpClient _httpClient = new HttpClient {};
public SlackClient(Uri webhookUrl)
{
_method = webhookUrl;
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
var response = await _httpClient.PostAsync(_method, requestContent);
return response;
}
}
And I call all of this in this Main:
public static void Main(string[] args)
{
Task.WaitAll(TalkToSlackAsync());
private static async Task TalkToSlackAsync()
{
var webhookUrl = new Uri("https://slack.com/api/files.upload");
var slackClient = new SlackClient(webhookUrl);
PostMessage PM = new PostMessage();
PM.channel = "DCW21NBHD";
var cont = PM.UploadFile();
var response = await slackClient.UploadFileAsync(cont);
string content = await response.Content.ReadAsStringAsync();
}
}
So far, so good! But now it gets interesting. I build a similar version, in which I use Newtonsoft's Json NuGet-package
Now, first the code:
the client:
public async Task<HttpResponseMessage> SendFileAsync(MultipartFormDataContent requestContent)
{
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "xoxp-MyToken");
var response = await _httpClient.PostAsync(UriMethod, requestContent);
return response;
}
the same Filestram-method for reading the file:
public class Message
{
public class GetFile // Just pass a path here as parameter!
{
public static string path = #"C:\Users\f.held\Desktop\Held-Docs\Download.jpg";
public static FileStream ReadFile()
{
FileInfo fileInfo = new FileInfo(path);
FileStream fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
return fs;
}
}
the Json-class which I serialize:
public class JsonObject
{
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
}
And the Main:
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WaitAll(SendMessage());
}
catch(Exception dudd)
{
Console.WriteLine(dudd);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/files.upload");
JsonObject JO = new JsonObject();
JO.channels = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "multipart/form-data");
var DeSon = JsonConvert.DeserializeObject(Json);
Console.WriteLine(DeSon);
Console.ReadKey();
var requestContent = new MultipartFormDataContent();
var fileContent = new StreamContent(Message.GetFile.ReadFile());
requestContent.Add(fileContent, "file", Path.GetFileName(Message.GetFile.path));
requestContent.Add(StringJson);
var ResponseFile = await client.SendFileAsync(requestContent);
Console.WriteLine(ResponseFile);
Console.ReadKey();
}
}
So, both SEEM to work. But the latter of these methods does NOT post the file to the declared channel - it merely uploads it to Slack. Which would be fine, because I could then work with the 'public_url' to publicise it on any channel. BUT - BIG BUT - with the first method, it immediately loads it to my channel! And it tells me so in the response I get from Slack. The responses are in both exactly the same - except for the timestamps and file_id etc. obviously. But the ending is different!
Here is the ending of the response from the old version:
"shares":{"private":{"DCW21NBHD":[{"reply_users":[],"reply_users_count":0,"reply_count":0,"ts":"1544025773.001700"}]}},"channels":[],"groups":[],"ims":["DCW21NBHD"]}}
and here is the answer from the new version:
"shares":{},"channels":[],"groups":[],"ims":[]}}
Okay now, why on god's green earth does one method do that and the other one does not? :D
Thanks to anybody who has some insight and knowledge on this specific "issue" and is willing to share!
As stated in the documentation for files.upload:
Present arguments as parameters in application/x-www-form-urlencoded
querystring or POST body. This method does not currently accept
application/json.
So the reason this does not work is that you are trying to provide the API parameters like channels as JSON, when this method does not support JSON. The result is that those properties are ignore, which is why the image is uploaded, but not shared in the designated channel.
To fix it simply provide your parameters as application/x-www-form-urlencoded querystring as you did in your 1st example.
Note that in general only a subset of the Slack API methods support using JSON for providing the parameters as listed here. If you want to use JSON, please double-check if the API method supports it, or stick with x-www-form-urlencoded (which is the standard for POST) to be on the safe side.

How to Seriailize an c# object into Json, while using httpClient?

I have a little program which should communicate with "Slack". In an older Version I used "Dictionary<string, string>" and then put them into UrlEncodedContent - which worked fine.
Now I am trying to create a Json-object, using Newtonsoft's Nuget-package and (in my opinion) formatting my object the way they say on their website.
Problem is, when I try to make a simple request, my program just runs to one specific line in the code(var response = await _httpClient.SendAsync(request);) and then it just ends. It doesn't throw an exception or display any kind of message, it simply ends on this line. I went through my code step by step while debugging, that's how I know it ends on exactly this line. And I just don't know why!
Now my code:
First, my object...
namespace BPS.Slack
{
public class JsonObject
{
//generally needed parameters
[JsonProperty("ok")]
public bool ok { get; set; }
[JsonProperty("error")]
public string error { get; set; }
[JsonProperty("channel")]
public string channel { get; set; }
[JsonProperty("token")]
private string token = "xoxp-MyToken";
[JsonProperty("as_user")]
public bool as_user = false;
[JsonProperty("username")]
public string username { get;set; }
//--------------------------------
//only needed for textmessages
[JsonProperty("text")]
public string text { get; set; }
//--------------------------------
//for posting messages with data attached
[JsonProperty("initial_comment")]
public string initial_comment { get; set; }
[JsonProperty("file")]
public string file { get; set; }
[JsonProperty("channels")]
public string channels { get; set; }
//--------------------------------
//for getting the latest message from a channel
[JsonProperty("count")]
public string count = "1";
[JsonProperty("unreads")]
public bool unreads = true;
}
}
now the client:
namespace BPS.Slack
{
public class BpsHttpClient
{
private readonly HttpClient _httpClient = new HttpClient { };
public Uri UriMethod { get; set; }
public BpsHttpClient(string webhookUrl)
{
UriMethod = new Uri(webhookUrl);
}
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
}
}
and the main
namespace TestArea
{
class MainArea
{
public static void Main( string[] args)
{
try
{
Task.WhenAll(SendMessage());
}
catch(Exception ass)
{
Console.WriteLine(ass);
Console.ReadKey();
}
}
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
MultipartFormDataContent content = new MultipartFormDataContent();
content.Add(StringJson);
var Response = await client.UploadFileAsync(content);
string AnswerContent = await Response.Content.ReadAsStringAsync();
Console.WriteLine(AnswerContent);
Console.ReadKey();
}
}
}
I had the same problem in my older version, BUT only as I wanted to DEserialize an answer I got from Slack. It had to do with my object I tried do deserialize the answer into. But this time I can not figure out what's wrong. But, as I said, I do not have any experience with using serialized objects as Json-property to send requests... anyone has an idea what is wrong with my code?
EDIT: This problem is kinda solved. But there is a follow up problem.
Okay, I found out that the reason for the abprubt termination was the
Task.WhenAll(SendMessage());
it should be
Task.WaitAll(SendMessage()); Why??? Somebody said I should use WhenAll, but obviously it doesn't work properly in this case...
Now I get a response from Slack, but now a different problem has arisen. When I use this method:
public async Task<HttpResponseMessage> UploadFileAsync(MultipartFormDataContent requestContent)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, UriMethod);
request.Content = requestContent;
var response = await _httpClient.SendAsync(request);
return response;
}
I allways get the answer:
{"ok":false,"error":"invalid_form_data"}
so I tried to explicitly tell it the 'mediaType', I tried "application/json" and others, but with all of them I get the same error. Here is the full method that calls the upper mehtod:
private static async Task SendMessage()
{
var client = new BpsHttpClient("https://slack.com/api/chat.postMessage");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
JO.text = "This is so much fun :D !";
var Json = JsonConvert.SerializeObject(JO, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
var StringJson = new StringContent(Json, Encoding.UTF8, "application/json");
var requestContent = new MultipartFormDataContent();
requestContent.Add(StringJson);
var Response = await client.UploadFileAsync(requestContent);
string AnswerContent = await Response.Content.ReadAsStringAsync();
}
When I use this method:
public async Task<HttpResponseMessage> SendMessageAsync(FormUrlEncodedContent content)
{
var response = await _httpClient.PostAsync(UriMethod, content);
return response;
}
so bascially I am passing "FormUrlEncodedContent" instead of "MultipartFormDataContent" in this, and then I get the response I want and can work wiht it. BUT this i of little use to me since I have to use "MultipartFormDataContent" to be able to send files with my requests.
Anyone have an idea what is failing here? Why does it not like the one content-type but the other one? I'd be gratefull for tipps and ideas!
You are serializing your object to Json and then adding it to a Multipart body, that's quite strange. Unless you're uploading binary data (eg Files), there is no need to use MultipartFormDataContent.
You are can directly post your JsonObject serialized as JSON:
public async Task<HttpResponseMessage> PostJsonAsync(StringContent content)
{
var response = await client.PostAsync(url, content);
return response;
}
var client = new BpsHttpClient("https://slack.com/api/im.history");
JsonObject JO = new JsonObject();
JO.channel = "DCW21NBHD";
var Json = JsonConvert.SerializeObject(JO);
var StringJson = new StringContent(Json, Encoding.UTF8);
var Response = await client.PostJsonAsync(content);
Also this is should be POST on the UploadFileAsync function.
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, UriMethod);
so I figured out that in the Main() the problem was this:
Task.WhenAll(SendMessage());
I should instead use:
Task.WaitAll(SendMessage());
Anyone who has more knowledge on this, please elaborate why!

Debugging Web API returns Resource no match error

When I try to debug the application by calling like
http://localhost:5/api/GetEmployeesDEV
It returns error like
No HTTP resource was found that matches the request URI 'http://localhost:57764/api/GetEmployeesDEV'. No action was found on the controller 'GetEmployeesDEV' that matches the request.
The ASP.NET Web API which makes a Call to the Odata endpoint and returns the response received by the call. And I have the below code for the Controller
public class GetEmployeesDEVController : ApiController
{
[HttpGet]
private async Task<EmployeeDTO.RootObject> Get()
{
string userName_Core = ConfigurationManager.AppSettings["core_Username"];
string password_Core = ConfigurationManager.AppSettings["core_Password"];
string BaseURL_Core = ConfigurationManager.AppSettings["BaseURL_Core"];
var byteArray_Core = Encoding.ASCII.GetBytes(userName_Core + ":" + password_Core);
EmployeeDTO.RootObject returnObj = new EmployeeDTO.RootObject();
try
{
// GET
using (var client_Core = new HttpClient())
{
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(delegate { return true; });
Uri uri = new Uri(BaseURL_Core);
client_Core.BaseAddress = uri;
client_Core.DefaultRequestHeaders.Accept.Clear();
client_Core.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client_Core.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray_Core));
string core_URL = BaseURL_Core;
var response = client_Core.GetAsync(core_URL).Result;
var responsedata = await response.Content.ReadAsStringAsync();
returnObj = JsonConvert.DeserializeObject<EmployeeDTO.RootObject>(responsedata);
}
}
catch (Exception ex)
{
throw ex;
}
return returnObj;
}
Not sure if I am missing something here. I didnot change anything with RouteConfig file
Please change the scope of the function
From private
private async Task<EmployeeDTO.RootObject> Get()
To public
public async Task<EmployeeDTO.RootObject> Get()
Action need to be public in order to be available for routing.

Web API 'Request' is null

I'm trying to return an HttpResponseException from a POST Web API action using 'Request':
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.NotFound, "File not in a correct size"));
When doing so, I get Value cannot be null. Parameter name: request.
Essentially - Request is null.
What am I missing?
Thank you
I found that my Request object was null within my ApiController because I have nested one ApiController call within another. The second, nested ApiController was therefore never initialized. You can't initialize it manually, but you can leverage the setter on the Request object to pass from wrapper to nested ApiController. A mockup is below, in my real code it fixed my Request.CreateErrorResponse(...) error.
public class WrapperController : ApiController
{
// POST api/wrapper
public void Post(ComplexWithChild value)
{
var other = value.otherdata;
var childcontroller = new ChildController();
childcontroller.Post(value.child); // ChildController is not initialized, and has null Request
/*inside ChildController...// causes null reference exception due to null Request
Request.CreateErrorResponse(HttpStatusCode.BadRequest, "my message");
*/
childcontroller.Request = this.Request;
childcontroller.Post(value.child); // ChildController uses same Request
/*inside ChildController...// this works now
Request.CreateErrorResponse(HttpStatusCode.BadRequest, "my message");
*/
}
}
public class ChildController : ApiController
{
public void Post(Child value)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, "my message"));
}
}
Use this:
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.InternalServerError);
message.Content = new ObjectContent(typeof(MessageResponse), "Invalid Size", GlobalConfiguration.Configuration.Formatters.JsonFormatter);
throw new HttpResponseException(message);
Note: you can change "Invalid Size" with any object you may want to return for the user.
e.g.:
public ErrorMessage
{
public string Error;
public int ErrorCode;
}
ErrorMessage msg = new ErrorMessage();
msg.Error = "Invalid Size";
msg.ErrorCode = 500;
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.InternalServerError);
message.Content = new ObjectContent(typeof(MessageResponse), msg, GlobalConfiguration.Configuration.Formatters.JsonFormatter);
throw new HttpResponseException(message);
Another solution for people like #DavB.cs who have nested ApiControllers like:
public class ParentController : ApiController
{
public IHttpActionResult Foo()
{
ChildRepository.Foo();
return Ok(...);
}
}
public class ChildRepository : ApiController
{
public HttpResponseMessage Foo()
{
// Do something
return Request.CreateResponse(...);
}
}
Is simply to just pass in Request from the ParentController as such:
public class ParentController : ApiController
{
public IHttpActionResult Foo()
{
ChildRepository.Foo(Request);
return Ok(...);
}
}
public class ChildRepository
{
public HttpResponseMessage Foo(HttpRequestMessage request)
{
// Do something
return request.CreateResponse(...);
}
}
Request comes from ApiController. This will get you past the 'Request is null' issue.
Hope this helps someone.
nizari's answer is correct however I ended up using the following:
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent("Invalid image dimensions. Image file must be " + image.Width + "x" + image.Height + "px"),
StatusCode = HttpStatusCode.Forbidden
});
and accessing the content on client side using jqXHR.responseText
Thanks everyone!
Expanding on nizari's answer, Here's what I did in order to include a list of ErrorMessage objects:
IEnumerable errorList = new List<ErrorMessage>();
// ...
var error = new HttpError( "There were errors." )
{
{ "Errors", errorList }
};
var message = new HttpResponseMessage( HttpStatusCode.BadRequest )
{
Content = new ObjectContent<HttpError>( error, GlobalConfiguration.Configuration.Formatters.JsonFormatter )
};
throw new HttpResponseException( message );
you have to create a new instance of the System.Net.Http.HttpRequestMessage. the below would work
var request = new HttpRequestMessage();
var response = request.CreateErrorResponse(HttpStatusCode.NotFound, "File not in a correct size");
throw new HttpResponseException(response);
HttpResponseMessage message = new HttpResponseMessage(HttpStatusCode.InternalServerError);
message.Content = new ObjectContent(typeof(MessageResponse),
"Invalid Size",
GlobalConfiguration.Configuration.Formatters.JsonFormatter);
throw new HttpResponseException(message);

How to get HttpClient Json serializer to ignore null values

I'm currently working with a console app which I'm using the HttpClient to interact with an Apache CouchDB database. I'm using this article: http://www.asp.net/web-api/overview/web-api-clients/calling-a-web-api-from-a-net-client
I'd like to ignore the null properties in my class when I'm serializing and sending a document to my database via the PostAsJsonSync but I'm not sure how:
public static HttpResponseMessage InsertDocument(object doc, string name, string db)
{
HttpResponseMessage result;
if (String.IsNullOrWhiteSpace(name)) result = clientSetup().PostAsJsonAsync(db, doc).Result;
else result = clientSetup().PutAsJsonAsync(db + String.Format("/{0}", name), doc).Result;
return result;
}
static HttpClient clientSetup()
{
HttpClientHandler handler = new HttpClientHandler();
handler.Credentials = new NetworkCredential("**************", "**************");
HttpClient client = new HttpClient(handler);
client.BaseAddress = new Uri("*********************");
//needed as otherwise returns plain text
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
Here's the class I'm serializing....
class TestDocument
{
public string Schema { get; set; }
public long Timestamp { get; set; }
public int Count { get; set; }
public string _rev { get; set; }
public string _id { get; set; } - would like this to be ignored if null
}
Any help much appreciated.
Assuming that you are using Json.NET to serialize your object, you should use the NullValueHandling property of the JsonProperty attribute
[JsonProperty(NullValueHandling=NullValueHandling.Ignore)]
Check out this great article and the online help for more details
If you need this behavior for all the properties of all the classes you're going to send (which is exactly the case that has led me to this question), I think this would be cleaner:
using ( HttpClient http = new HttpClient() )
{
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
TestDocument value = new TestDocument();
HttpContent content = new ObjectContent<TestDocument>( value, formatter );
await http.PutAsync( url, content );
}
This way there's no need to add attributes to your classes, and you still don't have to serialize all the values manually.
use HttpClient.PostAsync
JsonMediaTypeFormatter jsonFormat = new JsonMediaTypeFormatter();
jsonFormat.SerializerSettings.DefaultValueHandling = Newtonsoft.Json.DefaultValueHandling.Ignore;
jsonFormat.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
HttpResponseMessage res = c.PostAsync<T>(url, TObj, jsonFormat).Result;
I'm not sure you can do that with the PutAsJsonAsync as you have it right now. Json.NET can do this though, if you're able to use it, and a NuGet package exists if it helps. If you can use it, I'd rewrite the InsertDocument function to look like:
public static HttpResponseMessage InsertDocument(object doc, string name, string db)
{
HttpResponseMessage result;
string json = JsonConvert.SerializeObject(doc, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
if (String.IsNullOrWhiteSpace(name)) result = clientSetup().PostAsync(db, new StringContent(json, null, "application/json")).Result;
else result = clientSetup().PutAsync(db + String.Format("/{0}", name), new StringContent(json, null, "application/json")).Result;
return result;
}

Categories