Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebServiceTarget support for Posting JSON, XML + Injecting JSON serializer into NLog #1740

Merged
merged 22 commits into from
Nov 25, 2016

Conversation

tetrodoxin
Copy link
Contributor

@tetrodoxin tetrodoxin commented Nov 2, 2016

Added support for POST of JSON and XML documents and added respective tests. I tried to keep it simple, comprehensible and not to break with existing functionality.

I'd have to change existing tests in WebServiceTargetTests to extend their usability to the new types but I wasn't sure about altering existing tests of other authors, so I added new parts.

Have a look and tell me what you think about the direction it's heading.


This change is Reviewable

fixes #700

…rget

added corresponding tests to WebServiceTargetTests
@304NotModified
Copy link
Member

Cool!

Could you undo the moved methods? It would be easier to review and otherwise we have a hard time merging this also in the .NET Core branch. Thanks!

@304NotModified 304NotModified added this to the 4.4 milestone Nov 2, 2016
@tetrodoxin
Copy link
Contributor Author

Oh I see, that auto sorting and formatting of VS has created a diff-hell.
I think I'll deal with this tomorrow, just adding the new stuff at the bottom and try to prevent VS from exchanging TAB<=>spaces

Good night.

@tetrodoxin
Copy link
Contributor Author

tetrodoxin commented Nov 2, 2016

How do you want JSONSerializer to be 'injected' ?

For XmlSerializer too?

@304NotModified
Copy link
Member

For XmlSerializer too?

Maybe, but that's already in non system.web libraries so less a problem IMO

@tetrodoxin
Copy link
Contributor Author

So JSONSerializer injecting by defining an interface and adding a property to the target?

@304NotModified
Copy link
Member

So JSONSerializer injecting by defining an interface and adding a property to the target?

Yes, and it would be nice if the property is at higher level than this target. I thinkConfigurationItemFactory or a new class.

@304NotModified
Copy link
Member

304NotModified commented Nov 2, 2016

good night!

@tetrodoxin
Copy link
Contributor Author

So, tried a completed reformatting as good as possible.
Is it feasible this way?

I would then go and deal with JsonFormatter injection.

@codecov-io
Copy link

codecov-io commented Nov 3, 2016

Current coverage is 82% (diff: 83%)

Merging #1740 into master will increase coverage by 1%

@@             master      #1740   diff @@
==========================================
  Files           276        274     -2   
  Lines         17711      18224   +513   
  Methods        2768       2833    +65   
  Messages          0          0          
  Branches       2020       2085    +65   
==========================================
+ Hits          14271      14854   +583   
+ Misses         2987       2908    -79   
- Partials        453        462     +9   

Sunburst

Powered by Codecov. Last update 00e58af...f88a1d5

@tetrodoxin
Copy link
Contributor Author

Two possible ways spontaneously cross my mind:

  • Just add an public IJsonSerializer' JsonSerializer {get; set;} to ConfigurationItemFactory which in turn sets an property to every WebServiceTarget instance it creates
  • introduce a IServiceResolver interface and an InjectedAttribute that marks properties of objects created by ConfigurationItemFactory as injectable and so is done, if that resolver 'knows' the type of that property

The first option would be more simple and quickly done, the second option indeed slightly more complex but more extendible and may be used for further scenarios. On the other hand, if no one can think of any additional use cases, that would be a perfect example of overkill.

@tetrodoxin
Copy link
Contributor Author

tetrodoxin commented Nov 3, 2016

Interface so far (inspired by JsonSerializer by NewtonSoft):

public interface IJsonSerializer
{
    void Serialize(TextWriter writer, object value);
    void Serialize(TextWriter writer, object value, Type objectType);
    object Deserialize(TextReader reader, Type objectType);
    T Deserialize<T>(TextReader reader);
}

@304NotModified
Copy link
Member

I prefer the first one. More in line with the current options in NLog. :)

@tetrodoxin
Copy link
Contributor Author

After reflecting about it...
Which 'object' shall this serializer convert to JSON, actually? Because by now WebServiceTarget writes parameters that are named in config, one by one. There is no complex object to serialize.

@304NotModified
Copy link
Member

304NotModified commented Nov 3, 2016

Well then the simple json serializer will work.

Except that is an issue with the current implementation when there is a quote in the value AFAIK.

