Custom Responses in Wolfringo
WOLF server always sends a response to each sent message. Much like with messages, Wolfringo works with strongly typed responses - you can see all that are built-in here. They're serialized by the client using serializers.
The list should be moderately up-to-date, but if you want to add a new message supported by WOLF without waiting for Wolfringo update, or want to customize the built-in messages behave, you have an option to do so!
Tip
Wolfringo has base WolfResponse response class - if it offers all you need, you don't need to create a custom response type at all!
New Response Type
Adding a completely new response type is really easy in Wolfringo - all you need to do is create a new class that implements IWolfResponse (or inherit from WolfResponse default class) and add any other properties it requires. You can use Newtonsoft.JSON to modify serialization at will.
For example, here's implementation of ChatResponse:
public class ChatResponse : WolfResponse, IWolfResponse
{
[JsonProperty("uuid")]
public Guid ID { get; private set; }
[JsonProperty("timestamp")]
public WolfTimestamp Timestamp { get; private set; }
[JsonProperty("isSpam")]
public bool SpamFiltered { get; private set; }
[JsonConstructor]
protected ChatResponse() : base() { }
}
You don't need to "nest" body
, headers
, body.extended
, body.base
, metadata
and body.metadata
properties - by default, Wolfringo's built-in serializers will automatically handle that for you. If however you need to "nest" any different properties, you'll need to either "nest" them in your class, or register a custom serializer (see "Custom ResponseSerializer" below).
Mapping Message to Response Type
Custom Message Types
Mapping response type of a custom message type (one that you can edit code of) is really easy - simply use [ResponseType] attribute on the class.
[ResponseType(typeof(MyCustomResponse))]
public class MyCustomMessage : IWolfMessage
{
// ... other code here
}
Built-in Message Types
Wolfringo allows you to overwrite the response type of built-in messages if you wish to do so. To do that, you'll need to create a new IResponseTypeResolver.
The default ResponseTypeResolver in Wolfringo checks [ResponseType] attribute on the message class, and if there's none, it'll use fallback - which is whatever is provided to WolfClient.SendAsync<TResponse> method. It is recommended that you still respect [ResponseType] attribute after handling the "overwrite" for your custom one, to prevent breaking. You can see implementation of ResponseTypeResolver on GitHub for reference and guide.
Once you have programmed your custom IResponseTypeResolver implementation, you need to register it as a service with WolfClient - see Introduction to see how to do that.
Registering ResponseSerializer
WolfClient needs to have means to translate Response type to a concrete serializer. If you try sending the message without mapping a response serializer, WolfClient will log a warning, and attempt to use a fallback one. But if the message is received from the server, or you simply want to suppress the warning, you'll need to register it. You can use DefaultResponseSerializer, unless you need custom deserialization logic (which might be required in some cases due to the design of WOLF protocol) - in which case, you can register your custom one.
Registration of the IResponseSerializer is done by adding it to ISerializerProvider<Type, IResponseSerializer>'s options. This can be done easily by using WolfClientBuilder.
- Manually create an instance of ResponseSerializerProviderOptions.
- Add your serializer to Serializers dictionary.
- Use
WithDefaultResponseSerializers
method of WolfClientBuilder and pass your options as argument.
ResponseSerializerProviderOptions options = new ResponseSerializerProviderOptions();
options.Serializers[typeof(MyCustomResponse)] = new DefaultResponseSerializer();
_client = new WolfClientBuilder()
.WithDefaultResponseSerializers(options)
.Build();
Tip
If default ResponseSerializerProvider doesn't suffice your needs, you can create a custom one (see "Custom ResponseSerializer" below) and provide it to WolfClientBuilder as explained in Introduction. See ResponseSerializerProvider.cs and ResponseSerializerProviderOptions on GitHub for reference.
Custom ResponseSerializer
If your message needs more complex serialization than DefaultResponseSerializer provides, you can create a custom one by implementing IResponseSerializer (or even inheriting from DefaultResponseSerializer).
An example implementation of built-in TipAddMessageSerializer:
public class TipDetailsResponseSerializer : DefaultResponseSerializer, IResponseSerializer
{
private static readonly Type _tipDetailsResponseType = typeof(TipDetailsResponse);
public override IWolfResponse Deserialize(Type responseType, SerializedMessageData responseData)
{
JToken payload = GetResponseJson(responseData.Payload).DeepClone();
JArray charmList = payload.SelectToken("body.list") as JArray;
foreach (JObject charmDetails in charmList.Children().Cast<JObject>())
{
JToken value = charmDetails["charmId"];
if (value != null)
{
charmDetails.Remove("charmId");
charmDetails.Add("id", value);
}
}
return (TipDetailsResponse)base.Deserialize(responseType, new SerializedMessageData(payload, responseData.BinaryMessages));
}
protected override void ThrowIfInvalidType(Type responseType)
{
base.ThrowIfInvalidType(responseType);
if (!_tipDetailsResponseType.IsAssignableFrom(responseType))
throw new ArgumentException($"{this.GetType().Name} only works with responses of type {_tipDetailsResponseType.FullName}", nameof(responseType));
}
}
Once class is created, you need to register your serializer - follow the guide above, but instead adding DefaultResponseSerializer
, add instance of your custom class.
Tip
SerializationHelper in TehGM.Wolfringo.Messages.Serialization.Internal namespace provides a few useful methods, such as FlattenCommonProperties
(for handling "body" etc) or MovePropertyIfExists
.