How to deserialize asynchronously using System.Text.Json library? - c#

I have a json that I am deserializing by using NewtonSoftJson Json library as shown below:
public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
{
...
var request = Deserialize<GraphQLRequest>(httpContext.Request.Body);
....
}
public static T Deserialize<T>(Stream s)
{
using (var reader = new StreamReader(s))
using (var jsonReader = new JsonTextReader(reader))
{
var ser = new JsonSerializer();
return ser.Deserialize<T>(jsonReader);
}
}
Now I am trying to use System.Text.Json library to deserialize asynchronously so I read their docs and they do have DeserializeAsync method which I can use but when I replace content of above method like below then I get compilation error -
public static T Deserialize<T>(Stream s)
{
return JsonSerializer.DeserializeAsync<GraphQLRequest>(s, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}
);
}
Error I get is -
Cannot convert expression type 'System.Threading.Tasks.ValueTask<App.Api.Kestrel.GraphQLRequest?>' to return type 'T'
I am new to dotnet world so kinda confuse what is wrong I am doing here?

public static T Deserialize<T>(Stream s) is a generic method where T is type parameter which is passed by user, so you need to deserialize to T, not to GraphQLRequest.
async methods usually (but not always) retrun Task's, so you need to change your signature and implementation accordingly resulting in:
public static async Task<T> Deserialize<T>(Stream s)
{
return await JsonSerializer.DeserializeAsync<T>(s, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}
);
}
And then pass concrete type in the invocation (and use await):
public async Task InvokeAsync(HttpContext httpContext, ISchema schema)
{
...
GraphQLRequest request = await Deserialize<GraphQLRequest>(httpContext.Request.Body);
....
}
Since JsonSerializer.DeserializeAsync returns a ValueTask another option is to pass it through without awaiting:
public static ValueTask<T> Deserialize<T>(Stream s)
{
return JsonSerializer.DeserializeAsync<T>(s, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
}
);
}
The calling code will not change.

Related

Casting generic delegate Action<T> to Action<string>

I am working on implementing pub/sub pattern for cache use. The following class is suppose to be used to publish any type of data. And subscribers can attach a callback of Action T on the event. But I couldn't create a generic event at the class level without making the class generic and I don't want to do that. So I have tried to cast the values back and forth to string using JsonSerializer as follows.
public class CacheNotification
{
public Action<string> CachePublished;
public Task Publish<T>(T value)
{
var json = JsonSerializer.Serialize(value);
OnCachePublished(json);
return Task.CompletedTask;
}
public Task Subscribe<T>(Action<T> callback)
{
Action<string> newCallback = (string json) =>
callback(JsonSerializer.Deserialize<T>(json));
CachePublished += newCallback;
return Task.CompletedTask;
}
protected virtual void OnCachePublished(string value)
{
if(CachePublished != null){
CachePublished.Invoke(value);
}
}
}
So the problem is on the subscribe method, the casting I attempted only works for Action string. It fails on Action int or Action object
public Task Subscribe<T>(Action<T> callback)
{
Action<string> newCallback = (string json) =>
callback(JsonSerializer.Deserialize<T>(json));
CachePublished += newCallback;
return Task.CompletedTask;
}
Here is a test I used. This fails for second subscriber
Action<string> sub1Callback = msg =>
{
sub1Counter++;
};
Action<int> sub2Callback = num =>
{
sub2Counter++;
};
var sub1 = await cache.Subscribe(sub1Callback);
var sub2 = await cache.Subscribe(sub2Callback);
await cache.Publish("Value1");
Assert.Equal(1, sub1Counter);
Assert.Equal(1, sub2Counter);
your problem is here:
Action sub2Callback = num =>
after
await cache.Publish("Value1");
Your:
public Task Subscribe<T>(Action<T> callback)
{
Action<string> newCallback = (string json) => callback(JsonSerializer.Deserialize<T>(json));
CachePublished += newCallback;
return Task.CompletedTask;
}
tries to deserialize "Value1" to integer and it correctly fails. It cannot be done.
Try to send integer value instead of "Value1" and it should work.
Another way is to refactor your Subscribe method to be independent of given type, but this cannot be done according to your intentions I guess.

Send method return type as generic

I have some problem with redis generic caching because redis stores values as json and I have to deserialize it into my models but I can not because I'm using generic methods and I couldnt manage to solve this problem.
Redis get operations:
public T Get<T>(string key)
{
var type = typeof(T);
var result = default(object);
RedisInvoker(x => { result = x.Get<object>(key); });
var settings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
var deserializedObj = JsonConvert.DeserializeObject<T>(result.ToString(), settings);
return deserializedObj;
}
public object Get(string key)
{
var result = default(object);
RedisInvoker(x => { result = x.Get<object>(key); });
return result;
}
Caching Interception :
public override void Intercept(IInvocation invocation)
{
var methodName = string.Format($"{invocation.Method.ReflectedType.FullName}.{invocation.Method.Name}");
var arguments = invocation.Arguments.ToList();
var key = $"{methodName}({string.Join(",", arguments.Select(x => x?.ToString() ?? "<Null>"))})";
if (_cacheManager.IsAdded(key))
{
invocation.ReturnValue = _cacheManager.Get(key);
return;
}
invocation.Proceed();
_cacheManager.Add(key, invocation.ReturnValue, _duration);
}
What I want to do is get the method's returnType which is also generic and send it to Get method with generic type. But I couldn't send it like this :
var returnType = invocation.Method.ReturnType;
if (_cacheManager.IsAdded(key))
{
invocation.ReturnValue = _cacheManager.Get<returnType>(key);
return;
}
The error is:
returnType is a variable but used like a type
So you want to call .Get<T> with a type that you only know at runtime. The easy answer is; "don't". Instead create a non-generic method that you can call easily;
public object Get(Type type, string key) {...}
public T Get<T>(string key) => (T)Get(typeof(T), key);
Because the alternative involves reflection;
invocation.ReturnValue = typeof(...)
.GetMethod("Get")
.MakeGenericMethod(returnType)
.Invoke(instance, new object[]{ key });

Serialize and Deserialize AjaxFileUploadEventArgs - No parameterless constructor defined'

I'm changing a webform website to use StateServer and now I'm trying to find a way to serialize and deserialize AjaxFileUploadEventArgs, my code so far:
In the html I have:
<ajaxToolkit:AjaxFileUpload
ID="AAA"
runat="server"
OnUploadComplete="OnUploadComplete"
ViewStateMode="Enabled" />
Server:
protected void OnUploadComplete(object sender, AjaxFileUploadEventArgs file)
{
UpdateListInSession(file);
}
public static void UpdateListInSession(AjaxFileUploadEventArgs file)
{
var serializer = new JavaScriptSerializer();
var fileSerialized = serializer.Serialize(file);
}
public static AjaxFileUploadEventArgs GetLeadsListFromSession()
{
var serializer = new JavaScriptSerializer();
AjaxFileUploadEventArgs file = null;
AjaxFileUploadEventArgs deserializeFile =
serializer.Deserialize<AjaxFileUploadEventArgs>(
HttpContext.Current.Session[k_file] as string);
return deserializeFile;
}
The error:
System.MissingMethodException: 'No parameterless constructor defined for type of 'AjaxControlToolkit.AjaxFileUploadEventArgs'.'
Assuming you are using AjaxFileUploadEventArgs.cs from ajaxcontroltoolkit, the exception message is self-explanatory. The serializer you are using, JavaScriptSerializer, can only construct and deserialize a type with a parameterless constructor, but as shown in its reference source, AjaxFileUploadEventArgs only has a single constructor, which is parameterized:
public AjaxFileUploadEventArgs(string fileId, AjaxFileUploadState state, string statusMessage, string fileName, int fileSize, string contentType) {
// Initialize fields
}
So, what are your options to deserialize this type? Firstly, you could switch to json.net which supports parameterized constructors out of the box. Once Json.NET is installed, if you do:
var deserializeFile =
Newtonsoft.Json.JsonConvert.DeserializeObject<AjaxFileUploadEventArgs>(jsonString);
Then it simply works. Sample fiddle. Note that Microsoft's own documentation for JavaScriptSerializer states:
Json.NET should be used serialization and deserialization.
So this is likely the best solution.
If you cannot use Json.NET for whatever reason, you will need to write a custom JavaScriptConverter for AjaxFileUploadEventArgs such as the following:
public class AjaxFileUploadEventArgsConverter : JavaScriptConverter
{
public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
{
var args = new AjaxFileUploadEventArgs
(
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "FileId"),
serializer.ConvertItemToTypeOrDefault<AjaxFileUploadState>(dictionary, "State"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "StatusMessage"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "FileName"),
serializer.ConvertItemToTypeOrDefault<int>(dictionary, "FileSize"),
serializer.ConvertItemToTypeOrDefault<string>(dictionary, "ContentType")
)
{ PostedUrl = serializer.ConvertItemToTypeOrDefault<string>(dictionary, "PostedUrl") };
return args;
}
public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
{
throw new NotImplementedException();
}
public override IEnumerable<Type> SupportedTypes
{
get { return new[] { typeof(AjaxFileUploadEventArgs) }; }
}
}
public static class JavaScriptSerializerExtensions
{
public static T ConvertItemToTypeOrDefault<T>(this JavaScriptSerializer serializer, IDictionary<string, object> dictionary, string key)
{
object value;
if (!dictionary.TryGetValue(key, out value))
return default(T);
return serializer.ConvertToType<T>(value);
}
}
Then deserialize as follows:
var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new JavaScriptConverter[] { new AjaxFileUploadEventArgsConverter() });
var deserializeFile = serializer.Deserialize<AjaxFileUploadEventArgs>(jsonString);

Creating async version of a generic method

I have this generic method for Deserializing a type
public static T Deserialize<T>(string xmlString)
{
if (string.IsNullOrWhiteSpace(xmlString))
return default(T);
using (MemoryStream memStream = new MemoryStream(Encoding.Unicode.GetBytes(xmlString)))
{
memStream.Position = 0;
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
return (T)serializer.Deserialize(memStream);
}
}
Now I wish to make an async version which I tried like this.
public static Task DeserializeAsync(string xmlString)
{
return Task.Run(() =>
{
Deserialize(xmlString));
});
}
Obviously the async method has syntax errors because I am missing T.
I also tried this and I get errors.
public static Task<T> DeserializeAsync(string xmlString)
{
return Task.Run(() =>
{
Deserialize<T>(xmlString));
});
}
Can anyone show me the correct way of writing the async method?
You are just forgetting to declare your method as generic (note the <T>), and actually return the result of Deserialize:
public static Task<T> DeserializeAsync<T>(string xmlString)
{
return Task.Run(() =>
{
return Deserialize<T>(xmlString));
});
}
or more simply:
public static Task<T> DeserializeAsync<T>(string xmlString)
{
return Task.Run(() => Deserialize<T>(xmlString)));
}

Web API OData media type formatter when using $expand

I'm trying to create a MediaTypeFormatter to handle text/csv but running into a few problems when using $expand in the OData query.
Query:
http://localhost/RestBlog/api/Blogs/121?$expand=Comments
Controller:
[EnableQuery]
public IQueryable<Blog> GetBlog(int id)
{
return DbCtx.Blog.Where(x => x.blogID == id);
}
In my media type formatter:
private static MethodInfo _createStreamWriter =
typeof(CsvFormatter)
.GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(m => m.Name == "StreamWriter");
internal static void StreamWriter<T, X>(T results)
{
var queryableResult = results as IQueryable<X>;
if (queryableResult != null)
{
var actualResults = queryableResult.ToList<X>();
}
}
public override void WriteToStream(Type type, object value,
Stream writeStream, HttpContent content)
{
Type genericType = type.GetGenericArguments()[0];
_createStreamWriter.MakeGenericMethod(
new Type[] { value.GetType(), genericType })
.Invoke(null, new object[] { value }
);
}
Note that the type of value is System.Data.Entity.Infrastructure.DbQuery<System.Web.Http.OData.Query.Expressions.SelectExpandBinder.SelectAllAndExpand<Rest.Blog>> which means that it doesn't work.
The type of value should be IQueryable but upon casting it returns null.
When making a query without the $expand things work a lot more sensibly. What am I doing wrong?
I'm just trying to get at the data before even outputting as CSV, so guidance would be greatly appreciated.
If you look at the source code for OData Web API, you will see that
SelectExpandBinder.SelectAllAndExpand is a subclass of the generic class SelectExpandWrapper(TEntity) :
private class SelectAllAndExpand<TEntity> : SelectExpandWrapper<TEntity>
{
}
which itself is a subclass of non-generic SelectExpandWrapper:
internal class SelectExpandWrapper<TElement> : SelectExpandWrapper
{
// Implementation...
}
which in turn implements IEdmEntityObject and ISelectExpandWrapper:
internal abstract class SelectExpandWrapper : IEdmEntityObject, ISelectExpandWrapper
{
// Implementation...
}
This means that you have access to the ISelectExpandWrapper.ToDictionary method and can use it to get at the properties of the underlying entity:
public interface ISelectExpandWrapper
{
IDictionary<string, object> ToDictionary();
IDictionary<string, object> ToDictionary(Func<IEdmModel, IEdmStructuredType, IPropertyMapper> propertyMapperProvider);
}
Indeed this is how serialization to JSON is implemented in the framework as can be seen from SelectExpandWrapperConverter:
internal class SelectExpandWrapperConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
ISelectExpandWrapper selectExpandWrapper = value as ISelectExpandWrapper;
if (selectExpandWrapper != null)
{
serializer.Serialize(writer, selectExpandWrapper.ToDictionary(_mapperProvider));
}
}
// Other methods...
}
I was googled when i face that issue in my task.. i have clean implementation from this thread
first you need verify edm modelbuilder in proper way for expand
objects
you have to register edm model for blog and foreign key releations.then only it will sucess
Example
ODataModelBuilder builder = new ODataConventionModelBuilder();
builder.EntitySet<Blog>("blog");
builder.EntitySet<Profile>("profile");//ForeignKey releations of blog
builder.EntitySet<user>("user");//ForeignKey releations of profile
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel());
Then you need develop this formatter ..example source code us here
applogies for my english..
First of all we need to create a class which will be derived from MediaTypeFormatter abstract class. Here is the class with its constructors:
public class CSVMediaTypeFormatter : MediaTypeFormatter {
public CSVMediaTypeFormatter() {
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}
public CSVMediaTypeFormatter(
MediaTypeMapping mediaTypeMapping) : this() {
MediaTypeMappings.Add(mediaTypeMapping);
}
public CSVMediaTypeFormatter(
IEnumerable<MediaTypeMapping> mediaTypeMappings) : this() {
foreach (var mediaTypeMapping in mediaTypeMappings) {
MediaTypeMappings.Add(mediaTypeMapping);
}
}
}
Above, no matter which constructor you use, we always add text/csv media type to be supported for this formatter. We also allow custom MediaTypeMappings to be injected.
Now, we need to override two methods: MediaTypeFormatter.CanWriteType and MediaTypeFormatter.OnWriteToStreamAsync.
First of all, here is the CanWriteType method implementation. What this method needs to do is to determine if the type of the object is supported with this formatter or not in order to write it.
protected override bool CanWriteType(Type type) {
if (type == null)
throw new ArgumentNullException("type");
return isTypeOfIEnumerable(type);
}
private bool isTypeOfIEnumerable(Type type) {
foreach (Type interfaceType in type.GetInterfaces()) {
if (interfaceType == typeof(IEnumerable))
return true;
}
return false;
}
What this does here is to check if the object has implemented the IEnumerable interface. If so, then it is cool with that and can format the object. If not, it will return false and framework will ignore this formatter for that particular request.
And finally, here is the actual implementation. We need to do some work with reflection here in order to get the property names and values out of the value parameter which is a type of object:
protected override Task OnWriteToStreamAsync(
Type type,
object value,
Stream stream,
HttpContentHeaders contentHeaders,
FormatterContext formatterContext,
TransportContext transportContext) {
writeStream(type, value, stream, contentHeaders);
var tcs = new TaskCompletionSource<int>();
tcs.SetResult(0);
return tcs.Task;
}
private void writeStream(Type type, object value, Stream stream, HttpContentHeaders contentHeaders) {
//NOTE: We have check the type inside CanWriteType method
//If request comes this far, the type is IEnumerable. We are safe.
Type itemType = type.GetGenericArguments()[0];
StringWriter _stringWriter = new StringWriter();
_stringWriter.WriteLine(
string.Join<string>(
",", itemType.GetProperties().Select(x => x.Name )
)
);
foreach (var obj in (IEnumerable<object>)value) {
var vals = obj.GetType().GetProperties().Select(
pi => new {
Value = pi.GetValue(obj, null)
}
);
string _valueLine = string.Empty;
foreach (var val in vals) {
if (val.Value != null) {
var _val = val.Value.ToString();
//Check if the value contans a comma and place it in quotes if so
if (_val.Contains(","))
_val = string.Concat("\"", _val, "\"");
//Replace any \r or \n special characters from a new line with a space
if (_val.Contains("\r"))
_val = _val.Replace("\r", " ");
if (_val.Contains("\n"))
_val = _val.Replace("\n", " ");
_valueLine = string.Concat(_valueLine, _val, ",");
} else {
_valueLine = string.Concat(string.Empty, ",");
}
}
_stringWriter.WriteLine(_valueLine.TrimEnd(','));
}
var streamWriter = new StreamWriter(stream);
streamWriter.Write(_stringWriter.ToString());
}
We are partially done. Now, we need to make use out of this. I registered this formatter into the pipeline with the following code inside Global.asax Application_Start method:
GlobalConfiguration.Configuration.Formatters.Add(
new CSVMediaTypeFormatter(
new QueryStringMapping("format", "csv", "text/csv")
)
);
On my sample application, when you navigate to /api/cars?format=csv, it will get you a CSV file but without an extension. Go ahead and add the csv extension. Then, open it with Excel and you should see something similar to below:

Categories

Resources