private static string getJsonValueString(object value)
{
if (value == null) return "null";
else if (value is string) return string.Format("\"{0}\"", value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to escape the quote inside the string?

@tetrodoxin
Copy link
Contributor Author

Yup, now as you say it, forgot to escape strings.

@tetrodoxin
Copy link
Contributor Author

I'm on it by tomorrow.

thus a custom JSON serialize can be set in and injected via ConfigurationItemFactory into WebServiceTarget
@tetrodoxin
Copy link
Contributor Author

So, since the named parameters in config are just objects, I decided to add a slim serilizer interface, namely ICompactJsonSerializer, containing only a single method to serialize a value. In addition there is a default implementation, doing very rudimentary serialization and string escaping.
Tests for serializer are included.
Extended ConfigurationItemFactory so, that it provides a virtual method InterceptCreation, which is invoked right after a new object has been created via the instantiator in CreateInstance.
The base implementation of InterceptCreation realizes the injection of the JSON serializer into WebServiceTarget, if one was set by ConfigurationItemFactory.SetJsonSerializer()

Let me know, what you think... uhm, and I just see, I have to deal with Travis complaints :-/

@tetrodoxin
Copy link
Contributor Author

Don't understand this: AppVeyor is complaining about things, that aren't contained in that files ?!?

@tetrodoxin
Copy link
Contributor Author

So I'm tempted to say, we keep the serializer rudimentary and leave out serialization of collection types, because there are ready, mature serializers out there one can use, which have been the reason for this interface.

/// </summary>
public class DefaultJsonSerializer : IJsonSerializer
{
private static List<Type> NumericTypes = new List<Type>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please create use a hashset (because of the contains futher)

@304NotModified
Copy link
Member

Maybe have a level counter (default starting at 2) of how many levels down the object-graph that it will explore.

The easier and better way is to store the reference the a hashset? (and compare if it's already used)

@304NotModified
Copy link
Member

So I'm tempted to say, we keep the serializer rudimentary and leave out serialization of collection types,

Good point, doubting as I think we are almost there.

@tetrodoxin
Copy link
Contributor Author

So:
switch do hashset for numeric types
and
use a hashset to track visited references to prevent circular paths.

Allright?

@304NotModified
Copy link
Member

Yes that would be nice!

@tetrodoxin
Copy link
Contributor Author

I'm on it

@tetrodoxin
Copy link
Contributor Author

Although the question may be: how to handle reference loops? JSON.NET for example per default breaks with an exception. You can alter this behavior to 2 alternatives.
We could end with an exception or skip serialization of objects, when they're child of themselves. In both cases we track every 'path' of object serialization.

@snakefoot
Copy link
Contributor

Consider extracting the method JsonLayout.IsNumeric() into a helper-class, then you can skip the HashSet-Lookup.

@304NotModified
Copy link
Member

I prefer skip instead of exception

@tetrodoxin
Copy link
Contributor Author

Now it uses Hashset (and Contains() ) for checking current serialization path for loop. In the (unlikely) case of an infinite loop because of special implementations of IEnumerable for example, there is also an maximum recursion depth of 10, just protecting application from a stack overflow or out of memory.

@304NotModified
Copy link
Member

👍
cool. Maybe unlikely, but stackoverflows are one the worst issue you could have in an application. So thanks for fixing it!

string valueString = string.Empty;
if (!string.IsNullOrEmpty(parameterValue))
{
var sb = new StringBuilder(Math.Max(parameterValue.Length + 20, Target.Parameters.Count > 1 ? 256 : 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Target.Parameters.Count-check only makes sense when using the same StringBuilder for streaming all parameters.

Unless you optimize the (re-)usage of the StringBuilder, then it should just be:

var sb = new StringBuilder(parameterValue.Length + 20);

UrlHelper.EscapeDataEncode(parameterValue, sb, encodingFlags);
valueString = sb.ToString();
}

return string.Format("{0}={1}",
Copy link
Contributor

@snakefoot snakefoot Nov 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could it not be changed it into:

if (string.IsNullOrEmpty(parameterValue))
{
return string.Concat(parameter.Name, "=");
}
else
{
var sb = new StringBuilder(parameter.Name.Length + parameterValue.Length + 20);
sb.Append(parameter.Name);
sb.Append("=");
UrlHelper.EscapeDataEncode(parameterValue, sb, encodingFlags);
return sb.ToString();
}

(Could probably be optimized even more if re-using the StringBuilder)


protected override string GetFormattedContent(string parametersContent)
{
return string.Format("{{{0}}}", parametersContent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider changing into this:

return string.Concat("{{", parametersContent, "}}");

}
else
{
soapAction = Target.Namespace + "/" + Target.MethodName;
Copy link
Contributor

@snakefoot snakefoot Nov 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider changing into this:

soapAction = string.Concat(Target.Namespace, "/", Target.MethodName);

(Maybe also use string.Concat for the one without slash for consistency)

@304NotModified
Copy link
Member

This is ready to merge right?

}
else
{
var sb = new StringBuilder(parameterValue.Length + 20);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider changing it into:

var sb = new StringBuilder(parameter.Name.Length + parameterValue.Length + 20);

@304NotModified
Copy link
Member

Reviewed 1 of 17 files at r10, 1 of 4 files at r11, 21 of 22 files at r13, 1 of 1 files at r14.
Review status: all files reviewed at latest revision, 5 unresolved discussions.


Comments from Reviewable

@304NotModified
Copy link
Member

304NotModified commented Nov 24, 2016

@tetrodoxin I have one final comment: please don't create variables with just 1 char! l, c, d etc. Please do use meaningful names. Only in math single chars are allowed ;)

When those names are fixed, I will merge it.

@snakefoot it's OK for me to merge it and tweaks some last things with a new PR.

@304NotModified 304NotModified merged commit ecc64a4 into NLog:master Nov 25, 2016
@304NotModified 304NotModified changed the title WebServiceTarget support for JSON & Injecting JSON serializer into NLog WebServiceTarget support for JSON, XML + Injecting JSON serializer into NLog Dec 9, 2016
@304NotModified 304NotModified changed the title WebServiceTarget support for JSON, XML + Injecting JSON serializer into NLog WebServiceTarget support for Posting JSON, XML + Injecting JSON serializer into NLog Dec 9, 2016
@304NotModified 304NotModified added the documentation done all docs done (wiki, api docs, lists on nlog-project.org, xmldocs) label Jan 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation done all docs done (wiki, api docs, lists on nlog-project.org, xmldocs) feature needs documentation on wiki webservice-target
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants