Apply IsoDateTimeConverter to Incoming Request - c#

I created a Web API in .Net, I added the global settings below:
var jsonSettings = new JsonSerializerSettings();
jsonSettings .Converters.Add(new IsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-ddTHH:mm:ss.fffZ" });
Im always getting the date format yyyy-MM-ddTHH:mm:ss.fffZ but now I wish to change my setting to only convert on incoming not outgoing. e.g incoming 2022-08-22T13:42:27.407Z and outgoing 2022-08-22 13:42:27, I moved the setting to an ActionFilterAttribute OnActionExecuting, but doesnt work right. the date is already converted (ISO String setting 2 hours diff) when it reaches the OnActionExecuting function.
Is possible to change format for outgoing date format?

You could inherit from IsoDateTimeConverter so it overrides CanRead to return false. Once done, the new converter will apply only for writing, and default serialization will be used for reading.
First, create the following converter:
public class WriteOnlyIsoDateTimeConverter : IsoDateTimeConverter
{
public override bool CanRead => false;
}
And now the following unit test will pass:
var json = #"""2022-08-22T13:42:27.407Z""";
var jsonSettings = new JsonSerializerSettings();
jsonSettings.Converters.Add(new WriteOnlyIsoDateTimeConverter() { DateTimeFormat = "yyyy-MM-dd HH:mm:ss" });
var dateTime = JsonConvert.DeserializeObject<DateTime>(json, jsonSettings);
var json2 = JsonConvert.SerializeObject(dateTime, jsonSettings);
Assert.AreEqual(#"""2022-08-22 13:42:27""", json2);
Notes:
2022-08-22T13:42:27.407Z is already in the default serialization format used by Json.NET for DateTime objects, but if you need to change the default you can set JsonSerializerSettings.DateFormatString or JsonSerializerSettings.DateTimeZoneHandling. The converter will be used for writing, and the (modified) defaults will be used for reading.
If you want a converter to convert apply only when reading, override CanWrite instead.
If you do want your custom DateTime converter to handle reading as well as writing, you may need to set JsonSerializerSettings.DateParseHandling = DateParseHandling.None to prevent JsonTextReader from automatically recognizing and parsing it before the ReadJson() method of your converter is called. For details see Json.NET Disable the deserialization on DateTime.
Demo fiddle here.

Related

Custom JsonConverter added to JsonSerializerOptions.Converters not used by default

I have implemented a custom json converter for one of my classes since I want to use the constructor for deserialization.
I have added the custom converter to my Startup.cs class:
services.AddControllers()
.AddJsonOptions(opt =>
{
opt.JsonSerializerOptions.WriteIndented = false;
opt.JsonSerializerOptions.Converters.Add(new MyJsonConverter());
});
My custom json converter looks like this:
public class MyJsonConverter : JsonConverter<MyClass>
{
...
}
The problem is that when running
var result = await JsonSerializer.DeserializeAsync<RoleEntity[]>(json);
my custom json converter isn't triggered.
However, if instead specifying it explicitly like the following
var serializerOptions = new JsonSerializerOptions
{
Converters = { new MyJsonConverter() }
};
var result = await JsonSerializer.DeserializeAsync<RoleEntity[]>(json, serializerOptions);
it works.
I just can't see what the problem is. Am I missing something trivial here?
This is the expected behaviour. When you use JsonSerializer.DeserializeAsync directly without specifying any options it, well, won't use any custom options. The options you add in are for MVC (see this answer). You need to either continue using it as you wrote or register options as singleton which you could then resolve/inject and use within your JsonSerializer.DeserializeAsync call.
To add a new singleton you can use IServiceCollection.AddSingleton.
There are many ways to get the result you want (singleton options is just one of them) but the important part of this answer is that the options you define with .AddJsonOptions will not be used for direct calls to JsonSerializer-methods.

Use Log4net to generate json format log with custom layout/fields?

can we use Log4net to generate json format log with custom layout/fields?
I'm using log4net to log some information. Now, because of this and that, we need to log it as json format.
I'm using log4net.Ext.Json for this, it logs the information like this:
{"date":"2018-10-29T15:18:26.7785983-07:00","level":"INFO","logger":"Service.Services.LogService","message":"data_length: 10"}
{"date":"2018-10-29T15:18:26.7796462-07:00","level":"INFO","logger":"Service.Services.LogService","message":"max_parallelism: 1"}
However, since we will log lots of information, and we will feed this log to another program to analyze. So, we want to output it like:
{
"_index": "error_201843",
"_type": "error_web",
"_id": "AWaytV_hi121qded",
"_version": 1,
"_source": {
"ApplicationSource": "Data Feed",
"ErrorType": "RequestTimeout",
"XStackTrace": "",
"ErrorMessageText": ""
}
}
_index, _typem _id, _version are constant. _source data comes from the actual log fields.
How can we do this? Any ideas? I'm thinking of have a method to build the entire string, then output the string. But it should have a better way to do this, I think.
Thanks
We have been doing this for some time and can be done using a dynamic object. We have different log event types which are centralized and this allows the flexibility to add whatever information required. The loggingEvent event object is from log4net and an Append override in a custom appender.
JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore };
dynamic m = new System.Dynamic.ExpandoObject();
m.SessionId = _sessionId;
m.ProcessId = _processId.ToString();
m.ProcessName = _processName;
m.MachineName = _machineName;
m.UserName = _userName;
m.Level = loggingEvent.Level.DisplayName;
m.Domain = loggingEvent.Domain;
m.TimeStamp = loggingEvent.TimeStamp.ToString("yyyyMMddHHmmssfff");
m.MessageObject = loggingEvent.MessageObject;
if (loggingEvent.ExceptionObject != null)
{
m.Exception = loggingEvent.ExceptionObject.ToString();
m.StackTrace = loggingEvent.ExceptionObject.StackTrace;
}
//Convert the object to a json string
string msg = JsonConvert.SerializeObject(m, Formatting.None, _jsonSerializerSettings);

RestSharp - XmlSerializer not using the DateFormat property

I encountered a problem with the RestSharp library. Defaultly, it serializes DateTime objects using the format dd/MM/yyyy HH:mm:ss. That doesn't work well with my WCF service that only seems to accept yyyy-MM-ddTHH:mm:ss, so I tried to alter the serialization of a request with request.DateFormat = "yyyy-MM-ddTHH:mm:ss.
This property , even though set correctly, seems to be having zero impact on the serialization. At least when using the default RestSharp.Serializers.XmlSerializer. If I tried using the DotNetXmlSerializer, the DateFormat was working, but then the serializer didn't include my XMLNS link and added version & encoding line to the xml output, one or both of which wasn't compatible with the WCF service either.
Does anybody have any suggestions what am I doing wrong with the XmlSerializer ?
Here is the concerned codeblock:
var req = new RestRequest(endpoint, Method.POST);
req.RequestFormat = DataFormat.Xml;
//req.XmlSerializer = new DotNetXmlSerializer();
req.XmlSerializer = new XmlSerializer();
req.DateFormat = DATE_FORMAT;
req.AddBody(model, XMLNS);
Where private const string DATE_FORMAT = "yyyy-MM-ddTHH:mm:ss" and XMLNS is the URL used in the WCF requests (taken from the endpoint /help documentation).
Looks like RestRequest.DateFormat is only used when deserializing:
/// <summary>
/// Used by the default deserializers to explicitly set which date format string to use when parsing dates.
/// </summary>
public string DateFormat { get; set; }
For serializing you need to set it explicitly on the serializer:
req.XmlSerializer = new XmlSerializer { DateFormat = DATE_FORMAT };
Note that, for DotNetXmlSerializer, the underlying System.Xml.Serialization.XmlSerializer does not support custom DateTime formats, according to this answer.

Is it possible to return a pre-encoded JSON string in a SignalR hub method?

In an MVC project, I have a method on a hub similar to this:
public string Foo() { return DoCrazyThingThatReturnsJson(); }
Unfortunately SignalR (or something) takes the encoded JSON string and happily encodes it, then returns it, so browser be like LOLWTF. Is there a way to skip this second encoding?
Looking at this here:
We assume that any ArraySegment is already JSON serialized
it seems like something like this may work (note that I haven't tried it, so no promises):
string jsonString = ...; // your serialized data
var jsonBytes = new ArraySegment<byte>(Encoding.UTF8.GetBytes(jsonString));
Clients.Caller.sendJson(jsonBytes);
(The above only works for PersistentConnection because of what constitutes a value - for hubs, the data is wrapped in a container with the RPC info; I'm only leaving this in case using PersistentConnection is an option for you)
Another idea might be creating a container class for a JSON string, then using a custom converter that simply writes the string as-is, then register like this in the Signalr startup code:
var serializer = new JsonSerializer();
serializer.Converters.Add(new PreserializedConverter());
GlobalHost.DependencyResolver.Register(typeof(JsonSerializer), () => serializer);

ServiceStack UK Date Binding on HTTP POST

I am using a mono self hosted servicestack application with the ServiceStack.Razor rendering. In the application the user enters into a form a UK date (dd/mm/yyyy) but this is converted to a US date (mm/dd/yyyy) on a HTTP POST.
In a normal MVC application I would do this using model binding as shown here ASP.NET MVC3: Force controller to use date format dd/mm/yyyy
How do you do this in ServiceStack as I could not find anything about it.
You can use custom serializers/deserializers to globally control the serialization and deserialization of DateTime values:
In your AppHost:
using ServiceStack.Text;
JsConfig<DateTime>.SerializeFn = SerializeAsUKDate;
// Also, if you need to support nullable DateTimes:
JsConfig<DateTime?>.SerializeFn = SerializeAsNullableUKDate;
public static string SerializeAsUKDate(DateTime value)
{
// or whatever you prefer to specify the format/culture
return value.ToString("dd/MM/yyyy");
}
public static string SerializeAsNullableUKDate(DateTime? value)
{
return value.HasValue ? SerializeAsUKDate(value.Value) : null;
}
You may or may not need to specify DeSerializeFn to ensure that dates are parsed correctly. The ServiceStack.Text date deserializer is pretty robust.
JsConfig<DateTime>.DeSerializeFn = DeSerializeAsUKDate;
public static DateTime DeSerializeAsUKDate(string value)
{
// date parsing logic here
// ServiceStack.Text.Common.DateTimeSerializer has some helper methods you may want to leverage
}

Categories

Resources