I've been writing a small library that allows for easy querying of the Stack Exchange websockets.
I'm going to add an enum
to replace the manual SiteId
at every RequestParameter
so you can let that one slide (unless you have something else in mind).
A few thoughts I have:
RequestParameters
defines itsResponseDataType
asJsonConverter
while in reality I would prefer it to beDataConverter<?>
(which Java has). Can I go more specific? (I don't want the caller to have to define it)Even though it's
internal
, I feel like having aDataConverter<T>
field in myRequestParameters
is questionable. I am delegating the way I handle the request to the request itself. Granted, it can't be modified in any way but are there better options?
Program.cs
namespace Console
{
internal class Program
{
private static void Main(string[] args)
{
//ActiveQuestions();
NewestQuestionsByTag();
System.Console.ReadKey();
}
#region ActiveQuestions
private static void ActiveQuestions()
{
var settings = new ActiveQuestionsRequestParameters
{
SiteId = "155"
};
var socket = new StackSocket("wss://qa.sockets.stackexchange.com", settings);
socket.OnSocketReceive += OnActiveQuestionsDataReceived;
socket.Connect();
}
private static void OnActiveQuestionsDataReceived(object sender, SocketEventArgs e)
{
var data = e.Response.Data as ActiveQuestionsData;
if (data == null)
{
return;
}
System.Console.WriteLine("{0} - {1}", "Title", data.TitleEncodedFancy);
System.Console.WriteLine("{0} - {1}", "Tags", string.Join(", ", data.Tags));
System.Console.WriteLine("{0} - {1}", "Last activity", data.LastActivityDate);
System.Console.WriteLine("{0}", data.ApiSiteParameter);
System.Console.WriteLine(data.QuestionUrl);
System.Console.WriteLine();
}
#endregion
#region NewestQuestionsByTag
private static void NewestQuestionsByTag()
{
var settings = new NewestQuestionsByTagRequestParameters
{
SiteId = "1",
Tag = "Java"
};
var socket = new StackSocket("wss://qa.sockets.stackexchange.com", settings);
socket.OnSocketReceive += OnNewestQuestionsByTagDataReceived;
socket.Connect();
}
private static void OnNewestQuestionsByTagDataReceived(object sender, SocketEventArgs e)
{
var data = e.Response.Data as NewestQuestionsByTagData;
if (data == null)
{
return;
}
System.Console.WriteLine("{0} - {1}", "Title", data.Body);
System.Console.WriteLine("{0} - {1}", "Tags", string.Join(", ", data.Tags));
System.Console.WriteLine("{0} - {1}", "ID", data.Id);
System.Console.WriteLine("{0} - {1}", "Site ID", data.SiteId);
System.Console.WriteLine(data.Fetch);
System.Console.WriteLine();
}
#endregion
}
}
RequestParameters.cs
namespace Library.Requests
{
public abstract class RequestParameters
{
internal abstract string GetRequestValue();
internal abstract JsonConverter ResponseDataType { get; }
}
public sealed class ActiveQuestionsRequestParameters : RequestParameters
{
public string SiteId { get; set; }
internal override string GetRequestValue()
{
return SiteId + "-questions-active";
}
internal override JsonConverter ResponseDataType
{
get { return new DataConverter<ActiveQuestionsData>(); }
}
}
public sealed class NewestQuestionsByTagRequestParameters : RequestParameters
{
public string SiteId { get; set; }
public string Tag { get; set; }
internal override string GetRequestValue()
{
return SiteId + "-questions-newest-tag-" + Tag.ToLower();
}
internal override JsonConverter ResponseDataType
{
get { return new DataConverter<NewestQuestionsByTagData>(); }
}
}
}
Response.cs
namespace Library.Responses
{
public sealed class Response
{
[JsonProperty("action")]
public string Action { get; internal set; }
[JsonProperty("data")]
public Data Data { get; internal set; }
}
public abstract class Data
{
}
public sealed class ActiveQuestionsData : Data
{
[JsonProperty("siteBaseHostAddress")]
public string SiteBaseHostAddress { get; internal set; }
[JsonProperty("id")]
public string Id { get; internal set; }
[JsonProperty("titleEncodedFancy")]
public string TitleEncodedFancy { get; internal set; }
[JsonProperty("bodySummary")]
public string BodySummary { get; internal set; }
[JsonProperty("tags")]
public IEnumerable<string> Tags { get; internal set; }
[JsonProperty("lastActivityDate")]
[JsonConverter(typeof (EpochTimeConverter))]
public DateTime LastActivityDate { get; internal set; }
[JsonProperty("url")]
[JsonConverter(typeof (UriConverter))]
public Uri QuestionUrl { get; internal set; }
[JsonProperty("ownerUrl")]
[JsonConverter(typeof (UriConverter))]
public Uri OwnerUrl { get; internal set; }
[JsonProperty("ownerDisplayName")]
public string OwnerDisplayName { get; internal set; }
[JsonProperty("apiSiteParameter")]
public string ApiSiteParameter { get; internal set; }
}
public sealed class NewestQuestionsByTagData : Data
{
[JsonProperty("id")]
public string Id { get; internal set; }
[JsonProperty("body")]
public string Body { get; internal set; }
[JsonProperty("tags")]
public IEnumerable<string> Tags { get; internal set; }
[JsonProperty("siteid")]
public string SiteId { get; internal set; }
[JsonProperty("fetch")]
public bool Fetch { get; internal set; }
}
}
DataConverter.cs
namespace Library.Utilities
{
internal sealed class DataConverter<T> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (Data);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var value = reader.Value as string;
return JsonConvert.DeserializeObject<T>(value);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
EpochTimeConverter.cs
namespace Library.Utilities
{
internal sealed class EpochTimeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (DateTime);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
long timestamp;
long.TryParse(reader.Value.ToString(), out timestamp);
return new DateTime(1970, 1, 1).AddSeconds(timestamp);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
UriConverter
namespace Library.Utilities
{
internal sealed class UriConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof (Uri);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
return new Uri(reader.Value.ToString());
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
StackSocket.cs
namespace Library
{
public sealed class StackSocket : IDisposable
{
private readonly ClientWebSocket _socket = new ClientWebSocket();
private readonly Uri _uri;
private readonly RequestParameters _requestParameters;
private const int BufferSize = 4096;
private const int BufferAmplifier = 20;
public event EventHandler<SocketEventArgs> OnSocketReceive;
public StackSocket(string url, RequestParameters parameters)
{
_uri = new Uri(url);
_requestParameters = parameters;
}
public async void Connect()
{
if (_socket.State != WebSocketState.Open && _socket.State != WebSocketState.Connecting)
{
await _socket.ConnectAsync(_uri, CancellationToken.None);
}
var request = Encoding.UTF8.GetBytes(_requestParameters.GetRequestValue());
await
_socket.SendAsync(new ArraySegment<byte>(request), WebSocketMessageType.Text, true,
CancellationToken.None);
await Receive();
}
private async Task Receive()
{
var temporaryBuffer = new byte[BufferSize];
var buffer = new byte[BufferSize*BufferAmplifier];
var offset = 0;
while (_socket.State == WebSocketState.Open)
{
WebSocketReceiveResult response;
while (true)
{
response =
await _socket.ReceiveAsync(new ArraySegment<byte>(temporaryBuffer), CancellationToken.None);
temporaryBuffer.CopyTo(buffer, offset);
offset += response.Count;
temporaryBuffer = new byte[BufferSize];
if (response.EndOfMessage)
{
break;
}
}
if (response.MessageType == WebSocketMessageType.Close)
{
await
_socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close response received",
CancellationToken.None);
}
else
{
var result = Encoding.UTF8.GetString(buffer);
var responseObject = JsonConvert.DeserializeObject<Response>(result,
_requestParameters.ResponseDataType);
OnSocketReceive.Invoke(this, new SocketEventArgs {Response = responseObject});
buffer = new byte[BufferSize*BufferAmplifier];
offset = 0;
}
}
}
public void Dispose()
{
_socket.Dispose();
}
}
public class SocketEventArgs : EventArgs
{
public Response Response { get; set; }
}
}
using System;
? Why isSystem.Console
fully qualified? \$\endgroup\$Console
. A lack of imagination when it comes to naming my console project. \$\endgroup\$namespace Console
;) \$\endgroup\$