Serializing objects to URL encoded form data
source link: https://www.tuicool.com/articles/hit/va6niy6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
While messing with dictionaries to create form data for FormUrlEncodedContent so I can send data to server using HTTP client, I started thinking about easier and cleaner way to do it. I was writing integration tests and I wanted to re-use some model classes instead of dictionaries. Here’s how to do it. Sample of integration test is incluced.
Those who are currently writing integration tests for some ASP.NET Core web application may also find the following writings interesting:
Problem
I write integration tests for ASP.NET Core web application. There are tests that use HTTP POST to send some data to server. I can create POST body like shown here.
var formDictionary = new Dictionary<string, string>(); formDictionary.Add("Id", "1"); formDictionary.Add("Title", "Item title"); var formContent = new FormUrlEncodedContent(formDictionary); var response = await client.PostAsync(url, formContent);
I don’t like this code and I would use it only if other options turn out less optimal. Instead, I want something like shown here.
var folder = new MediaFolder { Id = 1, Title = "Folder title" }; var formBody = GetFormBodyOfObject(folder); // Act var response = await client.PostAsync(url, fromBody);
Or why not something more elegant like code here.
var folder = new MediaFolder { Id = 1, Title = "Folder title" }; // Act var response = await client.PostAsync(url, folder.ToFormBody());
This way I can use model classes from web application because these classes are used on forms anyway to move data between browser and server. If otherwise breaking change is introduced then compiler will tell me about it.
Serializing objects to Dictionary<string,string>
Thanks to Arnaud Auroux from Geek Learning we have ready-made solution to take. ToKeyValue() method by Arnaud turns object to JSON and then uses Newtonsoft.JSON library convert object to dictionary like FormUrlEncodedContent class wants.
ToKeyValue() method is the main work horse for my solution. I turned it to extension method and made serializer avoid cyclic references.
public static IDictionary<string, string> ToKeyValue(this object metaToken) { if (metaToken == null) { return null; } // Added by me: avoid cyclic references var serializer = new JsonSerializer { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; var token = metaToken as JToken; if (token == null) { // Modified by me: use serializer defined above return ToKeyValue(JObject.FromObject(metaToken, serializer)); } if (token.HasValues) { var contentData = new Dictionary<string, string>(); foreach (var child in token.Children().ToList()) { var childContent = child.ToKeyValue(); if (childContent != null) { contentData = contentData.Concat(childContent) .ToDictionary(k => k.Key, v => v.Value); } } return contentData; } var jValue = token as JValue; if (jValue?.Value == null) { return null; } var value = jValue?.Type == JTokenType.Date ? jValue?.ToString("o", CultureInfo.InvariantCulture) : jValue?.ToString(CultureInfo.InvariantCulture); return new Dictionary<string, string> { { token.Path, value } }; }
Now we can serialize our objects to Dictionary<string,string> and it also works with object hierarchies.
Serializing object to form data
But we are not there yet. ToKeyValue() extension method is useful if we want to modify dictionary before it goes to FormUrlEncodedContent. For cases when we don’t need any modifications to dictionary I wrote ToFormData() extension method.
public static FormUrlEncodedContent ToFormData(this object obj) { var formData = obj.ToKeyValue(); return new FormUrlEncodedContent(formData); }
Using these extension methods I can write integration tests like shown here.
[Fact] public async Task CreateFolder_CreatesFolderWithValidData() { // Arrange var factory = GetFactory(hasUser: true); var client = factory.CreateClient(); var url = "Home/CreateFolder"; var folder = new MediaFolder { Id = 1, Title = "Folder title" }; // Act var response = await client.PostAsync(url, folder.ToFormData()); // Assert response.EnsureSuccessStatusCode(); // Status Code 200-299 Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType.ToString()); }
Wrapping up
We started with annoying problem and ended up with pretty nice solution. Instead of manually filling dictionaries used for HTTP POST requests in integration tests we found nice JSON based solution by Arnaud Auroux and we built ToKeyValue() and ToFormData() extension methods to simplify object serialization to .NET Core HttpClient form data. These extension methods are general enough to have them in some common library for our projects.
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK