Initial commit
This commit is contained in:
Коммит
79fb704bfd
|
@ -0,0 +1,288 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace StreamJsonRpc {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StreamJsonRpc.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Both readable and writable are null..
|
||||
/// </summary>
|
||||
internal static string BothReadableWritableAreNull {
|
||||
get {
|
||||
return ResourceManager.GetString("BothReadableWritableAreNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Got a request to execute '{0}' but have no callback object. Dropping the request..
|
||||
/// </summary>
|
||||
internal static string DroppingRequestDueToNoTargetObject {
|
||||
get {
|
||||
return ResourceManager.GetString("DroppingRequestDueToNoTargetObject", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delimiter is empty string..
|
||||
/// </summary>
|
||||
internal static string EmptyDelimiter {
|
||||
get {
|
||||
return ResourceManager.GetString("EmptyDelimiter", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error writing JSON-RPC Result: {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string ErrorWritingJsonRpcResult {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorWritingJsonRpcResult", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failure deserializing incoming JSON-RPC '{0}': {1}.
|
||||
/// </summary>
|
||||
internal static string FailureDeserializingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("FailureDeserializingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JSON-RPC must not be null..
|
||||
/// </summary>
|
||||
internal static string JsonRpcCannotBeNull {
|
||||
get {
|
||||
return ResourceManager.GetString("JsonRpcCannotBeNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} has ref or out parameter(s), which is not supported.
|
||||
/// </summary>
|
||||
internal static string MethodHasRefOrOutParameters {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodHasRefOrOutParameters", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} is not public.
|
||||
/// </summary>
|
||||
internal static string MethodIsNotPublic {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodIsNotPublic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to '{0}' method name has different case from requested '{1}'.
|
||||
/// </summary>
|
||||
internal static string MethodNameCaseIsDifferent {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodNameCaseIsDifferent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} parameter(s): {1}, but the request supplies {2}.
|
||||
/// </summary>
|
||||
internal static string MethodParameterCountDoesNotMatch {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodParameterCountDoesNotMatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} parameters are not compatible with the request: {1}.
|
||||
/// </summary>
|
||||
internal static string MethodParametersNotCompatible {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodParametersNotCompatible", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to More than one target method found: {0}..
|
||||
/// </summary>
|
||||
internal static string MoreThanOneMethodFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MoreThanOneMethodFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reached end of stream..
|
||||
/// </summary>
|
||||
internal static string ReachedEndOfStream {
|
||||
get {
|
||||
return ResourceManager.GetString("ReachedEndOfStream", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Readable cannot read..
|
||||
/// </summary>
|
||||
internal static string ReadableCannotRead {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadableCannotRead", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Readable is not set..
|
||||
/// </summary>
|
||||
internal static string ReadableNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadableNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reading JSON-RPC from the stream failed with {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string ReadingJsonRpcStreamFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadingJsonRpcStreamFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Response is not error..
|
||||
/// </summary>
|
||||
internal static string ResponseIsNotError {
|
||||
get {
|
||||
return ResourceManager.GetString("ResponseIsNotError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stream has been disposed.
|
||||
/// </summary>
|
||||
internal static string StreamDisposed {
|
||||
get {
|
||||
return ResourceManager.GetString("StreamDisposed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The task is not completed..
|
||||
/// </summary>
|
||||
internal static string TaskNotCompleted {
|
||||
get {
|
||||
return ResourceManager.GetString("TaskNotCompleted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The task was cancelled..
|
||||
/// </summary>
|
||||
internal static string TaskWasCancelled {
|
||||
get {
|
||||
return ResourceManager.GetString("TaskWasCancelled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to find method '{0}/{1}' on {2} for the following reason: {3}.
|
||||
/// </summary>
|
||||
internal static string UnableToFindMethod {
|
||||
get {
|
||||
return ResourceManager.GetString("UnableToFindMethod", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unexpected error processing JSON-RPC '{0}': {1}.
|
||||
/// </summary>
|
||||
internal static string UnexpectedErrorProcessingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("UnexpectedErrorProcessingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unrecognized incoming JSON-RPC '{0}'.
|
||||
/// </summary>
|
||||
internal static string UnrecognizedIncomingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("UnrecognizedIncomingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Writable cannot write..
|
||||
/// </summary>
|
||||
internal static string WritableCannotWrite {
|
||||
get {
|
||||
return ResourceManager.GetString("WritableCannotWrite", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Writable is not set..
|
||||
/// </summary>
|
||||
internal static string WritableNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("WritableNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="BothReadableWritableAreNull" xml:space="preserve">
|
||||
<value>Both readable and writable are null.</value>
|
||||
<comment>'readable' and 'writable' are parameter names and should not be localized.</comment>
|
||||
</data>
|
||||
<data name="DroppingRequestDueToNoTargetObject" xml:space="preserve">
|
||||
<value>Got a request to execute '{0}' but have no callback object. Dropping the request.</value>
|
||||
<comment>{0} is the method name to execute.</comment>
|
||||
</data>
|
||||
<data name="EmptyDelimiter" xml:space="preserve">
|
||||
<value>Delimiter is empty string.</value>
|
||||
</data>
|
||||
<data name="ErrorWritingJsonRpcResult" xml:space="preserve">
|
||||
<value>Error writing JSON-RPC Result: {0}: {1}</value>
|
||||
<comment>{0} is the exception type, {1} is the exception message. "JSON-RPC" is a protocol</comment>
|
||||
</data>
|
||||
<data name="FailureDeserializingJsonRpc" xml:space="preserve">
|
||||
<value>Failure deserializing incoming JSON-RPC '{0}': {1}</value>
|
||||
<comment>{0} is the JSON that could not be deserialized, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="JsonRpcCannotBeNull" xml:space="preserve">
|
||||
<value>JSON-RPC must not be null.</value>
|
||||
</data>
|
||||
<data name="MethodHasRefOrOutParameters" xml:space="preserve">
|
||||
<value>{0} has ref or out parameter(s), which is not supported</value>
|
||||
<comment>{0} is the method signature.</comment>
|
||||
</data>
|
||||
<data name="MethodIsNotPublic" xml:space="preserve">
|
||||
<value>{0} is not public</value>
|
||||
<comment>{0} is the method signature.</comment>
|
||||
</data>
|
||||
<data name="MethodNameCaseIsDifferent" xml:space="preserve">
|
||||
<value>'{0}' method name has different case from requested '{1}'</value>
|
||||
<comment>{0} is the method signature, {1} is the requested method name.</comment>
|
||||
</data>
|
||||
<data name="MethodParameterCountDoesNotMatch" xml:space="preserve">
|
||||
<value>{0} parameter(s): {1}, but the request supplies {2}</value>
|
||||
<comment>{0} is the method signature, {1} is the method parameter count, {2} is the request parameter count.</comment>
|
||||
</data>
|
||||
<data name="MethodParametersNotCompatible" xml:space="preserve">
|
||||
<value>{0} parameters are not compatible with the request: {1}</value>
|
||||
<comment>{0} is the method signature, {1} is the error message.</comment>
|
||||
</data>
|
||||
<data name="MoreThanOneMethodFound" xml:space="preserve">
|
||||
<value>More than one target method found: {0}.</value>
|
||||
<comment>{0} is the list of method signatures.</comment>
|
||||
</data>
|
||||
<data name="ReachedEndOfStream" xml:space="preserve">
|
||||
<value>Reached end of stream.</value>
|
||||
</data>
|
||||
<data name="ReadableCannotRead" xml:space="preserve">
|
||||
<value>Readable cannot read.</value>
|
||||
</data>
|
||||
<data name="ReadableNotSet" xml:space="preserve">
|
||||
<value>Readable is not set.</value>
|
||||
</data>
|
||||
<data name="ReadingJsonRpcStreamFailed" xml:space="preserve">
|
||||
<value>Reading JSON-RPC from the stream failed with {0}: {1}</value>
|
||||
<comment>{0} is the exception type, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="ResponseIsNotError" xml:space="preserve">
|
||||
<value>Response is not error.</value>
|
||||
</data>
|
||||
<data name="StreamDisposed" xml:space="preserve">
|
||||
<value>Stream has been disposed</value>
|
||||
</data>
|
||||
<data name="TaskNotCompleted" xml:space="preserve">
|
||||
<value>The task is not completed.</value>
|
||||
</data>
|
||||
<data name="TaskWasCancelled" xml:space="preserve">
|
||||
<value>The task was cancelled.</value>
|
||||
</data>
|
||||
<data name="UnableToFindMethod" xml:space="preserve">
|
||||
<value>Unable to find method '{0}/{1}' on {2} for the following reason: {3}</value>
|
||||
<comment>{0} is the method name, {1} is arity, {2} is target class full name, {3} is the error list.</comment>
|
||||
</data>
|
||||
<data name="UnexpectedErrorProcessingJsonRpc" xml:space="preserve">
|
||||
<value>Unexpected error processing JSON-RPC '{0}': {1}</value>
|
||||
<comment>{0} is the json, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="UnrecognizedIncomingJsonRpc" xml:space="preserve">
|
||||
<value>Unrecognized incoming JSON-RPC '{0}'</value>
|
||||
<comment>{0} is the json.</comment>
|
||||
</data>
|
||||
<data name="WritableCannotWrite" xml:space="preserve">
|
||||
<value>Writable cannot write.</value>
|
||||
</data>
|
||||
<data name="WritableNotSet" xml:space="preserve">
|
||||
<value>Writable is not set.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,72 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{C8FE34FA-B409-4A57-A16F-4407AF728C65}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>StreamJsonRpc</RootNamespace>
|
||||
<AssemblyName>StreamJsonRpc</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>TRACE;DEBUG;DESKTOP;NET45</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE;DESKTOP;NET45</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\StreamJsonRpc.Shared\StreamJsonRpc.Shared.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"MicroBuild.VisualStudio": {
|
||||
"version": "1.0.117-rc",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"MicroBuild.Core": {
|
||||
"version": "0.2.0",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"Nerdbank.GitVersioning": {
|
||||
"version": "1.4.19",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"ReadOnlySourceTree": {
|
||||
"version": "0.1.36-beta",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"Newtonsoft.Json": "6.0.6",
|
||||
"Microsoft.VisualStudio.Threading": "14.1.114"
|
||||
},
|
||||
"frameworks": {
|
||||
"net45": { }
|
||||
},
|
||||
"runtimes": {
|
||||
"win": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|AnyCPU">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|AnyCPU">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>AnyCPU</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>927450a5-18bf-4378-8421-7a7c6864b6ea</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<NuSpecTemplate>StreamJsonRpc.nuspec</NuSpecTemplate>
|
||||
<NuProjPath>$(UserProfile)\.nuget\packages\NuProj\0.10.48-beta-gea4a31bbc5\tools\</NuProjPath>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(NuProjPath)\NuProj.props" Condition="Exists('$(NuProjPath)\NuProj.props')" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Id>StreamJsonRpc</Id>
|
||||
<Title>StreamJsonRpc</Title>
|
||||
<Authors>Microsoft</Authors>
|
||||
<Owners>Microsoft</Owners>
|
||||
<Summary>StreamJsonRpc.NuGet</Summary>
|
||||
<Description>StreamJsonRpc.NuGet</Description>
|
||||
<ReleaseNotes>
|
||||
</ReleaseNotes>
|
||||
<ProjectUrl>
|
||||
</ProjectUrl>
|
||||
<LicenseUrl>https://go.microsoft.com/fwlink/?LinkID=746386</LicenseUrl>
|
||||
<Copyright>Copyright © Microsoft</Copyright>
|
||||
<Tags>ServiceHub</Tags>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="StreamJsonRpc.nuspec" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StreamJsonRpc\StreamJsonRpc.csproj" />
|
||||
<ProjectReference Include="..\StreamJsonRpc.Desktop\StreamJsonRpc.Desktop.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(NuProjPath)\NuProj.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>placeholder</id>
|
||||
<version>$version$</version>
|
||||
<authors>aarnott</authors>
|
||||
<owners></owners>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<description>to be overwritten by project file</description>
|
||||
<releaseNotes></releaseNotes>
|
||||
<copyright></copyright>
|
||||
<tags></tags>
|
||||
<dependencies>
|
||||
<group targetFramework="dotnet">
|
||||
<dependency id="System.Reflection.TypeExtensions" version="4.0.0" />
|
||||
<dependency id="Microsoft.CSharp" version="4.0.0" />
|
||||
<dependency id="Microsoft.VisualStudio.Threading" version="14.1.114" />
|
||||
<dependency id="Microsoft.VisualStudio.Validation" version="14.1.111" />
|
||||
<dependency id="Newtonsoft.Json" version="6.0.6" />
|
||||
<dependency id="System.Collections" version="4.0.0" />
|
||||
<dependency id="System.Diagnostics.Debug" version="4.0.0" />
|
||||
<dependency id="System.Diagnostics.Tools" version="4.0.0" />
|
||||
<dependency id="System.Dynamic.Runtime" version="4.0.0" />
|
||||
<dependency id="System.Globalization" version="4.0.0" />
|
||||
<dependency id="System.IO" version="4.0.0" />
|
||||
<dependency id="System.Linq" version="4.0.0" />
|
||||
<dependency id="System.Reflection" version="4.0.0" />
|
||||
<dependency id="System.Resources.ResourceManager" version="4.0.0" />
|
||||
<dependency id="System.Runtime" version="4.0.0" />
|
||||
<dependency id="System.Runtime.Extensions" version="4.0.0" />
|
||||
<dependency id="System.Text.Encoding" version="4.0.0" />
|
||||
<dependency id="System.Threading" version="4.0.0" />
|
||||
<dependency id="System.Threading.Tasks" version="4.0.0" />
|
||||
</group>
|
||||
<group targetFramework="net45">
|
||||
<dependency id="Microsoft.VisualStudio.Threading" version="14.1.114" />
|
||||
<dependency id="Newtonsoft.Json" version="6.0.6" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
</package>
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"frameworks": {
|
||||
"net451": { }
|
||||
},
|
||||
"dependencies": {
|
||||
"MicroBuild.VisualStudio": {
|
||||
"version": "1.0.117-rc",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"MicroBuild.Core": {
|
||||
"version": "0.2.0",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"Nerdbank.GitVersioning": {
|
||||
"version": "1.4.19",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"NuProj": "0.10.48-beta-gea4a31bbc5",
|
||||
"ReadOnlySourceTree": {
|
||||
"version": "0.1.36-beta",
|
||||
"suppressParent": "none"
|
||||
}
|
||||
},
|
||||
"runtimes": {
|
||||
"win": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("StreamJsonRpc.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]
|
|
@ -0,0 +1,33 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
internal sealed class JsonRpcError
|
||||
{
|
||||
internal JsonRpcError(int code, string message) : this(code, message, data: null)
|
||||
{
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
internal JsonRpcError(int code, string message, dynamic data)
|
||||
{
|
||||
this.Code = code;
|
||||
this.Message = message;
|
||||
this.Data = data;
|
||||
}
|
||||
|
||||
[JsonProperty("code", Required = Required.Always)]
|
||||
internal int Code { get; private set; }
|
||||
|
||||
[JsonProperty("message", Required = Required.Always)]
|
||||
internal string Message { get; private set; }
|
||||
|
||||
[JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
|
||||
internal dynamic Data { get; private set; }
|
||||
|
||||
public string ErrorStack => this.Data?.stack;
|
||||
|
||||
public string ErrorCode =>this.Data?.code;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
namespace StreamJsonRpc
|
||||
{
|
||||
internal enum JsonRpcErrorCode : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the RPC call was made but the target threw an exception.
|
||||
/// </summary>
|
||||
InvocationError = -32000,
|
||||
|
||||
/// <summary>
|
||||
/// No callback object was given to the client but an RPC call was attempted.
|
||||
/// </summary>
|
||||
NoCallbackObject = -32001,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
|
||||
/// </summary>
|
||||
ParseError = -32700,
|
||||
|
||||
/// <summary>
|
||||
/// The JSON sent is not a valid Request object.
|
||||
/// </summary>
|
||||
InvalidRequest = -32600,
|
||||
|
||||
/// <summary>
|
||||
/// The method does not exist / is not available.
|
||||
/// </summary>
|
||||
MethodNotFound = -32601,
|
||||
|
||||
/// <summary>
|
||||
/// Invalid method parameter(s).
|
||||
/// </summary>
|
||||
InvalidParams = -32602,
|
||||
|
||||
/// <summary>
|
||||
/// Internal JSON-RPC error.
|
||||
/// </summary>
|
||||
InternalError = -32603
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
internal sealed class JsonRpcMessage
|
||||
{
|
||||
private JsonRpcMessage()
|
||||
{
|
||||
this.JsonRpcVersion = "2.0";
|
||||
}
|
||||
|
||||
private JsonRpcMessage(string method, object[] parameters, string id = null, string jsonrpc = "2.0")
|
||||
{
|
||||
this.Parameters = parameters?.Length == 0 ? null : JToken.FromObject(parameters);
|
||||
this.Method = method;
|
||||
this.Id = id;
|
||||
this.JsonRpcVersion = jsonrpc;
|
||||
}
|
||||
|
||||
[JsonProperty("jsonrpc")]
|
||||
public string JsonRpcVersion { get; private set; }
|
||||
|
||||
[JsonProperty("method")]
|
||||
public string Method { get; private set; }
|
||||
|
||||
[JsonProperty("id")]
|
||||
public string Id { get; private set; }
|
||||
|
||||
[JsonProperty("error")]
|
||||
public JsonRpcError Error { get; private set; }
|
||||
|
||||
[JsonProperty("params")]
|
||||
private JToken Parameters
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[JsonProperty("result")]
|
||||
private JToken Result
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool IsRequest => !string.IsNullOrEmpty(this.Method);
|
||||
|
||||
public bool IsResponse => this.IsError || this.Result != null;
|
||||
|
||||
public bool IsError => this.Error != null;
|
||||
|
||||
public bool IsNotification => this.Id == null;
|
||||
|
||||
public int ParameterCount => this.Parameters != null ? this.Parameters.Children().Count() : 0;
|
||||
|
||||
public static JsonRpcMessage CreateRequest(string id, string @method, object[] parameters)
|
||||
{
|
||||
return new JsonRpcMessage(method, parameters, id);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage CreateResult(string id, object result)
|
||||
{
|
||||
return new JsonRpcMessage()
|
||||
{
|
||||
Id = id,
|
||||
Result = result != null ? JToken.FromObject(result) : JValue.CreateNull(),
|
||||
};
|
||||
}
|
||||
|
||||
public static JsonRpcMessage CreateError(string id, JsonRpcErrorCode error, string message)
|
||||
{
|
||||
return CreateError(id, (int)error, message);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage CreateError(string id, JsonRpcErrorCode error, string message, object data)
|
||||
{
|
||||
return CreateError(id, (int)error, message, data);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage CreateError(string id, int error, string message)
|
||||
{
|
||||
return CreateError(id, error, message, data: null);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage CreateError(string id, int error, string message, object data)
|
||||
{
|
||||
return new JsonRpcMessage()
|
||||
{
|
||||
Id = id,
|
||||
Error = new JsonRpcError(error, message, data),
|
||||
};
|
||||
}
|
||||
|
||||
public T GetResult<T>()
|
||||
{
|
||||
return this.Result == null ? default(T) : this.Result.ToObject<T>();
|
||||
}
|
||||
|
||||
public object[] GetParameters(ParameterInfo[] parameterInfos)
|
||||
{
|
||||
if (this.Parameters == null || !this.Parameters.Children().Any())
|
||||
{
|
||||
return new object[0];
|
||||
}
|
||||
|
||||
if (parameterInfos == null || parameterInfos.Length == 0)
|
||||
{
|
||||
return this.Parameters.ToObject<object[]>();
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
var result = new List<object>(parameterInfos.Length);
|
||||
foreach (var parameter in this.Parameters.Children())
|
||||
{
|
||||
Type type = typeof(object);
|
||||
if (index < parameterInfos.Length)
|
||||
{
|
||||
type = parameterInfos[index].ParameterType;
|
||||
index++;
|
||||
}
|
||||
|
||||
object value = parameter.ToObject(type);
|
||||
result.Add(value);
|
||||
}
|
||||
|
||||
for (;index < parameterInfos.Length; index++)
|
||||
{
|
||||
result.Add(parameterInfos[index].HasDefaultValue ? parameterInfos[index].DefaultValue : null);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore,
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(this, settings);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage FromJson(string json)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
|
||||
};
|
||||
|
||||
return JsonConvert.DeserializeObject<JsonRpcMessage>(json, settings);
|
||||
}
|
||||
|
||||
public static JsonRpcMessage FromJson(JsonReader reader)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
|
||||
};
|
||||
|
||||
JsonSerializer serializer = JsonSerializer.Create(settings);
|
||||
|
||||
JsonRpcMessage result = serializer.Deserialize<JsonRpcMessage>(reader);
|
||||
if (result == null)
|
||||
{
|
||||
throw new JsonException(Resources.JsonRpcCannotBeNull);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace StreamJsonRpc
|
||||
{
|
||||
public enum DisconnectedReason
|
||||
{
|
||||
Unknown,
|
||||
StreamError,
|
||||
ParseError,
|
||||
Disposed
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
public class JsonRpcDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
public JsonRpcDisconnectedEventArgs(string description, DisconnectedReason reason)
|
||||
: this(description, reason, lastMessage: null, exception: null)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonRpcDisconnectedEventArgs(string description, DisconnectedReason reason, Exception exception)
|
||||
: this(description, reason, lastMessage: null, exception: exception)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonRpcDisconnectedEventArgs(string description, DisconnectedReason reason, string lastMessage)
|
||||
: this(description, reason, lastMessage: lastMessage, exception: null)
|
||||
{
|
||||
}
|
||||
|
||||
public JsonRpcDisconnectedEventArgs(string description, DisconnectedReason reason, string lastMessage, Exception exception)
|
||||
{
|
||||
Requires.NotNullOrWhiteSpace(description, nameof(description));
|
||||
|
||||
this.Description = description;
|
||||
this.Reason = reason;
|
||||
this.LastMessage = lastMessage;
|
||||
this.Exception = exception;
|
||||
}
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public DisconnectedReason Reason { get; }
|
||||
|
||||
public string LastMessage { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Remote RPC exception that indicates that the server target method threw an exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The details of the target method exception can be found on <see cref="RemoteStackTrace"/> and <see cref="RemoteErrorCode"/>.
|
||||
/// </remarks>
|
||||
#if DESKTOP
|
||||
[System.Serializable]
|
||||
#endif
|
||||
public class RemoteInvocationException : RemoteRpcException
|
||||
{
|
||||
internal RemoteInvocationException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public RemoteInvocationException(string message, string remoteStack, string remoteCode) : this(message)
|
||||
{
|
||||
this.RemoteStackTrace = remoteStack;
|
||||
this.RemoteErrorCode = remoteCode;
|
||||
}
|
||||
|
||||
#if DESKTOP
|
||||
protected RemoteInvocationException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
public string RemoteStackTrace { get; }
|
||||
|
||||
public string RemoteErrorCode { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Remote RPC exception that indicates that the requested target method was not found on the server.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Check the exception message for the reasons why the method was not found. It's possible that
|
||||
/// there was a method with the matching name, but it was not public, had ref or out params, or
|
||||
/// its arguments were incompatible with the arguments supplied by the client.
|
||||
/// </remarks>
|
||||
#if DESKTOP
|
||||
[System.Serializable]
|
||||
#endif
|
||||
public class RemoteMethodNotFoundException : RemoteRpcException
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="RemoteMethodNotFoundException"/> with supplied message and target method.
|
||||
/// </summary>
|
||||
/// <param name="message">Exception message describing why the method was not found.</param>
|
||||
/// <param name="targetMethod">Target method that was not found.</param>
|
||||
internal RemoteMethodNotFoundException(string message, string targetMethod) : base(message)
|
||||
{
|
||||
Requires.NotNullOrEmpty(targetMethod, nameof(targetMethod));
|
||||
this.TargetMethod = targetMethod;
|
||||
}
|
||||
|
||||
#if DESKTOP
|
||||
protected RemoteMethodNotFoundException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the target method that was not found.
|
||||
/// </summary>
|
||||
public string TargetMethod { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Base exception class for any exception that happen on the server side of JSON RPC communication.
|
||||
/// Descendants of this exception may be thrown by JSON RPC client in response to calling a server method.
|
||||
/// </summary>
|
||||
#if DESKTOP
|
||||
[System.Serializable]
|
||||
#endif
|
||||
public abstract class RemoteRpcException : Exception
|
||||
{
|
||||
protected RemoteRpcException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
protected RemoteRpcException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
#if DESKTOP
|
||||
protected RemoteRpcException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{ }
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Remote RPC exception that indicates that the server has no target object.
|
||||
/// </summary>
|
||||
#if DESKTOP
|
||||
[System.Serializable]
|
||||
#endif
|
||||
public class RemoteTargetNotSetException : RemoteRpcException
|
||||
{
|
||||
internal RemoteTargetNotSetException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
#if DESKTOP
|
||||
protected RemoteTargetNotSetException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context)
|
||||
{ }
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,584 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
public class JsonRpc : IDisposable
|
||||
{
|
||||
private class OutstandingCallData
|
||||
{
|
||||
internal object TaskCompletionSource { get; }
|
||||
|
||||
internal Action<JsonRpcMessage> CompletionHandler { get; }
|
||||
|
||||
internal OutstandingCallData(object taskCompletionSource, Action<JsonRpcMessage> completionHandler)
|
||||
{
|
||||
this.TaskCompletionSource = taskCompletionSource;
|
||||
this.CompletionHandler = completionHandler;
|
||||
}
|
||||
}
|
||||
|
||||
private const int BufferSize = 1024;
|
||||
private readonly object callbackTarget;
|
||||
private readonly Encoding encoding;
|
||||
private readonly Stream stream;
|
||||
private readonly object dispatcherMapLock = new object();
|
||||
private readonly object disconnectedEventLock = new object();
|
||||
private readonly Dictionary<string, OutstandingCallData> resultDispatcherMap = new Dictionary<string, OutstandingCallData>(StringComparer.Ordinal);
|
||||
|
||||
private readonly SplitJoinStream splitJoinStream;
|
||||
private readonly Task readLinesTask;
|
||||
private readonly CancellationTokenSource disposeCts = new CancellationTokenSource();
|
||||
|
||||
private int nextId = 1;
|
||||
private bool disposed;
|
||||
private bool hasDisconnectedEventBeenRaised;
|
||||
|
||||
public static JsonRpc Attach(Stream stream, object target = null)
|
||||
{
|
||||
return new JsonRpc(stream, target);
|
||||
}
|
||||
|
||||
protected JsonRpc(Stream stream, object target = null)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(stream));
|
||||
}
|
||||
|
||||
this.stream = stream;
|
||||
this.callbackTarget = target;
|
||||
this.encoding = Encoding.UTF8;
|
||||
|
||||
var options = new SplitJoinStreamOptions
|
||||
{
|
||||
Encoding = this.encoding,
|
||||
LeaveOpen = false,
|
||||
Readable = this.stream,
|
||||
Writable = this.stream,
|
||||
ReadTrailing = true
|
||||
};
|
||||
|
||||
this.splitJoinStream = new SplitJoinStream(options);
|
||||
this.readLinesTask = Task.Run(this.ReadAndHandleRequests, this.disposeCts.Token);
|
||||
}
|
||||
|
||||
private event EventHandler<JsonRpcDisconnectedEventArgs> onDisconnected;
|
||||
|
||||
public event EventHandler<JsonRpcDisconnectedEventArgs> Disconnected
|
||||
{
|
||||
add
|
||||
{
|
||||
Requires.NotNull(value, nameof(value));
|
||||
bool handlerAdded = false;
|
||||
lock (this.disconnectedEventLock)
|
||||
{
|
||||
if (!this.hasDisconnectedEventBeenRaised)
|
||||
{
|
||||
this.onDisconnected += value;
|
||||
handlerAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!handlerAdded)
|
||||
{
|
||||
value(this, new JsonRpcDisconnectedEventArgs(Resources.StreamDisposed, DisconnectedReason.Disposed));
|
||||
}
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
Requires.NotNull(value, nameof(value));
|
||||
this.onDisconnected -= value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a method on the server.
|
||||
/// </summary>
|
||||
/// <param name="targetName">The name of the method to invoke on the server. Must not be null or empty string.</param>
|
||||
/// <param name="arguments">Method arguments, must be serializable to JSON.</param>
|
||||
/// <returns>A task that completes when the server method executes.</returns>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Result task fails with this exception if the communication channel ends before the server indicates completion of the method.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteInvocationException">
|
||||
/// Result task fails with this exception if the server method throws an exception.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteMethodNotFoundException">
|
||||
/// Result task fails with this exception if the <paramref name="targetName"/> method is not found on the target object on the server.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteTargetNotSetException">
|
||||
/// Result task fails with this exception if the server has no target object.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">If <paramref name="targetName"/> is null.</exception>
|
||||
/// <exception cref="ObjectDisposedException">If this instance of <see cref="JsonRpc"/> has been disposed.</exception>
|
||||
public Task InvokeAsync(string targetName, params object[] arguments)
|
||||
{
|
||||
return this.InvokeAsync<object>(targetName, arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a method on the server and get back the result.
|
||||
/// </summary>
|
||||
/// <typeparam name="Result">Type of the method result</typeparam>
|
||||
/// <param name="targetName">The name of the method to invoke on the server. Must not be null or empty string.</param>
|
||||
/// <param name="arguments">Method arguments, must be serializable to JSON.</param>
|
||||
/// <returns>A task that completes when the server method executes and returns the result.</returns>
|
||||
/// <exception cref="OperationCanceledException">
|
||||
/// Result task fails with this exception if the communication channel ends before the result gets back from the server.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteInvocationException">
|
||||
/// Result task fails with this exception if the server method throws an exception.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteMethodNotFoundException">
|
||||
/// Result task fails with this exception if the <paramref name="targetName"/> method is not found on the target object on the server.
|
||||
/// </exception>
|
||||
/// <exception cref="RemoteTargetNotSetException">
|
||||
/// Result task fails with this exception if the server has no target object.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentNullException">If <paramref name="targetName"/> is null.</exception>
|
||||
/// <exception cref="ObjectDisposedException">If this instance of <see cref="JsonRpc"/> has been disposed.</exception>
|
||||
public Task<Result> InvokeAsync<Result>(string targetName, params object[] arguments)
|
||||
{
|
||||
if (targetName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetName));
|
||||
}
|
||||
|
||||
this.ThrowIfDisposed();
|
||||
string id = Interlocked.Increment(ref this.nextId).ToString(CultureInfo.InvariantCulture);
|
||||
return InvokeCoreAsync<Result>(id, targetName, arguments);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke a method on the server and don't wait for its completion, fire-and-forget style.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any error that happens on the server side is ignored.
|
||||
/// </remarks>
|
||||
/// <param name="targetName">The name of the method to invoke on the server. Must not be null or empty string.</param>
|
||||
/// <param name="arguments">Method arguments, must be serializable to JSON.</param>
|
||||
/// <returns>A task that completes when the notify request is sent to the channel to the server.</returns>
|
||||
/// <exception cref="ArgumentNullException">If <paramref name="targetName"/> is null.</exception>
|
||||
/// <exception cref="ObjectDisposedException">If this instance of <see cref="JsonRpc"/> has been disposed.</exception>
|
||||
public async Task NotifyAsync(string targetName, params object[] arguments)
|
||||
{
|
||||
if (targetName == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(targetName));
|
||||
}
|
||||
|
||||
this.ThrowIfDisposed();
|
||||
await this.InvokeCoreAsync<object>(id: null, targetName: targetName, arguments: arguments);
|
||||
}
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void OnJsonRpcDisconnected(JsonRpcDisconnectedEventArgs eventArgs)
|
||||
{
|
||||
EventHandler<JsonRpcDisconnectedEventArgs> handlersToInvoke = null;
|
||||
lock (this.disconnectedEventLock)
|
||||
{
|
||||
if (!this.hasDisconnectedEventBeenRaised)
|
||||
{
|
||||
this.hasDisconnectedEventBeenRaised = true;
|
||||
handlersToInvoke = this.onDisconnected;
|
||||
this.onDisconnected = null;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Fire the event first so that subscribers can interact with a non-disposed stream
|
||||
handlersToInvoke?.Invoke(this, eventArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose the stream and cancel pending requests in the finally block
|
||||
// So this is executed even if Disconnected event handler throws.
|
||||
this.disposeCts.Cancel();
|
||||
this.splitJoinStream.Dispose();
|
||||
this.CancelPendingRequests();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
this.disposed = true;
|
||||
if (disposing)
|
||||
{
|
||||
var disconnectedEventArgs = new JsonRpcDisconnectedEventArgs(Resources.StreamDisposed, DisconnectedReason.Disposed);
|
||||
this.OnJsonRpcDisconnected(disconnectedEventArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ThrowIfDisposed()
|
||||
{
|
||||
if (this.disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(this.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the specified RPC method
|
||||
/// </summary>
|
||||
/// <typeparam name="ReturnType">RPC method return type</typeparam>
|
||||
/// <param name="id">An identifier established by the Client that MUST contain a String, Number, or NULL value if included.
|
||||
/// If it is not included it is assumed to be a notification.</param>
|
||||
/// <param name="targetName">RPC method name</param>
|
||||
/// <param name="arguments">RPC method arguments</param>
|
||||
/// <returns></returns>
|
||||
protected virtual async Task<ReturnType> InvokeCoreAsync<ReturnType>(string id, string targetName, params object[] arguments)
|
||||
{
|
||||
// If somebody calls InvokeInternal<T>(id, "method", null), the null is not passed as an item in the array.
|
||||
// Instead, the compiler thinks that the null is the array itself and it'll pass null directly.
|
||||
// To account for this case, we check for null below.
|
||||
arguments = arguments ?? new object[] { null };
|
||||
|
||||
JsonRpcMessage request = JsonRpcMessage.CreateRequest(id, targetName, arguments);
|
||||
if (id == null)
|
||||
{
|
||||
await this.WriteAsync(request.ToJson());
|
||||
return default(ReturnType);
|
||||
}
|
||||
|
||||
var tcs = new TaskCompletionSource<ReturnType>();
|
||||
Action<JsonRpcMessage> dispatcher = (response) =>
|
||||
{
|
||||
lock (this.dispatcherMapLock)
|
||||
{
|
||||
this.resultDispatcherMap.Remove(id);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (response == null)
|
||||
{
|
||||
tcs.TrySetCanceled();
|
||||
}
|
||||
else if (response.IsError)
|
||||
{
|
||||
tcs.TrySetException(CreateExceptionFromRpcError(response, targetName));
|
||||
}
|
||||
else
|
||||
{
|
||||
tcs.TrySetResult(response.GetResult<ReturnType>());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
tcs.TrySetException(ex);
|
||||
}
|
||||
};
|
||||
|
||||
lock (this.dispatcherMapLock)
|
||||
{
|
||||
this.resultDispatcherMap.Add(id, new OutstandingCallData(tcs, dispatcher));
|
||||
}
|
||||
|
||||
await this.WriteAsync(request.ToJson()).ConfigureAwait(false);
|
||||
|
||||
// This task will be completed when the Response object comes back from the other end of the pipe
|
||||
await tcs.Task.NoThrowAwaitable();
|
||||
await Task.Yield(); // ensure we don't inline anything further, including the continuation of our caller.
|
||||
return await tcs.Task;
|
||||
}
|
||||
|
||||
private static RemoteRpcException CreateExceptionFromRpcError(JsonRpcMessage response, string targetName)
|
||||
{
|
||||
Requires.NotNull(response, nameof(response));
|
||||
Requires.Argument(response.IsError, nameof(response), Resources.ResponseIsNotError);
|
||||
|
||||
switch (response.Error.Code)
|
||||
{
|
||||
case (int)JsonRpcErrorCode.MethodNotFound:
|
||||
return new RemoteMethodNotFoundException(response.Error.Message, targetName);
|
||||
|
||||
case (int)JsonRpcErrorCode.NoCallbackObject:
|
||||
return new RemoteTargetNotSetException(response.Error.Message);
|
||||
|
||||
default:
|
||||
return new RemoteInvocationException(response.Error.Message, response.Error.ErrorStack, response.Error.ErrorCode);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<JsonRpcMessage> DispatchIncomingRequest(JsonRpcMessage request)
|
||||
{
|
||||
if (this.callbackTarget == null)
|
||||
{
|
||||
string message = string.Format(CultureInfo.CurrentCulture, Resources.DroppingRequestDueToNoTargetObject, request.Method);
|
||||
return JsonRpcMessage.CreateError(request.Id, JsonRpcErrorCode.NoCallbackObject, message);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var targetMethod = new TargetMethod(request, this.callbackTarget);
|
||||
if (!targetMethod.IsFound)
|
||||
{
|
||||
return JsonRpcMessage.CreateError(request.Id, JsonRpcErrorCode.MethodNotFound, targetMethod.LookupErrorMessage);
|
||||
}
|
||||
|
||||
object result = targetMethod.Invoke();
|
||||
if (!(result is Task))
|
||||
{
|
||||
return JsonRpcMessage.CreateResult(request.Id, result);
|
||||
}
|
||||
|
||||
return await ((Task)result).ContinueWith((t, id) => HandleInvocationTaskResult((string)id, t), request.Id, TaskScheduler.Default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CreateError(request.Id, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonRpcMessage HandleInvocationTaskResult(string id, Task t)
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(t));
|
||||
}
|
||||
|
||||
if (!t.IsCompleted)
|
||||
{
|
||||
throw new ArgumentException(Resources.TaskNotCompleted, nameof(t));
|
||||
}
|
||||
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
return CreateError(id, t.Exception);
|
||||
}
|
||||
|
||||
if (t.IsCanceled)
|
||||
{
|
||||
return JsonRpcMessage.CreateError(id, JsonRpcErrorCode.InvocationError, Resources.TaskWasCancelled);
|
||||
}
|
||||
|
||||
object taskResult = null;
|
||||
Type taskType = t.GetType();
|
||||
|
||||
// If t is a Task<SomeType>, it will have Result property.
|
||||
// If t is just a Task, there is no Result property on it.
|
||||
if (!taskType.Equals(typeof(Task)))
|
||||
{
|
||||
const string ResultPropertyName = nameof(Task<int>.Result);
|
||||
|
||||
// We can't really write direct code to deal with Task<T>, since we have no idea of T in this context, so we simply use reflection to
|
||||
// read the result at runtime.
|
||||
PropertyInfo resultProperty = taskType.GetTypeInfo().GetDeclaredProperty(ResultPropertyName);
|
||||
taskResult = resultProperty?.GetValue(t);
|
||||
}
|
||||
|
||||
return JsonRpcMessage.CreateResult(id, taskResult);
|
||||
}
|
||||
|
||||
private static JsonRpcMessage CreateError(string id, Exception exception)
|
||||
{
|
||||
if (exception == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(exception));
|
||||
}
|
||||
|
||||
if (exception is TargetInvocationException || (exception is AggregateException && exception.InnerException != null))
|
||||
{
|
||||
// Never let the outer (TargetInvocationException) escape because the inner is the interesting one to the caller, the outer is due to
|
||||
// the fact we are using reflection.
|
||||
exception = exception.InnerException;
|
||||
}
|
||||
|
||||
string message = $"{exception.Message}{Environment.NewLine}{exception.StackTrace}";
|
||||
var data = new { stack = exception.StackTrace, code = exception.HResult.ToString(CultureInfo.InvariantCulture) };
|
||||
return JsonRpcMessage.CreateError(id, JsonRpcErrorCode.InvocationError, message, data);
|
||||
}
|
||||
|
||||
private async Task ReadAndHandleRequests()
|
||||
{
|
||||
JsonRpcDisconnectedEventArgs disconnectedEventArgs = null;
|
||||
|
||||
try
|
||||
{
|
||||
while (!this.disposed)
|
||||
{
|
||||
string json = null;
|
||||
try
|
||||
{
|
||||
json = await this.splitJoinStream.ReadAsync(this.disposeCts.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var e = new JsonRpcDisconnectedEventArgs(string.Format(CultureInfo.CurrentCulture, Resources.ReadingJsonRpcStreamFailed, exception.GetType().Name, exception.Message),
|
||||
DisconnectedReason.StreamError,
|
||||
exception);
|
||||
|
||||
// Fatal error. Raise disconnected event.
|
||||
this.OnJsonRpcDisconnected(e);
|
||||
break;
|
||||
}
|
||||
|
||||
if (json == null)
|
||||
{
|
||||
// End of stream reached
|
||||
disconnectedEventArgs = new JsonRpcDisconnectedEventArgs(Resources.ReachedEndOfStream, DisconnectedReason.Disposed);
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HandleRpc(json);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var eventArgs = new JsonRpcDisconnectedEventArgs(
|
||||
string.Format(CultureInfo.CurrentCulture, Resources.UnexpectedErrorProcessingJsonRpc, json, exception.Message),
|
||||
DisconnectedReason.ParseError,
|
||||
json,
|
||||
exception);
|
||||
|
||||
// Fatal error. Raise disconnected event.
|
||||
this.OnJsonRpcDisconnected(eventArgs);
|
||||
|
||||
}
|
||||
}, this.disposeCts.Token);
|
||||
|
||||
#pragma warning restore CS4014
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (disconnectedEventArgs == null)
|
||||
{
|
||||
disconnectedEventArgs = new JsonRpcDisconnectedEventArgs(Resources.StreamDisposed, DisconnectedReason.Disposed);
|
||||
}
|
||||
|
||||
this.OnJsonRpcDisconnected(disconnectedEventArgs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRpc(string json)
|
||||
{
|
||||
JsonRpcMessage rpc;
|
||||
try
|
||||
{
|
||||
rpc = JsonRpcMessage.FromJson(json);
|
||||
}
|
||||
catch (JsonException exception)
|
||||
{
|
||||
var e = new JsonRpcDisconnectedEventArgs(string.Format(CultureInfo.CurrentCulture, Resources.FailureDeserializingJsonRpc, json, exception.Message),
|
||||
DisconnectedReason.ParseError,
|
||||
json,
|
||||
exception);
|
||||
|
||||
// Fatal error. Raise disconnected event.
|
||||
this.OnJsonRpcDisconnected(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rpc.IsRequest)
|
||||
{
|
||||
JsonRpcMessage result = await this.DispatchIncomingRequest(rpc);
|
||||
|
||||
if (!rpc.IsNotification)
|
||||
{
|
||||
try
|
||||
{
|
||||
await this.WriteAsync(result.ToJson());
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var e = new JsonRpcDisconnectedEventArgs(string.Format(CultureInfo.CurrentCulture, Resources.ErrorWritingJsonRpcResult, exception.GetType().Name, exception.Message),
|
||||
DisconnectedReason.StreamError,
|
||||
exception);
|
||||
|
||||
// Fatal error. Raise disconnected event.
|
||||
this.OnJsonRpcDisconnected(e);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (rpc.IsResponse)
|
||||
{
|
||||
OutstandingCallData data = null;
|
||||
lock (this.dispatcherMapLock)
|
||||
{
|
||||
if (this.resultDispatcherMap.TryGetValue(rpc.Id, out data))
|
||||
{
|
||||
this.resultDispatcherMap.Remove(rpc.Id);
|
||||
}
|
||||
}
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
data.CompletionHandler(rpc);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Not a request or return. Raise disconnected event.
|
||||
this.OnJsonRpcDisconnected(new JsonRpcDisconnectedEventArgs(
|
||||
string.Format(CultureInfo.CurrentCulture, Resources.UnrecognizedIncomingJsonRpc, json),
|
||||
DisconnectedReason.ParseError,
|
||||
json));
|
||||
}
|
||||
|
||||
private async Task WriteAsync(string data)
|
||||
{
|
||||
await this.splitJoinStream.WriteAsync(data, this.disposeCts.Token);
|
||||
}
|
||||
|
||||
private void CancelPendingRequests()
|
||||
{
|
||||
OutstandingCallData[] pendingRequests;
|
||||
lock (this.dispatcherMapLock)
|
||||
{
|
||||
pendingRequests = this.resultDispatcherMap.Values.ToArray();
|
||||
}
|
||||
|
||||
foreach (OutstandingCallData pendingRequest in pendingRequests)
|
||||
{
|
||||
pendingRequest.CompletionHandler(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
internal sealed class MethodSignature : IEquatable<MethodSignature>
|
||||
{
|
||||
private static readonly StringComparer typeNameComparer = StringComparer.Ordinal;
|
||||
|
||||
internal MethodSignature(MethodInfo methodInfo)
|
||||
{
|
||||
Requires.NotNull(methodInfo, nameof(methodInfo));
|
||||
this.MethodInfo = methodInfo;
|
||||
this.Parameters = methodInfo.GetParameters() ?? new ParameterInfo[0];
|
||||
}
|
||||
|
||||
internal MethodInfo MethodInfo { get; }
|
||||
|
||||
internal ParameterInfo[] Parameters { get; }
|
||||
|
||||
internal bool IsPublic => this.MethodInfo.IsPublic;
|
||||
|
||||
internal string Name => this.MethodInfo.Name;
|
||||
|
||||
internal int RequiredParamCount => this.Parameters.Count(pi => !pi.IsOptional);
|
||||
|
||||
internal int TotalParamCount => this.Parameters.Length;
|
||||
|
||||
internal bool HasOutOrRefParameters => this.Parameters.Any(pi => pi.IsOut || pi.ParameterType.IsByRef);
|
||||
|
||||
bool IEquatable<MethodSignature>.Equals(MethodSignature other)
|
||||
{
|
||||
if (Object.ReferenceEquals(other, null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Object.ReferenceEquals(other, this) || Object.ReferenceEquals(this.Parameters, other.Parameters))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.Parameters.Length != other.Parameters.Length)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < this.Parameters.Length; index++)
|
||||
{
|
||||
if (!MethodSignature.typeNameComparer.Equals(
|
||||
this.Parameters[index].ParameterType.AssemblyQualifiedName,
|
||||
other.Parameters[index].ParameterType.AssemblyQualifiedName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return (obj is MethodSignature) && ((IEquatable<MethodSignature>)this).Equals((MethodSignature)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
uint result = 0;
|
||||
int bitCount = sizeof(uint) * 8;
|
||||
const int shift = 1;
|
||||
|
||||
foreach (ParameterInfo parameter in this.MethodInfo.GetParameters())
|
||||
{
|
||||
// Shifting result 1 bit per each parameter so that the hash is different for
|
||||
// methods with the same parameter types at different location, e.g.
|
||||
// foo(int, string) and foo(string, int)
|
||||
// This will work fine for up to 32 (64 on x64) parameters,
|
||||
// which should be more than enough for the most applications.
|
||||
result = result << shift | result >> (bitCount - shift);
|
||||
result ^= (uint)MethodSignature.typeNameComparer.GetHashCode(parameter.ParameterType.AssemblyQualifiedName);
|
||||
}
|
||||
|
||||
return (int)result;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return this.MethodInfo.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
internal sealed class TargetMethod
|
||||
{
|
||||
private readonly HashSet<string> errorMessages = new HashSet<string>(StringComparer.Ordinal);
|
||||
private readonly JsonRpcMessage request;
|
||||
private readonly object callbackObject;
|
||||
private readonly MethodInfo method;
|
||||
private readonly object[] parameters;
|
||||
|
||||
internal TargetMethod(JsonRpcMessage request, object callbackObject)
|
||||
{
|
||||
Requires.NotNull(request, nameof(request));
|
||||
Requires.NotNull(callbackObject, nameof(callbackObject));
|
||||
|
||||
this.request = request;
|
||||
this.callbackObject = callbackObject;
|
||||
|
||||
var targetMethods = new Dictionary<MethodSignature, object[]>();
|
||||
for (TypeInfo t = callbackObject.GetType().GetTypeInfo(); t != null; t = t.BaseType?.GetTypeInfo())
|
||||
{
|
||||
foreach (MethodSignature method in t.GetDeclaredMethods(request.Method).Select(method => new MethodSignature(method)))
|
||||
{
|
||||
if (!targetMethods.ContainsKey(method))
|
||||
{
|
||||
object[] parameters = TargetMethod.TryGetParameters(request, method, this.errorMessages);
|
||||
if (parameters != null)
|
||||
{
|
||||
targetMethods.Add(method, parameters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetMethods.Count == 1)
|
||||
{
|
||||
KeyValuePair<MethodSignature, object[]> methodWithParameters = targetMethods.First();
|
||||
this.method = methodWithParameters.Key.MethodInfo;
|
||||
this.parameters = methodWithParameters.Value;
|
||||
}
|
||||
else if (targetMethods.Count > 1)
|
||||
{
|
||||
this.method = null;
|
||||
this.parameters = null;
|
||||
this.errorMessages.Add(string.Format(CultureInfo.CurrentCulture, Resources.MoreThanOneMethodFound, string.Join("; ", targetMethods.Keys)));
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsFound => this.method != null;
|
||||
|
||||
internal string LookupErrorMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.CurrentCulture,
|
||||
Resources.UnableToFindMethod,
|
||||
this.request.Method,
|
||||
this.request.ParameterCount,
|
||||
this.callbackObject.GetType().FullName,
|
||||
string.Join("; ", this.errorMessages));
|
||||
}
|
||||
}
|
||||
|
||||
internal object Invoke()
|
||||
{
|
||||
if (this.method == null)
|
||||
{
|
||||
throw new InvalidOperationException(this.LookupErrorMessage);
|
||||
}
|
||||
|
||||
return this.method.Invoke(!this.method.IsStatic ? this.callbackObject : null, this.parameters);
|
||||
}
|
||||
|
||||
private static object[] TryGetParameters(JsonRpcMessage request, MethodSignature method, HashSet<string> errors)
|
||||
{
|
||||
if (!method.IsPublic)
|
||||
{
|
||||
errors.Add(string.Format(CultureInfo.CurrentCulture, Resources.MethodIsNotPublic, method));
|
||||
return null;
|
||||
}
|
||||
|
||||
// The method name and the number of parameters must match
|
||||
if (!string.Equals(method.Name, request.Method, StringComparison.Ordinal))
|
||||
{
|
||||
errors.Add(string.Format(CultureInfo.CurrentCulture, Resources.MethodNameCaseIsDifferent, method, request.Method));
|
||||
return null;
|
||||
}
|
||||
|
||||
if (method.HasOutOrRefParameters)
|
||||
{
|
||||
errors.Add(string.Format(CultureInfo.CurrentCulture, Resources.MethodHasRefOrOutParameters, method));
|
||||
return null;
|
||||
}
|
||||
|
||||
int paramCount = request.ParameterCount;
|
||||
if (paramCount < method.RequiredParamCount || paramCount > method.TotalParamCount)
|
||||
{
|
||||
string methodParameterCount;
|
||||
if (method.RequiredParamCount == method.TotalParamCount)
|
||||
{
|
||||
methodParameterCount = method.RequiredParamCount.ToString(CultureInfo.CurrentCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
methodParameterCount = string.Format(CultureInfo.CurrentCulture, "{0} - {1}", method.RequiredParamCount, method.TotalParamCount);
|
||||
}
|
||||
|
||||
errors.Add(string.Format(CultureInfo.CurrentCulture,
|
||||
Resources.MethodParameterCountDoesNotMatch,
|
||||
method,
|
||||
methodParameterCount,
|
||||
request.ParameterCount));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parameters must be compatible
|
||||
try
|
||||
{
|
||||
return request.GetParameters(method.Parameters);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
errors.Add(string.Format(CultureInfo.CurrentCulture, Resources.MethodParametersNotCompatible, method, exception.Message));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
internal class SplitJoinStream : IDisposable
|
||||
{
|
||||
internal const int BufferSize = 1024;
|
||||
|
||||
private readonly Stream readable;
|
||||
private readonly StringBuilder readBuffer;
|
||||
private readonly Stream writable;
|
||||
private readonly string delimiter;
|
||||
private readonly Encoding encoding;
|
||||
private readonly Decoder decoder;
|
||||
private readonly bool leaveOpen;
|
||||
private readonly bool readTrailing;
|
||||
|
||||
public SplitJoinStream(SplitJoinStreamOptions options)
|
||||
{
|
||||
Requires.NotNull(options, nameof(options));
|
||||
Requires.Argument(options.Readable != null || options.Writable != null, nameof(options), Resources.BothReadableWritableAreNull);
|
||||
Requires.Argument(options.Readable == null || options.Readable.CanRead, nameof(options), Resources.ReadableCannotRead);
|
||||
Requires.Argument(options.Writable == null || options.Writable.CanWrite, nameof(options), Resources.WritableCannotWrite);
|
||||
Requires.Argument(options.Delimiter != string.Empty, nameof(options), Resources.EmptyDelimiter);
|
||||
|
||||
this.readable = options.Readable;
|
||||
this.writable = options.Writable;
|
||||
this.delimiter = options.Delimiter ?? "\0";
|
||||
this.encoding = options.Encoding ?? Encoding.UTF8;
|
||||
this.decoder = this.encoding.GetDecoder();
|
||||
this.leaveOpen = options.LeaveOpen;
|
||||
this.readTrailing = options.ReadTrailing;
|
||||
|
||||
if (this.readable != null)
|
||||
{
|
||||
this.readBuffer = new StringBuilder(BufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public async Task<string> ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Verify.Operation(this.readable != null, Resources.ReadableNotSet);
|
||||
|
||||
string result = this.PopStringFromReadBuffer();
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var byteBuffer = new byte[BufferSize];
|
||||
while (string.IsNullOrEmpty(result))
|
||||
{
|
||||
// We could have used StreamReader, but it doesn't support cancellation.
|
||||
// So we have to fall back to using the stream directly and Decoder() for decoding it.
|
||||
// Decoder takes care if there are un-decoded leftovers from the previous reads.
|
||||
int byteCount = await this.readable.ReadAsync(byteBuffer, 0, byteBuffer.Length, cancellationToken);
|
||||
if (byteCount == 0)
|
||||
{
|
||||
// End of stream reached
|
||||
result = this.readTrailing && this.readBuffer.Length > 0 ? this.readBuffer.ToString() : null;
|
||||
this.readBuffer.Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
int count = this.decoder.GetCharCount(byteBuffer, 0, byteCount);
|
||||
var buffer = new char[count];
|
||||
count = this.decoder.GetChars(byteBuffer, 0, byteCount, buffer, 0);
|
||||
|
||||
this.readBuffer.Append(buffer, 0, count);
|
||||
|
||||
int startIndex = Math.Max(0, this.readBuffer.Length - count - this.delimiter.Length + 1);
|
||||
result = this.PopStringFromReadBuffer(startIndex);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(string message, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Verify.Operation(this.writable != null, Resources.WritableNotSet);
|
||||
|
||||
var bytes = this.encoding.GetBytes(message + this.delimiter);
|
||||
await this.writable.WriteAsync(bytes, 0, bytes.Length, cancellationToken);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!this.leaveOpen)
|
||||
{
|
||||
if (this.readable != null)
|
||||
{
|
||||
this.readable.Dispose();
|
||||
}
|
||||
|
||||
if (this.writable != null && this.writable != this.readable)
|
||||
{
|
||||
this.writable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string PopStringFromReadBuffer(int startIndex = 0)
|
||||
{
|
||||
int index = startIndex;
|
||||
while (index < this.readBuffer.Length - this.delimiter.Length + 1)
|
||||
{
|
||||
if (this.readBuffer[index] == this.delimiter[0])
|
||||
{
|
||||
bool found = true;
|
||||
for (int j = 1; j < this.delimiter.Length; j++)
|
||||
{
|
||||
if (this.readBuffer[index + j] != this.delimiter[j])
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
string result = this.readBuffer.ToString(0, index);
|
||||
this.readBuffer.Remove(0, index + this.delimiter.Length);
|
||||
if (index > 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace StreamJsonRpc
|
||||
{
|
||||
internal class SplitJoinStreamOptions
|
||||
{
|
||||
public Stream Readable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Stream Writable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public string Delimiter
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Encoding Encoding
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool LeaveOpen
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool ReadTrailing
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>df6b6dc7-7c77-42f2-84cb-e520b2771375</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>StreamJsonRpc.Shared</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)AssemblyInfo.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataContracts\JsonRpcError.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataContracts\JsonRpcErrorCode.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)DataContracts\JsonRpcMessage.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EventArgs\DisconnectedReason.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)EventArgs\JsonRpcDisconnectedEventArgs.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Exceptions\RemoteInvocationException.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Exceptions\RemoteMethodNotFoundException.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Exceptions\RemoteRpcException.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Exceptions\RemoteTargetNotSetException.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)JsonRpc.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Reflection\MethodSignature.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Reflection\TargetMethod.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SplitJoinStream\SplitJoinStream.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)SplitJoinStream\SplitJoinStreamOptions.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>df6b6dc7-7c77-42f2-84cb-e520b2771375</ProjectGuid>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<PropertyGroup />
|
||||
<Import Project="StreamJsonRpc.Shared.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,76 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using StreamJsonRpc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
public class JsonRpcRawStreamTests
|
||||
{
|
||||
private static TimeSpan TestTimeout => Debugger.IsAttached ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(5);
|
||||
private readonly CancellationTokenSource timeoutTokenSource = new CancellationTokenSource(TestTimeout);
|
||||
private CancellationToken TimeoutToken => this.timeoutTokenSource.Token;
|
||||
|
||||
[Fact]
|
||||
public async Task JsonRpcClosesStreamAfterDisconnectedEvent()
|
||||
{
|
||||
var server = new Server();
|
||||
|
||||
var streams = Nerdbank.FullDuplexStream.CreateStreams();
|
||||
|
||||
var clientStream = streams.Item2;
|
||||
|
||||
// Use wrapped stream to see when the stream is closed and disposed.
|
||||
var serverStream = new WrappedStream(streams.Item1);
|
||||
|
||||
// Subscribe to disconnected event on the server stream
|
||||
var serverStreamDisconnected = new TaskCompletionSource<object>();
|
||||
serverStream.Disconnected += (sender, args) => serverStreamDisconnected.SetResult(null);
|
||||
|
||||
using (JsonRpc serverRpc = JsonRpc.Attach(serverStream, server))
|
||||
{
|
||||
// Subscribe to disconnected event on json rpc
|
||||
var disconnectedEventFired = new TaskCompletionSource<JsonRpcDisconnectedEventArgs>();
|
||||
object disconnectedEventSender = null;
|
||||
serverRpc.Disconnected += delegate (object sender, JsonRpcDisconnectedEventArgs e)
|
||||
{
|
||||
// The stream must not be disposed when the Disconnected even fires
|
||||
Assert.True(serverStream.IsConnected);
|
||||
|
||||
disconnectedEventSender = sender;
|
||||
disconnectedEventFired.SetResult(e);
|
||||
};
|
||||
|
||||
// Send a bad json to the server
|
||||
using (var split = new SplitJoinStream(new SplitJoinStreamOptions { Writable = clientStream, LeaveOpen = true }))
|
||||
{
|
||||
await split.WriteAsync("{");
|
||||
}
|
||||
|
||||
// The server must fire disonnected event because bad json must make it disconnect
|
||||
JsonRpcDisconnectedEventArgs args = await disconnectedEventFired.Task.WithCancellation(this.TimeoutToken);
|
||||
|
||||
Assert.Same(serverRpc, disconnectedEventSender);
|
||||
Assert.NotNull(args);
|
||||
Assert.NotNull(args.Description);
|
||||
Assert.Equal(DisconnectedReason.ParseError, args.Reason);
|
||||
Assert.Equal("{", args.LastMessage);
|
||||
Assert.NotNull(args.Exception);
|
||||
|
||||
// Server must dispose the stream now
|
||||
await serverStreamDisconnected.Task.WithCancellation(this.TimeoutToken);
|
||||
Assert.True(serverStream.Disposed);
|
||||
Assert.False(serverStream.IsEndReached);
|
||||
}
|
||||
}
|
||||
|
||||
public class Server
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,395 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Newtonsoft.Json;
|
||||
using StreamJsonRpc;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
public class JsonRpcTests : IDisposable
|
||||
{
|
||||
private const int CustomTaskResult = 100;
|
||||
private const string HubName = "TestHub";
|
||||
|
||||
private static TimeSpan TestTimeout => Debugger.IsAttached ? Timeout.InfiniteTimeSpan : TimeSpan.FromSeconds(5);
|
||||
private readonly CancellationTokenSource timeoutTokenSource;
|
||||
private readonly ITestOutputHelper logger;
|
||||
|
||||
private readonly Server server;
|
||||
private readonly Stream serverStream;
|
||||
private readonly JsonRpc serverRpc;
|
||||
|
||||
private readonly Stream clientStream;
|
||||
private readonly JsonRpc clientRpc;
|
||||
|
||||
public JsonRpcTests(ITestOutputHelper logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
TaskCompletionSource<JsonRpc> serverRpcTcs = new TaskCompletionSource<JsonRpc>();
|
||||
this.timeoutTokenSource = new CancellationTokenSource(TestTimeout);
|
||||
|
||||
this.server = new Server();
|
||||
|
||||
var streams = Nerdbank.FullDuplexStream.CreateStreams();
|
||||
this.serverStream = streams.Item1;
|
||||
this.clientStream = streams.Item2;
|
||||
|
||||
this.serverRpc = JsonRpc.Attach(this.serverStream, this.server);
|
||||
this.clientRpc = JsonRpc.Attach(this.clientStream);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this.timeoutTokenSource.Dispose();
|
||||
this.serverRpc.Dispose();
|
||||
this.clientRpc.Dispose();
|
||||
this.serverStream.Dispose();
|
||||
this.clientStream.Dispose();
|
||||
}
|
||||
|
||||
protected CancellationToken TimeoutToken => this.timeoutTokenSource.Token;
|
||||
|
||||
[Fact]
|
||||
public async Task CanInvokeMethodOnServer()
|
||||
{
|
||||
string TestLine = "TestLine1" + new string('a', 1024 * 1024);
|
||||
string result1 = await this.clientRpc.InvokeAsync<string>(nameof(Server.ServerMethod), TestLine);
|
||||
Assert.Equal(TestLine + "!", result1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanInvokeTaskMethodOnServer()
|
||||
{
|
||||
await this.clientRpc.InvokeAsync(nameof(Server.ServerMethodThatReturnsTask));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanInvokeMethodThatReturnsCustomTask()
|
||||
{
|
||||
int result = await this.clientRpc.InvokeAsync<int>(nameof(Server.ServerMethodThatReturnsCustomTask));
|
||||
Assert.StrictEqual(CustomTaskResult, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanInvokeMethodThatReturnsCancelledTask()
|
||||
{
|
||||
RemoteInvocationException exception = await Assert.ThrowsAnyAsync<RemoteInvocationException>(() => this.clientRpc.InvokeAsync(nameof(Server.ServerMethodThatReturnsCancelledTask)));
|
||||
Assert.Null(exception.RemoteErrorCode);
|
||||
Assert.Null(exception.RemoteStackTrace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanInvokeMethodThatReturnsTaskOfInternalClass()
|
||||
{
|
||||
// JSON RPC cannot invoke non-public members. A public member cannot have Task<NonPublicType> result.
|
||||
// Though it can have result of just Task type, and return a Task<NonPublicType>, and dev hub supports that.
|
||||
InternalClass result = await this.clientRpc.InvokeAsync<InternalClass>(nameof(Server.MethodThatReturnsTaskOfInternalClass));
|
||||
Assert.NotNull(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPassExceptionFromServer()
|
||||
{
|
||||
const int COR_E_UNAUTHORIZEDACCESS = unchecked((int)0x80070005);
|
||||
RemoteInvocationException exception = await Assert.ThrowsAnyAsync<RemoteInvocationException>(() => this.clientRpc.InvokeAsync(nameof(Server.MethodThatThrowsUnauthorizedAccessException)));
|
||||
Assert.NotNull(exception.RemoteStackTrace);
|
||||
Assert.StrictEqual(COR_E_UNAUTHORIZEDACCESS.ToString(CultureInfo.InvariantCulture), exception.RemoteErrorCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPassAndCallPrivateMethodsObjects()
|
||||
{
|
||||
var result = await this.clientRpc.InvokeAsync<Foo>(nameof(Server.MethodThatAcceptsFoo), new Foo { Bar = "bar", Bazz = 1000 });
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("bar!", result.Bar);
|
||||
Assert.Equal(1001, result.Bazz);
|
||||
|
||||
result = await this.clientRpc.InvokeAsync<Foo>(nameof(Server.MethodThatAcceptsFoo), new { Bar = "bar", Bazz = 1000 });
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("bar!", result.Bar);
|
||||
Assert.Equal(1001, result.Bazz);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCallMethodWithDefaultParameters()
|
||||
{
|
||||
var result = await this.clientRpc.InvokeAsync<int>(nameof(Server.MethodWithDefaultParameter), 10);
|
||||
Assert.Equal(20, result);
|
||||
|
||||
result = await this.clientRpc.InvokeAsync<int>(nameof(Server.MethodWithDefaultParameter), 10, 20);
|
||||
Assert.Equal(30, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanPassNull()
|
||||
{
|
||||
var result = await this.clientRpc.InvokeAsync<object>(nameof(Server.MethodThatAccceptsAndReturnsNull), null);
|
||||
Assert.Null(result);
|
||||
Assert.True(this.server.NullPassed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanSendNotification()
|
||||
{
|
||||
await this.clientRpc.NotifyAsync(nameof(Server.NotificationMethod), "foo");
|
||||
Assert.Equal("foo", await this.server.NotificationReceived);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCallAsyncMethod()
|
||||
{
|
||||
string result = await this.clientRpc.InvokeAsync<string>(nameof(Server.AsyncMethod), "test");
|
||||
Assert.Equal("test!", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCallAsyncMethodThatThrows()
|
||||
{
|
||||
RemoteInvocationException exception = await Assert.ThrowsAnyAsync<RemoteInvocationException>(() => this.clientRpc.InvokeAsync<string>(nameof(Server.AsyncMethodThatThrows)));
|
||||
Assert.NotNull(exception.RemoteStackTrace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCallOverloadedMethod()
|
||||
{
|
||||
int result = await this.clientRpc.InvokeAsync<int>(nameof(Server.OverloadedMethod), new Foo { Bar = "bar-bar", Bazz = -100 });
|
||||
Assert.Equal(1, result);
|
||||
|
||||
result = await this.clientRpc.InvokeAsync<int>(nameof(Server.OverloadedMethod), 40);
|
||||
Assert.Equal(40, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsIfCannotFindMethod()
|
||||
{
|
||||
await Assert.ThrowsAsync(typeof(RemoteMethodNotFoundException), () => this.clientRpc.InvokeAsync("missingMethod", 50));
|
||||
await Assert.ThrowsAsync(typeof(RemoteMethodNotFoundException), () => this.clientRpc.InvokeAsync(nameof(Server.OverloadedMethod), new { X = 100 }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsIfTargetNotSet()
|
||||
{
|
||||
await Assert.ThrowsAsync(typeof(RemoteTargetNotSetException), () => this.serverRpc.InvokeAsync(nameof(Server.OverloadedMethod)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public async Task DisconnectedEventIsFired(bool disposeRpc)
|
||||
{
|
||||
var disconnectedEventFired = new TaskCompletionSource<JsonRpcDisconnectedEventArgs>();
|
||||
|
||||
// Subscribe to disconnected event
|
||||
object disconnectedEventSender = null;
|
||||
this.serverRpc.Disconnected += delegate (object sender, JsonRpcDisconnectedEventArgs e)
|
||||
{
|
||||
disconnectedEventSender = sender;
|
||||
disconnectedEventFired.SetResult(e);
|
||||
};
|
||||
|
||||
// Close server or client stream.
|
||||
if (disposeRpc)
|
||||
{
|
||||
this.serverRpc.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.serverStream.Dispose();
|
||||
}
|
||||
|
||||
JsonRpcDisconnectedEventArgs args = await disconnectedEventFired.Task.WithCancellation(this.TimeoutToken);
|
||||
Assert.Same(this.serverRpc, disconnectedEventSender);
|
||||
Assert.NotNull(args);
|
||||
Assert.NotNull(args.Description);
|
||||
|
||||
// Confirm that an event handler added after disconnection also gets raised.
|
||||
disconnectedEventFired = new TaskCompletionSource<JsonRpcDisconnectedEventArgs>();
|
||||
this.serverRpc.Disconnected += delegate (object sender, JsonRpcDisconnectedEventArgs e)
|
||||
{
|
||||
disconnectedEventSender = sender;
|
||||
disconnectedEventFired.SetResult(e);
|
||||
};
|
||||
|
||||
args = await disconnectedEventFired.Task;
|
||||
Assert.Same(this.serverRpc, disconnectedEventSender);
|
||||
Assert.NotNull(args);
|
||||
Assert.NotNull(args.Description);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCallMethodOnBaseClass()
|
||||
{
|
||||
string result = await this.clientRpc.InvokeAsync<string>(nameof(Server.BaseMethod));
|
||||
Assert.Equal("base", result);
|
||||
|
||||
result = await this.clientRpc.InvokeAsync<string>(nameof(Server.VirtualBaseMethod));
|
||||
Assert.Equal("child", result);
|
||||
|
||||
result = await this.clientRpc.InvokeAsync<string>(nameof(Server.RedeclaredBaseMethod));
|
||||
Assert.Equal("child", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CannotCallPrivateMethod()
|
||||
{
|
||||
await Assert.ThrowsAsync<RemoteMethodNotFoundException>(() => this.clientRpc.InvokeAsync(nameof(Server.InternalMethod), 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CannotCallMethodWithOutParameter()
|
||||
{
|
||||
await Assert.ThrowsAsync<RemoteMethodNotFoundException>(() => this.clientRpc.InvokeAsync(nameof(Server.MethodWithOutParameter), 20));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CannotCallMethodWithRefParameter()
|
||||
{
|
||||
await Assert.ThrowsAsync<RemoteMethodNotFoundException>(() => this.clientRpc.InvokeAsync(nameof(Server.MethodWithRefParameter), 20));
|
||||
}
|
||||
|
||||
public class BaseClass
|
||||
{
|
||||
public string BaseMethod() => "base";
|
||||
|
||||
public virtual string VirtualBaseMethod() => "base";
|
||||
|
||||
public string RedeclaredBaseMethod() => "base";
|
||||
}
|
||||
|
||||
public class Server : BaseClass
|
||||
{
|
||||
private readonly TaskCompletionSource<string> notificationTcs = new TaskCompletionSource<string>();
|
||||
|
||||
public bool NullPassed { get; private set; }
|
||||
|
||||
public Task<string> NotificationReceived => this.notificationTcs.Task;
|
||||
|
||||
public static string ServerMethod(string argument)
|
||||
{
|
||||
return argument + "!";
|
||||
}
|
||||
|
||||
public override string VirtualBaseMethod() => "child";
|
||||
|
||||
public new string RedeclaredBaseMethod() => "child";
|
||||
|
||||
internal void InternalMethod()
|
||||
{
|
||||
}
|
||||
|
||||
public Task ServerMethodThatReturnsCustomTask()
|
||||
{
|
||||
var result = new CustomTask();
|
||||
result.Start();
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task ServerMethodThatReturnsTask()
|
||||
{
|
||||
await Task.Yield();
|
||||
}
|
||||
|
||||
public Task ServerMethodThatReturnsCancelledTask()
|
||||
{
|
||||
var tcs = new TaskCompletionSource<object>();
|
||||
tcs.SetCanceled();
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void MethodThatThrowsUnauthorizedAccessException()
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
public Foo MethodThatAcceptsFoo(Foo foo)
|
||||
{
|
||||
return new Foo
|
||||
{
|
||||
Bar = foo.Bar + "!",
|
||||
Bazz = foo.Bazz + 1,
|
||||
};
|
||||
}
|
||||
|
||||
public static int MethodWithDefaultParameter(int x, int y = 10)
|
||||
{
|
||||
return x + y;
|
||||
}
|
||||
|
||||
public object MethodThatAccceptsAndReturnsNull(object value)
|
||||
{
|
||||
this.NullPassed = value == null;
|
||||
return null;
|
||||
}
|
||||
|
||||
public void NotificationMethod(string arg)
|
||||
{
|
||||
this.notificationTcs.SetResult(arg);
|
||||
}
|
||||
|
||||
public async Task<string> AsyncMethod(string arg)
|
||||
{
|
||||
await Task.Yield();
|
||||
return arg + "!";
|
||||
}
|
||||
|
||||
public async Task AsyncMethodThatThrows()
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
public Task MethodThatReturnsTaskOfInternalClass()
|
||||
{
|
||||
var result = new Task<InternalClass>(() => new InternalClass());
|
||||
result.Start();
|
||||
return result;
|
||||
}
|
||||
|
||||
public int OverloadedMethod(Foo foo)
|
||||
{
|
||||
Assert.NotNull(foo);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int OverloadedMethod(int i)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
public int MethodWithOutParameter(out int i)
|
||||
{
|
||||
i = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public void MethodWithRefParameter(ref int i)
|
||||
{
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public class Foo
|
||||
{
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public string Bar { get; set; }
|
||||
public int Bazz { get; set; }
|
||||
}
|
||||
|
||||
private class CustomTask : Task<int>
|
||||
{
|
||||
public CustomTask() : base(() => 0) { }
|
||||
|
||||
public new int Result { get { return CustomTaskResult; } }
|
||||
}
|
||||
|
||||
internal class InternalClass
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Resources;
|
||||
using System.Reflection;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("StreamJsonRpc.Tests")]
|
||||
[assembly: AssemblyProduct("StreamJsonRpc.Tests")]
|
||||
[assembly: AssemblyCopyright("Copyright © Microsoft 2016")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
|
@ -0,0 +1,203 @@
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StreamJsonRpc;
|
||||
using Xunit;
|
||||
|
||||
public class SplitJoinStreamTests
|
||||
{
|
||||
private const string DefaultDelimiter = "\0";
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadTrailing()
|
||||
{
|
||||
const string delimiter = "\r\n";
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions { Delimiter = delimiter, ReadTrailing = true },
|
||||
"foo" + delimiter + "bar" + delimiter + delimiter + delimiter + "bazz",
|
||||
"foo", "bar", "bazz");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadTrailingWithoutDelimiters()
|
||||
{
|
||||
const string delimiter = "|";
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions { Delimiter = delimiter, ReadTrailing = true },
|
||||
"foo",
|
||||
"foo");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadWithLongDelimiters()
|
||||
{
|
||||
var delimiter = new string('a', 4200);
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions { Delimiter = delimiter, ReadTrailing = true },
|
||||
"foo" + delimiter + "bar" + delimiter + "bazz",
|
||||
"foo", "bar", "bazz");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadMultibyteCharAcrossBufferBoundary()
|
||||
{
|
||||
var str = 'a' + new string('Ж', SplitJoinStream.BufferSize / 2);
|
||||
Assert.Equal(Encoding.UTF8.GetByteCount(str), SplitJoinStream.BufferSize + 1);
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions(),
|
||||
str + DefaultDelimiter + str + DefaultDelimiter,
|
||||
str, str);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadMultibyteDelimiterAcrossBufferBoundary()
|
||||
{
|
||||
const string delimiter = "Ж";
|
||||
var str = new string(' ', SplitJoinStream.BufferSize - 1);
|
||||
Assert.Equal(Encoding.UTF8.GetByteCount(delimiter), 2);
|
||||
Assert.Equal(Encoding.UTF8.GetByteCount(str + delimiter), SplitJoinStream.BufferSize + 1);
|
||||
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions { Delimiter = delimiter },
|
||||
str + delimiter + str + delimiter,
|
||||
str, str);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadSkippingTrailing()
|
||||
{
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions(),
|
||||
DefaultDelimiter + "foo" + DefaultDelimiter + "bar" + DefaultDelimiter + "bazz",
|
||||
"foo", "bar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadSkippingTrailingWithoutDelimiters()
|
||||
{
|
||||
await this.RunReadTest(new SplitJoinStreamOptions(), "foo" /* Nothing to read */);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadLongString()
|
||||
{
|
||||
var longString = new string('a', 4200);
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions(),
|
||||
longString + DefaultDelimiter + longString + DefaultDelimiter + DefaultDelimiter + longString,
|
||||
longString, longString);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadFromStreamWithOnlyDelimiters()
|
||||
{
|
||||
await this.RunReadTest(new SplitJoinStreamOptions(), DefaultDelimiter + DefaultDelimiter + DefaultDelimiter /* Nothing to read */);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadFromEmptyStream()
|
||||
{
|
||||
await this.RunReadTest(new SplitJoinStreamOptions {}, "" /* Nothing to read */);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadUTF32()
|
||||
{
|
||||
await this.RunReadTest(
|
||||
new SplitJoinStreamOptions { Encoding = Encoding.UTF32 },
|
||||
"foo" + DefaultDelimiter + "bar" + DefaultDelimiter + DefaultDelimiter + DefaultDelimiter + "bazz",
|
||||
"foo", "bar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCancelRead()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
using (var split = new SplitJoinStream(new SplitJoinStreamOptions { Readable = new MemoryStream(new byte[] { 32, 46 }) }))
|
||||
{
|
||||
await Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await split.ReadAsync(cts.Token));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDisposeStreamsByDefault()
|
||||
{
|
||||
var options = new SplitJoinStreamOptions
|
||||
{
|
||||
Readable = new MemoryStream(new byte[] { 32, 46 }),
|
||||
Writable = new MemoryStream(new byte[] { 55, 45 }),
|
||||
// LeaveOpen = false, - this is the default
|
||||
};
|
||||
|
||||
using (var split = new SplitJoinStream(options)) {}
|
||||
Assert.False(options.Writable.CanWrite);
|
||||
Assert.False(options.Readable.CanWrite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanLeaveStreamsOpen()
|
||||
{
|
||||
var options = new SplitJoinStreamOptions
|
||||
{
|
||||
Readable = new MemoryStream(new byte[] { 32, 46 }),
|
||||
Writable = new MemoryStream(new byte[] { 55, 45 }),
|
||||
LeaveOpen = true,
|
||||
};
|
||||
|
||||
using (var split = new SplitJoinStream(options)) { }
|
||||
Assert.True(options.Writable.CanWrite);
|
||||
Assert.True(options.Readable.CanWrite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanWrite()
|
||||
{
|
||||
const string StringToWrite = "foo";
|
||||
using (var writable = new MemoryStream())
|
||||
{
|
||||
using (var split = new SplitJoinStream(new SplitJoinStreamOptions { Writable = writable, LeaveOpen = true }))
|
||||
{
|
||||
await split.WriteAsync(StringToWrite);
|
||||
}
|
||||
|
||||
writable.Position = 0;
|
||||
using (var reader = new StreamReader(writable))
|
||||
{
|
||||
string actual = reader.ReadToEnd();
|
||||
Assert.Equal(StringToWrite + DefaultDelimiter, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCancelWrite()
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
using (var split = new SplitJoinStream(new SplitJoinStreamOptions { Writable = new MemoryStream() }))
|
||||
{
|
||||
await Assert.ThrowsAsync(typeof(TaskCanceledException), async () => await split.WriteAsync("foo", cts.Token));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunReadTest(SplitJoinStreamOptions options, string input, params string[] expectedReads)
|
||||
{
|
||||
var encoding = options.Encoding ?? Encoding.UTF8;
|
||||
options.Readable = new MemoryStream(encoding.GetBytes(input));
|
||||
using (var split = new SplitJoinStream(options))
|
||||
{
|
||||
foreach (string expectedRead in expectedReads)
|
||||
{
|
||||
string message = await split.ReadAsync();
|
||||
Assert.Equal(expectedRead, message);
|
||||
}
|
||||
|
||||
string messageAfterStreamEnds = await split.ReadAsync();
|
||||
Assert.Null(messageAfterStreamEnds);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>
|
||||
</RootNamespace>
|
||||
<AssemblyName>StreamJsonRpc.Tests</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- A reference to the entire .NET Framework is automatically included -->
|
||||
<ProjectReference Include="..\StreamJsonRpc\StreamJsonRpc.csproj">
|
||||
<Project>{dfbd1bca-eae0-4454-9e97-fa9bd9a0f03a}</Project>
|
||||
<Name>StreamJsonRpc</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="JsonRpcRawStreamTests.cs" />
|
||||
<Compile Include="JsonRpcTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SplitJoinStreamTests.cs" />
|
||||
<Compile Include="WrappedStream.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="project.json" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,211 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
internal class WrappedStream : Stream
|
||||
{
|
||||
protected readonly Stream stream;
|
||||
|
||||
private bool isConnected;
|
||||
private bool isEndReached;
|
||||
private bool disposed;
|
||||
private EventHandler disconnectedListeners;
|
||||
|
||||
public WrappedStream(Stream stream)
|
||||
{
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
public bool Disposed => this.disposed;
|
||||
|
||||
public bool IsEndReached => this.isEndReached;
|
||||
|
||||
#region overridden Stream members
|
||||
|
||||
public override bool CanRead => this.stream.CanRead;
|
||||
|
||||
public override bool CanSeek => this.stream.CanSeek;
|
||||
|
||||
public override bool CanTimeout => this.stream.CanTimeout;
|
||||
|
||||
public override bool CanWrite => this.stream.CanWrite;
|
||||
|
||||
public override long Length => this.stream.Length;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.stream.Position;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
this.stream.Position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override int ReadTimeout => stream.ReadTimeout;
|
||||
|
||||
public override int WriteTimeout => this.stream.WriteTimeout;
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
bool result = this.GetConnected();
|
||||
this.SetConnected(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool GetConnected()
|
||||
{
|
||||
return !this.isEndReached && !this.disposed;
|
||||
}
|
||||
|
||||
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
return this.stream.CopyToAsync(destination, bufferSize, cancellationToken);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
this.stream.Flush();
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
return this.stream.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
int result = this.stream.Read(buffer, offset, count);
|
||||
if (result == 0)
|
||||
{
|
||||
this.EndReached();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
int result = await this.stream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
if (result == 0)
|
||||
{
|
||||
this.EndReached();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
int result = this.stream.ReadByte();
|
||||
if (result == -1)
|
||||
{
|
||||
this.EndReached();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
return this.stream.Seek(offset, origin);
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
this.stream.SetLength(value);
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
this.stream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
await this.stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
this.UpdateConnectedState();
|
||||
this.stream.WriteByte(value);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (!this.disposed)
|
||||
{
|
||||
this.disposed = true;
|
||||
this.stream.Dispose();
|
||||
this.UpdateConnectedState();
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public event EventHandler Disconnected
|
||||
{
|
||||
add
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
this.disconnectedListeners += value;
|
||||
this.UpdateConnectedState();
|
||||
}
|
||||
}
|
||||
remove
|
||||
{
|
||||
this.disconnectedListeners -= value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void UpdateConnectedState()
|
||||
{
|
||||
this.SetConnected(this.GetConnected());
|
||||
}
|
||||
|
||||
private void EndReached()
|
||||
{
|
||||
if (!this.isEndReached)
|
||||
{
|
||||
this.isEndReached = true;
|
||||
this.UpdateConnectedState();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetConnected(bool value)
|
||||
{
|
||||
if (this.isConnected != value)
|
||||
{
|
||||
this.isConnected = value;
|
||||
if (!value)
|
||||
{
|
||||
this.disconnectedListeners?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"Nerdbank.FullDuplexStream": "1.0.1",
|
||||
"xunit": "2.1.0",
|
||||
"xunit.runner.visualstudio": "2.1.0"
|
||||
},
|
||||
"frameworks": {
|
||||
"net46": { }
|
||||
},
|
||||
"runtimes": {
|
||||
"win": { }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25401.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamJsonRpc", "StreamJsonRpc\StreamJsonRpc.csproj", "{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamJsonRpc.Tests", "StreamJsonRpc.Tests\StreamJsonRpc.Tests.csproj", "{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StreamJsonRpc.Desktop", "StreamJsonRpc.Desktop\StreamJsonRpc.Desktop.csproj", "{C8FE34FA-B409-4A57-A16F-4407AF728C65}"
|
||||
EndProject
|
||||
Project("{FF286327-C783-4F7A-AB73-9BCBAD0D4460}") = "StreamJsonRpc.NuGet", "StreamJsonRpc.NuGet\StreamJsonRpc.NuGet.nuproj", "{927450A5-18BF-4378-8421-7A7C6864B6EA}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "StreamJsonRpc.Shared", "StreamJsonRpc.Shared\StreamJsonRpc.Shared.shproj", "{DF6B6DC7-7C77-42F2-84CB-E520B2771375}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
StreamJsonRpc.Shared\StreamJsonRpc.Shared.projitems*{c8fe34fa-b409-4a57-a16f-4407af728c65}*SharedItemsImports = 4
|
||||
StreamJsonRpc.Shared\StreamJsonRpc.Shared.projitems*{df6b6dc7-7c77-42f2-84cb-e520b2771375}*SharedItemsImports = 13
|
||||
StreamJsonRpc.Shared\StreamJsonRpc.Shared.projitems*{dfbd1bca-eae0-4454-9e97-fa9bd9a0f03a}*SharedItemsImports = 4
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8BF355B2-E3B0-4615-BFC1-7563EADC4F8B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C8FE34FA-B409-4A57-A16F-4407AF728C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C8FE34FA-B409-4A57-A16F-4407AF728C65}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C8FE34FA-B409-4A57-A16F-4407AF728C65}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C8FE34FA-B409-4A57-A16F-4407AF728C65}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{927450A5-18BF-4378-8421-7A7C6864B6EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{927450A5-18BF-4378-8421-7A7C6864B6EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{927450A5-18BF-4378-8421-7A7C6864B6EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{927450A5-18BF-4378-8421-7A7C6864B6EA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,289 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace StreamJsonRpc {
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StreamJsonRpc.Resources", typeof(Resources).GetTypeInfo().Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Both readable and writable are null..
|
||||
/// </summary>
|
||||
internal static string BothReadableWritableAreNull {
|
||||
get {
|
||||
return ResourceManager.GetString("BothReadableWritableAreNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Got a request to execute '{0}' but have no callback object. Dropping the request..
|
||||
/// </summary>
|
||||
internal static string DroppingRequestDueToNoTargetObject {
|
||||
get {
|
||||
return ResourceManager.GetString("DroppingRequestDueToNoTargetObject", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delimiter is empty string..
|
||||
/// </summary>
|
||||
internal static string EmptyDelimiter {
|
||||
get {
|
||||
return ResourceManager.GetString("EmptyDelimiter", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error writing JSON RPC Result: {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string ErrorWritingJsonRpcResult {
|
||||
get {
|
||||
return ResourceManager.GetString("ErrorWritingJsonRpcResult", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Failure deserializing incoming JSON RPC '{0}': {1}.
|
||||
/// </summary>
|
||||
internal static string FailureDeserializingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("FailureDeserializingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JSON RPC must not be null..
|
||||
/// </summary>
|
||||
internal static string JsonRpcCannotBeNull {
|
||||
get {
|
||||
return ResourceManager.GetString("JsonRpcCannotBeNull", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} has ref or out parameter(s), which is not supported.
|
||||
/// </summary>
|
||||
internal static string MethodHasRefOrOutParameters {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodHasRefOrOutParameters", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} is not public.
|
||||
/// </summary>
|
||||
internal static string MethodIsNotPublic {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodIsNotPublic", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to '{0}' method name has different case from requested '{1}'.
|
||||
/// </summary>
|
||||
internal static string MethodNameCaseIsDifferent {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodNameCaseIsDifferent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} parameter(s): {1}, but the request supplies {2}.
|
||||
/// </summary>
|
||||
internal static string MethodParameterCountDoesNotMatch {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodParameterCountDoesNotMatch", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} parameters are not compatible with the request: {1}.
|
||||
/// </summary>
|
||||
internal static string MethodParametersNotCompatible {
|
||||
get {
|
||||
return ResourceManager.GetString("MethodParametersNotCompatible", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to More than one target method found: {0}..
|
||||
/// </summary>
|
||||
internal static string MoreThanOneMethodFound {
|
||||
get {
|
||||
return ResourceManager.GetString("MoreThanOneMethodFound", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reached end of stream..
|
||||
/// </summary>
|
||||
internal static string ReachedEndOfStream {
|
||||
get {
|
||||
return ResourceManager.GetString("ReachedEndOfStream", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Readable cannot read..
|
||||
/// </summary>
|
||||
internal static string ReadableCannotRead {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadableCannotRead", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Readable is not set..
|
||||
/// </summary>
|
||||
internal static string ReadableNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadableNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Reading JSON RPC from the stream failed with {0}: {1}.
|
||||
/// </summary>
|
||||
internal static string ReadingJsonRpcStreamFailed {
|
||||
get {
|
||||
return ResourceManager.GetString("ReadingJsonRpcStreamFailed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Response is not error..
|
||||
/// </summary>
|
||||
internal static string ResponseIsNotError {
|
||||
get {
|
||||
return ResourceManager.GetString("ResponseIsNotError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Stream has been disposed.
|
||||
/// </summary>
|
||||
internal static string StreamDisposed {
|
||||
get {
|
||||
return ResourceManager.GetString("StreamDisposed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The task is not completed..
|
||||
/// </summary>
|
||||
internal static string TaskNotCompleted {
|
||||
get {
|
||||
return ResourceManager.GetString("TaskNotCompleted", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The task was cancelled..
|
||||
/// </summary>
|
||||
internal static string TaskWasCancelled {
|
||||
get {
|
||||
return ResourceManager.GetString("TaskWasCancelled", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to find method '{0}/{1}' on {2} for the following reasons: {3}.
|
||||
/// </summary>
|
||||
internal static string UnableToFindMethod {
|
||||
get {
|
||||
return ResourceManager.GetString("UnableToFindMethod", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unexpected error processing JSON RPC '{0}': {1}.
|
||||
/// </summary>
|
||||
internal static string UnexpectedErrorProcessingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("UnexpectedErrorProcessingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unrecognized incoming JSON RPC '{0}'.
|
||||
/// </summary>
|
||||
internal static string UnrecognizedIncomingJsonRpc {
|
||||
get {
|
||||
return ResourceManager.GetString("UnrecognizedIncomingJsonRpc", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Writable cannot write..
|
||||
/// </summary>
|
||||
internal static string WritableCannotWrite {
|
||||
get {
|
||||
return ResourceManager.GetString("WritableCannotWrite", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Writable is not set..
|
||||
/// </summary>
|
||||
internal static string WritableNotSet {
|
||||
get {
|
||||
return ResourceManager.GetString("WritableNotSet", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="BothReadableWritableAreNull" xml:space="preserve">
|
||||
<value>Both readable and writable are null.</value>
|
||||
</data>
|
||||
<data name="DroppingRequestDueToNoTargetObject" xml:space="preserve">
|
||||
<value>Got a request to execute '{0}' but have no callback object. Dropping the request.</value>
|
||||
<comment>{0} is the method name to execute.</comment>
|
||||
</data>
|
||||
<data name="EmptyDelimiter" xml:space="preserve">
|
||||
<value>Delimiter is empty string.</value>
|
||||
</data>
|
||||
<data name="ErrorWritingJsonRpcResult" xml:space="preserve">
|
||||
<value>Error writing JSON RPC Result: {0}: {1}</value>
|
||||
<comment>{0} is the exception type, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="FailureDeserializingJsonRpc" xml:space="preserve">
|
||||
<value>Failure deserializing incoming JSON RPC '{0}': {1}</value>
|
||||
<comment>{0} is the JSON RPC, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="JsonRpcCannotBeNull" xml:space="preserve">
|
||||
<value>JSON RPC must not be null.</value>
|
||||
</data>
|
||||
<data name="MethodHasRefOrOutParameters" xml:space="preserve">
|
||||
<value>{0} has ref or out parameter(s), which is not supported</value>
|
||||
<comment>{0} is the method signature.</comment>
|
||||
</data>
|
||||
<data name="MethodIsNotPublic" xml:space="preserve">
|
||||
<value>{0} is not public</value>
|
||||
<comment>{0} is the method signature.</comment>
|
||||
</data>
|
||||
<data name="MethodNameCaseIsDifferent" xml:space="preserve">
|
||||
<value>'{0}' method name has different case from requested '{1}'</value>
|
||||
<comment>{0} is the method signature, {1} is the requested method name.</comment>
|
||||
</data>
|
||||
<data name="MethodParameterCountDoesNotMatch" xml:space="preserve">
|
||||
<value>{0} parameter(s): {1}, but the request supplies {2}</value>
|
||||
<comment>{0} is the method signature, {1} is the method parameter count, {2} is the request parameter count.</comment>
|
||||
</data>
|
||||
<data name="MethodParametersNotCompatible" xml:space="preserve">
|
||||
<value>{0} parameters are not compatible with the request: {1}</value>
|
||||
<comment>{0} is the method signature, {1} is the error message.</comment>
|
||||
</data>
|
||||
<data name="MoreThanOneMethodFound" xml:space="preserve">
|
||||
<value>More than one target method found: {0}.</value>
|
||||
<comment>{0} is the list of method signatures.</comment>
|
||||
</data>
|
||||
<data name="ReachedEndOfStream" xml:space="preserve">
|
||||
<value>Reached end of stream.</value>
|
||||
</data>
|
||||
<data name="ReadableCannotRead" xml:space="preserve">
|
||||
<value>Readable cannot read.</value>
|
||||
</data>
|
||||
<data name="ReadableNotSet" xml:space="preserve">
|
||||
<value>Readable is not set.</value>
|
||||
</data>
|
||||
<data name="ReadingJsonRpcStreamFailed" xml:space="preserve">
|
||||
<value>Reading JSON RPC from the stream failed with {0}: {1}</value>
|
||||
<comment>{0} is the exception type, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="ResponseIsNotError" xml:space="preserve">
|
||||
<value>Response is not error.</value>
|
||||
</data>
|
||||
<data name="StreamDisposed" xml:space="preserve">
|
||||
<value>Stream has been disposed</value>
|
||||
</data>
|
||||
<data name="TaskNotCompleted" xml:space="preserve">
|
||||
<value>The task is not completed.</value>
|
||||
</data>
|
||||
<data name="TaskWasCancelled" xml:space="preserve">
|
||||
<value>The task was cancelled.</value>
|
||||
</data>
|
||||
<data name="UnableToFindMethod" xml:space="preserve">
|
||||
<value>Unable to find method '{0}/{1}' on {2} for the following reasons: {3}</value>
|
||||
<comment>{0} is the method name, {1} is arity, {2} is target class full name, {3} is the error list.</comment>
|
||||
</data>
|
||||
<data name="UnexpectedErrorProcessingJsonRpc" xml:space="preserve">
|
||||
<value>Unexpected error processing JSON RPC '{0}': {1}</value>
|
||||
<comment>{0} is the json, {1} is the exception message.</comment>
|
||||
</data>
|
||||
<data name="UnrecognizedIncomingJsonRpc" xml:space="preserve">
|
||||
<value>Unrecognized incoming JSON RPC '{0}'</value>
|
||||
<comment>{0} is the json.</comment>
|
||||
</data>
|
||||
<data name="WritableCannotWrite" xml:space="preserve">
|
||||
<value>Writable cannot write.</value>
|
||||
</data>
|
||||
<data name="WritableNotSet" xml:space="preserve">
|
||||
<value>Writable is not set.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{DFBD1BCA-EAE0-4454-9E97-FA9BD9A0F03A}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>StreamJsonRpc</RootNamespace>
|
||||
<AssemblyName>StreamJsonRpc</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkProfile>Profile111</TargetFrameworkProfile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="project.json" />
|
||||
<!-- A reference to the entire .NET Framework is automatically included -->
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DesignTime>True</DesignTime>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
<Import Project="..\StreamJsonRpc.Shared\StreamJsonRpc.Shared.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"MicroBuild.VisualStudio": {
|
||||
"version": "1.0.117-rc",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"MicroBuild.Core": {
|
||||
"version": "0.2.0",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"Nerdbank.GitVersioning": {
|
||||
"version": "1.4.19",
|
||||
"suppressParent": "none"
|
||||
},
|
||||
"Newtonsoft.Json": "6.0.6",
|
||||
"NuSpec.ReferenceGenerator": "1.4.2",
|
||||
"Microsoft.VisualStudio.Threading": "14.1.114",
|
||||
"ReadOnlySourceTree": {
|
||||
"version": "0.1.36-beta",
|
||||
"suppressParent": "none"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
".NETPortable,Version=v4.5,Profile=Profile111": { }
|
||||
},
|
||||
"runtimes": {
|
||||
"win": { }
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче