Is there a utility to serialise an object as HTTP content type "application/x-www-form-urlencoded"? - http

I've never had to do this before, because it's always only been an actual form that I've posted as that content type, but recently I had to post three variables like that, and I resorted to a sordid concatenation with & and =:
var content = new StringContent("grant_type=password&username=" + username + "&password=" + password.ToClearString(), Encoding.UTF8,
"application/x-www-form-urlencoded");
I'm sure there must be a utility method that would do that, and do it better, with any necessary encoding. What would that be?

If this is a POCO and just using the Newtonsoft library, you can use this as well:
public static class FormUrlEncodedContentExtension
{
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj)
{
var json = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var keyValues = JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
var content = new FormUrlEncodedContent(keyValues);
return content;
}
}
And a sample usage would be:
var myObject = new MyObject {Grant_Type = "TypeA", Username = "Hello", Password = "World"};
var request = new HttpRequestMessage(HttpMethod.Post, "/path/to/post/to")
{
Content = myObject.ToFormUrlEncodedContent()
};
var client = new HttpClient {BaseAddress = new Uri("http://www.mywebsite.com")};
var response = await client.SendAsync(request);

Use reflection to get the property names and values and then use them to create a System.Net.Http.FormUrlEncodedContent
public static class FormUrlEncodedContentExtension {
public static FormUrlEncodedContent ToFormUrlEncodedContent(this object obj) {
var nameValueCollection = obj.GetType()
.GetProperties()
.ToDictionary(p => p.Name, p => (p.GetValue(obj) ?? "").ToString());
var content = new FormUrlEncodedContent(nameValueCollection);
return content;
}
}
From there it is a simple matter of calling the extension method on an object to convert it to a FormUrlEncodedContent
var model = new MyModel {
grant_type = "...",
username = "...",
password = "..."
};
var content = model.ToFormUrlEncodedContent();

You should be able to use string interpolation for that. Something like:
var content = new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
Or wrap this inside a helper/factory method:
public static class StringContentFactory
{
public static StringContent Build(string username, string password)
{
return new StringContent($"grant_type=password&username={username}&password={password}", Encoding.UTF8, "application/x-www-form-urlencoded");
}
}

Related

Web API is not able to serialize the DateTime sent as "LOGINDATE": "2020-04-05T01:00:21.45+04:00". it only accepts Date(milliseconds)

Maybe I am confused or due to working from home I am not able to think.
I have Web API 2.0 project which is receiving models in end points. I have android client which is sending Date in milliseconds format and for same end point i have MVC client which is sending date in "LOGINDATE": "2020-04-05T01:00:21.45+04:00". the date from MVC is not accepted by API and gives an error on model state like
System.FormatException: DateTime content
'2020-04-05T10:52:42.333+04:00' does not start with '/Date(' and end
with ')/' as required for JSON.
I have forced the WebApi to use the newtonsoft json formatter but when i debug the exception i found
System.Runtime.Serialization.Json.JsonReaderDelegator.ParseJsonDateInDefaultFormat(String originalDateTimeValue)
Following is the WEBAPICONFIG code for forcing use the newtonsoft mediatype.
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.UseDataContractJsonSerializer = true;
JsonSerializerSettings CustomJsonSetting = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
DateTimeZoneHandling = DateTimeZoneHandling.Local,
DateFormatHandling = DateFormatHandling.IsoDateFormat,
//DateFormatString = "yyyy-mm-dd HH:MM:ss"
};
CustomJsonSetting.Converters.Clear();
CustomJsonSetting.Converters.Add(new IsoDateTimeConverter());
JsonConvert.DefaultSettings = () => CustomJsonSetting;
jsonFormatter.SerializerSettings = CustomJsonSetting;
config.Formatters.Clear();
config.Formatters.Insert(0,jsonFormatter);
Then from MVC client i am compressing GZIP and sending the call like following:
var frmt = UniversalFormatters.GetJsonFormatter();
if (IsCompressionEnabled)
{
var json = JsonConvert.SerializeObject(request, frmt.SerializerSettings);
//new JavaScriptSerializer().Serialize(request);
//
client.DefaultRequestHeaders.Add("pHubCompression", "1");
var content = new CompressedContent(
new StringContent(json, Encoding.UTF8, "application/json"),
CompressionMethod.GZip);
return await client.PostAsync(EndPoint, content);
}
else
return await client.PostAsync(EndPoint, request, frmt);
And Universal Formatter is defined as
public static JsonMediaTypeFormatter GetJsonFormatter()
{
if (JsonFormatter==null)
{
JsonFormatter = new JsonMediaTypeFormatter();
JsonFormatter.UseDataContractJsonSerializer = true;
JsonFormatter.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
JsonFormatter.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
JsonFormatter.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
JsonFormatter.SerializerSettings.DateFormatHandling = DateFormatHandling.IsoDateFormat;
JsonFormatter.SerializerSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ss";
}
return JsonFormatter;
}
I have done a bit R&D and people mentioned to create my own date formater which is a work around but i wanted to know if there is something wrong with my setting.
My Model is defined like following:
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://MyProj.Core.Entities", Name = "RequestObject{0}")]
public class RequestObject<T> : IDisposable//, IEntity
//where T : IEntity, IEntityCollection, ICompositeEntity
{
private UserInfo m_userInfo;
private T m_obj;}
and UserInfo is defined like
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://MyProj.Business.Contracts", Name = "UserInfo")]
public partial class UserInfo : BaseEntity, IEntity, IDisposable
{
[System.Runtime.Serialization.DataMember(EmitDefaultValue = true)]
public DateTime? LOGINDATE { set; get; }}
Sorry for my bad English.
according to above comment by #dbc I removed JsonFormatter.UseDataContractJsonSerializer = true; this line from my startups and everything starts working. Thanks for the help.

Read Asp.Net Core Response body in ActionFilterAttribute

I'm using Asp.Net Core as a Rest Api Service.
I need access to request and response in ActionFilter. Actually, I found the request in OnActionExcecuted but I can't read the response result.
I'm trying to return value as follow:
[HttpGet]
[ProducesResponseType(typeof(ResponseType), (int)HttpStatusCode.OK)]
[Route("[action]")]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
var model = await _responseServices.Get(cancellationToken);
return Ok(model);
}
And in ActionFilter OnExcecuted method as follow:
_request = context.HttpContext.Request.ReadAsString().Result;
_response = context.HttpContext.Response.ReadAsString().Result; //?
I'm trying to get the response in ReadAsString as an Extension method as follow:
public static async Task<string> ReadAsString(this HttpResponse response)
{
var initialBody = response.Body;
var buffer = new byte[Convert.ToInt32(response.ContentLength)];
await response.Body.ReadAsync(buffer, 0, buffer.Length);
var body = Encoding.UTF8.GetString(buffer);
response.Body = initialBody;
return body;
}
But, there is no result!
How I can get the response in OnActionExcecuted?
Thanks, everyone for taking the time to try and help explain
If you're logging for json result/ view result , you don't need to read the whole response stream. Simply serialize the context.Result:
public class MyFilterAttribute : ActionFilterAttribute
{
private ILogger<MyFilterAttribute> logger;
public MyFilterAttribute(ILogger<MyFilterAttribute> logger){
this.logger = logger;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
var result = context.Result;
if (result is JsonResult json)
{
var x = json.Value;
var status = json.StatusCode;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
if(result is ViewResult view){
// I think it's better to log ViewData instead of the finally rendered template string
var status = view.StatusCode;
var x = view.ViewData;
var name = view.ViewName;
this.logger.LogInformation(JsonConvert.SerializeObject(x));
}
else{
this.logger.LogInformation("...");
}
}
I know there is already an answer but I want to also add that the problem is the MVC pipeline has not populated the Response.Body when running an ActionFilter so you cannot access it. The Response.Body is populated by the MVC middleware.
If you want to read Response.Body then you need to create your own custom middleware to intercept the call when the Response object has been populated. There are numerous websites that can show you how to do this. One example is here.
As discussed in the other answer, if you want to do it in an ActionFilter you can use the context.Result to access the information.
For logging whole request and response in the ASP.NET Core filter pipeline you can use Result filter attribute
public class LogRequestResponseAttribute : TypeFilterAttribute
{
public LogRequestResponseAttribute() : base(typeof(LogRequestResponseImplementation)) { }
private class LogRequestResponseImplementation : IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var requestHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Request.Headers);
Log.Information("requestHeaders: " + requestHeadersText);
var requestBodyText = await CommonLoggingTools.FormatRequestBody(context.HttpContext.Request);
Log.Information("requestBody: " + requestBodyText);
await next();
var responseHeadersText = CommonLoggingTools.SerializeHeaders(context.HttpContext.Response.Headers);
Log.Information("responseHeaders: " + responseHeadersText);
var responseBodyText = await CommonLoggingTools.FormatResponseBody(context.HttpContext.Response);
Log.Information("responseBody: " + responseBodyText);
}
}
}
In Startup.cs add
app.UseMiddleware<ResponseRewindMiddleware>();
services.AddScoped<LogRequestResponseAttribute>();
Somewhere add static class
public static class CommonLoggingTools
{
public static async Task<string> FormatRequestBody(HttpRequest request)
{
//This line allows us to set the reader for the request back at the beginning of its stream.
request.EnableRewind();
//We now need to read the request stream. First, we create a new byte[] with the same length as the request stream...
var buffer = new byte[Convert.ToInt32(request.ContentLength)];
//...Then we copy the entire request stream into the new buffer.
await request.Body.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
//We convert the byte[] into a string using UTF8 encoding...
var bodyAsText = Encoding.UTF8.GetString(buffer);
//..and finally, assign the read body back to the request body, which is allowed because of EnableRewind()
request.Body.Position = 0;
return $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {bodyAsText}";
}
public static async Task<string> FormatResponseBody(HttpResponse response)
{
//We need to read the response stream from the beginning...
response.Body.Seek(0, SeekOrigin.Begin);
//...and copy it into a string
string text = await new StreamReader(response.Body).ReadToEndAsync();
//We need to reset the reader for the response so that the client can read it.
response.Body.Seek(0, SeekOrigin.Begin);
response.Body.Position = 0;
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
return $"{response.StatusCode}: {text}";
}
public static string SerializeHeaders(IHeaderDictionary headers)
{
var dict = new Dictionary<string, string>();
foreach (var item in headers.ToList())
{
//if (item.Value != null)
//{
var header = string.Empty;
foreach (var value in item.Value)
{
header += value + " ";
}
// Trim the trailing space and add item to the dictionary
header = header.TrimEnd(" ".ToCharArray());
dict.Add(item.Key, header);
//}
}
return JsonConvert.SerializeObject(dict, Formatting.Indented);
}
}
public class ResponseRewindMiddleware {
private readonly RequestDelegate next;
public ResponseRewindMiddleware(RequestDelegate next) {
this.next = next;
}
public async Task Invoke(HttpContext context) {
Stream originalBody = context.Response.Body;
try {
using (var memStream = new MemoryStream()) {
context.Response.Body = memStream;
await next(context);
//memStream.Position = 0;
//string responseBody = new StreamReader(memStream).ReadToEnd();
memStream.Position = 0;
await memStream.CopyToAsync(originalBody);
}
} finally {
context.Response.Body = originalBody;
}
}
You can also do...
string response = "Hello";
if (result is ObjectResult objectResult)
{
var status = objectResult.StatusCode;
var value = objectResult.Value;
var stringResult = objectResult.ToString();
responce = (JsonConvert.SerializeObject(value));
}
I used this in a .net core app.
Hope it helps.

Response on created context keeps giving me NullStream

I'm trying to write a middleware for batch requests i .net core 2.0.
So far the I have splitted the request, pipe each request on to the controllers.
The controllers return value, but for some reason the response on the created context that I parse to the controllers keeps giving me a NullStream in the body, so I think that there is something that I miss in my setup.
The code looks like this:
var json = await streamHelper.StreamToJson(context.Request.Body);
var requests = JsonConvert.DeserializeObject<IEnumerable<RequestModel>>(json);
var responseBody = new List<ResponseModel>();
foreach (var request in requests)
{
var newRequest = new HttpRequestFeature
{
Body = request.Body != null ? new MemoryStream(Encoding.ASCII.GetBytes(request.Body)) : null,
Headers = context.Request.Headers,
Method = request.Method,
Path = request.RelativeUrl,
PathBase = string.Empty,
Protocol = context.Request.Protocol,
Scheme = context.Request.Scheme,
QueryString = context.Request.QueryString.Value
};
var newRespone = new HttpResponseFeature();
var requestLifetimeFeature = new HttpRequestLifetimeFeature();
var features = CreateDefaultFeatures(context.Features);
features.Set<IHttpRequestFeature>(newRequest);
features.Set<IHttpResponseFeature>(newRespone);
features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
var innerContext = _factory.Create(features);
await _next(innerContext);
var responseJson = await streamHelper.StreamToJson(innerContext.Response.Body);
I'm not sure what it is I'm missing in the setup, since innerContext.Response.Body isn't set.
One of the endpoints that I use for testing and that gets hit looks like this
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
I found the error, or two errors for it to work.
First I had to change my newResponse to
var newRespone = new HttpResponseFeature{ Body = new MemoryStream() };
Since HttpResponseFeature sets Body to Stream.Null in the constructor.
When that was done, then Body kept giving an empty string when trying to read it. That was fixed by setting the Position to Zero like
innerContext.Response.Body.Position = 0;

401 (Unauthorized) when trying to query DocumentDb through REST API

I want to make an SQL Query to my Azure DocumentDb. I've got a quite messy code for now but that's how it looks
public string GetResources(string collection) {
var client = new System.Net.Http.HttpClient();
client.DefaultRequestHeaders.Add("x-ms-date", utc_date);
client.DefaultRequestHeaders.Add("x-ms-version", "2015-08-06");
client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/query+json"));
//GET a document
var verb = "POST";
var resourceType = "docs";
var resourceLink = string.Format("dbs/{0}/colls/{1}/docs", databaseId, collection);
var resourceId = (idBased) ? resourceLink : "";
var authHeader = GenerateAuthToken(verb, resourceId, resourceType, masterKey, "master", "1.0");
Console.WriteLine(authHeader);
client.DefaultRequestHeaders.Remove("authorization");
client.DefaultRequestHeaders.Add("authorization", authHeader);
var q = new DbQuery {
Query = "SELECT * FROM root"
};
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("query", q.Query));
return PostAsync(resourceLink, postData, client).Result;
}
public async Task<string> PostAsync(string uri, List<KeyValuePair<string, string>> data, HttpClient httpClient)
{
var content = new FormUrlEncodedContent(data);
Console.WriteLine(httpClient.DefaultRequestHeaders.Authorization);
var response = await httpClient.PostAsync(new Uri(baseUri, uri), content);
response.EnsureSuccessStatusCode();
string postContent = await response.Content.ReadAsStringAsync();
return await Task.Run(() => postContent);
}
string GenerateAuthToken(string verb, string resourceId, string resourceType, string key, string keyType, string tokenVersion)
{
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
string verbInput = verb ?? "";
string resourceIdInput = resourceId ?? "";
string resourceTypeInput = resourceType ?? "";
string payLoad = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}\n{1}\n{2}\n{3}\n{4}\n",
verb.ToLowerInvariant(),
resourceType.ToLowerInvariant(),
resourceId,
utc_date.ToLowerInvariant(),
""
);
byte[] hashPayLoad = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payLoad));
string signature = Convert.ToBase64String(hashPayLoad);
return System.Net.WebUtility.UrlEncode(String.Format(System.Globalization.CultureInfo.InvariantCulture, "type={0}&ver={1}&sig={2}",
keyType,
tokenVersion,
signature));
}
I have a request to get a document by its Id and I use the same approach. It works fine. I believe the problem could be with my resourceLink but honestly I tried a lot of version and all with no result.. what am I missing here?
the value for resourceLink will depend on whether you are using id based routing, or rid based routing.
it looks like you are using id based routing
so, for a query, the resourceLink should be
string.Format("dbs/{0}/colls/{1}/docs", databaseId, collectionId);
and resourceId should be the same,
string.Format("dbs/{0}/colls/{1}", databaseId, collectionId)
POST is a little different in that it requires specific headers set.
The REST documentation for Querying DocumentDB resources using the REST API should document what is needed - https://msdn.microsoft.com/en-us/library/azure/dn783363.aspx
For further examples of what the resourceLink & resourceId and a working eample have a look at the REST from .NET samples we just published.
https://github.com/Azure/azure-documentdb-dotnet/blob/d3f8e9c731bc92816d023719e7e780b7a9546ca2/samples/rest-from-.net/Program.cs#L151-L164
For POST I use something like this:
var resourceLink = string.Format("dbs/{0}/colls/{1}", DataBaseName, DocumentCollectionName);
using (var client = GetClient(
"POST",
resourceLink,
"docs",
PrimaryKey))
{
try
{
var content =
new StringContent("{\"query\":\"SELECT * FROM root\"}", Encoding.UTF8, "application/query+json");
content.Headers.ContentType.CharSet = "";
var r = client.PostAsync(
new Uri(
new Uri(EndpointUri), resourceLink + "/docs"), content).Result;
}
catch (Exception ex)
{
}
}}
and it works for me

Accessing the returned XML from an API call

I have the following action method to perform an API call:-
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Rack rack, FormCollection formValues)
{
if (ModelState.IsValid) {
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = "testuser";
query["password"] = ///.....
query["assetType"] = "Rack";
query["operation"] = "AddAsset";
var url = new UriBuilder("http://win-spdev:8400/servlets/AssetServlet");
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
}
The return XML from the API call looks as follow:-
<operation>
<operationstatus>Failure</operationstatus>
<message>Rack already exists.Unable to add</message>
</operation>
but how i can reach the message and operationstaus and according to them to display an appropriate message . i use to serialize the returned Json such as , but i am not sure how to do so for the xML:-
var serializer = new JavaScriptSerializer();
var myObject = serializer.Deserialize<newprocess>(json);
string activityid = myObject.activityId;
Just load it into an XmlDocument.
Untested and from the top of my head:
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(theXML);
var status = xmlDoc.SelectSingleNode("/operation/operationstatus").InnerText;
var message = xmlDoc.SelectSingleNode("/operation/message").InnerText;
If you using ASP.NET mvc, I believe you can use HttpClient, instead of WebClient:
Define result class:
public class operation
{
public string operationstatus{get;set;}
public string message{get;set;}
}
And then use it for automatic deserilization:
var client = new HttpClient();
var result = client.PostAsync(url,
new FormUrlEncodedContent(new Dictionary<string, string>{
{"username","testuser"},
{"assetType","Rack"}}))
.Result.Content
.ReadAsAsync<operation>().Result;

Resources