diff --git a/.gitignore b/.gitignore index 44e70d3..1521050 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,7 @@ obj buildlog.txt build package +/.vs/Spring.Rest.2019/DesignTimeBuild +/packages +*.nupkg +/.vs diff --git a/NetStandard-Spring.Rest.build b/NetStandard-Spring.Rest.build new file mode 100644 index 0000000..14355ad --- /dev/null +++ b/NetStandard-Spring.Rest.build @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Spring.Rest.2019.sln b/Spring.Rest.2019.sln new file mode 100644 index 0000000..13de22d --- /dev/null +++ b/Spring.Rest.2019.sln @@ -0,0 +1,51 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28803.202 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spring.Http.Converters.NJson", "netstandard\Spring.Http.Converters.NJson\Spring.Http.Converters.NJson.csproj", "{913F4241-F1CC-41F8-BF57-9FC50C0A7438}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "net-standard", "net-standard", "{F8F3A420-F5AC-402A-8F96-32134079EAD0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spring.Rest", "netstandard\Spring.Rest\Spring.Rest.csproj", "{A965978B-4A95-47D1-872E-34ACF2B289C9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spring.Rest.Testing", "netstandard\Spring.Rest.Testing\Spring.Rest.Testing.csproj", "{CFFEC718-01D5-4816-9361-1145811A280E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spring.Rest.Tests", "netstandard\Spring.Rest.Tests\Spring.Rest.Tests.csproj", "{A5CA3523-08A4-4EA4-B082-12F6659365DA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {913F4241-F1CC-41F8-BF57-9FC50C0A7438}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {913F4241-F1CC-41F8-BF57-9FC50C0A7438}.Debug|Any CPU.Build.0 = Debug|Any CPU + {913F4241-F1CC-41F8-BF57-9FC50C0A7438}.Release|Any CPU.ActiveCfg = Release|Any CPU + {913F4241-F1CC-41F8-BF57-9FC50C0A7438}.Release|Any CPU.Build.0 = Release|Any CPU + {A965978B-4A95-47D1-872E-34ACF2B289C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A965978B-4A95-47D1-872E-34ACF2B289C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A965978B-4A95-47D1-872E-34ACF2B289C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A965978B-4A95-47D1-872E-34ACF2B289C9}.Release|Any CPU.Build.0 = Release|Any CPU + {CFFEC718-01D5-4816-9361-1145811A280E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFFEC718-01D5-4816-9361-1145811A280E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFFEC718-01D5-4816-9361-1145811A280E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFFEC718-01D5-4816-9361-1145811A280E}.Release|Any CPU.Build.0 = Release|Any CPU + {A5CA3523-08A4-4EA4-B082-12F6659365DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5CA3523-08A4-4EA4-B082-12F6659365DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5CA3523-08A4-4EA4-B082-12F6659365DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5CA3523-08A4-4EA4-B082-12F6659365DA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {913F4241-F1CC-41F8-BF57-9FC50C0A7438} = {F8F3A420-F5AC-402A-8F96-32134079EAD0} + {A965978B-4A95-47D1-872E-34ACF2B289C9} = {F8F3A420-F5AC-402A-8F96-32134079EAD0} + {CFFEC718-01D5-4816-9361-1145811A280E} = {F8F3A420-F5AC-402A-8F96-32134079EAD0} + {A5CA3523-08A4-4EA4-B082-12F6659365DA} = {F8F3A420-F5AC-402A-8F96-32134079EAD0} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + NAntAddinLastFileName = Spring.build + SolutionGuid = {B28287A7-8FB3-443E-99A7-C43E0233F282} + EndGlobalSection +EndGlobal diff --git a/Spring.Rest.NetCore.build b/Spring.Rest.NetCore.build new file mode 100644 index 0000000..109c7cb --- /dev/null +++ b/Spring.Rest.NetCore.build @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build-netcore-nuget.cmd b/build-netcore-nuget.cmd new file mode 100644 index 0000000..7773d83 --- /dev/null +++ b/build-netcore-nuget.cmd @@ -0,0 +1,5 @@ +@echo off +@echo Running full Build Script, capturing output to buildlog.txt file... +tools\NAnt\bin\nant.exe nuget -f:Spring.Rest.NetCore.build > buildlog.txt +@echo Launching text file viewer to display buildlog.txt contents... +start "ignored but required placeholder window title argument" buildlog.txt \ No newline at end of file diff --git a/lib/net/2.0/Common.Logging.Core.dll b/lib/net/2.0/Common.Logging.Core.dll new file mode 100644 index 0000000..e01ff3d Binary files /dev/null and b/lib/net/2.0/Common.Logging.Core.dll differ diff --git a/lib/net/2.0/Common.Logging.Core.xml b/lib/net/2.0/Common.Logging.Core.xml new file mode 100644 index 0000000..193c514 --- /dev/null +++ b/lib/net/2.0/Common.Logging.Core.xml @@ -0,0 +1,876 @@ + + + + Common.Logging.Core + + + + + Indicates that the marked method builds string by format pattern and (optional) arguments. + Parameter, which contains format string, should be given in constructor. The format string + should be in -like form + + + [StringFormatMethod("message")] + public void ShowError(string message, params object[] args) { /* do something */ } + public void Foo() { + ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + } + + + + + Specifies which parameter of an annotated method should be treated as format-string + + + + + The name of the string parameter being formatted + + + + + The type of method that is passed into e.g. + and allows the callback method to "submit" it's message to the underlying output system. + + the format argument as in + the argument list as in + + Erich Eichinger + + + + Interface for basic operations to read .NET application configuration information. + + Provides a simple abstraction to handle BCL API differences between .NET 1.x and 2.0. Also + useful for testing scenarios. + Mark Pollack + + + + Parses the configuration section and returns the resulting object. + + +

+ Primary purpose of this method is to allow us to parse and + load configuration sections using the same API regardless + of the .NET framework version. +

+ + See also System.Configuration.ConfigurationManager +
+ Name of the configuration section. + Object created by a corresponding IConfigurationSectionHandler. +
+ + + A simple logging interface abstracting logging APIs. + + + + Implementations should defer calling a message's until the message really needs + to be logged to avoid performance penalties. + + + Each log method offers to pass in a instead of the actual message. + Using this style has the advantage to defer possibly expensive message argument evaluation and formatting (and formatting arguments!) until the message gets + actually logged. If the message is not logged at all (e.g. due to settings), + you won't have to pay the peformance penalty of creating the message. + + + + The example below demonstrates using callback style for creating the message, where the call to the + and the underlying only happens, if level is enabled: + + Log.Debug( m=>m("result is {0}", random.NextDouble()) ); + Log.Debug(delegate(m) { m("result is {0}", random.NextDouble()); }); + + + + Mark Pollack + Bruno Baia + Erich Eichinger + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Debug. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Info. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Warn. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Error. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Fatal. + + + + Checks if this logger is enabled for the level. + + + + + Checks if this logger is enabled for the level. + + + + + Checks if this logger is enabled for the level. + + + + + Checks if this logger is enabled for the level. + + + + + Checks if this logger is enabled for the level. + + + + + Checks if this logger is enabled for the level. + + + + + Returns the global context for variables + + + + + Returns the thread-specific context for variables + + + + + LoggerFactoryAdapter interface is used internally by LogManager + Only developers wishing to write new Common.Logging adapters need to + worry about this interface. + + Gilles Bayon + + + + Get a ILog instance by type. + + The type to use for the logger + + + + + Get a ILog instance by key. + + The key of the logger + + + + + Interface for LogManager + + + + + Reset the infrastructure to its default settings. This means, that configuration settings + will be re-read from section <common/logging> of your app.config. + + + This is mainly used for unit testing, you wouldn't normally use this in your applications.
+ Note: instances already handed out from this LogManager are not(!) affected. + Resetting LogManager only affects new instances being handed out. +
+
+ + + Reset the infrastructure to its default settings. This means, that configuration settings + will be re-read from section <common/logging> of your app.config. + + + This is mainly used for unit testing, you wouldn't normally use this in your applications.
+ Note: instances already handed out from this LogManager are not(!) affected. + Resetting LogManager only affects new instances being handed out. +
+ + the instance to obtain settings for + re-initializing the LogManager. + +
+ + + Gets the logger by calling + on the currently configured using the type of the calling class. + + + This method needs to inspect the StackTrace in order to determine the calling + class. This of course comes with a performance penalty, thus you shouldn't call it too + often in your application. + + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + The type. + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified key. + + The key. + the logger instance obtained from the current + + + + The key of the default configuration section to read settings from. + + + You can always change the source of your configuration settings by setting another instance + on . + + + + + Gets the configuration reader used to initialize the LogManager. + + Primarily used for testing purposes but maybe useful to obtain configuration + information from some place other than the .NET application configuration file. + The configuration reader. + + + + Gets or sets the adapter. + + The adapter. + + + + A context for logger variables + + + + + Sets the value of a new or existing variable within the global context + + The key of the variable that is to be added + The value to add + + + + Gets the value of a variable within the global context + + The key of the variable to get + The value or null if not found + + + + Checks if a variable is set within the global context + + The key of the variable to check for + True if the variable is set + + + + Removes a variable from the global context by key + + The key of the variable to remove + + + + Clears the global context variables + + + + + The 7 possible logging levels + + Gilles Bayon + + + + All logging levels + + + + + A trace logging level + + + + + A debug logging level + + + + + A info logging level + + + + + A warn logging level + + + + + An error logging level + + + + + A fatal logging level + + + + + Do not log anything. + + +
+
diff --git a/lib/net/2.0/Common.Logging.dll b/lib/net/2.0/Common.Logging.dll index 5c1feac..7eb4c37 100644 Binary files a/lib/net/2.0/Common.Logging.dll and b/lib/net/2.0/Common.Logging.dll differ diff --git a/lib/net/2.0/Common.Logging.xml b/lib/net/2.0/Common.Logging.xml index 0c0c340..e23b214 100644 --- a/lib/net/2.0/Common.Logging.xml +++ b/lib/net/2.0/Common.Logging.xml @@ -1,3094 +1,2774 @@ - - - - Common.Logging - - - - - Various utility methods for using during factory and logger instance configuration - - Erich Eichinger - - - - Initialize all members before any of this class' methods can be accessed (avoids beforeFieldInit) - - - - - Adds the parser to the list of known type parsers. - - - .NET intrinsic types are pre-registerd: short, int, long, float, double, decimal, bool - - - - - Retrieves the named value from the specified . - - may be null - the value's key - if is not null, the value returned by values[name]. null otherwise. - - - - Retrieves the named value from the specified . - - may be null - the value's key - the default value, if not found - if is not null, the value returned by values[name]. null otherwise. - - - - Returns the first nonnull, nonempty value among its arguments. - - - Returns null, if the initial list was null or empty. - - - - - - Returns the first nonnull, nonempty value among its arguments. - - - Also - - - - - Tries parsing into an enum of the type of . - - the default value to return if parsing fails - the string value to parse - the successfully parsed value, otherwise. - - - - Tries parsing into the specified return type. - - the default value to return if parsing fails - the string value to parse - the successfully parsed value, otherwise. - - - - Throws a if is null. - - - - - Throws a if is null. - - - - - Throws a if an object of type is not - assignable to type . - - - - - Throws a if an object of type is not - assignable to type . - - - - - Ensures any exception thrown by the given is wrapped with an - . - - - If already throws a ConfigurationException, it will not be wrapped. - - the action to execute - the message to be set on the thrown - args to be passed to to format the message - - - - Ensures any exception thrown by the given is wrapped with an - . - - - If already throws a ConfigurationException, it will not be wrapped. - - the action to execute - the message to be set on the thrown - args to be passed to to format the message - - - - A delegate converting a string representation into the target type - - - - - An anonymous action delegate with no arguments and no return value. - - - - - - An anonymous action delegate with no arguments and no return value. - - - - - - Implementation of that uses the standard .NET - configuration APIs, ConfigurationSettings in 1.x and ConfigurationManager in 2.0 - - Mark Pollack - - - - Interface for basic operations to read .NET application configuration information. - - Provides a simple abstraction to handle BCL API differences between .NET 1.x and 2.0. Also - useful for testing scenarios. - Mark Pollack - - - - Parses the configuration section and returns the resulting object. - - -

- Primary purpose of this method is to allow us to parse and - load configuration sections using the same API regardless - of the .NET framework version. -

-
- Name of the configuration section. - Object created by a corresponding . - -
- - - Parses the configuration section and returns the resulting object. - - Name of the configuration section. - - Object created by a corresponding . - - -

- Primary purpose of this method is to allow us to parse and - load configuration sections using the same API regardless - of the .NET framework version. -

-
- -
- - - Container used to hold configuration information from config file. - - Gilles Bayon - - - - - - - The type - that will be used for creating - - - Additional user supplied properties that are passed to the - 's constructor. - - - - - The type that will be used for creating - instances. - - - - - Additional user supplied properties that are passed to the 's constructor. - - - - - This namespace contains various utility classes. - - - - - An implementation of that caches loggers handed out by this factory. - - - Implementors just need to override . - - Erich Eichinger - - - - LoggerFactoryAdapter interface is used internally by LogManager - Only developers wishing to write new Common.Logging adapters need to - worry about this interface. - - Gilles Bayon - - - - Get a ILog instance by type. - - The type to use for the logger - - - - - Get a ILog instance by name. - - The name of the logger - - - - - Creates a new - - - - - - Purges all loggers from cache - - - - - Create the specified named logger instance - - - Derived factories need to implement this method to create the - actual logger instance. - - - - - Get a ILog instance by . - - Usually the of the current class. - - An ILog instance either obtained from the internal cache or created by a call to . - - - - - Get a ILog instance by name. - - Usually a 's Name or FullName property. - - An ILog instance either obtained from the internal cache or created by a call to . - - - - - Get or create a ILog instance by name. - - Usually a 's Name or FullName property. - - An ILog instance either obtained from the internal cache or created by a call to . - - - - - Provides base implementation common for most logger adapters - - Erich Eichinger - - - - A simple logging interface abstracting logging APIs. - - - - Implementations should defer calling a message's until the message really needs - to be logged to avoid performance penalties. - - - Each log method offers to pass in a instead of the actual message. - Using this style has the advantage to defer possibly expensive message argument evaluation and formatting (and formatting arguments!) until the message gets - actually logged. If the message is not logged at all (e.g. due to settings), - you won't have to pay the peformance penalty of creating the message. - - - - The example below demonstrates using callback style for creating the message, where the call to the - and the underlying only happens, if level is enabled: - - Log.Debug( m=>m("result is {0}", random.NextDouble()) ); - Log.Debug(delegate(m) { m("result is {0}", random.NextDouble()); }); - - - - Mark Pollack - Bruno Baia - Erich Eichinger - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Debug. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Info. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Warn. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Error. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Fatal. - - - - Checks if this logger is enabled for the level. - - - - - Checks if this logger is enabled for the level. - - - - - Checks if this logger is enabled for the level. - - - - - Checks if this logger is enabled for the level. - - - - - Checks if this logger is enabled for the level. - - - - - Checks if this logger is enabled for the level. - - - - - Holds the method for writing a message to the log system. - - - - - Creates a new logger instance using for - writing log events to the underlying log system. - - - - - - Override this method to use a different method than - for writing log events to the underlying log system. - - - Usually you don't need to override thise method. The default implementation returns - null to indicate that the default handler should be - used. - - - - - Actually sends the message to the underlying log system. - - the level of this log event. - the message to log - the exception to log (may be null) - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack trace of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack trace. - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack Debug of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack Debug. - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Debug. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Debug. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack Info of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack Info. - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Info. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Info. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack Warn of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack Warn. - - - - Log a message with the level. - - An that supplies culture-specific formatting Warnrmation. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting Warnrmation. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Warn. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Warn. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack Error of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack Error. - - - - Log a message with the level. - - An that supplies culture-specific formatting Errorrmation. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting Errorrmation. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Error. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Error. - - - - Log a message object with the level. - - The message object to log. - - - - Log a message object with the level including - the stack Fatal of the passed - as a parameter. - - The message object to log. - The exception to log, including its stack Fatal. - - - - Log a message with the level. - - An that supplies culture-specific formatting Fatalrmation. - The format of the message object to log. - - - - - Log a message with the level. - - An that supplies culture-specific formatting Fatalrmation. - The format of the message object to log. - The exception to log. - - - - - Log a message with the level. - - The format of the message object to log. - the list of format arguments - - - - Log a message with the level. - - The format of the message object to log. - The exception to log. - the list of format arguments - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Fatal. - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Log a message with the level using a callback to obtain the message - - - Using this method avoids the cost of creating a message and evaluating message arguments - that probably won't be logged due to loglevel settings. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Fatal. - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Checks if this logger is enabled for the level. - - - Override this in your derived class to comply with the underlying logging system - - - - - Represents a method responsible for writing a message to the log system. - - - - - This namespace contains convenience base classes for implementing your own s. - - - - - Abstract class providing a standard implementation of simple loggers. - - Erich Eichinger - - - - Creates and initializes a the simple logger. - - The name, usually type name of the calling class, of the logger. - The current logging threshold. Messages recieved that are beneath this threshold will not be logged. - Include level in the log message. - Include the current time in the log message. - Include the instance name in the log message. - The date and time format to use in the log message. - - - - Appends the formatted message to the specified . - - the that receíves the formatted message. - - - - - - - Determines if the given log level is currently enabled. - - - - - - - The name of the logger. - - - - - Include the current log level in the log message. - - - - - Include the current time in the log message. - - - - - Include the instance name in the log message. - - - - - The current logging threshold. Messages recieved that are beneath this threshold will not be logged. - - - - - The date and time format to use in the log message. - - - - - Determines Whether is set. - - - - - Returns if the current is greater than or - equal to . If it is, all messages will be sent to . - - - - - Returns if the current is greater than or - equal to . If it is, all messages will be sent to . - - - - - Returns if the current is greater than or - equal to . If it is, only messages with a of - , , , and - will be sent to . - - - - - Returns if the current is greater than or - equal to . If it is, only messages with a of - , , and - will be sent to . - - - - - Returns if the current is greater than or - equal to . If it is, only messages with a of - and will be sent to . - - - - - Returns if the current is greater than or - equal to . If it is, only messages with a of - will be sent to . - - - - - Base factory implementation for creating simple instances. - - Default settings are LogLevel.All, showDateTime = true, showLogName = true, and no DateTimeFormat. - The keys in the NameValueCollection to configure this adapter are the following - - level - showDateTime - showLogName - dateTimeFormat - - - - - Gilles Bayon - Mark Pollack - Erich Eichinger - - - - Initializes a new instance of the class. - - - Looks for level, showDateTime, showLogName, dateTimeFormat items from - for use when the GetLogger methods are called. - for more information on how to use the - standard .NET application configuraiton file (App.config/Web.config) - to configure this adapter. - - The name value collection, typically specified by the user in - a configuration section named common/logging. - - - - Create the specified logger instance - - - - - Derived factories need to implement this method to create the - actual logger instance. - - a new logger instance. Must never be null! - - - - The default to use when creating new instances. - - - - - The default setting to use when creating new instances. - - - - - The default setting to use when creating new instances. - - - - - The default setting to use when creating new instances. - - - - - The default setting to use when creating new instances. - - - - - A logger created by that - sends all log events to the owning adapter's - - Erich Eichinger - - - - The adapter that created this logger instance. - - - - - Allows to retrieve the last logged event instance captured by this logger - - - - - Create a new logger instance. - - - - - Create a new and send it to - - - - - - - - A logging event captured by - - Erich Eichinger - - - - The logger that logged this event - - - - - The level used to log this event - - - - - The raw message object - - - - - A logged exception - - - - - Create a new event instance - - - - - Retrieves the formatted message text - - - - - An adapter, who's loggers capture all log events and send them to . - Retrieve the list of log events from . - - - This logger factory is mainly for debugging and test purposes. - - This is an example how you might use this adapter for testing: - - // configure for capturing - CapturingLoggerFactoryAdapter adapter = new CapturingLoggerFactoryAdapter(); - LogManager.Adapter = adapter; - - // reset capture state - adapter.Clear(); - // log something - ILog log = LogManager.GetCurrentClassLogger(); - log.DebugFormat("Current Time:{0}", DateTime.Now); - - // check logged data - Assert.AreEqual(1, adapter.LoggerEvents.Count); - Assert.AreEqual(LogLevel.Debug, adapter.LastEvent.Level); - - - - Erich Eichinger - - - - Clears all captured events - - - - - Resets the to null. - - - - - Holds the list of logged events. - - - To access this collection in a multithreaded application, put a lock on the list instance. - - - - - instances send their captured log events to this method. - - - - - Get a instance for the given type. - - - - - Get a instance for the given name. - - - - - Holds the last log event received from any of this adapter's loggers. - - - - - A implementation sending all System.Diagnostics.Trace output to - the Common.Logging infrastructure. - - - This listener captures all output sent by calls to System.Diagnostics.Trace and - and and sends it to an instance.
- The instance to be used is obtained by calling - . The name of the logger is created by passing - this listener's and any source or category passed - into this listener (see or for example). -
- - The snippet below shows how to add and configure this listener to your app.config: - - <system.diagnostics> - <sharedListeners> - <add name="Diagnostics" - type="Common.Logging.Simple.CommonLoggingTraceListener, Common.Logging" - initializeData="DefaultTraceEventType=Information; LoggerNameFormat={listenerName}.{sourceName}"> - <filter type="System.Diagnostics.EventTypeFilter" initializeData="Information"/> - </add> - </sharedListeners> - <trace> - <listeners> - <add name="Diagnostics" /> - </listeners> - </trace> - </system.diagnostics> - - - Erich Eichinger -
- - - Creates a new instance with the default name "Diagnostics" and "Trace". - - - - - Creates a new instance initialized with properties from the . string. - - - is a semicolon separated string of name/value pairs, where each pair has - the form key=value. E.g. - "Name=MyLoggerName;LogLevel=Debug" - - a semicolon separated list of name/value pairs. - - - - Creates a new instance initialized with the specified properties. - - name/value configuration properties. - - - - Logs the given message to the Common.Logging infrastructure. - - the eventType - the name or category name passed into e.g. . - the id of this event - the message format - the message arguments - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by . - - - - - Writes message to logger provided by - - - - - Writes message to logger provided by - - - - - Writes message to logger provided by - - - - - Writes message to logger provided by - - - - - Writes message to logger provided by - - - - - Writes message to logger provided by - - - - - Sets the default to use for logging - all events emitted by .Write(...) and - .WriteLine(...) methods. - - - This listener captures all output sent by calls to and - sends it to an instance using the specified - on . - - - - - Format to use for creating the logger name. Defaults to "{listenerName}.{sourceName}". - - - Available placeholders are: - - {listenerName}: the configured name of this listener instance. - {sourceName}: the trace source name an event originates from (see e.g. . - - - - - - Sends log messages to . - - Gilles Bayon - - - - Creates and initializes a logger that writes messages to . - - The name, usually type name of the calling class, of the logger. - The current logging threshold. Messages recieved that are beneath this threshold will not be logged. - Include the current log level in the log message. - Include the current time in the log message. - Include the instance name in the log message. - The date and time format to use in the log message. - - - - Do the actual logging by constructing the log message using a then - sending the output to . - - The of the message. - The log message. - An optional associated with the message. - - - - Factory for creating instances that write data to . - - - - - Gilles Bayon - Mark Pollack - Erich Eichinger - - - - Initializes a new instance of the class using default - settings. - - - - - Initializes a new instance of the class. - - - Looks for level, showDateTime, showLogName, dateTimeFormat items from - for use when the GetLogger methods are called. - for more information on how to use the - standard .NET application configuraiton file (App.config/Web.config) - to configure this adapter. - - The name value collection, typically specified by the user in - a configuration section named common/logging. - - - - Creates a new instance. - - - - - This namespace contains out-of-the-box adapters to intrinsic systems, namely - and . - - - - - Silently ignores all log messages. - - Gilles Bayon - Erich Eichinger - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack trace. - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Debug. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Debug. - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting information. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Info. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Info. - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Warnrmation. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Warnrmation. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Warn. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Warn. - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Errorrmation. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Errorrmation. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Error. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Error. - - - - Ignores message. - - - - - - Ignores message. - - - - - - - Ignores message. - - The format of the message object to log. - - - - - Ignores message. - - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Fatalrmation. - The format of the message object to log. - the list of message format arguments - - - - Ignores message. - - An that supplies culture-specific formatting Fatalrmation. - The format of the message object to log. - The exception to log. - the list of message format arguments - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Fatal. - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - - - - Ignores message. - - An that supplies culture-specific formatting information. - A callback used by the logger to obtain the message if log level is matched - The exception to log, including its stack Fatal. - - - - Always returns . - - - - - Always returns . - - - - - Always returns . - - - - - Always returns . - - - - - Always returns . - - - - - Always returns . - - - - - Factory for creating instances that silently ignores - logging requests. - - - - Gilles Bayon - - - - Constructor - - - - - Constructor - - - - - Get a ILog instance by type - - - - - - - Get a ILog instance by type name - - - - - - - Logger sending everything to the trace output stream using . - - - Beware not to use in combination with this logger as - this would result in an endless loop for obvious reasons! - - - - Gilles Bayon - Erich Eichinger - - - - Creates a new TraceLogger instance. - - whether to use or for logging. - the name of this logger - the default log level to use - Include the current log level in the log message. - Include the current time in the log message. - Include the instance name in the log message. - The date and time format to use in the log message. - - - - Determines if the given log level is currently enabled. - checks if is true. - - - - - Do the actual logging. - - - - - - - - Called after deserialization completed. - - - - - Used to defer message formatting until it is really needed. - - - This class also improves performance when multiple - s are configured. - - - - - Factory for creating instances that send - everything to the output stream. - - - Beware not to use in combination with this logger factory - as this would result in an endless loop for obvious reasons! - - - - - Gilles Bayon - Mark Pollack - Erich Eichinger - - - - Initializes a new instance of the class using default settings. - - - - - Initializes a new instance of the class. - - - Looks for level, showDateTime, showLogName, dateTimeFormat items from - for use when the GetLogger methods are called. - for more information on how to use the - standard .NET application configuraiton file (App.config/Web.config) - to configure this adapter. - - The name value collection, typically specified by the user in - a configuration section named common/logging. - - - - Creates a new instance. - - - - - Whether to use .TraceXXXX(string,object[]) methods for logging - or . - - - - - The exception that is thrown when a configuration system error has occurred with Common.Logging - - Mark Pollack - - - Creates a new instance of the ObjectsException class. - - - - Creates a new instance of the ConfigurationException class. with the specified message. - - - A message about the exception. - - - - - Creates a new instance of the ConfigurationException class with the specified message - and root cause. - - - A message about the exception. - - - The root exception that is being wrapped. - - - - - Creates a new instance of the ConfigurationException class. - - - The - that holds the serialized object data about the exception being thrown. - - - The - that contains contextual information about the source or destination. - - - - - Used in an application's configuration file (App.Config or Web.Config) to configure the logging subsystem. - - - An example configuration section that writes log messages to the Console using the - built-in Console Logger. - - <configuration> - <configSections> - <sectionGroup name="common"> - <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" /> - </sectionGroup> - </configSections> - <common> - <logging> - <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging"> - <arg key="showLogName" value="true" /> - <arg key="showDataTime" value="true" /> - <arg key="level" value="ALL" /> - <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" /> - </factoryAdapter> - </logging> - </common> - </configuration> - - - - - - Ensure static fields get initialized before any class member - can be accessed (avoids beforeFieldInit) - - - - - Constructor - - - - - Retrieves the of the logger the use by looking at the logFactoryAdapter element - of the logging configuration element. - - - - A object containing the specified type that implements - along with zero or more properties that will be - passed to the logger factory adapter's constructor as an . - - - - - Verifies that the logFactoryAdapter element appears once in the configuration section. - - settings of a parent section - atm this must always be null - Additional information about the configuration process. - The configuration section to apply an XPath query too. - - A object containing the specified logFactoryAdapter type - along with user supplied configuration properties. - - - - - Verifies that the logFactoryAdapter element appears once in the configuration section. - - The parent of the current item. - Additional information about the configuration process. - The configuration section to apply an XPath query too. - - A object containing the specified logFactoryAdapter type - along with user supplied configuration properties. - - - - - The type of method that is passed into e.g. - and allows the callback method to "submit" it's message to the underlying output system. - - the format argument as in - the argument list as in - - Erich Eichinger - - - - The 7 possible logging levels - - Gilles Bayon - - - - All logging levels - - - - - A trace logging level - - - - - A debug logging level - - - - - A info logging level - - - - - A warn logging level - - - - - An error logging level - - - - - A fatal logging level - - - - - Do not log anything. - - - - - Use the LogManager's or - methods to obtain instances for logging. - - - For configuring the underlying log system using application configuration, see the example - at . - For configuring programmatically, see the example section below. - - - The example below shows the typical use of LogManager to obtain a reference to a logger - and log an exception: - - - ILog log = LogManager.GetLogger(this.GetType()); - ... - try - { - /* .... */ - } - catch(Exception ex) - { - log.ErrorFormat("Hi {0}", ex, "dude"); - } - - - The example below shows programmatic configuration of the underlying log system: - - - // create properties - NameValueCollection properties = new NameValueCollection(); - properties["showDateTime"] = "true"; - - // set Adapter - Common.Logging.LogManager.Adapter = new - Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter(properties); - - - - - - - - Gilles Bayon - - - - The name of the default configuration section to read settings from. - - - You can always change the source of your configuration settings by setting another instance - on . - - - - - Performs static 1-time init of LogManager by calling - - - - - Reset the infrastructure to its default settings. This means, that configuration settings - will be re-read from section <common/logging> of your app.config. - - - This is mainly used for unit testing, you wouldn't normally use this in your applications.
- Note: instances already handed out from this LogManager are not(!) affected. - Resetting LogManager only affects new instances being handed out. -
-
- - - Reset the infrastructure to its default settings. This means, that configuration settings - will be re-read from section <common/logging> of your app.config. - - - This is mainly used for unit testing, you wouldn't normally use this in your applications.
- Note: instances already handed out from this LogManager are not(!) affected. - Resetting LogManager only affects new instances being handed out. -
- - the instance to obtain settings for - re-initializing the LogManager. - -
- - - Gets the logger by calling - on the currently configured using the type of the calling class. - - - This method needs to inspect the in order to determine the calling - class. This of course comes with a performance penalty, thus you shouldn't call it too - often in your application. - - - the logger instance obtained from the current - - - - Gets the logger by calling - on the currently configured using the specified type. - - the logger instance obtained from the current - - - - Gets the logger by calling - on the currently configured using the specified type. - - The type. - the logger instance obtained from the current - - - - Gets the logger by calling - on the currently configured using the specified name. - - The name. - the logger instance obtained from the current - - - - Builds the logger factory adapter. - - a factory adapter instance. Is never null. - - - - Builds a instance from the given - using . - - - the instance. Is never null - - - - Gets the configuration reader used to initialize the LogManager. - - Primarily used for testing purposes but maybe useful to obtain configuration - information from some place other than the .NET application configuration file. - The configuration reader. - - - - Gets or sets the adapter. - - The adapter. - - - - This namespace contains all core classes making up the Common.Logging framework. - - - - - This assembly contains the core functionality of the Common.Logging framework. - In particular, checkout and for usage information. - - - - - Indicates classes or members to be ignored by NCover - - - Note, the name is chosen, because TestDriven.NET uses it as //ea argument to "Test With... Coverage" - - Erich Eichinger - - - -

Overview

- - There are a variety of logging implementations for .NET currently in use, log4net, Enterprise - Library Logging, NLog, to name the most popular. The downside of having differerent implementation - is that they do not share a common interface and therefore impose a particular logging - implementation on the users of your library. To solve this dependency problem the Common.Logging - library introduces a simple abstraction to allow you to select a specific logging implementation at - runtime. - - - The library is based on work done by the developers of IBatis.NET and it's usage is inspired by - log4net. Many thanks to the developers of those projects! - -

Usage

- - The core logging library Common.Logging provides the base logging interface as - well as the global that you use to instrument your code: - - - ILog log = LogManager.GetLogger(this.GetType()); - - log.DebugFormat("Hi {0}", "dude"); - - - To output the information logged, you need to tell Common.Logging, what underlying logging system - to use. Common.Logging already includes simple console and trace based logger implementations - usable out of the box. Adding the following configuration snippet to your app.config causes - Common.Logging to output all information to the console: - - - <configuration> - <configSections> - <sectionGroup name="common"> - <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" /> - </sectionGroup> - </configSections> - - <common> - <logging> - <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging"> - <arg key="level" value="DEBUG" /> - </factoryAdapter> - </logging> - </common> - </configuration> - -

Customizing

- - In the case you want to integrate your own logging system that is not supported by Common.Logging yet, it is easily - possible to implement your own plugin by implementing . - For convenience there is a base implementation available that usually - makes implementing your own adapter a breeze. - -

<system.diagnostics> Integration

- - If your code already uses the .NET framework's built-in System.Diagnostics.Trace - system, you can use to redirect all trace output to the - Common.Logging infrastructure. - -
-
-
-
+ + + + Common.Logging + + + + + Indicates classes or members to be ignored by NCover + + + Note, the key is chosen, because TestDriven.NET uses it as //ea argument to "Test With... Coverage" + + Erich Eichinger + + + + The exception that is thrown when a configuration system error has occurred with Common.Logging + + Mark Pollack + + + Creates a new instance of the ObjectsException class. + + + + Creates a new instance of the ConfigurationException class. with the specified message. + + + A message about the exception. + + + + + Creates a new instance of the ConfigurationException class with the specified message + and root cause. + + + A message about the exception. + + + The root exception that is being wrapped. + + + + + Creates a new instance of the ConfigurationException class. + + + The + that holds the serialized object data about the exception being thrown. + + + The + that contains contextual information about the source or destination. + + + + + Various utility methods for using during factory and logger instance configuration + + Erich Eichinger + + + + Initialize all members before any of this class' methods can be accessed (avoids beforeFieldInit) + + + + + Adds the parser to the list of known type parsers. + + + .NET intrinsic types are pre-registerd: short, int, long, float, double, decimal, bool + + + + + Retrieves the named value from the specified . + + may be null + the value's key + if is not null, the value returned by values[key]. null otherwise. + + + + Retrieves the named value from the specified . + + may be null + the value's key + the default value, if not found + if is not null, the value returned by values[key]. null otherwise. + + + + Returns the first nonnull, nonempty value among its arguments. + + + Returns null, if the initial list was null or empty. + + + + + + Returns the first nonnull, nonempty value among its arguments. + + + Also + + + + + Tries parsing into an enum of the type of . + + the default value to return if parsing fails + the string value to parse + the successfully parsed value, otherwise. + + + + Tries parsing into the specified return type. + + the default value to return if parsing fails + the string value to parse + the successfully parsed value, otherwise. + + + + Throws a if is null. + + + + + Throws a if is null. + + + + + Throws a if an object of type is not + assignable to type . + + + + + Throws a if an object of type is not + assignable to type . + + + + + Ensures any exception thrown by the given is wrapped with an + . + + + If already throws a ConfigurationException, it will not be wrapped. + + the action to execute + the message to be set on the thrown + args to be passed to to format the message + + + + Ensures any exception thrown by the given is wrapped with an + . + + + If already throws a ConfigurationException, it will not be wrapped. + + the action to execute + the message to be set on the thrown + args to be passed to to format the message + + + + A delegate converting a string representation into the target type + + + + + An anonymous action delegate with no arguments and no return value. + + + + + + An anonymous action delegate with no arguments and no return value. + + + + + + Implementation of that uses the standard .NET + configuration APIs, ConfigurationSettings in 1.x and ConfigurationManager in 2.0 + + Mark Pollack + + + + Parses the configuration section and returns the resulting object. + Using the System.Configuration.ConfigurationManager + + Name of the configuration section. + + Object created by a corresponding IConfigurationSectionHandler" + + +

+ Primary purpose of this method is to allow us to parse and + load configuration sections using the same API regardless + of the .NET framework version. +

+
+
+ + + Container used to hold configuration information from config file. + + Gilles Bayon + + + + + + + The type + that will be used for creating + + + Additional user supplied properties that are passed to the + 's constructor. + + + + + The type that will be used for creating + instances. + + + + + Additional user supplied properties that are passed to the 's constructor. + + + + + Substitute NameValueCollection in System.Collections.Specialized. + + + + + Creates a new instance of NameValueCollection. + + + + + Gets the values (only a single one) for the specified key (configuration name) + + The key. + an array with one value, or null if no value exist + + + + Gets or sets the value with the specified key. + + + The value corrsponding to the key, or null if no value exist + + The key. + value store for the key + + + + Helper class for working with NameValueCollection + + + + + Convert a into the corresponding + common logging equivalent + + The properties. + + + + + An implementation of that caches loggers handed out by this factory. + + + Implementors just need to override . + + Erich Eichinger + + + + Creates a new instance, the logger cache being case-sensitive. + + + + + Creates a new instance, the logger cache being . + + + + + + Purges all loggers from cache + + + + + Create the specified named logger instance + + + Derived factories need to implement this method to create the + actual logger instance. + + + + + Get a ILog instance by . + + Usually the of the current class. + + An ILog instance either obtained from the internal cache or created by a call to . + + + + + Get a ILog instance by key. + + Usually a 's Name or FullName property. + + An ILog instance either obtained from the internal cache or created by a call to . + + + + + Get or create a ILog instance by key. + + Usually a 's Name or FullName property. + + An ILog instance either obtained from the internal cache or created by a call to . + + + + + Provides base implementation suitable for almost all logger adapters + + Erich Eichinger + + + + Holds the method for writing a message to the log system. + + + + + Creates a new logger instance using for + writing log events to the underlying log system. + + + + + + Override this method to use a different method than + for writing log events to the underlying log system. + + + Usually you don't need to override thise method. The default implementation returns + null to indicate that the default handler should be + used. + + + + + Actually sends the message to the underlying log system. + + the level of this log event. + the message to log + the exception to log (may be null) + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack trace of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack trace. + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack Debug of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack Debug. + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Debug. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Debug. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack Info of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack Info. + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Info. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Info. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack Warn of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack Warn. + + + + Log a message with the level. + + An that supplies culture-specific formatting Warnrmation. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting Warnrmation. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Warn. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Warn. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack Error of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack Error. + + + + Log a message with the level. + + An that supplies culture-specific formatting Errorrmation. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting Errorrmation. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Error. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Error. + + + + Log a message object with the level. + + The message object to log. + + + + Log a message object with the level including + the stack Fatal of the passed + as a parameter. + + The message object to log. + The exception to log, including its stack Fatal. + + + + Log a message with the level. + + An that supplies culture-specific formatting Fatalrmation. + The format of the message object to log. + + + + + Log a message with the level. + + An that supplies culture-specific formatting Fatalrmation. + The format of the message object to log. + The exception to log. + + + + + Log a message with the level. + + The format of the message object to log. + the list of format arguments + + + + Log a message with the level. + + The format of the message object to log. + The exception to log. + the list of format arguments + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Fatal. + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Log a message with the level using a callback to obtain the message + + + Using this method avoids the cost of creating a message and evaluating message arguments + that probably won't be logged due to loglevel settings. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Fatal. + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Checks if this logger is enabled for the level. + + + Override this in your derived class to comply with the underlying logging system + + + + + Returns the global context for variables + + + + + Returns the thread-specific context for variables + + + + + Format message on demand. + + + + + Initializes a new instance of the class. + + The format message callback. + + + + Initializes a new instance of the class. + + The format provider. + The format message callback. + + + + Calls and returns result. + + + + + + Format string on demand. + + + + + Initializes a new instance of the class. + + The format provider. + The message. + The args. + + + + Runs on supplied arguemnts. + + string + + + + Represents a method responsible for writing a message to the log system. + + + + + Use the LogManager's or + methods to obtain instances for logging. + + + For configuring the underlying log system using application configuration, see the example + at System.Configuration.ConfigurationManager + For configuring programmatically, see the example section below. + + + The example below shows the typical use of LogManager to obtain a reference to a logger + and log an exception: + + + ILog log = LogManager.GetLogger(this.GetType()); + ... + try + { + /* .... */ + } + catch(Exception ex) + { + log.ErrorFormat("Hi {0}", ex, "dude"); + } + + + The example below shows programmatic configuration of the underlying log system: + + + // create properties + NameValueCollection properties = new NameValueCollection(); + properties["showDateTime"] = "true"; + + // set Adapter + Common.Logging.LogManager.Adapter = new + Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter(properties); + + + + + + + Gilles Bayon + + + + Performs static 1-time init of LogManager by calling + + + + + Reset the infrastructure to its default settings. This means, that configuration settings + will be re-read from section <common/logging> of your app.config. + + + This is mainly used for unit testing, you wouldn't normally use this in your applications.
+ Note: instances already handed out from this LogManager are not(!) affected. + Resetting LogManager only affects new instances being handed out. +
+
+ + + Reset the infrastructure to its default settings. This means, that configuration settings + will be re-read from section <common/logging> of your app.config. + + + This is mainly used for unit testing, you wouldn't normally use this in your applications.
+ Note: instances already handed out from this LogManager are not(!) affected. + Resetting LogManager only affects new instances being handed out. +
+ + the instance to obtain settings for + re-initializing the LogManager. + +
+ + + Gets the logger by calling + on the currently configured using the type of the calling class. + + + This method needs to inspect the in order to determine the calling + class. This of course comes with a performance penalty, thus you shouldn't call it too + often in your application. + + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the type of the calling class. + + + This method needs to inspect the in order to determine the calling + class. This of course comes with a performance penalty, thus you shouldn't call it too + often in your application. + + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + The type. + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified type. + + The type. + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified key. + + The key. + the logger instance obtained from the current + + + + Gets the logger by calling + on the currently configured using the specified key. + + The key. + the logger instance obtained from the current + + + + Builds the logger factory adapter. + + a factory adapter instance. Is never null. + + + + Builds a instance from the given + using . + + + the instance. Is never null + + + + The key of the default configuration section to read settings from. + + + You can always change the source of your configuration settings by setting another instance + on . + + + + + The key of the default configuration section to read settings from. + + + You can always change the source of your configuration settings by setting another instance + on . + + + + + Gets the configuration reader used to initialize the LogManager. + + Primarily used for testing purposes but maybe useful to obtain configuration + information from some place other than the .NET application configuration file. + The configuration reader. + + + + Gets the configuration reader used to initialize the LogManager. + + Primarily used for testing purposes but maybe useful to obtain configuration + information from some place other than the .NET application configuration file. + The configuration reader. + + + + Gets or sets the adapter. + + The adapter. + + + + Gets or sets the adapter. + + The adapter. + + + + Abstract class providing a standard implementation of simple loggers. + + Erich Eichinger + + + + Creates and initializes a the simple logger. + + The key, usually type key of the calling class, of the logger. + The current logging threshold. Messages recieved that are beneath this threshold will not be logged. + Include level in the log message. + Include the current time in the log message. + Include the instance key in the log message. + The date and time format to use in the log message. + + + + Appends the formatted message to the specified . + + the that receíves the formatted message. + + + + + + + Determines if the given log level is currently enabled. + + + + + + + The key of the logger. + + + + + Include the current log level in the log message. + + + + + Include the current time in the log message. + + + + + Include the instance key in the log message. + + + + + The current logging threshold. Messages recieved that are beneath this threshold will not be logged. + + + + + The date and time format to use in the log message. + + + + + Determines Whether is set. + + + + + Returns if the current is greater than or + equal to . If it is, all messages will be sent to . + + + + + Returns if the current is greater than or + equal to . If it is, all messages will be sent to . + + + + + Returns if the current is greater than or + equal to . If it is, only messages with a of + , , , and + will be sent to . + + + + + Returns if the current is greater than or + equal to . If it is, only messages with a of + , , and + will be sent to . + + + + + Returns if the current is greater than or + equal to . If it is, only messages with a of + and will be sent to . + + + + + Returns if the current is greater than or + equal to . If it is, only messages with a of + will be sent to . + + + + + Base factory implementation for creating simple instances. + + Default settings are LogLevel.All, showDateTime = true, showLogName = true, and no DateTimeFormat. + The keys in the NameValueCollection to configure this adapter are the following + + level + showDateTime + showLogName + dateTimeFormat + + + Here is an example how to implement your own logging adapter: + + public class ConsoleOutLogger : AbstractSimpleLogger + { + public ConsoleOutLogger(string logName, LogLevel logLevel, bool showLevel, bool showDateTime, + bool showLogName, string dateTimeFormat) + : base(logName, logLevel, showLevel, showDateTime, showLogName, dateTimeFormat) + { + } + + protected override void WriteInternal(LogLevel level, object message, Exception e) + { + // Use a StringBuilder for better performance + StringBuilder sb = new StringBuilder(); + FormatOutput(sb, level, message, e); + + // Print to the appropriate destination + Console.Out.WriteLine(sb.ToString()); + } + } + + public class ConsoleOutLoggerFactoryAdapter : AbstractSimpleLoggerFactoryAdapter + { + public ConsoleOutLoggerFactoryAdapter(NameValueCollection properties) + : base(properties) + { } + + protected override ILog CreateLogger(string key, LogLevel level, bool showLevel, bool + showDateTime, bool showLogName, string dateTimeFormat) + { + ILog log = new ConsoleOutLogger(key, level, showLevel, showDateTime, showLogName, + dateTimeFormat); + return log; + } + } + + + + Gilles Bayon + Mark Pollack + Erich Eichinger + + + + Initializes a new instance of the class. + + + Looks for level, showDateTime, showLogName, dateTimeFormat items from + for use when the GetLogger methods are called. + System.Configuration.ConfigurationManager for more information on how to use the + standard .NET application configuraiton file (App.config/Web.config) + to configure this adapter. + + The key value collection, typically specified by the user in + a configuration section named common/logging. + + + + Initializes a new instance of the class with + default settings for the loggers created by this factory. + + + + + Create the specified logger instance + + + + + Derived factories need to implement this method to create the + actual logger instance. + + a new logger instance. Must never be null! + + + + The default to use when creating new instances. + + + + + The default setting to use when creating new instances. + + + + + The default setting to use when creating new instances. + + + + + The default setting to use when creating new instances. + + + + + The default setting to use when creating new instances. + + + + + A logger created by that + sends all log events to the owning adapter's + + Erich Eichinger + + + + The adapter that created this logger instance. + + + + + Clears all captured events + + + + + Resets the to null. + + + + + Holds the list of logged events. + + + To access this collection in a multithreaded application, put a lock on the list instance. + + + + + instances send their captured log events to this method. + + + + + Create a new logger instance. + + + + + Create a new and send it to + + + + + + + + Holds the last log event received from any of this adapter's loggers. + + + + + A logging event captured by + + Erich Eichinger + + + + The logger that logged this event + + + + + The level used to log this event + + + + + The raw message object + + + + + A logged exception + + + + + Create a new event instance + + + + + Retrieves the formatted message text + + + + + An adapter, who's loggers capture all log events and send them to . + Retrieve the list of log events from . + + + This logger factory is mainly for debugging and test purposes. + + This is an example how you might use this adapter for testing: + + // configure for capturing + CapturingLoggerFactoryAdapter adapter = new CapturingLoggerFactoryAdapter(); + LogManager.Adapter = adapter; + + // reset capture state + adapter.Clear(); + // log something + ILog log = LogManager.GetCurrentClassLogger(); + log.DebugFormat("Current Time:{0}", DateTime.Now); + + // check logged data + Assert.AreEqual(1, adapter.LoggerEvents.Count); + Assert.AreEqual(LogLevel.Debug, adapter.LastEvent.Level); + + + + Erich Eichinger + + + + Clears all captured events + + + + + Resets the to null. + + + + + Holds the list of logged events. + + + To access this collection in a multithreaded application, put a lock on the list instance. + + + + + instances send their captured log events to this method. + + + + + Get a instance for the given type. + + + + + Get a instance for the given key. + + + + + Holds the last log event received from any of this adapter's loggers. + + + + + Sends log messages to . + + Gilles Bayon + + + + Creates and initializes a logger that writes messages to . + + The key, usually type key of the calling class, of the logger. + The current logging threshold. Messages recieved that are beneath this threshold will not be logged. + Include the current log level in the log message. + Include the current time in the log message. + Include the instance key in the log message. + The date and time format to use in the log message. + + + + Do the actual logging by constructing the log message using a then + sending the output to . + + The of the message. + The log message. + An optional associated with the message. + + + + Factory for creating instances that write data using . + + + + Below is an example how to configure this adapter: + + <configuration> + + <configSections> + <sectionGroup key="common"> + <section key="logging" + type="Common.Logging.ConfigurationSectionHandler, Common.Logging" + requirePermission="false" /> + </sectionGroup> + </configSections> + + <common> + <logging> + <factoryAdapter type="Common.Logging.Simple.DebugLoggerFactoryAdapter, Common.Logging"> + <arg key="level" value="ALL" /> + </factoryAdapter> + </logging> + </common> + + </configuration> + + + + + Gilles Bayon + Mark Pollack + Erich Eichinger + + + + Initializes a new instance of the class using default + settings. + + + + + Initializes a new instance of the class. + + + Looks for level, showDateTime, showLogName, dateTimeFormat items from + for use when the GetLogger methods are called. + for more information on how to use the + standard .NET application configuraiton file (App.config/Web.config) + to configure this adapter. + + The key value collection, typically specified by the user in + a configuration section named common/logging. + + + + Initializes a new instance of the class with + default settings for the loggers created by this factory. + + + + + Creates a new instance. + + + + + + + + + Silently ignores all log messages. + + Gilles Bayon + Erich Eichinger + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack trace. + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Debug. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Debug. + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting information. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Info. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Info. + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Warnrmation. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Warnrmation. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Warn. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Warn. + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Errorrmation. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Errorrmation. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Error. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Error. + + + + Ignores message. + + + + + + Ignores message. + + + + + + + Ignores message. + + The format of the message object to log. + + + + + Ignores message. + + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Fatalrmation. + The format of the message object to log. + the list of message format arguments + + + + Ignores message. + + An that supplies culture-specific formatting Fatalrmation. + The format of the message object to log. + The exception to log. + the list of message format arguments + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Fatal. + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + + + + Ignores message. + + An that supplies culture-specific formatting information. + A callback used by the logger to obtain the message if log level is matched + The exception to log, including its stack Fatal. + + + + Always returns . + + + + + Always returns . + + + + + Always returns . + + + + + Always returns . + + + + + Always returns . + + + + + Always returns . + + + + + Returns the global context for variables + + + + + Returns the thread-specific context for variables + + + + + Factory for creating instances that silently ignores + logging requests. + + + This logger adapter is the default used by Common.Logging if unconfigured. Using this logger adapter is the most efficient + way to suppress any logging output. + + Below is an example how to configure this adapter: + + <configuration> + + <configSections> + <sectionGroup key="common"> + <section key="logging" + type="Common.Logging.ConfigurationSectionHandler, Common.Logging" + requirePermission="false" /> + </sectionGroup> + </configSections> + + <common> + <logging> + <factoryAdapter type="Common.Logging.Simple.NoOpLoggerFactoryAdapter, Common.Logging"> + <arg key="level" value="ALL" /> + </factoryAdapter> + </logging> + </common> + + </configuration> + + + + Gilles Bayon + + + + Constructor + + + + + Constructor + + + + + Get a ILog instance by type + + + + + + + Get a ILog instance by type key + + + + + + + A null-functionality implementation of + + + + + Sets the value of a new or existing variable within the global context + + The key of the variable that is to be added + The value to add + + + + Gets the value of a variable within the global context + + The key of the variable to get + The value or null if not found + + + + Checks if a variable is set within the global context + + The key of the variable to check for + True if the variable is set + + + + Removes a variable from the global context by key + + The key of the variable to remove + + + + Clears the global context variables + + + + + A implementation sending all System.Diagnostics.Trace output to + the Common.Logging infrastructure. + + + This listener captures all output sent by calls to System.Diagnostics.Trace and + and and sends it to an instance.
+ The instance to be used is obtained by calling + . The name of the logger is created by passing + this listener's and any source or category passed + into this listener (see or for example). +
+ + The snippet below shows how to add and configure this listener to your app.config: + + <system.diagnostics> + <sharedListeners> + <add name="Diagnostics" + type="Common.Logging.Simple.CommonLoggingTraceListener, Common.Logging" + initializeData="DefaultTraceEventType=Information; LoggerNameFormat={listenerName}.{sourceName}"> + <filter type="System.Diagnostics.EventTypeFilter" initializeData="Information"/> + </add> + </sharedListeners> + <trace> + <listeners> + <add name="Diagnostics" /> + </listeners> + </trace> + </system.diagnostics> + + + Erich Eichinger +
+ + + Creates a new instance with the default name "Diagnostics" and "Trace". + + + + + Creates a new instance initialized with properties from the . string. + + + is a semicolon separated string of name/value pairs, where each pair has + the form key=value. E.g. + "Name=MyLoggerName;LogLevel=Debug" + + a semicolon separated list of name/value pairs. + + + + Creates a new instance initialized with the specified properties. + + name/value configuration properties. + + + + Logs the given message to the Common.Logging infrastructure. + + the eventType + the name or category name passed into e.g. . + the id of this event + the message format + the message arguments + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by . + + + + + Writes message to logger provided by + + + + + Writes message to logger provided by + + + + + Writes message to logger provided by + + + + + Writes message to logger provided by + + + + + Writes message to logger provided by + + + + + Writes message to logger provided by + + + + + Sets the default to use for logging + all events emitted by .Write(...) and + .WriteLine(...) methods. + + + This listener captures all output sent by calls to and + sends it to an instance using the specified + on . + + + + + Format to use for creating the logger name. Defaults to "{listenerName}.{sourceName}". + + + Available placeholders are: + + {listenerName}: the configured name of this listener instance. + {sourceName}: the trace source name an event originates from (see e.g. . + + + + + + Used in an application's configuration file (App.Config or Web.Config) to configure the logging subsystem. + + + An example configuration section that writes log messages to the Console using the + built-in Console Logger. + + <configuration> + <configSections> + <sectionGroup name="common"> + <section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" /> + </sectionGroup> + </configSections> + <common> + <logging> + <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging"> + <arg key="showLogName" value="true" /> + <arg key="showDataTime" value="true" /> + <arg key="level" value="ALL" /> + <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" /> + </factoryAdapter> + </logging> + </common> + </configuration> + + + + + + Ensure static fields get initialized before any class member + can be accessed (avoids beforeFieldInit) + + + + + Constructor + + + + + Retrieves the of the logger the use by looking at the logFactoryAdapter element + of the logging configuration element. + + + + A object containing the specified type that implements + along with zero or more properties that will be + passed to the logger factory adapter's constructor as an . + + + + + Verifies that the logFactoryAdapter element appears once in the configuration section. + + settings of a parent section - atm this must always be null + Additional information about the configuration process. + The configuration section to apply an XPath query too. + + A object containing the specified logFactoryAdapter type + along with user supplied configuration properties. + + + + + Verifies that the logFactoryAdapter element appears once in the configuration section. + + The parent of the current item. + Additional information about the configuration process. + The configuration section to apply an XPath query too. + + A object containing the specified logFactoryAdapter type + along with user supplied configuration properties. + + + + + Sends log messages to . + + Gilles Bayon + + + + Creates and initializes a logger that writes messages to . + + The name, usually type name of the calling class, of the logger. + The current logging threshold. Messages recieved that are beneath this threshold will not be logged. + Include the current log level in the log message. + Include the current time in the log message. + Include the instance name in the log message. + The date and time format to use in the log message. + + + + Creates and initializes a logger that writes messages to . + + The name, usually type name of the calling class, of the logger. + The current logging threshold. Messages recieved that are beneath this threshold will not be logged. + Include the current log level in the log message. + Include the current time in the log message. + Include the instance name in the log message. + The date and time format to use in the log message. + Use color when writing the log message. + + + + Do the actual logging by constructing the log message using a then + sending the output to . + + The of the message. + The log message. + An optional associated with the message. + + + + Factory for creating instances that write data to . + + + + Below is an example how to configure this adapter: + + <configuration> + + <configSections> + <sectionGroup name="common"> + <section name="logging" + type="Common.Logging.ConfigurationSectionHandler, Common.Logging" + requirePermission="false" /> + </sectionGroup> + </configSections> + + <common> + <logging> + <factoryAdapter type="Common.Logging.Simple.ConsoleOutLoggerFactoryAdapter, Common.Logging"> + <arg key="level" value="ALL" /> + </factoryAdapter> + </logging> + </common> + + </configuration> + + + + + + + Gilles Bayon + Mark Pollack + Erich Eichinger + + + + Initializes a new instance of the class using default + settings. + + + + + Initializes a new instance of the class. + + + Looks for level, showDateTime, showLogName, dateTimeFormat items from + for use when the GetLogger methods are called. + for more information on how to use the + standard .NET application configuraiton file (App.config/Web.config) + to configure this adapter. + + The name value collection, typically specified by the user in + a configuration section named common/logging. + + + + Constructor for binary backwards compatibility with non-portableversions + + The properties. + + + + Initializes a new instance of the class with + default settings for the loggers created by this factory. + + + + + Initializes a new instance of the class with + default settings for the loggers created by this factory. + + + + + Creates a new instance. + + + + + Logger sending everything to the trace output stream using . + + + Beware not to use in combination with this logger as + this would result in an endless loop for obvious reasons! + + + + Gilles Bayon + Erich Eichinger + + + + Creates a new TraceLogger instance. + + whether to use or for logging. + the name of this logger + the default log level to use + Include the current log level in the log message. + Include the current time in the log message. + Include the instance name in the log message. + The date and time format to use in the log message. + + + + Determines if the given log level is currently enabled. + checks if is true. + + + + + Do the actual logging. + + + + + + + + Called after deserialization completed. + + + + + Used to defer message formatting until it is really needed. + + + This class also improves performance when multiple + s are configured. + + + + + Factory for creating instances that send + everything to the output stream. + + + Beware not to use in combination with this logger factory + as this would result in an endless loop for obvious reasons! + + Below is an example how to configure this adapter: + + <configuration> + + <configSections> + <sectionGroup name="common"> + <section name="logging" + type="Common.Logging.ConfigurationSectionHandler, Common.Logging" + requirePermission="false" /> + </sectionGroup> + </configSections> + + <common> + <logging> + <factoryAdapter type="Common.Logging.Simple.TraceLoggerFactoryAdapter, Common.Logging"> + <arg key="level" value="ALL" /> + </factoryAdapter> + </logging> + </common> + + </configuration> + + + + + + + Gilles Bayon + Mark Pollack + Erich Eichinger + + + + Initializes a new instance of the class using default settings. + + + + + Initializes a new instance of the class. + + + Looks for level, showDateTime, showLogName, dateTimeFormat items from + for use when the GetLogger methods are called. + for more information on how to use the + standard .NET application configuraiton file (App.config/Web.config) + to configure this adapter. + + The name value collection, typically specified by the user in + a configuration section named common/logging. + + + + Initializes a new instance of the class with + default settings for the loggers created by this factory. + + + + + Creates a new instance. + + + + + Whether to use .TraceXXXX(string,object[]) methods for logging + or . + + +
+
diff --git a/netstandard-build.cmd b/netstandard-build.cmd new file mode 100644 index 0000000..d316e6c --- /dev/null +++ b/netstandard-build.cmd @@ -0,0 +1,8 @@ +@echo off +@echo Running full Build Script, capturing output to buildlog.txt file... +#tools\NAnt\bin\nant.exe build -f:NetStandard-Spring.Rest.build > buildlog.txt + +tools\NAnt\bin\nant.exe package-nuget -f:NetStandard-Spring.Rest.build > buildlog.txt +@echo Launching text file viewer to display buildlog.txt contents... +start "ignored but required placeholder window title argument" buildlog.txt + diff --git a/netstandard/CommonAssemblyInfo.cs b/netstandard/CommonAssemblyInfo.cs new file mode 100644 index 0000000..9f4e808 --- /dev/null +++ b/netstandard/CommonAssemblyInfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +//------------------------------------------------------------------------------ +// +// ˴ɹɡ +// ʱ汾:4.0.30319.42000 +// +// ԴļĸĿܻᵼ²ȷΪ +// ɴ룬ЩĽᶪʧ +// +//------------------------------------------------------------------------------ + +[assembly: CLSCompliantAttribute(true)] +[assembly: ComVisibleAttribute(false)] +[assembly: AssemblyProductAttribute("Spring.NET REST Client Framework 2.0.0 for .NET Standard 2.0")] +[assembly: AssemblyCompanyAttribute("http://www.springframework.net/")] +[assembly: AssemblyCopyrightAttribute("Copyright 2020 SpringSource")] +[assembly: AssemblyTrademarkAttribute("Apache License, Version 2.0")] +[assembly: AssemblyCultureAttribute("")] +[assembly: AssemblyVersionAttribute("2.0.0.522")] +[assembly: AssemblyConfigurationAttribute("net-standard-2.0; 2.0.0.522; dev")] + diff --git a/netstandard/Directory.Build.props b/netstandard/Directory.Build.props new file mode 100644 index 0000000..9e9b88b --- /dev/null +++ b/netstandard/Directory.Build.props @@ -0,0 +1,20 @@ + + + false + false + false + + 3.4.1 + + SpringSource + http://springframework.net/images/SpringSource_Leaves32x32.png + http://www.springframework.net/license.html + http://www.springframework.net/ + Library + + + latest + net45 + netstandard2.0 + + diff --git a/netstandard/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs b/netstandard/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs new file mode 100644 index 0000000..b8e60f0 --- /dev/null +++ b/netstandard/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs @@ -0,0 +1,102 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +using Newtonsoft.Json; + +namespace Spring.Http.Converters.Json +{ + /// + /// Implementation of that can read and write JSON + /// using the Json.NET (Newtonsoft.Json) library. + /// + /// + /// + /// This implementation supports getting/setting values from JSON directly, + /// without the need to deserialize/serialize to a .NET class. + /// + /// + /// By default, this converter supports 'application/json' media type. + /// This can be overridden by setting the property. + /// + /// + /// Bruno Baia + public class NJsonHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + public NJsonHttpMessageConverter() : + base(new MediaType("application", "json")) + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return true; + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + // Read from the message stream + using (StreamReader reader = new StreamReader(message.Body)) + using (JsonTextReader jsonReader = new JsonTextReader(reader)) + { + JsonSerializer jsonSerializer = new JsonSerializer(); + return jsonSerializer.Deserialize(jsonReader); + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Write to the message stream + message.Body = delegate(Stream stream) + { + using (StreamWriter writer = new StreamWriter(stream)) + using (JsonTextWriter jsonWriter = new JsonTextWriter(writer)) + { + JsonSerializer jsonSerializer = new JsonSerializer(); + jsonSerializer.Serialize(jsonWriter, content); + } + }; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Http.Converters.NJson/Spring.Http.Converters.NJson.csproj b/netstandard/Spring.Http.Converters.NJson/Spring.Http.Converters.NJson.csproj new file mode 100644 index 0000000..ac76772 --- /dev/null +++ b/netstandard/Spring.Http.Converters.NJson/Spring.Http.Converters.NJson.csproj @@ -0,0 +1,20 @@ + + + + $(TargetNetStandardVersion);$(TargetFullFrameworkVersion) + + + + + + + + + + + + + + + + diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionException.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionException.cs new file mode 100644 index 0000000..1eb6a37 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionException.cs @@ -0,0 +1,81 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Rest.Client.Testing +{ + /// + /// The exception that is thrown when an assertion fails. + /// + /// Bruno Baia +#if !SILVERLIGHT && !CF_3_5 + [Serializable] +#endif + public class AssertionException : Exception + { + /// + /// Creates a new instance of the class. + /// + public AssertionException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public AssertionException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// A message about the exception. + /// The root exception that is being wrapped. + public AssertionException(string message, Exception rootCause) + : base(message, rootCause) + { + } + +#if !SILVERLIGHT && !CF_3_5 + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected AssertionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } +#endif + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionUtils.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionUtils.cs new file mode 100644 index 0000000..df859a5 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/AssertionUtils.cs @@ -0,0 +1,78 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Represents an unit test framework independent assertion class. + /// + /// Lukas Krecan + /// Arjen Poutsma + /// Craig Walls + /// Bruno Baia (.NET) + public static class AssertionUtils + { + /// + /// Fails a test with the given message. + /// + /// The message. + /// Always. + public static void Fail(string message) + { + throw new AssertionException(message); + } + + /// + /// Asserts that a condition is true. If not, throws an with the given message. + /// + /// The condition to test for. + /// The message. + /// If condition is false. + public static void IsTrue(bool condition, string message) + { + if (!condition) + { + Fail(message); + } + } + + /// + /// Asserts that two objects are equal. If not, an is thrown with the given message. + /// + /// The expected value. + /// The actual value. + /// The message. + /// If objects are not equal. + public static void AreEqual(object expected, object actual, string message) + { + if (expected == null && actual == null) + { + return; + } + if (expected != null && expected.Equals(actual)) + { + return; + } + Fail(String.Format("{0} [expected:<{1}> but was:<{2}>]", message, expected, actual)); + } + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/IRequestActions.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/IRequestActions.cs new file mode 100644 index 0000000..4d2a086 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/IRequestActions.cs @@ -0,0 +1,50 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Allows for setting up responses and additional expectations on requests. + /// + /// + /// Implementations of this interface are returned by . + /// + /// Lukas Krecan + /// Arjen Poutsma + /// Craig Walls + /// Bruno Baia (.NET) + public interface IRequestActions + { + /// + /// Allows for further expectations to be set on the request. + /// + /// The request expectations. + /// The request expectations. + IRequestActions AndExpect(RequestMatcher requestMatcher); + + /// + /// Allows for reponse creation on the request. + /// + /// The response creator. + void AndRespond(ResponseCreator responseCreator); + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequest.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequest.cs new file mode 100644 index 0000000..f9bbd3f --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequest.cs @@ -0,0 +1,189 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +using Spring.Util; +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Mock implementation of . + /// Implements to form a fluent API. + /// + /// Arjen Poutsma + /// Lukas Krecan + /// Craig Walls + /// Bruno Baia (.NET) + public class MockClientHttpRequest : IClientHttpRequest, IRequestActions + { + private IList requestMatchers = new List(); + private ResponseCreator responseCreator; + + private Uri uri; + private HttpMethod method; + private HttpHeaders headers = new HttpHeaders(); + private Action body; + + #region IClientHttpRequest Members + + /// + /// Gets or sets the HTTP method of the request. + /// + public HttpMethod Method + { + get { return this.method; } + set { this.method = value; } + } + + /// + /// Gets or sets the URI of the request. + /// + public Uri Uri + { + get { return this.uri; } + set { this.uri = value; } + } + + /// + /// Gets the message headers. + /// + public HttpHeaders Headers + { + get { return this.headers; } + } + + /// + /// Sets the delegate that writes the body message as a stream. + /// + public Action Body + { + set { this.body = value; } + } + +#if !SILVERLIGHT + /// + /// Execute this request, resulting in a that can be read. + /// + /// The response result of the execution + public IClientHttpResponse Execute() + { + foreach (RequestMatcher requestMatcher in this.requestMatchers) + { + requestMatcher(this); + } + + if (this.responseCreator != null) + { + return responseCreator(this); + } + else + { + return null; + } + } +#endif + +#if !CF_3_5 + /// + /// Execute this request asynchronously. + /// + /// + /// An optional user-defined object that is passed to the method invoked + /// when the asynchronous operation completes. + /// + /// + /// The to perform when the asynchronous execution completes. + /// + public void ExecuteAsync(object state, Action executeCompleted) + { + foreach (RequestMatcher requestMatcher in this.requestMatchers) + { + requestMatcher(this); + } + + IClientHttpResponse response = null; + if (this.responseCreator != null) + { + response = responseCreator(this); + } + executeCompleted(new ClientHttpRequestCompletedEventArgs(response, null, false, state)); + } + + /// + /// Cancels a pending asynchronous operation. + /// + public void CancelAsync() + { + } +#endif + + #endregion + + #region IRequestActions Members + + /// + /// Allows for further expectations to be set on the request. + /// + /// The request expectations. + /// The request expectations. + public IRequestActions AndExpect(RequestMatcher requestMatcher) + { + ArgumentUtils.AssertNotNull(requestMatcher, "requestMatcher"); + this.requestMatchers.Add(requestMatcher); + return this; + } + + /// + /// Allows for reponse creation on the request. + /// + /// The response creator. + public void AndRespond(ResponseCreator responseCreator) + { + ArgumentUtils.AssertNotNull(responseCreator, "responseCreator"); + this.responseCreator = responseCreator; + } + + #endregion + + /// + /// Returns the body content as a string. + /// + /// A String representation of the body. + public string GetBodyAsString() + { + if (this.body != null) + { + using (MemoryStream stream = new MemoryStream()) + { + this.body(stream); + byte[] bodyAsBytes = stream.ToArray(); + return Encoding.UTF8.GetString(bodyAsBytes, 0, bodyAsBytes.Length); + } + } + return null; + } + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequestFactory.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequestFactory.cs new file mode 100644 index 0000000..7d1a5a3 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpRequestFactory.cs @@ -0,0 +1,93 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using Spring.Util; +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Mock implementation of . + /// Contains a list of expected s, and iterates over those. + /// + /// Arjen Poutsma + /// Lukas Krecan + /// Craig Walls + /// Bruno Baia (.NET) + public class MockClientHttpRequestFactory : IClientHttpRequestFactory + { + private IList expectedRequests = new List(); + private IEnumerator requestEnumerator; + + /// + /// Create a new for the specified URI and HTTP method. + /// + /// The URI to create a request for. + /// The HTTP method to execute. + /// The created request. + public IClientHttpRequest CreateRequest(Uri uri, HttpMethod method) + { + ArgumentUtils.AssertNotNull(uri, "uri"); + ArgumentUtils.AssertNotNull(method, "method"); + + if (this.requestEnumerator == null) + { + requestEnumerator = expectedRequests.GetEnumerator(); + } + if (!this.requestEnumerator.MoveNext()) + { + throw new AssertionException("No further requests expected"); + } + + MockClientHttpRequest currentRequest = this.requestEnumerator.Current; + currentRequest.Uri = uri; + currentRequest.Method = method; + return currentRequest; + } + + internal MockClientHttpRequest ExpectNewRequest() + { + if (this.requestEnumerator != null) + { + throw new InvalidCastException("Can not expect another request, the test is already underway"); + } + + MockClientHttpRequest request = new MockClientHttpRequest(); + this.expectedRequests.Add(request); + return request; + } + + internal void VerifyRequests() + { + if (this.expectedRequests.Count == 0) + { + return; + } + if (this.requestEnumerator == null || this.requestEnumerator.MoveNext()) + { + throw new AssertionException("Further request(s) expected"); + } + } + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpResponse.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpResponse.cs new file mode 100644 index 0000000..3af8efb --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockClientHttpResponse.cs @@ -0,0 +1,108 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Mock implementation of . + /// + /// Arjen Poutsma + /// Lukas Krecan + /// Craig Walls + /// Bruno Baia (.NET) + public class MockClientHttpResponse : IClientHttpResponse + { + private HttpStatusCode statusCode; + private string statusDescription; + private HttpHeaders headers; + private Stream body; + + /// + /// Creates a new instance of . + /// + /// The body of the response as a stream. + /// The response headers. + /// The response status code. + /// The response status description. + public MockClientHttpResponse(Stream body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + this.body = body; + this.headers = headers; + this.statusCode = statusCode; + this.statusDescription = statusDescription; + } + + #region IClientHttpResponse Members + + /// + /// Gets the HTTP status code of the response. + /// + public HttpStatusCode StatusCode + { + get { return this.statusCode; } + } + + /// + /// Gets the HTTP status description of the response. + /// + public string StatusDescription + { + get { return this.statusDescription; } + } + + /// + /// Gets the message headers. + /// + public HttpHeaders Headers + { + get { return this.headers; } + } + + /// + /// Gets the body of the message as a stream. + /// + public Stream Body + { + get { return this.body; } + } + + /// + /// Closes this response, freeing any resources created. + /// + public void Close() + { + this.body.Close(); + } + + void IDisposable.Dispose() + { + this.Close(); + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockRestServiceServer.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockRestServiceServer.cs new file mode 100644 index 0000000..514d471 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/MockRestServiceServer.cs @@ -0,0 +1,99 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Util; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Main entry point for client-side REST testing. + /// Typically used to test a , set up expectations on request messages, and create response messages. + /// + /// + /// The typical usage of this class is: + /// + /// + /// Create a instance by calling method. + /// + /// + /// Set up request expectation by calling . + /// More request expectations can be set up by chaining calls, + /// possibly by using the default method extensions provided. + /// + /// + /// Create an appropriate response message by calling , + /// possibly by using the default method extensions provided. + /// + /// + /// Call . + /// + /// + /// Note that because of the 'fluent' API offered by this class (and related classes), + /// you can typically use the Code Completion features (i.e. ctrl-space) in your IDE to set up the mocks. + /// + /// Arjen Poutsma + /// Lukas Krecan + /// Craig Walls + /// Bruno Baia (.NET) + public class MockRestServiceServer + { + private MockClientHttpRequestFactory mockRequestFactory; + + private MockRestServiceServer(MockClientHttpRequestFactory mockRequestFactory) + { + ArgumentUtils.AssertNotNull(mockRequestFactory, "mockRequestFactory"); + this.mockRequestFactory = mockRequestFactory; + } + + /// + /// Creates a instance based on the given . + /// + /// The RestTemplate. + /// The created server. + public static MockRestServiceServer CreateServer(RestTemplate restTemplate) + { + ArgumentUtils.AssertNotNull(restTemplate, "restTemplate"); + + MockClientHttpRequestFactory mockRequestFactory = new MockClientHttpRequestFactory(); + restTemplate.RequestFactory = mockRequestFactory; + + return new MockRestServiceServer(mockRequestFactory); + } + + /// + /// Returns a object that allows for creating the response, or to set up request expectations. + /// + /// The response actions. + public IRequestActions ExpectNewRequest() + { + MockClientHttpRequest request = this.mockRequestFactory.ExpectNewRequest(); + return request; + } + + /// + /// Verifies that all request expectations were met. + /// + /// In case of unmet expectations. + public void Verify() + { + this.mockRequestFactory.VerifyRequests(); + } + } +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestActionsExtensions.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestActionsExtensions.cs new file mode 100644 index 0000000..a2ae5e5 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestActionsExtensions.cs @@ -0,0 +1,201 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; + +using Spring.IO; +using Spring.Http; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Extensions methods for interface. + /// + /// Bruno Baia + public static class RequestActionsExtensions + { + #region AndExpect Extensions + + /// + /// Expects the given request . + /// + /// The to set up expectation on. + /// The HTTP method. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectMethod(this IRequestActions requestActions, HttpMethod method) + { + return requestActions.AndExpect(RequestMatchers.MatchMethod(method)); + } + + /// + /// Expects the given request URI. + /// + /// The to set up expectation on. + /// the request URI. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectUri(this IRequestActions requestActions, string uri) + { + return requestActions.AndExpect(RequestMatchers.MatchUri(uri)); + } + + /// + /// Expects the given request URI. + /// + /// The to set up expectation on. + /// the request URI. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectUri(this IRequestActions requestActions, Uri uri) + { + return requestActions.AndExpect(RequestMatchers.MatchUri(uri)); + } + + /// + /// Expects the given request header. + /// + /// The to set up expectation on. + /// The header name. + /// The header value. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectHeader(this IRequestActions requestActions, string header, string value) + { + return requestActions.AndExpect(RequestMatchers.MatchHeader(header, value)); + } + + /// + /// Expects that the specified request header contains the given substring. + /// + /// The to set up expectation on. + /// The header name. + /// The substring that must appear in the header. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectHeaderContains(this IRequestActions requestActions, string header, string value) + { + return requestActions.AndExpect(RequestMatchers.MatchHeaderContains(header, value)); + } + + /// + /// Expects the given request body content. + /// + /// The to set up expectation on. + /// The request body. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectBody(this IRequestActions requestActions, string body) + { + return requestActions.AndExpect(RequestMatchers.MatchBody(body)); + } + + /// + /// Expects that the request body contains the given substring. + /// + /// The to set up expectation on. + /// The request body. + /// + /// The to set up responses and additional expectations on the request. + /// + public static IRequestActions AndExpectBodyContains(this IRequestActions requestActions, string body) + { + return requestActions.AndExpect(RequestMatchers.MatchBodyContains(body)); + } + + #endregion + + #region AndRespond Extensions + + /// + /// Responds with the given response body, headers, status code, and status description. + /// + /// The to set up response on. + /// The body of the response. + /// The response headers. + /// The response status code. + /// The response status description. + public static void AndRespondWith(this IRequestActions requestActions, + string body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + requestActions.AndRespond(ResponseCreators.CreateWith(body, headers, statusCode, statusDescription)); + } + + /// + /// Responds with the given response body and headers. The response status code is HTTP 200 (OK). + /// + /// The to set up response on. + /// The body of the response. + /// The response headers. + public static void AndRespondWith(this IRequestActions requestActions, + string body, HttpHeaders headers) + { + requestActions.AndRespond(ResponseCreators.CreateWith(body, headers, HttpStatusCode.OK, "OK")); + } + + /// + /// Responds with the given response body (from a ), headers, status code, and status description. + /// + /// The to set up response on. + /// The containing the body of the response. + /// The response headers. + /// The response status code. + /// The response status description. + public static void AndRespondWith(this IRequestActions requestActions, + IResource body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + requestActions.AndRespond(ResponseCreators.CreateWith(body, headers, statusCode, statusDescription)); + } + + /// + /// Responds with the given response body (from a ) and headers. The response status code is HTTP 200 (OK). + /// + /// The to set up response on. + /// The containing the body of the response. + /// The response headers. + public static void AndRespondWith(this IRequestActions requestActions, + IResource body, HttpHeaders headers) + { + requestActions.AndRespond(ResponseCreators.CreateWith(body, headers, HttpStatusCode.OK, "OK")); + } + + #endregion + } +} + +#if NET_2_0 && !NET_3_5 +namespace System.Runtime.CompilerServices +{ + /// + /// Manufactured Extension Attribute to permit .NET 2.0 to support extension methods. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + internal class ExtensionAttribute : Attribute + { + } +} +#endif diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatcher.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatcher.cs new file mode 100644 index 0000000..3c49701 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatcher.cs @@ -0,0 +1,35 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Matches the given request message against the expectations. + /// + /// The request to make assertions on. + /// If expectations are not met. + /// Bruno Baia (.NET) + /// Lukas Krecan + /// Arjen Poutsma + /// Craig Walls + public delegate void RequestMatcher(IClientHttpRequest request); +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatchers.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatchers.cs new file mode 100644 index 0000000..07742cb --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/RequestMatchers.cs @@ -0,0 +1,154 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Factory methods for classes. + /// Typically used to provide input for . + /// + /// Arjen Poutsma + /// Craig Walls + /// Bruno Baia (.NET) + public static class RequestMatchers + { + /// + /// Creates a that expect the given request . + /// + /// The HTTP method. + /// The request matcher. + public static RequestMatcher MatchMethod(HttpMethod method) + { + return delegate(IClientHttpRequest request) + { + AssertionUtils.AreEqual(method, request.Method, "Unexpected HTTP method"); + }; + } + + /// + /// Creates a that expect the given request URI. + /// + /// the request URI. + /// The request matcher. + public static RequestMatcher MatchUri(string uri) + { + return MatchUri(new Uri(uri)); + } + + /// + /// Creates a that expect the given request URI. + /// + /// the request URI. + /// The request matcher. + public static RequestMatcher MatchUri(Uri uri) + { + return delegate(IClientHttpRequest request) + { + AssertionUtils.AreEqual(uri, request.Uri, "Unexpected URI"); + }; + } + + /// + /// Creates a that expect the given request header. + /// + /// The header name. + /// The header value. + /// The request matcher. + public static RequestMatcher MatchHeader(string header, string value) + { + return delegate(IClientHttpRequest request) + { + string[] actualHeaderValues = request.Headers.GetValues(header); + AssertionUtils.IsTrue(actualHeaderValues != null, String.Format("Expected header '{0}' missing", header)); + + bool foundMatch = false; + foreach (string actualHeaderValue in actualHeaderValues) + { + if (actualHeaderValue.Equals(value)) + { + foundMatch = true; + break; + } + } + AssertionUtils.IsTrue(foundMatch, String.Format("Header '{0}' didn't have expected value [expected:<{1}> but was:<{2}>]", header, value, request.Headers[header])); + }; + } + + /// + /// Creates a that expect that the specified request header contains a subtring. + /// + /// The header name. + /// The substring that must appear in the header. + /// The request matcher. + public static RequestMatcher MatchHeaderContains(string header, string value) + { + return delegate(IClientHttpRequest request) + { + string[] actualHeaderValues = request.Headers.GetValues(header); + AssertionUtils.IsTrue(actualHeaderValues != null, String.Format("Expected header '{0}' missing", header)); + + bool foundMatch = false; + foreach (string actualHeaderValue in actualHeaderValues) + { + if (actualHeaderValue.Contains(value)) + { + foundMatch = true; + break; + } + } + AssertionUtils.IsTrue(foundMatch, String.Format("Header '{0}' didn't contain expected value [expected:<{1}> in:<{2}>]", header, value, request.Headers[header])); + }; + } + + /// + /// Creates a that expect the given request body content. + /// + /// The request body. + /// The request matcher. + public static RequestMatcher MatchBody(string body) + { + return delegate(IClientHttpRequest request) + { + MockClientHttpRequest mockRequest = request as MockClientHttpRequest; + AssertionUtils.AreEqual(body, mockRequest.GetBodyAsString(), "Unexpected body content"); + }; + } + + /// + /// Creates a that expect to contain the given request body content. + /// + /// The substring that must appear in the body content. + /// The request matcher. + public static RequestMatcher MatchBodyContains(string body) + { + return delegate(IClientHttpRequest request) + { + MockClientHttpRequest mockRequest = request as MockClientHttpRequest; + string actualBody = mockRequest.GetBodyAsString(); + AssertionUtils.IsTrue(actualBody.Contains(body), String.Format("Body didn't contain expected content [expected:<{0}> in:<{1}>]", body, actualBody)); + }; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreator.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreator.cs new file mode 100644 index 0000000..15729a3 --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreator.cs @@ -0,0 +1,35 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Creates a response for the given request. + /// + /// The request. + /// The created . + /// Bruno Baia (.NET) + /// Lukas Krecan + /// Arjen Poutsma + /// Craig Walls + public delegate IClientHttpResponse ResponseCreator(IClientHttpRequest request); +} diff --git a/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreators.cs b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreators.cs new file mode 100644 index 0000000..4275c2d --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Rest/Client/Testing/ResponseCreators.cs @@ -0,0 +1,107 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; +using System.Text; +using System.Net; + +using Spring.IO; +using Spring.Util; +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Testing +{ + /// + /// Factory methods for classes. + /// Typically used to provide input for . + /// + /// Arjen Poutsma + /// Craig Walls + /// Bruno Baia (.NET) + public static class ResponseCreators + { + /// + /// Creates a that respond with + /// the given response body, headers, status code, and status description. + /// + /// The body of the response. + /// The response headers. + /// The response status code. + /// The response status description. + /// A . + public static ResponseCreator CreateWith( + string body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + ArgumentUtils.AssertNotNull(body, "body"); + + return delegate(IClientHttpRequest request) + { + return new MockClientHttpResponse(new MemoryStream(Encoding.UTF8.GetBytes(body)), headers, statusCode, statusDescription); + }; + } + + /// + /// Creates a that respond with + /// the given response body and headers. + /// The response status code is HTTP 200 (OK). + /// + /// The body of the response. + /// The response headers. + /// A . + public static ResponseCreator CreateWith(string body, HttpHeaders headers) + { + return CreateWith(body, headers, HttpStatusCode.OK, "OK"); + } + + /// + /// Creates a that respond with + /// the given response body (from a ), headers, status code, and status description. + /// + /// The containing the body of the response. + /// The response headers. + /// The response status code. + /// The response status description. + /// A . + public static ResponseCreator CreateWith( + IResource body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + ArgumentUtils.AssertNotNull(body, "body"); + + return delegate(IClientHttpRequest request) + { + return new MockClientHttpResponse(body.GetStream(), headers, statusCode, statusDescription); + }; + } + + /// + /// Creates a that respond with + /// the given response body (from a ) and headers. + /// The response status code is HTTP 200 (OK). + /// + /// The containing the body of the response. + /// The response headers. + /// A . + public static ResponseCreator CreateWith(IResource body, HttpHeaders headers) + { + return CreateWith(body, headers, HttpStatusCode.OK, "OK"); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Testing/Spring.Rest.Testing.csproj b/netstandard/Spring.Rest.Testing/Spring.Rest.Testing.csproj new file mode 100644 index 0000000..1c3b1dc --- /dev/null +++ b/netstandard/Spring.Rest.Testing/Spring.Rest.Testing.csproj @@ -0,0 +1,16 @@ + + + + $(TargetNetStandardVersion);$(TargetFullFrameworkVersion) + + + + + + + + + + + + diff --git a/netstandard/Spring.Rest.Tests/Http/Client/AbstractClientHttpRequestFactoryIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Client/AbstractClientHttpRequestFactoryIntegrationTests.cs new file mode 100644 index 0000000..b510a5f --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Client/AbstractClientHttpRequestFactoryIntegrationTests.cs @@ -0,0 +1,510 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; +using System.Text; +using System.ServiceModel; +using System.ServiceModel.Web; +using System.Threading; + +using NUnit.Framework; + +namespace Spring.Http.Client +{ + /// + /// Integration tests for IClientHttpRequestFactory implementations. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public abstract class AbstractClientHttpRequestFactoryIntegrationTests + { + private IClientHttpRequestFactory requestFactory; + + private const string BASE_URL = "http://localhost:1337"; + private WebServiceHost webServiceHost; + + [SetUp] + public void SetUp() + { + requestFactory = this.CreateRequestFactory(); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(BASE_URL)); + this.ConfigureWebServiceHost(webServiceHost); + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + protected abstract IClientHttpRequestFactory CreateRequestFactory(); + + protected virtual void ConfigureWebServiceHost(WebServiceHost webServiceHost) + { + WebHttpBinding httpBinding = new WebHttpBinding(); + webServiceHost.AddServiceEndpoint(typeof(TestService), httpBinding, ""); + } + + protected virtual IClientHttpRequest CreateRequest(string path, HttpMethod method) + { + Uri uri = new Uri(BASE_URL + path); + IClientHttpRequest request = requestFactory.CreateRequest(uri, method); + Assert.AreEqual(method, request.Method, "Invalid HTTP method"); + Assert.AreEqual(uri, request.Uri, "Invalid HTTP URI"); + + return request; + } + + #region Sync + + [Test] + public void Status() + { + IClientHttpRequest request = this.CreateRequest("/status/notfound", HttpMethod.GET); + + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.NotFound, response.StatusCode, "Invalid status code"); + Assert.AreEqual("Status NotFound", response.StatusDescription, "Invalid status description"); + } + } + + [Test] + public void Echo() + { + IClientHttpRequest request = this.CreateRequest("/echo", HttpMethod.PUT); + request.Headers.ContentType = new MediaType("text", "plain", "utf-8"); + String headerName = "MyHeader"; + String headerValue1 = "value1"; + request.Headers.Add(headerName, headerValue1); + String headerValue2 = "value2"; + request.Headers.Add(headerName, headerValue2); + + byte[] body = Encoding.UTF8.GetBytes("Hello World"); + request.Body = delegate(Stream stream) + { + stream.Write(body, 0, body.Length); + }; + + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + Assert.AreEqual("value1,value2", response.Headers[headerName], "Header values not found"); + using (BinaryReader reader = new BinaryReader(response.Body)) + { + byte[] result = reader.ReadBytes((int)response.Headers.ContentLength); + Assert.AreEqual(body, result, "Invalid body"); + } + } + } + + [Test] + [ExpectedException(typeof(InvalidOperationException), + ExpectedMessage = "Client HTTP request already executed or is currently executing.")] + public void MultipleExecute() + { + IClientHttpRequest request = this.CreateRequest("/status/ok", HttpMethod.GET); + + request.Execute(); + request.Execute(); + } + + [Test] + public void ThreadSafeCreateAndExecute() + { + ThreadInfo threadInfo = new ThreadInfo(); + Thread[] threads = new Thread[10]; + for (int i = 0; i < 10; i++) + { + Thread thread = new Thread(new ParameterizedThreadStart( + delegate(object obj) + { + try + { + Echo(); + } + catch + { + ((ThreadInfo)obj).HasFailed = true; + } + })); + thread.Start(threadInfo); + threads[i] = thread; + } + foreach (Thread thread in threads) + { + thread.Join(); + } + Assert.IsFalse(threadInfo.HasFailed); + } + + [Test] + public void HttpMethods() + { + AssertHttpMethod("get", HttpMethod.GET); + AssertHttpMethod("head", HttpMethod.HEAD); + AssertHttpMethod("post", HttpMethod.POST); + AssertHttpMethod("put", HttpMethod.PUT); + AssertHttpMethod("options", HttpMethod.OPTIONS); + AssertHttpMethod("delete", HttpMethod.DELETE); + } + + private void AssertHttpMethod(String path, HttpMethod method) + { + IClientHttpRequest request = this.CreateRequest("/methods/" + path, method); + request.Headers.ContentLength = 0; + + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + Assert.AreEqual(path, response.StatusDescription, "Invalid status description"); + } + } + + #endregion + + #region Async + + [Test] + public void StatusAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + IClientHttpRequest request = this.CreateRequest("/status/notfound", HttpMethod.GET); + + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual(HttpStatusCode.NotFound, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual("Status NotFound", args.Response.StatusDescription, "Invalid status description"); + } + catch(Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void EchoAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + IClientHttpRequest request = this.CreateRequest("/echo", HttpMethod.PUT); + request.Headers.ContentType = new MediaType("text", "plain", "utf-8"); + String headerName = "MyHeader"; + String headerValue1 = "value1"; + request.Headers.Add(headerName, headerValue1); + String headerValue2 = "value2"; + request.Headers.Add(headerName, headerValue2); + + byte[] body = Encoding.UTF8.GetBytes("Hello World"); + request.Body = delegate(Stream stream) + { + stream.Write(body, 0, body.Length); + }; + + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual(HttpStatusCode.OK, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual("value1,value2", args.Response.Headers[headerName], "Header values not found"); + using (BinaryReader reader = new BinaryReader(args.Response.Body)) + { + byte[] result = reader.ReadBytes((int)args.Response.Headers.ContentLength); + Assert.AreEqual(body, result, "Invalid body"); + } + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + [ExpectedException(typeof(InvalidOperationException), + ExpectedMessage = "Client HTTP request already executed or is currently executing.")] + public void MultipleExecuteAsync() + { + IClientHttpRequest request = this.CreateRequest("/status/ok", HttpMethod.GET); + + request.ExecuteAsync(null, null); + request.ExecuteAsync(null, null); + } + + [Test] + public void ThreadSafeCreateAndExecuteAsync() + { + ThreadInfo threadInfo = new ThreadInfo(); + Thread[] threads = new Thread[10]; + for (int i = 0; i < 10; i++) + { + Thread thread = new Thread(new ParameterizedThreadStart( + delegate(object obj) + { + try + { + EchoAsync(); + } + catch + { + ((ThreadInfo)obj).HasFailed = true; + } + })); + thread.Start(threadInfo); + threads[i] = thread; + } + foreach (Thread thread in threads) + { + thread.Join(); + } + Assert.IsFalse(threadInfo.HasFailed); + } + + [Test] + public void HttpMethodsAsync() + { + AssertHttpMethodAsync("get", HttpMethod.GET); + AssertHttpMethodAsync("head", HttpMethod.HEAD); + AssertHttpMethodAsync("post", HttpMethod.POST); + AssertHttpMethodAsync("put", HttpMethod.PUT); + AssertHttpMethodAsync("options", HttpMethod.OPTIONS); + AssertHttpMethodAsync("delete", HttpMethod.DELETE); + } + + private void AssertHttpMethodAsync(String path, HttpMethod method) + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + IClientHttpRequest request = this.CreateRequest("/methods/" + path, method); + request.Headers.ContentLength = 0; + + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual(HttpStatusCode.OK, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual(path, args.Response.StatusDescription, "Invalid status description"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void CancelAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + IClientHttpRequest request = this.CreateRequest("/sleep/2", HttpMethod.GET); + + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsTrue(args.Cancelled, "Invalid response"); + + WebException webEx = args.Error as WebException; + Assert.IsNotNull(webEx, "Invalid response exception"); + Assert.AreEqual(WebExceptionStatus.RequestCanceled, webEx.Status, "Invalid response exception status"); + + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + request.CancelAsync(); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + #endregion + + public class ThreadInfo + { + private bool _hasFailed; + + public bool HasFailed + { + get { return _hasFailed; } + set { _hasFailed = value; } + } + } + + #region Test service + + [ServiceContract] + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] + public class TestService + { + [OperationContract] + [WebInvoke(UriTemplate = "echo", Method = "PUT")] + public Stream Echo(Stream message) + { + WebOperationContext context = WebOperationContext.Current; + foreach (string headerName in context.IncomingRequest.Headers) + { + context.OutgoingResponse.Headers[headerName] = context.IncomingRequest.Headers[headerName]; + } + context.OutgoingResponse.StatusCode = HttpStatusCode.OK; + + return message; + } + + [OperationContract] + [WebGet(UriTemplate = "status/ok")] + public void StatusOk() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "Status OK"; + } + + [OperationContract] + [WebGet(UriTemplate = "status/notfound")] + public void StatusNotFound() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotFound; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "Status NotFound"; + } + + [OperationContract] + [WebGet(UriTemplate = "methods/get")] + public void MethodsGet() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "get"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "methods/delete", Method = "DELETE")] + public void MethodsDelete() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "delete"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "methods/head", Method = "HEAD")] + public void MethodsHead() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "head"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "methods/options", Method = "OPTIONS")] + public void MethodsOptions() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "options"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "methods/post", Method = "POST")] + public void MethodsPost() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "post"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "methods/put", Method = "PUT")] + public void MethodsPut() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "put"; + } + + [OperationContract] + [WebGet(UriTemplate = "sleep/{seconds}")] + public void Sleep(string seconds) + { + Thread.Sleep(TimeSpan.FromSeconds(Int32.Parse(seconds))); + + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "Status OK"; + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/BasicSigningRequestInterceptorIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/BasicSigningRequestInterceptorIntegrationTests.cs new file mode 100644 index 0000000..9fa0c47 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/BasicSigningRequestInterceptorIntegrationTests.cs @@ -0,0 +1,146 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; +using System.ServiceModel; +using System.ServiceModel.Web; +using System.ServiceModel.Security; +using System.IdentityModel.Tokens; +using System.IdentityModel.Selectors; + +using NUnit.Framework; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Integration tests for the BasicSigningRequestInterceptor class. + /// + /// Bruno Baia + [TestFixture] + public class BasicSigningRequestInterceptorIntegrationTests + { + private const string BASE_URL = "http://localhost:1337"; + private WebServiceHost webServiceHost; + + [SetUp] + public void SetUp() + { + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(BASE_URL)); + + WebHttpBinding httpBinding1 = new WebHttpBinding(); + httpBinding1.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly; + httpBinding1.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; + webServiceHost.AddServiceEndpoint(typeof(TestService), httpBinding1, "/basic"); + + webServiceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; + webServiceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator(); + + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + private IClientHttpRequest CreateRequest(BasicSigningRequestInterceptor requestInterceptor, string path, HttpMethod method) + { + IClientHttpRequestFactory requestFactory = new InterceptingClientHttpRequestFactory( + new WebClientHttpRequestFactory(), + new IClientHttpRequestInterceptor[] { requestInterceptor }); + + Uri uri = new Uri(BASE_URL + path); + IClientHttpRequest request = requestFactory.CreateRequest(uri, method); + Assert.AreEqual(method, request.Method, "Invalid HTTP method"); + Assert.AreEqual(uri, request.Uri, "Invalid HTTP URI"); + + return request; + } + + [Test] + public void BasicAuthorizationKO() + { + IClientHttpRequest request = this.CreateRequest( + new BasicSigningRequestInterceptor("bruno", "baia"), "/basic/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode, "Invalid status code"); + } + } + + [Test] + public void BasicAuthorizationOK() + { + IClientHttpRequest request = this.CreateRequest( + new BasicSigningRequestInterceptor("login", "password"), "/basic/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + } + } + + + #region Test classes + + public class CustomUserNamePasswordValidator : UserNamePasswordValidator + { + public override void Validate(string userName, string password) + { + if (userName == "login" && password == "password") + { + return; + } + throw new SecurityTokenException("Unknown username or incorrect password"); + } + } + + #endregion + + #region Test service + + [ServiceContract] + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] + public class TestService + { + [OperationContract] + [WebInvoke(UriTemplate = "echo", Method = "PUT")] + public Stream Echo(Stream message) + { + WebOperationContext context = WebOperationContext.Current; + foreach (string headerName in context.IncomingRequest.Headers) + { + context.OutgoingResponse.Headers[headerName] = context.IncomingRequest.Headers[headerName]; + } + context.OutgoingResponse.StatusCode = HttpStatusCode.OK; + + return message; + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/InterceptingClientHttpRequestFactoryTests.cs b/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/InterceptingClientHttpRequestFactoryTests.cs new file mode 100644 index 0000000..3cc98bb --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Client/Interceptor/InterceptingClientHttpRequestFactoryTests.cs @@ -0,0 +1,594 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Threading; + +using NUnit.Framework; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Integration tests for InterceptingClientHttpRequestFactory implementations. + /// + /// Arjen Poutsma + /// Bruno Baia + [TestFixture] + public class InterceptingClientHttpRequestFactoryTests + { + private InterceptingClientHttpRequestFactory requestFactory; + + private RequestFactoryMock requestFactoryMock; + private RequestMock requestMock; + private ResponseMock responseMock; + + [SetUp] + public void SetUp() + { + responseMock = new ResponseMock(); + requestMock = new RequestMock(responseMock); + requestFactoryMock = new RequestFactoryMock(requestMock); + } + + [Test] + public void Creation() + { + NoOpRequestFactoryInterceptor interceptor1 = new NoOpRequestFactoryInterceptor(); + NoOpRequestFactoryInterceptor interceptor2 = new NoOpRequestFactoryInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + + Assert.IsTrue(interceptor1.invoked); + Assert.IsTrue(interceptor2.invoked); + Assert.IsTrue(this.requestFactoryMock.created); + Assert.IsFalse(this.requestMock.executed); + + IClientHttpResponse response = request.Execute(); + Assert.IsTrue(this.requestMock.executed); + Assert.AreSame(this.responseMock, response); + } + + [Test] + public void BeforeExecution() + { + NoOpRequestBeforeInterceptor interceptor1 = new NoOpRequestBeforeInterceptor(); + NoOpRequestBeforeInterceptor interceptor2 = new NoOpRequestBeforeInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + + Assert.IsFalse(interceptor1.invoked); + Assert.IsFalse(interceptor2.invoked); + Assert.IsTrue(this.requestFactoryMock.created); + Assert.IsFalse(this.requestMock.executed); + + IClientHttpResponse response = request.Execute(); + + Assert.IsTrue(interceptor1.invoked); + Assert.IsTrue(interceptor2.invoked); + Assert.IsTrue(this.requestMock.executed); + Assert.AreSame(this.responseMock, response); + } + + [Test] + public void SyncExecution() + { + NoOpRequestSyncInterceptor interceptor1 = new NoOpRequestSyncInterceptor(); + NoOpRequestSyncInterceptor interceptor2 = new NoOpRequestSyncInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + + Assert.IsFalse(interceptor1.invoked); + Assert.IsFalse(interceptor2.invoked); + Assert.IsTrue(this.requestFactoryMock.created); + Assert.IsFalse(this.requestMock.executed); + + IClientHttpResponse response = request.Execute(); + + Assert.IsTrue(interceptor1.invoked); + Assert.IsTrue(interceptor2.invoked); + Assert.IsTrue(this.requestMock.executed); + Assert.AreSame(this.responseMock, response); + } + + [Test] + public void AsyncExecution() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + NoOpRequestAsyncInterceptor interceptor1 = new NoOpRequestAsyncInterceptor(); + ChangeHeaderInterceptor interceptor2 = new ChangeHeaderInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreSame(this.responseMock, args.Response, "Invalid response"); + Assert.IsNotNull(args.Response.Headers.Get("AfterAsyncExecution")); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + + Assert.IsTrue(interceptor1.invoked); + Assert.IsTrue(this.requestMock.executed); + Assert.IsNotNull(responseMock.Headers.Get("AfterAsyncExecution")); + } + + [Test] + public void NoSyncExecution() + { + NoOpExecutionInterceptor interceptor1 = new NoOpExecutionInterceptor(); + NoOpRequestSyncInterceptor interceptor2 = new NoOpRequestSyncInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + IClientHttpResponse response = request.Execute(); + + Assert.IsFalse(interceptor2.invoked); + Assert.IsFalse(requestMock.executed); + } + + [Test] + public void NoAsyncExecution() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + NoOpExecutionInterceptor interceptor1 = new NoOpExecutionInterceptor(); + NoOpRequestAsyncInterceptor interceptor2 = new NoOpRequestAsyncInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor1, interceptor2 }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + request.ExecuteAsync(null, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.Fail("No Execution"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + //manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + + Assert.IsFalse(interceptor2.invoked); + Assert.IsFalse(requestMock.executed); + } + + [Test] + public void ChangeHeaders() + { + ChangeHeaderInterceptor interceptor = new ChangeHeaderInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + + Assert.IsNotNull(requestMock.Headers.Get("AfterCreation")); + Assert.IsNull(requestMock.Headers.Get("BeforeSyncExecution")); + Assert.IsNull(responseMock.Headers.Get("AfterSyncExecution")); + + request.Execute(); + + Assert.IsNotNull(requestMock.Headers.Get("BeforeSyncExecution")); + Assert.IsNotNull(responseMock.Headers.Get("AfterSyncExecution")); + } + + [Test] + public void ChangeUri() + { + ChangeUriRequestFactoryInterceptor interceptor = new ChangeUriRequestFactoryInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + Assert.AreEqual(new Uri("http://example.com/2"), requestMock.Uri); + } + + [Test] + public void ChangeMethod() + { + ChangeMethodRequestFactoryInterceptor interceptor = new ChangeMethodRequestFactoryInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + Assert.AreEqual(HttpMethod.POST, requestMock.Method); + } + + [Test] + public void ChangeBody() + { + ChangeBodyInterceptor interceptor = new ChangeBodyInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + request.Execute(); + + MemoryStream stream = new MemoryStream(); + requestMock.Body(stream); + stream.Position = 0; + string result = null; + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + result = reader.ReadToEnd(); + } + Assert.AreEqual("New body", result); + } + + [Test] + public void ChangeResponse() + { + ChangeResponseInterceptor interceptor = new ChangeResponseInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + IClientHttpResponse response = request.Execute(); + + Assert.AreNotSame(this.responseMock, response); + } + + [Test] + public void ChangeResponseAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + Object state = new Object(); + + ChangeResponseInterceptor interceptor = new ChangeResponseInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new IClientHttpRequestInterceptor[] { interceptor }); + + IClientHttpRequest request = requestFactory.CreateRequest(new Uri("http://example.com"), HttpMethod.GET); + request.ExecuteAsync(state, delegate(ClientHttpRequestCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreNotSame(this.responseMock, args.Response, "Invalid response"); + Assert.AreEqual(state, args.UserState); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + #region Inner classes + + private sealed class NoOpRequestFactoryInterceptor : IClientHttpRequestFactoryInterceptor + { + public bool invoked; + + public IClientHttpRequest Create(IClientHttpRequestFactoryCreation creation) + { + this.invoked = true; + return creation.Create(); + } + } + private sealed class NoOpRequestBeforeInterceptor : IClientHttpRequestBeforeInterceptor + { + public bool invoked; + + public void BeforeExecute(IClientHttpRequestContext request) + { + this.invoked = true; + } + } + + private sealed class NoOpRequestSyncInterceptor : IClientHttpRequestSyncInterceptor + { + public bool invoked; + + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + this.invoked = true; + return execution.Execute(); + } + } + + private sealed class NoOpRequestAsyncInterceptor : IClientHttpRequestAsyncInterceptor + { + public bool invoked; + + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + this.invoked = true; + execution.ExecuteAsync(); + } + } + + private sealed class NoOpExecutionInterceptor : + IClientHttpRequestSyncInterceptor, + IClientHttpRequestAsyncInterceptor + { + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + return null; //execution.Execute(); + } + + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + //execution.ExecuteAsync(); + } + } + + private sealed class ChangeHeaderInterceptor : + IClientHttpRequestFactoryInterceptor, + IClientHttpRequestSyncInterceptor, + IClientHttpRequestAsyncInterceptor + { + // IClientHttpRequestFactoryInterceptor + public IClientHttpRequest Create(IClientHttpRequestFactoryCreation creation) + { + IClientHttpRequest request = creation.Create(); + request.Headers.Add("AfterCreation", "MyValue"); + return request; + } + + // IClientHttpRequestSyncInterceptor + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + execution.Headers.Add("BeforeSyncExecution", "MyValue"); + IClientHttpResponse response = execution.Execute(); + response.Headers.Add("AfterSyncExecution", "MyValue"); + return response; + } + + // IClientHttpRequestAsyncInterceptor + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + execution.Headers.Add("BeforeAsyncExecution", "MyValue"); + execution.ExecuteAsync( + delegate(IClientHttpResponseAsyncContext ctx) + { + ctx.Response.Headers.Add("AfterAsyncExecution", "MyValue"); + }); + } + } + + private sealed class ChangeUriRequestFactoryInterceptor : IClientHttpRequestFactoryInterceptor + { + public IClientHttpRequest Create(IClientHttpRequestFactoryCreation creation) + { + creation.Uri = new Uri("http://example.com/2"); + return creation.Create(); + } + } + + private sealed class ChangeMethodRequestFactoryInterceptor : IClientHttpRequestFactoryInterceptor + { + public IClientHttpRequest Create(IClientHttpRequestFactoryCreation creation) + { + creation.Method = HttpMethod.POST; + return creation.Create(); + } + } + + private sealed class ChangeBodyInterceptor : + IClientHttpRequestSyncInterceptor, + IClientHttpRequestAsyncInterceptor + { + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + byte[] bytes = Encoding.UTF8.GetBytes("New body"); + execution.Body = delegate(Stream stream) + { + stream.Write(bytes, 0, bytes.Length); + }; + return execution.Execute(); + } + + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + byte[] bytes = Encoding.UTF8.GetBytes("New body"); + execution.Body = delegate(Stream stream) + { + stream.Write(bytes, 0, bytes.Length); + }; + execution.ExecuteAsync(); + } + } + + private sealed class ChangeResponseInterceptor : + IClientHttpRequestSyncInterceptor, + IClientHttpRequestAsyncInterceptor + { + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + execution.Execute(); + return new ResponseMock(); + } + + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + execution.ExecuteAsync( + delegate(IClientHttpResponseAsyncContext ctx) + { + ctx.Response = new ResponseMock(); + }); + } + } + + private sealed class RequestFactoryMock : IClientHttpRequestFactory + { + private RequestMock requestMock; + + public bool created = false; + + public RequestFactoryMock(RequestMock requestMock) + { + this.requestMock = requestMock; + } + + public IClientHttpRequest CreateRequest(Uri uri, HttpMethod method) + { + this.created = true; + this.requestMock.Uri = uri; + this.requestMock.Method = method; + return requestMock; + } + } + + private sealed class RequestMock : IClientHttpRequest + { + private Uri uri; + private HttpMethod method; + private HttpHeaders headers = new HttpHeaders(); + private Action body; + + private ResponseMock responseMock; + + public bool executed = false; + + public RequestMock(ResponseMock responseMock) + { + this.responseMock = responseMock; + } + + public HttpMethod Method + { + get { return this.method; } + set { this.method = value; } + } + + public Uri Uri + { + get { return this.uri; } + set { this.uri = value; } + } + + public HttpHeaders Headers + { + get { return this.headers; } + } + + public Action Body + { + get { return this.body; } + set { this.body = value; } + } + + public IClientHttpResponse Execute() + { + this.executed = true; + return this.responseMock; + } + + public void ExecuteAsync(object state, Action executeCompleted) + { + this.executed = true; + executeCompleted(new ClientHttpRequestCompletedEventArgs(this.responseMock, null, false, state)); + } + + public void CancelAsync() + { + } + } + + private sealed class ResponseMock : IClientHttpResponse + { + private HttpHeaders headers = new HttpHeaders(); + + public HttpStatusCode StatusCode + { + get { return HttpStatusCode.OK; } + } + + public string StatusDescription + { + get { return String.Empty; } + } + + public HttpHeaders Headers + { + get { return this.headers; } + } + + public Stream Body + { + get { return null; } + } + + public void Close() + { + } + + public void Dispose() + { + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Client/WebClientHttpRequestFactoryIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Client/WebClientHttpRequestFactoryIntegrationTests.cs new file mode 100644 index 0000000..4b78e81 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Client/WebClientHttpRequestFactoryIntegrationTests.cs @@ -0,0 +1,194 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Text; +using System.ServiceModel; +using System.ServiceModel.Web; +using System.ServiceModel.Security; +using System.IdentityModel.Tokens; +using System.IdentityModel.Selectors; + +using NUnit.Framework; + +namespace Spring.Http.Client +{ + /// + /// Integration tests for the WebClientHttpRequestFactory class. + /// + /// Bruno Baia + [TestFixture] + public class WebClientHttpRequestFactoryIntegrationTests : AbstractClientHttpRequestFactoryIntegrationTests + { + private WebClientHttpRequestFactory webRequestFactory; + + protected override IClientHttpRequestFactory CreateRequestFactory() + { + webRequestFactory = new WebClientHttpRequestFactory(); + return webRequestFactory; + } + + protected override void ConfigureWebServiceHost(WebServiceHost webServiceHost) + { + base.ConfigureWebServiceHost(webServiceHost); + + WebHttpBinding httpBinding1 = new WebHttpBinding(); + httpBinding1.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly; + httpBinding1.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic; + webServiceHost.AddServiceEndpoint(typeof(TestService), httpBinding1, "/basic"); + + WebHttpBinding httpBinding2 = new WebHttpBinding(); + httpBinding2.Security.Mode = WebHttpSecurityMode.TransportCredentialOnly; + httpBinding2.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm; + webServiceHost.AddServiceEndpoint(typeof(TestService), httpBinding2, "/ntlm"); + + webServiceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom; + webServiceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNamePasswordValidator(); + } + + [Test] + public void Timeout() + { + this.webRequestFactory.Timeout = 1000; + + IClientHttpRequest request = this.CreateRequest("/sleep/2", HttpMethod.GET); + try + { + request.Execute(); + Assert.Fail("Execute should failed !"); + } + catch (Exception ex) + { + WebException webEx = ex as WebException; + Assert.IsNotNull(webEx, "Invalid response exception"); + Assert.AreEqual(WebExceptionStatus.Timeout, webEx.Status, "Invalid response exception status"); + } + } + + [Test] + public void BasicAuthorizationKO() + { + this.webRequestFactory.Credentials = new NetworkCredential("bruno", "password"); + + IClientHttpRequest request = this.CreateRequest("/basic/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.Forbidden, response.StatusCode, "Invalid status code"); + } + } + + [Test] + public void BasicAuthorizationOK() + { + this.webRequestFactory.Credentials = new NetworkCredential("login", "password"); + + IClientHttpRequest request = this.CreateRequest("/basic/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + } + } + + [Test] + public void NtlmAuthorizationKO() + { + IClientHttpRequest request = this.CreateRequest("/ntlm/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode, "Invalid status code"); + } + } + + [Test] + public void NtlmAuthorizationOK() + { + this.webRequestFactory.UseDefaultCredentials = true; + + IClientHttpRequest request = this.CreateRequest("/ntlm/echo", HttpMethod.PUT); + request.Headers.ContentLength = 0; + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + } + } + + [Test] + public void SpecialHeaders() // related to HttpWebRequest implementation + { + IClientHttpRequest request = this.CreateRequest("/echo", HttpMethod.PUT); + + request.Headers.Accept = new MediaType[] { MediaType.ALL }; + request.Headers.ContentType = MediaType.TEXT_PLAIN; + request.Headers["Connection"] = "close"; + request.Headers.ContentLength = 0; +#if NET_4_0 + request.Headers.Date = DateTime.Now; +#endif + request.Headers["Expect"] = "bla"; + request.Headers.IfModifiedSince = DateTime.Now; + request.Headers["Referer"] = "http://www.springframework.net/"; + //request.Headers["Transfer-Encoding"] = "Identity"; + request.Headers["User-Agent"] = "Unit tests"; + + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "Invalid status code"); + } + } + + [Test(Description="SPRNETREST-28")] + public void RangeHeader() + { + IClientHttpRequest request = this.CreateRequest("/echo", HttpMethod.PUT); + + request.Headers.ContentLength = 0; + request.Headers["Range"] = "bytes=128-1024"; + + using (IClientHttpResponse response = request.Execute()) + { + Assert.AreEqual("bytes=128-1024", response.Headers["Range"]); + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "OK"); + } + } + + + #region Test classes + + public class CustomUserNamePasswordValidator : UserNamePasswordValidator + { + public override void Validate(string userName, string password) + { + if (userName == "login" && password == "password") + { + return; + } + throw new SecurityTokenException("Unknown username or incorrect password"); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs new file mode 100644 index 0000000..d06580c --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs @@ -0,0 +1,87 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using NUnit.Framework; + +namespace Spring.Http.Converters +{ + /// + /// Unit tests for the ByteArrayHttpMessageConverter class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class ByteArrayHttpMessageConverterTests + { + private ByteArrayHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new ByteArrayHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(byte[]), new MediaType("application", "octet-stream"))); + Assert.IsTrue(converter.CanRead(typeof(byte[]), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(byte[]), MediaType.ALL)); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("application", "octet-stream"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(byte[]), new MediaType("application", "octet-stream"))); + Assert.IsTrue(converter.CanWrite(typeof(byte[]), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(byte[]), MediaType.ALL)); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("application", "octet-stream"))); + } + + [Test] + public void Read() + { + byte[] body = new byte[] { 0x1, 0x2 }; + + MockHttpInputMessage message = new MockHttpInputMessage(body); + message.Headers.ContentLength = body.Length; + + byte[] result = converter.Read(message); + Assert.AreEqual(body.Length, result.Length, "Invalid result"); + Assert.AreEqual(body[0], result[0], "Invalid result"); + Assert.AreEqual(body[1], result[1], "Invalid result"); + } + + [Test] + public void Write() + { + byte[] body = new byte[] { 0x1, 0x2 }; + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(body, message.GetBodyAsBytes(), "Invalid result"); + Assert.AreEqual(new MediaType("application", "octet-stream"), message.Headers.ContentType, "Invalid content-type"); + //Assert.AreEqual(2, message.Headers.ContentLength, "Invalid content-length"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs new file mode 100644 index 0000000..dea831e --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs @@ -0,0 +1,111 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Globalization; +using System.ServiceModel.Syndication; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Unit tests for the Atom10FeedHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class Atom10FeedHttpMessageConverterTests + { + private Atom10FeedHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new Atom10FeedHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("application", "atom+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationItem), new MediaType("application", "atom+xml"))); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("application", "atom+xml"))); + Assert.IsFalse(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "plain"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(SyndicationFeed), new MediaType("application", "atom+xml"))); + Assert.IsTrue(converter.CanWrite(typeof(SyndicationItem), new MediaType("application", "atom+xml"))); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("application", "atom+xml"))); + Assert.IsFalse(converter.CanWrite(typeof(SyndicationFeed), new MediaType("text", "plain"))); + } + + [Test] + public void Read() + { + DateTimeOffset offset = DateTimeOffset.UtcNow; + + string body = String.Format("Test FeedThis is a test feedAtom10FeedHttpMessageConverterTests.WriteCopyright 2010{0}Bruno Baïahttp://www.springframework.net/bbaiabruno.baia@springframework.net", + offset.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture)); + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + SyndicationFeed result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("Atom10FeedHttpMessageConverterTests.Write", result.Id, "Invalid result"); + Assert.AreEqual("Test Feed", result.Title.Text, "Invalid result"); + Assert.AreEqual("This is a test feed", result.Description.Text, "Invalid result"); + Assert.IsTrue(result.Links.Count == 1, "Invalid result"); + Assert.AreEqual(new Uri("http://www.springframework.net/Feed"), result.Links[0].Uri, "Invalid result"); + Assert.AreEqual("Copyright 2010", result.Copyright.Text, "Invalid result"); + Assert.IsTrue(result.Authors.Count == 1, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Authors[0].Name, "Invalid result"); + Assert.AreEqual("bruno.baia@springframework.net", result.Authors[0].Email, "Invalid result"); + Assert.AreEqual("http://www.springframework.net/bbaia", result.Authors[0].Uri, "Invalid result"); + } + + [Test] + public void Write() + { + DateTimeOffset offset = DateTimeOffset.UtcNow; + + string expectedBody = String.Format("Test FeedThis is a test feedAtom10FeedHttpMessageConverterTests.WriteCopyright 2010{0}Bruno Baïahttp://www.springframework.net/bbaiabruno.baia@springframework.net", + offset.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture)); + + SyndicationFeed body = new SyndicationFeed("Test Feed", "This is a test feed", new Uri("http://www.springframework.net/Feed"), "Atom10FeedHttpMessageConverterTests.Write", offset); + SyndicationPerson sp = new SyndicationPerson("bruno.baia@springframework.net", "Bruno Baïa", "http://www.springframework.net/bbaia"); + body.Authors.Add(sp); + body.Copyright = new TextSyndicationContent("Copyright 2010"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "atom+xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + } +} +#endif diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs new file mode 100644 index 0000000..d34b279 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,172 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Collections.Generic; +using System.ServiceModel; +using System.ServiceModel.Web; +using System.ServiceModel.Syndication; + +using Spring.Rest.Client; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Integration tests for the SyndicationFeed based IHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class FeedHttpMessageConverterIntegrationTests + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(FeedHttpMessageConverterIntegrationTests)); + + #endregion + + private WebServiceHost webServiceHost; + private string uri = "http://localhost:1337"; + private RestTemplate template; + + [SetUp] + public void SetUp() + { + template = new RestTemplate(uri); + template.MessageConverters = new List(); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + [Test] + public void Rss20GetForObject() + { + template.MessageConverters.Add(new Rss20FeedHttpMessageConverter()); + + SyndicationFeed result = template.GetForObject("feed"); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("Test Feed", result.Title.Text, "Invalid content"); + Assert.AreEqual("This is a test feed", result.Description.Text, "Invalid content"); + } + + [Test] + public void Rss20PostForMessage() + { + template.MessageConverters.Add(new Rss20FeedHttpMessageConverter()); + + SyndicationItem item = new SyndicationItem("Bruno's item", "Bruno's content", null); + + HttpResponseMessage result = template.PostForMessage("feed/entry", item); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("Syndication item added with title 'Bruno's item'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void Atom10GetForObject() + { + template.MessageConverters.Add(new Atom10FeedHttpMessageConverter()); + + SyndicationFeed result = template.GetForObject("feed/?format=atom"); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("Test Feed", result.Title.Text, "Invalid content"); + Assert.AreEqual("This is a test feed", result.Description.Text, "Invalid content"); + } + + [Test] + public void Atom10PostForMessage() + { + template.MessageConverters.Add(new Atom10FeedHttpMessageConverter()); + + SyndicationItem item = new SyndicationItem("Bruno's item", "Bruno's content", null); + + HttpResponseMessage result = template.PostForMessage("feed/entry", item); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("Syndication item added with title 'Bruno's item'", result.StatusDescription, "Invalid status description"); + } + + #region REST test service + + [ServiceContract] + [ServiceKnownType(typeof(Atom10FeedFormatter))] + [ServiceKnownType(typeof(Rss20FeedFormatter))] + [ServiceKnownType(typeof(Atom10ItemFormatter))] + [ServiceKnownType(typeof(Rss20ItemFormatter))] + public class TestService + { + [OperationContract] + [WebGet(UriTemplate = "feed/", BodyStyle = WebMessageBodyStyle.Bare)] + public SyndicationFeedFormatter CreateFeed() + { + // Create a new Syndication Feed. + SyndicationFeed feed = new SyndicationFeed("Test Feed", "This is a test feed", null); + List items = new List(); + + // Create a new Syndication Item. + SyndicationItem item = new SyndicationItem("An item", "Item content", null); + items.Add(item); + feed.Items = items; + + // Return ATOM or RSS based on uri + // rss -> http://localhost:1337/feed/ + // atom -> http://localhost:1337/feed/?format=atom + string query = WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters["format"]; + SyndicationFeedFormatter formatter = null; + if (query == "atom") + { + formatter = new Atom10FeedFormatter(feed); + WebOperationContext.Current.OutgoingResponse.ContentType = "application/atom+xml"; + } + else + { + formatter = new Rss20FeedFormatter(feed); + WebOperationContext.Current.OutgoingResponse.ContentType = "application/rss+xml"; + } + + return formatter; + } + + [OperationContract] + [WebInvoke(UriTemplate = "feed/entry")] + public void AddEntry(SyndicationItemFormatter item) + { + WebOperationContext context = WebOperationContext.Current; + + // Add entry + // .. + + context.OutgoingResponse.StatusCode = HttpStatusCode.Created; + context.OutgoingResponse.StatusDescription = String.Format("Syndication item added with title '{0}'", item.Item.Title.Text); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs new file mode 100644 index 0000000..da304dd --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs @@ -0,0 +1,109 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Globalization; +using System.ServiceModel.Syndication; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Unit tests for the Rss20FeedHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class Rss20FeedHttpMessageConverterTests + { + private Rss20FeedHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new Rss20FeedHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("application", "rss+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationItem), new MediaType("application", "rss+xml"))); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("application", "rss+xml"))); + Assert.IsFalse(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "plain"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(SyndicationFeed), new MediaType("application", "rss+xml"))); + Assert.IsTrue(converter.CanWrite(typeof(SyndicationItem), new MediaType("application", "rss+xml"))); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("application", "rss+xml"))); + Assert.IsFalse(converter.CanWrite(typeof(SyndicationFeed), new MediaType("text", "plain"))); + } + + [Test] + public void Read() + { + DateTimeOffset offset = DateTimeOffset.UtcNow; + + string body = String.Format("Test Feedhttp://www.springframework.net/FeedThis is a test feedCopyright 2010bruno.baia@springframework.net{0}Atom10FeedHttpMessageConverterTests.Write", + offset.ToString("ddd, dd MMM yyyy HH:mm:ss Z", CultureInfo.InvariantCulture)); + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + SyndicationFeed result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("Atom10FeedHttpMessageConverterTests.Write", result.Id, "Invalid result"); + Assert.AreEqual("Test Feed", result.Title.Text, "Invalid result"); + Assert.AreEqual("This is a test feed", result.Description.Text, "Invalid result"); + Assert.IsTrue(result.Links.Count == 1, "Invalid result"); + Assert.AreEqual(new Uri("http://www.springframework.net/Feed"), result.Links[0].Uri, "Invalid result"); + Assert.AreEqual("Copyright 2010", result.Copyright.Text, "Invalid result"); + Assert.IsTrue(result.Authors.Count == 1, "Invalid result"); + Assert.AreEqual("bruno.baia@springframework.net", result.Authors[0].Email, "Invalid result"); + } + + [Test] + public void Write() + { + DateTimeOffset offset = DateTimeOffset.UtcNow; + + string expectedBody = String.Format("Test Feedhttp://www.springframework.net/FeedThis is a test feedCopyright 2010bruno.baia@springframework.net{0}Atom10FeedHttpMessageConverterTests.Write", + offset.ToString("ddd, dd MMM yyyy HH:mm:ss Z", CultureInfo.InvariantCulture)); + + SyndicationFeed body = new SyndicationFeed("Test Feed", "This is a test feed", new Uri("http://www.springframework.net/Feed"), "Atom10FeedHttpMessageConverterTests.Write", offset); + SyndicationPerson sp = new SyndicationPerson("bruno.baia@springframework.net", "Bruno Baïa", "http://www.springframework.net/bbaia"); + body.Authors.Add(sp); + body.Copyright = new TextSyndicationContent("Copyright 2010"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "rss+xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + } +} +#endif diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/FileInfoHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/FileInfoHttpMessageConverterTests.cs new file mode 100644 index 0000000..5843ec7 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/FileInfoHttpMessageConverterTests.cs @@ -0,0 +1,109 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; + +using NUnit.Framework; + +namespace Spring.Http.Converters +{ + /// + /// Unit tests for the FileInfoHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class FileInfoHttpMessageConverterTests + { + private FileInfoHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new FileInfoHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsFalse(converter.CanRead(typeof(FileInfo), MediaType.ALL)); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(FileInfo), new MediaType("application", "octet-stream"))); + Assert.IsTrue(converter.CanWrite(typeof(FileInfo), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(FileInfo), MediaType.ALL)); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("application", "octet-stream"))); + } + + //[Test] + //public void Write() + //{ + // FileInfo body = new FileInfo(@"C:\File.txt"); + + // MockHttpOutputMessage message = new MockHttpOutputMessage(); + + // converter.Write(body, null, message); + + // Assert.AreEqual(body, message.GetBodyAsBytes(), "Invalid result"); + // Assert.AreEqual(new MediaType("text", "plain"), message.Headers.ContentType, "Invalid content-type"); + //} + + [Test] + public void WriteWithUnknownExtension() + { + FileInfo body = new FileInfo(@"C:\Dummy.unknown"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + //Assert.AreEqual(body, message.GetBodyAsBytes(), "Invalid result"); + Assert.AreEqual(new MediaType("application", "octet-stream"), message.Headers.ContentType, "Invalid content-type"); + } + + [Test] + public void WriteWithKnownExtension() + { + FileInfo body = new FileInfo(@"C:\Dummy.txt"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(new MediaType("text", "plain"), message.Headers.ContentType, "Invalid content-type"); + } + + + [Test] + public void WriteWithCustomExtension() + { + FileInfo body = new FileInfo(@"C:\Dummy.myext"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.MimeMapping.Add(".myext", "spring/custom"); + converter.Write(body, null, message); + + Assert.AreEqual(new MediaType("spring", "custom"), message.Headers.ContentType, "Invalid content-type"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/FileToUpload.png b/netstandard/Spring.Rest.Tests/Http/Converters/FileToUpload.png new file mode 100644 index 0000000..54ffa3e Binary files /dev/null and b/netstandard/Spring.Rest.Tests/Http/Converters/FileToUpload.png differ diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/FormHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/FormHttpMessageConverterTests.cs new file mode 100644 index 0000000..b63c9d2 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/FormHttpMessageConverterTests.cs @@ -0,0 +1,179 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Collections.Specialized; + +using NUnit.Framework; + +using Spring.IO; + +namespace Spring.Http.Converters +{ + /// + /// Unit tests for the FormHttpMessageConverter class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class FormHttpMessageConverterTests + { + private FormHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new FormHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(NameValueCollection), new MediaType("application", "x-www-form-urlencoded"))); + Assert.IsFalse(converter.CanRead(typeof(NameValueCollection), new MediaType("application", "xml"))); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("application", "x-www-form-urlencoded"))); + Assert.IsFalse(converter.CanRead(typeof(IDictionary), new MediaType("multipart","form-data"))); + } + + [Test] + public void CanReadAddedSupportedMediaType() + { + converter.SupportedMediaTypes.Add(MediaType.TEXT_XML); + + Assert.IsTrue(converter.CanRead(typeof(NameValueCollection), new MediaType("text", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(NameValueCollection), new MediaType("application", "x-www-form-urlencoded"))); + Assert.IsFalse(converter.CanWrite(typeof(NameValueCollection), new MediaType("application", "xml"))); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("application", "x-www-form-urlencoded"))); + Assert.IsTrue(converter.CanWrite(typeof(IDictionary), new MediaType("multipart", "form-data"))); + Assert.IsFalse(converter.CanWrite(typeof(IDictionary), new MediaType("application", "xml"))); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("multipart", "form-data"))); + } + + [Test] + public void ReadForm() + { + String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; + Encoding charSet = Encoding.GetEncoding("ISO-8859-1"); + MediaType mediaType = new MediaType("application", "x-www-form-urlencoded", charSet); + + MockHttpInputMessage message = new MockHttpInputMessage(body, charSet); + message.Headers.ContentType = mediaType; + + NameValueCollection result = converter.Read(message); + Assert.AreEqual(3, result.Count, "Invalid result"); + Assert.AreEqual(1, result.GetValues(0).Length, "Invalid result"); + Assert.AreEqual("value 1", result.GetValues(0)[0], "Invalid result"); + Assert.AreEqual(2, result.GetValues("name 2").Length, "Invalid result"); + Assert.AreEqual("value 2+1", result.GetValues("name 2")[0], "Invalid result"); + Assert.AreEqual("value 2+2", result.GetValues("name 2")[1], "Invalid result"); + Assert.IsNull(result["name 3"], "Invalid result"); + } + + [Test] + public void WriteForm() + { + string expectedBody = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; + NameValueCollection body = new NameValueCollection(); + body.Add("name 1", "value 1"); + body.Add("name 2", "value 2+1"); + body.Add("name 2", "value 2+2"); + body.Add("name 3", null); + Encoding charSet = Encoding.GetEncoding("ISO-8859-1"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, MediaType.APPLICATION_FORM_URLENCODED, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(charSet), "Invalid result"); + Assert.AreEqual(new MediaType("application", "x-www-form-urlencoded"), message.Headers.ContentType, "Invalid content-type"); + //Assert.AreEqual(charSet.GetBytes(expectedBody).Length, message.Headers.ContentLength, "Invalid content-length"); + } + + [Test] + public void WriteMultipartWithFileInfo() + { + string fileToUpload = Path.Combine(Environment.CurrentDirectory, @"Http\Converters\FileToUpload.png"); + + IDictionary parts = new Dictionary(); + parts.Add("name 1", "value 1"); + parts.Add("name 2", "value 2+1"); + HttpEntity entity = new HttpEntity(""); + entity.Headers.ContentType = MediaType.TEXT_XML; + parts.Add("xml", entity); + parts.Add("logo", new FileInfo(fileToUpload)); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(parts, MediaType.MULTIPART_FORM_DATA, message); + + MediaType contentType = message.Headers.ContentType; + Assert.IsNotNull(contentType, "Invalid content-type"); + Assert.AreEqual("multipart", contentType.Type, "Invalid content-type"); + Assert.AreEqual("form-data", contentType.Subtype, "Invalid content-type"); + string boundary = contentType.GetParameter("boundary"); + Assert.IsNotNull(boundary, "Invalid content-type"); + + string result = message.GetBodyAsString(Encoding.UTF8); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"name 1\"\r\nContent-Type: text/plain;charset=ISO-8859-1\r\n\r\nvalue 1\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"name 2\"\r\nContent-Type: text/plain;charset=ISO-8859-1\r\n\r\nvalue 2+1\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"xml\"\r\nContent-Type: text/xml\r\n\r\n\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"logo\"; filename=\"FileToUpload.png\"\r\nContent-Type: image/png\r\n\r\n"), "Invalid content-disposition"); + } + + [Test] + public void WriteMultipartWithIResource() + { + string fileToUpload = Path.Combine(Environment.CurrentDirectory, @"Http\Converters\FileToUpload.png"); + + IDictionary parts = new Dictionary(); + parts.Add("name 1", "value 1"); + parts.Add("name 2", "value 2+1"); + HttpEntity entity = new HttpEntity(""); + entity.Headers.ContentType = MediaType.TEXT_XML; + parts.Add("xml", entity); + parts.Add("logo", new FileResource(fileToUpload)); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(parts, MediaType.MULTIPART_FORM_DATA, message); + + MediaType contentType = message.Headers.ContentType; + Assert.IsNotNull(contentType, "Invalid content-type"); + Assert.AreEqual("multipart", contentType.Type, "Invalid content-type"); + Assert.AreEqual("form-data", contentType.Subtype, "Invalid content-type"); + string boundary = contentType.GetParameter("boundary"); + Assert.IsNotNull(boundary, "Invalid content-type"); + + string result = message.GetBodyAsString(Encoding.UTF8); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"name 1\"\r\nContent-Type: text/plain;charset=ISO-8859-1\r\n\r\nvalue 1\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"name 2\"\r\nContent-Type: text/plain;charset=ISO-8859-1\r\n\r\nvalue 2+1\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"xml\"\r\nContent-Type: text/xml\r\n\r\n\r\n"), "Invalid content-disposition"); + Assert.IsTrue(result.Contains("--" + boundary + "\r\nContent-Disposition: form-data; name=\"logo\"; filename=\"FileToUpload.png\"\r\nContent-Type: image/png\r\n\r\n"), "Invalid content-disposition"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Json/DataContractJsonHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Json/DataContractJsonHttpMessageConverterTests.cs new file mode 100644 index 0000000..03e7ef3 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Json/DataContractJsonHttpMessageConverterTests.cs @@ -0,0 +1,107 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Text; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Json +{ + /// + /// Unit tests for the DataContractJsonHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class DataContractJsonHttpMessageConverterTests + { + private DataContractJsonHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new DataContractJsonHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(CustomClass), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanRead(typeof(CustomClass), new MediaType("text", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(CustomClass), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanWrite(typeof(CustomClass), new MediaType("text", "xml"))); + } + + [Test] + public void Read() + { + string body = "{\"ID\":\"1\",\"Name\":\"Bruno Baïa\"}"; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + CustomClass result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + } + + [Test] + public void Write() + { + string expectedBody = "{\"ID\":\"1\",\"Name\":\"Bruno Baïa\"}"; + CustomClass body = new CustomClass("1", "Bruno Baïa"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "json"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + #region Test classes + + public class CustomClass + { + public string ID { get; set; } + + public string Name { get; set; } + + public CustomClass() + { + } + + public CustomClass(string id, string name) + { + this.ID = id; + this.Name = name; + } + } + + #endregion + } +} +#endif diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs new file mode 100644 index 0000000..b817f5f --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,244 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Collections.Generic; +using System.ServiceModel; +using System.ServiceModel.Web; + +using Spring.Json; +using Spring.Rest.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Json +{ + /// + /// Integration tests for the DataContractJsonHttpMessageConverter, NJsonHttpMessageConverter and SpringJsonHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class JsonHttpMessageConverterIntegrationTests + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(JsonHttpMessageConverterIntegrationTests)); + + #endregion + + private WebServiceHost webServiceHost; + private string uri = "http://localhost:1337"; + private RestTemplate template; + private MediaType contentType; + + [SetUp] + public void SetUp() + { + template = new RestTemplate(uri); + template.MessageConverters = new List(); + + contentType = new MediaType("application", "json"); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + [Test] + public void GetForJson() + { + template.MessageConverters.Add(new StringHttpMessageConverter()); + + string resultAsString = template.GetForObject("user/{id}", 1); + Assert.AreEqual("{\"ID\":\"1\",\"Name\":\"Bruno Baïa\"}", resultAsString, "Invalid content"); + } + + [Test] + public void GetForObject() + { + template.MessageConverters.Add(new DataContractJsonHttpMessageConverter()); + + User result = template.GetForObject("user/{id}", 1); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("1", result.ID, "Invalid content"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid content"); + } + + [Test] + public void GetForJToken() + { + template.MessageConverters.Add(new NJsonHttpMessageConverter()); + + JToken result = template.GetForObject("user/{id}", 1); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("1", result.Value("ID"), "Invalid content"); + Assert.AreEqual("Bruno Baïa", result.Value("Name"), "Invalid content"); + } + + [Test] + public void GetForJsonValue() + { + template.MessageConverters.Add(new SpringJsonHttpMessageConverter()); + + JsonValue result = template.GetForObject("user/{id}", 1); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("1", result.GetValue("ID"), "Invalid content"); + Assert.AreEqual("Bruno Baïa", result.GetValue("Name"), "Invalid content"); + } + + [Test] + public void PostJsonForMessage() + { + template.MessageConverters.Add(new StringHttpMessageConverter()); + + HttpEntity entity = new HttpEntity("{\"Name\":\"Lisa Baia\"}"); + entity.Headers.ContentType = MediaType.APPLICATION_JSON; + + HttpResponseMessage result = template.PostForMessage("user", entity); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void PostObjectForMessage() + { + template.MessageConverters.Add(new DataContractJsonHttpMessageConverter()); + + User user = new User() { Name = "Lisa Baia" }; + + HttpResponseMessage result = template.PostForMessage("user", user); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void PostJTokenForMessage() + { + template.MessageConverters.Add(new NJsonHttpMessageConverter()); + + JObject user = new JObject( + new JProperty("Name", new JValue("Lisa Baia"))); + + HttpResponseMessage result = template.PostForMessage("user", user); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void PostJsonValueForMessage() + { + template.MessageConverters.Add(new SpringJsonHttpMessageConverter()); + + JsonObject user = new JsonObject(); + user.AddValue("Name", new JsonValue("Lisa Baia")); + + HttpResponseMessage result = template.PostForMessage("user", user); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + #region REST test service + + //[DataContract] + public class User + { + //[DataMember] + public string ID { get; set; } + + //[DataMember] + public string Name { get; set; } + } + + [ServiceContract] + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] + public class TestService + { + private IList users; + + public TestService() + { + users = new List(); + users.Add(new User() { ID = "1", Name = "Bruno Baïa" }); + users.Add(new User() { ID = "2", Name = "Marie Baia" }); + } + + [OperationContract] + [WebGet(UriTemplate = "user/{id}", ResponseFormat = WebMessageFormat.Json)] + public User GetUser(string id) + { + WebOperationContext context = WebOperationContext.Current; + + foreach (User user in this.users) + { + if (user.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + { + return user; + } + } + + context.OutgoingResponse.SetStatusAsNotFound(String.Format("User with id '{0}' not found", id)); + return null; + } + + [OperationContract] + [WebInvoke(UriTemplate = "user", Method = "POST", RequestFormat = WebMessageFormat.Json)] + public void Create(User user) + { + WebOperationContext context = WebOperationContext.Current; + + UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; + UriTemplate template = new UriTemplate("/user/{id}"); + + MediaType mediaType = MediaType.Parse(context.IncomingRequest.ContentType); + + if (!String.IsNullOrEmpty(user.ID)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' already exists", user.ID); + return; + } + + user.ID = (users.Count + 1).ToString(); // generate new ID + + users.Add(user); + + Uri uri = template.BindByPosition(match.BaseUri, user.ID); + context.OutgoingResponse.SetStatusAsCreated(uri); + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' created with '{1}'", user.ID, user.Name); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Json/NJsonHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Json/NJsonHttpMessageConverterTests.cs new file mode 100644 index 0000000..186c9e4 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Json/NJsonHttpMessageConverterTests.cs @@ -0,0 +1,150 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Text; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Json +{ + /// + /// Unit tests for the NJsonHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class NJsonHttpMessageConverterTests + { + private NJsonHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new NJsonHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(CustomClass), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JArray), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanRead(typeof(CustomClass), new MediaType("text", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(CustomClass), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JArray), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanWrite(typeof(CustomClass), new MediaType("text", "xml"))); + } + + [Test] + public void ReadClass() + { + string body = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + CustomClass result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual(1, result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + } + + [Test] + public void ReadJToken() + { + string body = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + JToken result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual(1, result.Value("ID"), "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Value("Name"), "Invalid result"); + } + + [Test] + public void WriteClass() + { + string expectedBody = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + CustomClass body = new CustomClass(1, "Bruno Baïa"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "json"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + [Test] + public void WriteJToken() + { + string expectedBody = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + JObject body = new JObject( + new JProperty("ID", new JValue(1)), + new JProperty("Name", new JValue("Bruno Baïa"))); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "json"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + #region Test classes + + public class CustomClass + { + private int _id; + public int ID + { + get { return _id; } + set { _id = value; } + } + + private string _name; + public string Name + { + get { return _name; } + set { _name = value; } + } + + public CustomClass() + { + } + + public CustomClass(int id, string name) + { + this.ID = id; + this.Name = name; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Json/SpringJsonHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Json/SpringJsonHttpMessageConverterTests.cs new file mode 100644 index 0000000..0dd71fe --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Json/SpringJsonHttpMessageConverterTests.cs @@ -0,0 +1,209 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; + +using Spring.Json; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Json +{ + /// + /// Unit tests for the SpringJsonHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class SpringJsonHttpMessageConverterTests + { + private SpringJsonHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterDeserializer(typeof(CustomClass1), new CustomClass1Deserializer()); + mapper.RegisterSerializer(typeof(CustomClass2), new CustomClass2Serializer()); + + converter = new SpringJsonHttpMessageConverter(mapper); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(CustomClass1), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanRead(typeof(CustomClass2), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JsonValue), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JsonObject), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanRead(typeof(JsonValue), new MediaType("text", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsFalse(converter.CanWrite(typeof(CustomClass1), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanWrite(typeof(CustomClass2), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JsonValue), new MediaType("application", "json"))); + Assert.IsTrue(converter.CanRead(typeof(JsonArray), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanWrite(typeof(CustomClass1), new MediaType("text", "xml"))); + } + + [Test] + public void ReadClass() + { + string body = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + CustomClass1 result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual(1, result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + } + + [Test] + public void ReadJsonValue() + { + string body = "{\"ID\":1,\"Name\":\"Bruno Baïa\"}"; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + JsonValue result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual(1, result.GetValue("ID"), "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.GetValue("Name"), "Invalid result"); + } + + [Test] + public void WriteClass() + { + string expectedBody = "{\"ID\":1,\"Age\":31}"; + CustomClass2 body = new CustomClass2(1, 31); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "json"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + [Test] + public void WriteJsonValue() + { + string expectedBody = "{\"ID\":1,\"Age\":31}"; + JsonObject body = new JsonObject(); + body.AddValue("ID", new JsonValue(1)); + body.AddValue("Age", new JsonValue(31)); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "json"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + #region Test classes + + public class CustomClass1 + { + private long _id; + public long ID + { + get { return _id; } + set { _id = value; } + } + + private string _name; + public string Name + { + get { return _name; } + set { _name = value; } + } + + public CustomClass1() + { + } + + public CustomClass1(long id, string name) + { + this.ID = id; + this.Name = name; + } + } + + public class CustomClass2 + { + private long _id; + public long ID + { + get { return _id; } + set { _id = value; } + } + + private int _age; + public int Age + { + get { return _age; } + set { _age = value; } + } + + public CustomClass2() + { + } + + public CustomClass2(long id, int age) + { + this.ID = id; + this.Age = age; + } + } + + public class CustomClass1Deserializer : IJsonDeserializer + { + public object Deserialize(JsonValue value, JsonMapper mapper) + { + CustomClass1 result = new CustomClass1(); + result.ID = value.GetValue("ID"); + result.Name = value.GetValue("Name"); + return result; + } + } + + public class CustomClass2Serializer : IJsonSerializer + { + public JsonValue Serialize(object obj, JsonMapper mapper) + { + CustomClass2 data = obj as CustomClass2; + + JsonObject result = new JsonObject(); + result.AddValue("ID", new JsonValue(data.ID)); + result.AddValue("Age", new JsonValue(data.Age)); + return result; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Resource.txt b/netstandard/Spring.Rest.Tests/Http/Converters/Resource.txt new file mode 100644 index 0000000..6dc6390 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Resource.txt @@ -0,0 +1 @@ +Hello from resource! \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/ResourceHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/ResourceHttpMessageConverterTests.cs new file mode 100644 index 0000000..09ed65e --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/ResourceHttpMessageConverterTests.cs @@ -0,0 +1,130 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; +using System.Text; + +using NUnit.Framework; + +using Spring.IO; + +namespace Spring.Http.Converters +{ + /// + /// Unit tests for the ResourceHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class ResourceHttpMessageConverterTests + { + private ResourceHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new ResourceHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(IResource), MediaType.ALL)); + Assert.IsTrue(converter.CanRead(typeof(FileResource), MediaType.ALL)); + Assert.IsTrue(converter.CanRead(typeof(IResource), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("text", "plain"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(IResource), MediaType.ALL)); + Assert.IsTrue(converter.CanWrite(typeof(FileResource), MediaType.ALL)); + Assert.IsTrue(converter.CanWrite(typeof(IResource), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanWrite(typeof(string), new MediaType("text", "plain"))); + } + + [Test] + public void Read() + { + byte[] body = new byte[] { 0x1, 0x2 }; + + MockHttpInputMessage message = new MockHttpInputMessage(body); + + IResource resource = converter.Read(message); + + Assert.IsNotNull(resource, "Invalid result"); + Assert.IsTrue(resource is ByteArrayResource, "Invalid result"); + byte[] result = ((ByteArrayResource)resource).Bytes; + Assert.AreEqual(body[0], result[0], "Invalid result"); + Assert.AreEqual(body[1], result[1], "Invalid result"); + } + + [Test] + public void Write() + { + IResource body = new AssemblyResource("assembly://Spring.Rest.Tests/Spring.Http.Converters/Resource.txt"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.IsTrue(message.GetBodyAsBytes().Length > 0, "Invalid result"); + Assert.AreEqual(new MediaType("text", "plain"), message.Headers.ContentType, "Invalid content-type"); + } + + [Test] + public void WriteWithUnknownExtension() + { + IResource body = new FileResource(@"C:\Dummy.unknown"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(new MediaType("application", "octet-stream"), message.Headers.ContentType, "Invalid content-type"); + } + + [Test] + public void WriteWithKnownExtension() + { + IResource body = new FileResource(@"C:\Dummy.txt"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(new MediaType("text", "plain"), message.Headers.ContentType, "Invalid content-type"); + } + + + [Test] + public void WriteWithCustomExtension() + { + IResource body = new FileResource(@"C:\Dummy.myext"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.MimeMapping.Add(".myext", "spring/custom"); + converter.Write(body, null, message); + + Assert.AreEqual(new MediaType("spring", "custom"), message.Headers.ContentType, "Invalid content-type"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/StringHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/StringHttpMessageConverterTests.cs new file mode 100644 index 0000000..fb57c60 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/StringHttpMessageConverterTests.cs @@ -0,0 +1,107 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Text; + +using NUnit.Framework; + +namespace Spring.Http.Converters +{ + /// + /// Unit tests for the StringHttpMessageConverter class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class StringHttpMessageConverterTests + { + private StringHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new StringHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(string), new MediaType("text", "plain"))); + Assert.IsTrue(converter.CanRead(typeof(string), MediaType.ALL)); + Assert.IsTrue(converter.CanRead(typeof(string), new MediaType("application", "xml"))); + Assert.IsFalse(converter.CanRead(typeof(int[]), new MediaType("text", "plain"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(string), new MediaType("text", "plain"))); + Assert.IsTrue(converter.CanWrite(typeof(string), MediaType.ALL)); + Assert.IsTrue(converter.CanWrite(typeof(string), new MediaType("application", "xml"))); + Assert.IsFalse(converter.CanWrite(typeof(int[]), new MediaType("text", "plain"))); + } + + [Test] + public void Read() + { + string body = "Hello Bruno Baïa"; + Encoding charSet = Encoding.UTF8; + MediaType mediaType = new MediaType("text", "plain", charSet); + + MockHttpInputMessage message = new MockHttpInputMessage(body, charSet); + message.Headers.ContentType = mediaType; + + string result = converter.Read(message); + Assert.AreEqual(body, result, "Invalid result"); + } + + [Test] + public void WriteDefaultCharset() + { + string body = "H\u00e9llo W\u00f6rld"; + Encoding charSet = Encoding.GetEncoding("ISO-8859-1"); + MediaType mediaType = new MediaType("text", "plain", charSet); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(body, message.GetBodyAsString(charSet), "Invalid result"); + Assert.AreEqual(mediaType, message.Headers.ContentType, "Invalid content-type"); + //Assert.AreEqual(charSet.GetBytes(body).Length, message.Headers.ContentLength, "Invalid content-length"); + } + + [Test] + public void WriteUTF8() + { + string body = "H\u00e9llo W\u00f6rld"; + Encoding charSet = Encoding.UTF8; + MediaType mediaType = new MediaType("text", "plain", charSet); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, mediaType, message); + + Assert.AreEqual(body, message.GetBodyAsString(charSet), "Invalid result"); + Assert.AreEqual(mediaType, message.Headers.ContentType, "Invalid content-type"); + //Assert.AreEqual(charSet.GetBytes(body).Length, message.Headers.ContentLength, "Invalid content-length"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs new file mode 100644 index 0000000..c7ff0f2 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs @@ -0,0 +1,129 @@ +#if NET_3_0 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Text; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the DataContractHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class DataContractHttpMessageConverterTests + { + private DataContractHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new DataContractHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(DataContractClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(DataContractClass), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(DataContractClass), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanRead(typeof(DataContractClass), new MediaType("text", "plain"))); + Assert.IsTrue(converter.CanRead(typeof(CollectionDataContractClass), new MediaType("application", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(DataContractClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(DataContractClass), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(DataContractClass), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanWrite(typeof(DataContractClass), new MediaType("text", "plain"))); + Assert.IsTrue(converter.CanWrite(typeof(CollectionDataContractClass), new MediaType("application", "xml"))); + } + + [Test] + public void Read() + { + string body = @" + + 1Bruno Baïa + "; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + DataContractClass result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + } + + [Test] + public void Write() + { + string expectedBody = "1Bruno Baïa"; + DataContractClass body = new DataContractClass("1", "Bruno Baïa"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + #region Test classes + + //[DataContract] + public class DataContractClass + { + //[DataMember] + public string ID { get; set; } + + //[DataMember] + public string Name { get; set; } + + public DataContractClass() + { + } + + public DataContractClass(string id, string name) + { + this.ID = id; + this.Name = name; + } + } + + //[CollectionDataContract] + public class CollectionDataContractClass : List + { + public CollectionDataContractClass() + : base() + { + } + } + + #endregion + } +} +#endif diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs new file mode 100644 index 0000000..fbb94e9 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs @@ -0,0 +1,102 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Linq; +using System.Xml.Linq; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the XElementHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XElementHttpMessageConverterTests + { + private XElementHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new XElementHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(XElement), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(XElement), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(XElement), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanRead(typeof(XElement), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanRead(typeof(String), new MediaType("application", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(XElement), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(XElement), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(XElement), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanWrite(typeof(XElement), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanWrite(typeof(String), new MediaType("application", "xml"))); + } + + [Test] + public void Read() + { + string body = ""; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + XElement result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + //XElement xResult = result.Elements() + // .Where(x => x.Name == "TestElement" && x.Attribute("testAttribute").Value == "value") + // .Single(); + XElement xResult = (from el in result.Elements() + where el.Name == "TestElement" && el.Attribute("testAttribute").Value == "value" + select el) + .Single(); + Assert.IsNotNull(xResult, "Invalid result"); + } + + [Test] + public void Write() + { + XElement body = new XElement("Root", + new XElement("TestElement", 1), + new XElement("TestElement", 2)); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(body.ToString(SaveOptions.DisableFormatting), message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + } +} +#endif diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs new file mode 100644 index 0000000..58e0572 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs @@ -0,0 +1,95 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Xml; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the XmlDocumentHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XmlDocumentHttpMessageConverterTests + { + private XmlDocumentHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new XmlDocumentHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(XmlDocument), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(XmlDocument), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(XmlDocument), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanRead(typeof(XmlDocument), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanRead(typeof(String), new MediaType("application", "xml"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(XmlDocument), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(XmlDocument), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(XmlDocument), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanWrite(typeof(XmlDocument), new MediaType("text", "plain"))); + Assert.IsFalse(converter.CanWrite(typeof(String), new MediaType("application", "xml"))); + } + + [Test] + public void Read() + { + string body = ""; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + XmlDocument result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + XmlNode xmlNodeResult = result.SelectSingleNode("//TestElement"); + Assert.IsNotNull(xmlNodeResult, "Invalid result"); + Assert.AreEqual("TestElement", xmlNodeResult.LocalName, "Invalid result"); + Assert.IsNotNull(xmlNodeResult.Attributes["testAttribute"], "Invalid result"); + Assert.AreEqual("value", xmlNodeResult.Attributes["testAttribute"].Value, "Invalid result"); + } + + [Test] + public void Write() + { + XmlDocument body = new XmlDocument(); + body.LoadXml(""); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(body.OuterXml, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs new file mode 100644 index 0000000..88df654 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,241 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Xml.Linq; +using System.Collections.Generic; +using System.ServiceModel; +using System.ServiceModel.Web; +using System.Runtime.Serialization; + +using Spring.Rest.Client; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Integration tests for the Xml based IHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XmlHttpMessageConverterIntegrationTests + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(XmlHttpMessageConverterIntegrationTests)); + + #endregion + + private WebServiceHost webServiceHost; + private string uri = "http://localhost:1337"; + private RestTemplate template; + + [SetUp] + public void SetUp() + { + template = new RestTemplate(uri); + template.MessageConverters = new List(); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + [Test] + public void DataContractGetForObject() + { + template.MessageConverters.Add(new DataContractHttpMessageConverter()); + + User result = template.GetForObject("user/dc/{id}", "1"); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("1", result.ID, "Invalid content"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid content"); + } + + [Test] + public void DataContractPostForMessage() + { + template.MessageConverters.Add(new DataContractHttpMessageConverter()); + + User user = new User() { Name = "Lisa Baia" }; + + HttpResponseMessage result = template.PostForMessage("user/dc", user); + Assert.AreEqual(new Uri(new Uri(uri), "/user/dc/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void XElementGetForObject() + { + template.MessageConverters.Add(new XElementHttpMessageConverter()); + + XElement result = template.GetForObject("user/xml/{id}", "1"); + Assert.IsNotNull(result, "Invalid content"); + Assert.AreEqual("1", result.Element("ID").Value, "Invalid content"); + Assert.AreEqual("Bruno Baïa", result.Element("Name").Value, "Invalid content"); + } + + [Test] + public void XElementPostForMessage() + { + template.MessageConverters.Add(new XElementHttpMessageConverter()); + + XElement user = new XElement("User", + new XElement("Name", "Lisa Baia")); + + HttpResponseMessage result = template.PostForMessage("user/xml", user); + Assert.AreEqual(new Uri(new Uri(uri), "/user/xml/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + } + + #region REST test service + + [DataContract] + public class User + { + [DataMember] + public string ID { get; set; } + + [DataMember] + public string Name { get; set; } + } + + [ServiceContract] + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] + public class TestService + { + private IList users; + + public TestService() + { + users = new List(); + users.Add(new User() { ID = "1", Name = "Bruno Baïa" }); + users.Add(new User() { ID = "2", Name = "Marie Baia" }); + } + + [OperationContract] + [WebGet(UriTemplate = "user/dc/{id}")] + public User GetUserDataContract(string id) + { + WebOperationContext context = WebOperationContext.Current; + + foreach (User user in this.users) + { + if (user.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + { + return user; + } + } + + context.OutgoingResponse.SetStatusAsNotFound(String.Format("User with id '{0}' not found", id)); + return null; + } + + [OperationContract] + [WebInvoke(UriTemplate = "user/dc", Method = "POST")] + public void CreateDataContract(User user) + { + WebOperationContext context = WebOperationContext.Current; + + UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; + UriTemplate template = new UriTemplate("/user/dc/{id}"); + + MediaType mediaType = MediaType.Parse(context.IncomingRequest.ContentType); + + if (!String.IsNullOrEmpty(user.ID)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' already exists", user.ID); + return; + } + + user.ID = (users.Count + 1).ToString(); // generate new ID + + users.Add(user); + + Uri uri = template.BindByPosition(match.BaseUri, user.ID); + context.OutgoingResponse.SetStatusAsCreated(uri); + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' created with '{1}'", user.ID, user.Name); + } + + [OperationContract] + [WebGet(UriTemplate = "user/xml/{id}")] + public XElement GetUserXElement(string id) + { + WebOperationContext context = WebOperationContext.Current; + + foreach (User user in this.users) + { + if (user.ID.Equals(id, StringComparison.InvariantCultureIgnoreCase)) + { + return new XElement("User", + new XElement("ID", user.ID), + new XElement("Name", user.Name)); + } + } + + context.OutgoingResponse.SetStatusAsNotFound(String.Format("User with id '{0}' not found", id)); + return null; + } + + [OperationContract] + [WebInvoke(UriTemplate = "user/xml", Method = "POST")] + public void CreateXElement(XElement user) + { + WebOperationContext context = WebOperationContext.Current; + + UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; + UriTemplate template = new UriTemplate("/user/xml/{id}"); + + MediaType mediaType = MediaType.Parse(context.IncomingRequest.ContentType); + + if (user.Element("ID") != null && !String.IsNullOrEmpty(user.Element("ID").Value)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' already exists", user.Element("ID")); + return; + } + + User newUser = new User(); + newUser.ID = (users.Count + 1).ToString(); // generate new ID + newUser.Name = user.Element("Name").Value; + + users.Add(newUser); + + Uri uri = template.BindByPosition(match.BaseUri, newUser.ID); + context.OutgoingResponse.SetStatusAsCreated(uri); + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' created with '{1}'", newUser.ID, newUser.Name); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs new file mode 100644 index 0000000..12dd3d2 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs @@ -0,0 +1,123 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Text; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the XmlSerializableHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XmlSerializableHttpMessageConverterTests + { + private XmlSerializableHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + converter = new XmlSerializableHttpMessageConverter(); + } + + [Test] + public void CanRead() + { + Assert.IsTrue(converter.CanRead(typeof(CustomClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(CustomClass), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(CustomClass), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanRead(typeof(CustomClass), new MediaType("text", "plain"))); + } + + [Test] + public void CanWrite() + { + Assert.IsTrue(converter.CanWrite(typeof(CustomClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(CustomClass), new MediaType("text", "xml"))); + Assert.IsTrue(converter.CanWrite(typeof(CustomClass), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsFalse(converter.CanWrite(typeof(CustomClass), new MediaType("text", "plain"))); + } + + [Test] + public void Read() + { + string body = @" + 1 + Bruno Baïa + "; + + MockHttpInputMessage message = new MockHttpInputMessage(body, Encoding.UTF8); + + CustomClass result = converter.Read(message); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + } + + [Test] + public void Write() + { + string expectedBody = "1Bruno Baïa"; + CustomClass body = new CustomClass("1", "Bruno Baïa"); + + MockHttpOutputMessage message = new MockHttpOutputMessage(); + + converter.Write(body, null, message); + + Assert.AreEqual(expectedBody, message.GetBodyAsString(Encoding.UTF8), "Invalid result"); + Assert.AreEqual(new MediaType("application", "xml"), message.Headers.ContentType, "Invalid content-type"); + //Assert.IsTrue(message.Headers.ContentLength > -1, "Invalid content-length"); + } + + #region Test classes + + public class CustomClass + { + private string _id; + private string _name; + + public string ID + { + get { return _id; } + set { _id = value; } + } + + public string Name + { + get { return _name; } + set { _name = value; } + } + + public CustomClass() + { + } + + public CustomClass(string id, string name) + { + this._id = id; + this._name = name; + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/HttpHeadersTests.cs b/netstandard/Spring.Rest.Tests/Http/HttpHeadersTests.cs new file mode 100644 index 0000000..901f76c --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/HttpHeadersTests.cs @@ -0,0 +1,336 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using NUnit.Framework; + +namespace Spring.Http +{ + /// + /// Unit tests for the HttpHeaders class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class HttpHeadersTests + { + private HttpHeaders headers; + + [SetUp] + public void SetUp() + { + headers = new HttpHeaders(); + } + + [Test] + public void AcceptGet() + { + Assert.IsEmpty(headers.Accept); + + headers.Add("Accept", "text/plain; q=0.5"); + headers.Add("Accept", "text/html"); + headers.Add("Accept", "text/x-dvi; q=0.8"); + headers.Add("Accept", "text/x-c"); + + MediaType[] mediaTypes = headers.Accept; + Assert.NotNull(mediaTypes, "No media types returned"); + Assert.AreEqual(4, mediaTypes.Length, "Invalid amount of media types"); + Assert.AreEqual("text/plain;q=0.5", mediaTypes[0].ToString()); + Assert.AreEqual("text/html", mediaTypes[1].ToString()); + Assert.AreEqual("text/x-dvi;q=0.8", mediaTypes[2].ToString()); + Assert.AreEqual("text/x-c", mediaTypes[3].ToString()); + } + + [Test] + public void AcceptSet() + { + MediaType mediaType1 = new MediaType("text", "html"); + MediaType mediaType2 = new MediaType("text", "plain"); + MediaType[] mediaTypes = new MediaType[2] { mediaType1, mediaType2 }; + + headers.Accept = mediaTypes; + Assert.AreEqual(mediaTypes, headers.Accept, "Invalid Accept header"); + Assert.AreEqual("text/html,text/plain", headers["Accept"], "Invalid Accept header"); + } + + + //[Test] + //public void acceptCharsets() + //{ + // Charset charset1 = Charset.forName("UTF-8"); + // Charset charset2 = Charset.forName("ISO-8859-1"); + // List charsets = new ArrayList(2); + // charsets.add(charset1); + // charsets.add(charset2); + // headers.setAcceptCharset(charsets); + // Assert.AreEqual("Invalid Accept header", charsets, headers.getAcceptCharset()); + // Assert.AreEqual("Invalid Accept header", "utf-8, iso-8859-1", headers.getFirst("Accept-Charset")); + //} + + [Test] + public void AllowGet() + { + Assert.IsEmpty(headers.Allow); + + headers.Add("Allow", "PUT"); + headers.Add("Allow", "POST"); + + HttpMethod[] methods = headers.Allow; + Assert.NotNull(methods, "No methods returned"); + Assert.AreEqual(2, methods.Length, "Invalid amount of HTTP methods"); + Assert.AreEqual(HttpMethod.PUT, methods[0]); + Assert.AreEqual(HttpMethod.POST, methods[1]); + } + + [Test] + public void AllowSet() + { + HttpMethod[] methods = new HttpMethod[2] { HttpMethod.GET, HttpMethod.POST }; + + headers.Allow = methods; + Assert.AreEqual(methods, headers.Allow, "Invalid Allow header"); + Assert.AreEqual("GET,POST", headers["Allow"], "Invalid Allow header"); + } + + [Test] + public void ContentLength() + { + long length = 42; + + headers.ContentLength = length; + Assert.AreEqual(length, headers.ContentLength, "Invalid Content-Length header"); + Assert.AreEqual("42", headers["Content-Length"], "Invalid Content-Length header"); + } + + [Test] + public void ContentTypeGet() + { + Assert.IsNull(headers.ContentType); + + headers.Set("Content-Type", "text/html;charset=UTF-8"); + + Assert.AreEqual("text/html;charset=UTF-8", headers.ContentType.ToString(), "Invalid Content-Type header"); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ContentTypeGetMultipleValues() + { + headers.Add("Content-Type", "text/html"); + headers.Add("Content-Type", "application/xml"); + + MediaType mediaType = headers.ContentType; + } + + [Test] + public void ContentTypeSet() + { + MediaType contentType = new MediaType("text", "html", "UTF-8"); + + headers.ContentType = contentType; + Assert.AreEqual(contentType, headers.ContentType, "Invalid Content-Type header"); + Assert.AreEqual("text/html;charset=UTF-8", headers["Content-Type"], "Invalid Content-Type header"); + } + + [Test] + public void Location() + { + Uri location = new Uri("http://www.example.com/hotels"); + + headers.Location = location; + Assert.AreEqual(location, headers.Location, "Invalid Location header"); + Assert.AreEqual("http://www.example.com/hotels", headers["Location"], "Invalid Location header"); + } + + [Test] + public void ETag() + { + string eTag = "v2.6"; + + headers.ETag = eTag; + Assert.AreEqual(eTag, headers.ETag, "Invalid ETag header"); + Assert.AreEqual("\"v2.6\"", headers["ETag"], "Invalid ETag header"); + } + + [Test] + public void ETagWithWeaknessIndicator() + { + string eTag = "W/\"v2.6\""; + + headers.ETag = eTag; + Assert.AreEqual(eTag, headers.ETag, "Invalid ETag header"); + Assert.AreEqual(eTag, headers["ETag"], "Invalid ETag header"); + } + + [Test] + public void IfNoneMatchGet() + { + Assert.IsEmpty(headers.IfNoneMatch); + + headers.Add("If-None-Match", "v1.0"); + headers.Add("If-None-Match", "v2.0"); + + string[] eTags = headers.IfNoneMatch; + Assert.NotNull(eTags, "No eTags returned"); + Assert.AreEqual(2, eTags.Length, "Invalid amount of eTags"); + Assert.AreEqual("v1.0", eTags[0]); + Assert.AreEqual("v2.0", eTags[1]); + } + + [Test] + public void IfNoneMatchSet() + { + string ifNoneMatch1 = "v2.6"; + string ifNoneMatch2 = "v2.7"; + string[] ifNoneMatchArray = new string[2] { ifNoneMatch1, ifNoneMatch2 }; + + headers.IfNoneMatch = ifNoneMatchArray; + Assert.AreEqual(ifNoneMatchArray, headers.IfNoneMatch, "Invalid If-None-Match header"); + Assert.AreEqual("\"v2.6\",\"v2.7\"", headers.Get("If-None-Match"), "Invalid If-None-Match header"); + } + + [Test] + public void Date() + { + DateTime date = new DateTime(2008, 12, 18, 10, 20, 00, DateTimeKind.Utc); + + headers.Date = date; + Assert.AreEqual(date, headers.Date, "Invalid Date header"); + Assert.AreEqual("Thu, 18 Dec 2008 10:20:00 GMT", headers["date"], "Invalid Date header"); + + // RFC 850 + headers.Set("Date", "Thursday, 18-Dec-08 10:20:00 GMT"); + Assert.AreEqual(date, headers.Date, "Invalid Date header"); + } + + //[Test]//(expected = IllegalArgumentException.class) + //public void DateInvalid() + //{ + // headers.Set("Date", "Foo Bar Baz"); + // Assert.IsNotNull(headers.Date); + //} + + //[Test] + //public void dateOtherLocale() { + // Locale defaultLocale = Locale.getDefault(); + // try { + // Locale.setDefault(new Locale("nl", "nl")); + // Calendar calendar = new GregorianCalendar(2008, 11, 18, 11, 20); + // calendar.setTimeZone(TimeZone.getTimeZone("CET")); + // long date = calendar.getTimeInMillis(); + // headers.setDate(date); + // Assert.AreEqual("Invalid Date header", "Thu, 18 Dec 2008 10:20:00 GMT", headers.getFirst("date")); + // Assert.AreEqual("Invalid Date header", date, headers.getDate()); + // } + // finally { + // Locale.setDefault(defaultLocale); + // } + //} + + [Test] + public void LastModified() + { + DateTime date = new DateTime(2008, 12, 18, 10, 20, 00, DateTimeKind.Utc); + + headers.LastModified = date; + Assert.AreEqual(date, headers.LastModified, "Invalid Last-Modified header"); + Assert.AreEqual("Thu, 18 Dec 2008 10:20:00 GMT", headers["Last-Modified"], "Invalid Last-Modified header"); + } + + [Test] + public void Expires() + { + string date = "Thu, 18 Dec 2008 10:20:00 GMT"; + + headers.Expires = date; + Assert.AreEqual(date, headers.Expires, "Invalid Expires header"); + Assert.AreEqual("Thu, 18 Dec 2008 10:20:00 GMT", headers["Expires"], "Invalid Expires header"); + } + + [Test] + public void IfModifiedSince() + { + DateTime date = new DateTime(2008, 12, 18, 10, 20, 00, DateTimeKind.Utc); + + headers.IfModifiedSince = date; + Assert.AreEqual(date, headers.IfModifiedSince, "Invalid If-Modified-Since header"); + Assert.AreEqual("Thu, 18 Dec 2008 10:20:00 GMT", headers["If-Modified-Since"], "Invalid If-Modified-Since header"); + } + + [Test] + public void Pragma() + { + string pragma = "no-cache"; + + headers.Pragma = pragma; + Assert.AreEqual(pragma, headers.Pragma, "Invalid Pragma header"); + Assert.AreEqual("no-cache", headers["pragma"], "Invalid Pragma header"); + } + + [Test] + public void CacheControl() + { + string cacheControl = "no-cache"; + + headers.CacheControl = cacheControl; + Assert.AreEqual(cacheControl, headers.CacheControl, "Invalid Cache-Control header"); + Assert.AreEqual("no-cache", headers["cache-control"], "Invalid Cache-Control header"); + } + + //[Test] + //public void contentDisposition() { + // headers.setContentDispositionFormData("name", null); + // Assert.AreEqual("Invalid Content-Disposition header", "form-data; name=\"name\"", headers.getFirst("Content-Disposition")); + + // headers.setContentDispositionFormData("name", "filename"); + // Assert.AreEqual("Invalid Content-Disposition header", "form-data; name=\"name\"; filename=\"filename\"", headers.getFirst("Content-Disposition")); + //} + + [Test] + public void GetSingleValue() + { + headers.Add("HeaderName", "1,2,3"); + + Assert.AreEqual("1,2,3", headers.GetSingleValue("HeaderName")); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void GetSingleValueWithMultipleValues() + { + headers.Add("HeaderName", "1"); + headers.Add("HeaderName", "2"); + headers.Add("HeaderName", "3"); + + headers.GetSingleValue("HeaderName"); + } + + [Test] + public void GetMultiValues() + { + headers.Add("HeaderName", "1,2,3"); + Assert.AreEqual(3, headers.GetMultiValues("HeaderName").Length); + + headers.Add("HeaderName", "4"); + Assert.AreEqual(4, headers.GetMultiValues("HeaderName").Length); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/HttpMethodTests.cs b/netstandard/Spring.Rest.Tests/Http/HttpMethodTests.cs new file mode 100644 index 0000000..1e9c2cb --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/HttpMethodTests.cs @@ -0,0 +1,89 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Http +{ + /// + /// Unit tests for the HttpMethod class. + /// + /// Bruno Baia + [TestFixture] + public class HttpMethodTests + { + [Test] + public void Equals() + { + Assert.IsTrue(HttpMethod.GET.Equals(new HttpMethod("GET"))); + Assert.IsTrue(((object)HttpMethod.GET).Equals(new HttpMethod("GET"))); + } + + [Test] + public void EqualsCaseInsensitive() + { + Assert.IsTrue(new HttpMethod("get").Equals(HttpMethod.GET)); + } + + [Test] + public void EqualsNull() + { + Assert.IsFalse(HttpMethod.GET.Equals(null)); + } + + [Test] + public void EqualsOperator() + { + Assert.IsTrue(HttpMethod.DELETE == new HttpMethod("Delete")); + } + + [Test] + public void NotEqualsOperator() + { + Assert.IsTrue(HttpMethod.GET != HttpMethod.OPTIONS); + } + + [Test] + public void GetHashCodeTest() + { + IDictionary dictionary = new Dictionary(); + dictionary.Add(HttpMethod.GET, "value for get"); + Assert.IsTrue(dictionary.ContainsKey(new HttpMethod("get"))); + } + + [Test] + public void Equatable() + { + IList list = new List(); + list.Add(HttpMethod.GET); + Assert.IsTrue(list.Contains(new HttpMethod("Get"))); + } + + [Test] + public void ToStringTest() + { + Assert.AreEqual("GET", HttpMethod.GET.ToString()); + Assert.AreEqual("Get", new HttpMethod("Get").ToString()); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/HttpUtilsTests.cs b/netstandard/Spring.Rest.Tests/Http/HttpUtilsTests.cs new file mode 100644 index 0000000..3b64070 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/HttpUtilsTests.cs @@ -0,0 +1,71 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Http +{ + /// + /// Unit tests for the HttpUtils class. + /// + /// Bruno Baia + [TestFixture] + public class HttpUtilsTests + { + [Test] + public void UrlEncode() + { + Assert.AreEqual("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~%21%27%28%29%2A", + HttpUtils.UrlEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*")); + Assert.AreEqual("%3F%3D%23", HttpUtils.UrlEncode("?=#")); + } + + [Test] + public void UrlDecode() + { + Assert.AreEqual("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*", + HttpUtils.UrlDecode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~%21%27%28%29%2A")); + Assert.AreEqual("?=#", HttpUtils.UrlDecode("%3F%3D%23")); + } + + [Test] + public void FormEncode() + { + Assert.AreEqual("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~%21%27%28%29%2A", + HttpUtils.FormEncode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*")); + Assert.AreEqual("%3F%3D%23", HttpUtils.FormEncode("?=#")); + Assert.AreEqual("+", HttpUtils.FormEncode(" ")); + Assert.AreEqual("%2B", HttpUtils.FormEncode("+")); + } + + [Test] + public void FormDecode() + { + Assert.AreEqual("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*", + HttpUtils.FormDecode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~%21%27%28%29%2A")); + Assert.AreEqual("?=#", HttpUtils.FormDecode("%3F%3D%23")); + Assert.AreEqual(" ", HttpUtils.FormDecode("+")); + Assert.AreEqual("+", HttpUtils.FormDecode("%2B")); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/MediaTypeTests.cs b/netstandard/Spring.Rest.Tests/Http/MediaTypeTests.cs new file mode 100644 index 0000000..70607ec --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/MediaTypeTests.cs @@ -0,0 +1,494 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Http +{ + /// + /// Unit tests for the MediaType class. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + [TestFixture] + public class MediaTypeTests + { + [Test] + public void Equals() + { + Assert.IsTrue(MediaType.TEXT_HTML.Equals(new MediaType("text", "html"))); + Assert.IsTrue(((object)MediaType.TEXT_HTML).Equals(new MediaType("text", "html"))); + } + + [Test] + public void EqualsCaseInsensitive() + { + Assert.IsTrue(MediaType.TEXT_PLAIN.Equals(new MediaType("Text", "Plain"))); + } + + [Test] + public void EqualsNull() + { + Assert.IsFalse(MediaType.IMAGE_GIF.Equals(null)); + } + + [Test] + public void EqualsOperator() + { + Assert.IsTrue(MediaType.TEXT_PLAIN == new MediaType("text", "plain")); + } + + [Test] + public void NotEqualsOperator() + { + Assert.IsTrue(MediaType.TEXT_PLAIN != MediaType.TEXT_HTML); + } + + [Test] + public void GetHashCodeTest() + { + IDictionary dictionary = new Dictionary(); + dictionary.Add(MediaType.TEXT_HTML, "value for text/html"); + Assert.IsTrue(dictionary.ContainsKey(new MediaType("Text", "Html"))); + } + + [Test] + public void Equatable() + { + IList list = new List(); + list.Add(MediaType.APPLICATION_JSON); + Assert.IsTrue(list.Contains(new MediaType("application", "json"))); + } + + [Test] + public void ToStringTest() + { + MediaType mediaType = new MediaType("text", "plain", 0.7); + String result = mediaType.ToString(); + Assert.AreEqual("text/plain;q=0.7", result, "Invalid toString() returned"); + } + + [Test] + public void Includes() + { + MediaType textPlain = MediaType.TEXT_PLAIN; + Assert.IsTrue(textPlain.Includes(textPlain), "Equal types is not inclusive"); + MediaType allText = new MediaType("text"); + + Assert.IsTrue(allText.Includes(textPlain), "All subtypes is not inclusive"); + Assert.IsFalse(textPlain.Includes(allText), "All subtypes is inclusive"); + + Assert.IsTrue(MediaType.ALL.Includes(textPlain), "All types is not inclusive"); + Assert.IsFalse(textPlain.Includes(MediaType.ALL), "All types is inclusive"); + + Assert.IsTrue(MediaType.ALL.Includes(textPlain), "All types is not inclusive"); + Assert.IsFalse(textPlain.Includes(MediaType.ALL), "All types is inclusive"); + + MediaType applicationSoapXml = new MediaType("application", "soap+xml"); + MediaType applicationWildcardXml = new MediaType("application", "*+xml"); + + Assert.IsTrue(applicationSoapXml.Includes(applicationSoapXml)); + Assert.IsTrue(applicationWildcardXml.Includes(applicationWildcardXml)); + + Assert.IsTrue(applicationWildcardXml.Includes(applicationSoapXml)); + Assert.IsFalse(applicationSoapXml.Includes(applicationWildcardXml)); + } + + [Test] + public void IsCompatible() + { + MediaType textPlain = MediaType.TEXT_PLAIN; + Assert.IsTrue(textPlain.IsCompatibleWith(textPlain), "Equal types is not compatible"); + MediaType allText = new MediaType("text"); + + Assert.IsTrue(allText.IsCompatibleWith(textPlain), "All subtypes is not compatible"); + Assert.IsTrue(textPlain.IsCompatibleWith(allText), "All subtypes is not compatible"); + + Assert.IsTrue(MediaType.ALL.IsCompatibleWith(textPlain), "All types is not compatible"); + Assert.IsTrue(textPlain.IsCompatibleWith(MediaType.ALL), "All types is not compatible"); + + Assert.IsTrue(MediaType.ALL.IsCompatibleWith(textPlain), "All types is not compatible"); + Assert.IsTrue(textPlain.IsCompatibleWith(MediaType.ALL), "All types is compatible"); + + MediaType applicationSoapXml = new MediaType("application", "soap+xml"); + MediaType applicationWildcardXml = new MediaType("application", "*+xml"); + + Assert.IsTrue(applicationSoapXml.IsCompatibleWith(applicationSoapXml)); + Assert.IsTrue(applicationWildcardXml.IsCompatibleWith(applicationWildcardXml)); + + Assert.IsTrue(applicationWildcardXml.IsCompatibleWith(applicationSoapXml)); + Assert.IsTrue(applicationSoapXml.IsCompatibleWith(applicationWildcardXml)); + } + + [Test] + public void GetDefaultQualityValue() + { + MediaType mediaType = new MediaType("text", "plain"); + Assert.AreEqual(1, mediaType.QualityValue, "Invalid quality value"); + } + + [Test] + public void Parse() + { + string s = "audio/*; q=0.2"; + MediaType mediaType = MediaType.Parse(s); + Assert.AreEqual("audio", mediaType.Type, "Invalid type"); + Assert.AreEqual("*", mediaType.Subtype, "Invalid subtype"); + Assert.AreEqual(0.2, mediaType.QualityValue, "Invalid quality factor"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ParseNoSubtype() + { + MediaType.Parse("audio"); + } + + [Test] + [ExpectedException(typeof(ArgumentException))] + public void ParseNoSubtypeSlash() + { + MediaType.Parse("audio/"); + } + + [Test] + public void ParseURLConnectionMediaType() + { + string s = "*; q=.2"; + MediaType mediaType = MediaType.Parse(s); + Assert.AreEqual(mediaType.Type, "*", "Invalid type"); + Assert.AreEqual("*", mediaType.Subtype, "Invalid subtype"); + Assert.AreEqual(0.2, mediaType.QualityValue, "Invalid quality factor"); + } + + [Test] + public void CompareTo() + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audio = new MediaType("audio"); + MediaType audioWave = new MediaType("audio", "wave"); + MediaType audioBasicLevel = new MediaType("audio", "basic", SingletonDictionary("level", "1")); + MediaType audioBasic07 = new MediaType("audio", "basic", 0.7); + + // equal + Assert.AreEqual(0, audioBasic.CompareTo(audioBasic), "Invalid comparison result"); + Assert.AreEqual(0, audio.CompareTo(audio), "Invalid comparison result"); + Assert.AreEqual(0, audioBasicLevel.CompareTo(audioBasicLevel), "Invalid comparison result"); + + Assert.IsTrue(audioBasicLevel.CompareTo(audio) > 0, "Invalid comparison result"); + + List expected = new List(); + expected.Add(audio); + expected.Add(audioBasic); + expected.Add(audioBasicLevel); + expected.Add(audioBasic07); + expected.Add(audioWave); + + List result = new List(expected); + // shuffle & sort 10 times + for (int i = 0; i < 10; i++) + { + Shuffle(result); + result.Sort(); + + for (int j = 0; j < result.Count; j++) + { + Assert.AreSame(expected[j], result[j], "Invalid media type at " + j + ", run " + i); + } + } + } + + [Test] + public void CompareToConsistentWithEquals() + { + MediaType m1 = MediaType.Parse("text/html; q=0.7; charset=iso-8859-1"); + MediaType m2 = MediaType.Parse("text/html; charset=iso-8859-1; q=0.7"); + + Assert.AreEqual(m1, m2, "Media types not equal"); + Assert.AreEqual(0, m1.CompareTo(m2), "compareTo() not consistent with equals"); + Assert.AreEqual(0, m2.CompareTo(m1), "compareTo() not consistent with equals"); + + m1 = MediaType.Parse("text/html; q=0.7; charset=iso-8859-1"); + m2 = MediaType.Parse("text/html; Q=0.7; charset=iso-8859-1"); + Assert.AreEqual(m1, m2, "Media types not equal"); + Assert.AreEqual(0, m1.CompareTo(m2), "compareTo() not consistent with equals"); + Assert.AreEqual(0, m2.CompareTo(m1), "compareTo() not consistent with equals"); + } + + [Test] + public void CompareToCaseSensitivity() + { + MediaType m1 = new MediaType("audio", "basic"); + MediaType m2 = new MediaType("Audio", "Basic"); + Assert.AreEqual(0, m1.CompareTo(m2), "Invalid comparison result"); + Assert.AreEqual(0, m2.CompareTo(m1), "Invalid comparison result"); + + m1 = new MediaType("audio", "basic", SingletonDictionary("foo", "bar")); + m2 = new MediaType("audio", "basic", SingletonDictionary("Foo", "bar")); + Assert.AreEqual(0, m1.CompareTo(m2), "Invalid comparison result"); + Assert.AreEqual(0, m2.CompareTo(m1), "Invalid comparison result"); + + m1 = new MediaType("audio", "basic", SingletonDictionary("foo", "bar")); + m2 = new MediaType("audio", "basic", SingletonDictionary("foo", "Bar")); + Assert.IsTrue(m1.CompareTo(m2) != 0, "Invalid comparison result"); + Assert.IsTrue(m2.CompareTo(m1) != 0, "Invalid comparison result"); + } + + [Test] + public void SpecificityComparator() + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audioWave = new MediaType("audio", "wave"); + MediaType audio = new MediaType("audio"); + MediaType audio03 = new MediaType("audio", "*", 0.3); + MediaType audio07 = new MediaType("audio", "*", 0.7); + MediaType audioBasicLevel = new MediaType("audio", "basic", SingletonDictionary("level", "1")); + MediaType textHtml = new MediaType("text", "html"); + MediaType all = MediaType.ALL; + + IComparer comp = MediaType.SPECIFICITY_COMPARER; + + // equal + Assert.AreEqual(0, comp.Compare(audioBasic,audioBasic), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio, audio), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio07, audio07), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio03, audio03), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audioBasicLevel, audioBasicLevel), "Invalid comparison result"); + + // specific to unspecific + Assert.IsTrue(comp.Compare(audioBasic, audio) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audioBasic, all) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio, all) < 0, "Invalid comparison result"); + + // unspecific to specific + Assert.IsTrue(comp.Compare(audio, audioBasic) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audioBasic) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audio) > 0, "Invalid comparison result"); + + // qualifiers + Assert.IsTrue(comp.Compare(audio, audio07) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio07, audio) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio07, audio03) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio03, audio07) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio03, all) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audio03) > 0, "Invalid comparison result"); + + // other parameters + Assert.IsTrue(comp.Compare(audioBasic, audioBasicLevel) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audioBasicLevel, audioBasic) < 0, "Invalid comparison result"); + + // different types + Assert.AreEqual(0, comp.Compare(audioBasic, textHtml), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(textHtml, audioBasic), "Invalid comparison result"); + + // different subtypes + Assert.AreEqual(0, comp.Compare(audioBasic, audioWave), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audioWave, audioBasic), "Invalid comparison result"); + } + + [Test] + public void SortBySpecificityRelated() + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audio = new MediaType("audio"); + MediaType audio03 = new MediaType("audio", "*", 0.3); + MediaType audio07 = new MediaType("audio", "*", 0.7); + MediaType audioBasicLevel = new MediaType("audio", "basic", SingletonDictionary("level", "1")); + MediaType all = MediaType.ALL; + + List expected = new List(); + expected.Add(audioBasicLevel); + expected.Add(audioBasic); + expected.Add(audio); + expected.Add(audio07); + expected.Add(audio03); + expected.Add(all); + + List result = new List(expected); + // shuffle & sort 10 times + for (int i = 0; i < 10; i++) + { + Shuffle(result); + MediaType.SortBySpecificity(result); + + for (int j = 0; j < result.Count; j++) + { + Assert.AreSame(expected[j], result[j], "Invalid media type at " + j); + } + } + } + + [Test] + public void SortBySpecificityUnrelated() // Check stable sort + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audioWave = new MediaType("audio", "wave"); + MediaType textHtml = new MediaType("text", "html"); + + List expected = new List(); + expected.Add(textHtml); + expected.Add(audioBasic); + expected.Add(audioWave); + + List result = new List(expected); + MediaType.SortBySpecificity(result); + + for (int i = 0; i < result.Count; i++) + { + Assert.AreSame(expected[i], result[i], "Invalid media type at " + i); + } + } + + [Test] + public void QualityComparator() + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audioWave = new MediaType("audio", "wave"); + MediaType audio = new MediaType("audio"); + MediaType audio03 = new MediaType("audio", "*", 0.3); + MediaType audio07 = new MediaType("audio", "*", 0.7); + MediaType audioBasicLevel = new MediaType("audio", "basic", SingletonDictionary("level", "1")); + MediaType textHtml = new MediaType("text", "html"); + MediaType all = MediaType.ALL; + + IComparer comp = MediaType.QUALITY_VALUE_COMPARER; + + // equal + Assert.AreEqual(0, comp.Compare(audioBasic, audioBasic), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio, audio), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio07, audio07), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audio03, audio03), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audioBasicLevel, audioBasicLevel), "Invalid comparison result"); + + // specific to unspecific + Assert.IsTrue(comp.Compare(audioBasic, audio) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audioBasic, all) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio, all) < 0, "Invalid comparison result"); + + // unspecific to specific + Assert.IsTrue(comp.Compare(audio, audioBasic) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audioBasic) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audio) > 0, "Invalid comparison result"); + + // qualifiers + Assert.IsTrue(comp.Compare(audio, audio07) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio07, audio) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio07, audio03) < 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio03, audio07) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audio03, all) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(all, audio03) < 0, "Invalid comparison result"); + + // other parameters + Assert.IsTrue(comp.Compare(audioBasic, audioBasicLevel) > 0, "Invalid comparison result"); + Assert.IsTrue(comp.Compare(audioBasicLevel, audioBasic) < 0, "Invalid comparison result"); + + // different types + Assert.AreEqual(0, comp.Compare(audioBasic, textHtml), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(textHtml, audioBasic), "Invalid comparison result"); + + // different subtypes + Assert.AreEqual(0, comp.Compare(audioBasic, audioWave), "Invalid comparison result"); + Assert.AreEqual(0, comp.Compare(audioWave, audioBasic), "Invalid comparison result"); + } + + [Test] + public void SortByQualityRelated() + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audio = new MediaType("audio"); + MediaType audio03 = new MediaType("audio", "*", 0.3); + MediaType audio07 = new MediaType("audio", "*", 0.7); + MediaType audioBasicLevel = new MediaType("audio", "basic", SingletonDictionary("level", "1")); + MediaType all = MediaType.ALL; + + List expected = new List(); + expected.Add(audioBasicLevel); + expected.Add(audioBasic); + expected.Add(audio); + expected.Add(all); + expected.Add(audio07); + expected.Add(audio03); + + List result = new List(expected); + // shuffle & sort 10 times + for (int i = 0; i < 10; i++) + { + Shuffle(result); + MediaType.SortByQualityValue(result); + + for (int j = 0; j < result.Count; j++) + { + Assert.AreSame(expected[j], result[j], "Invalid media type at " + j); + } + } + } + + [Test] + public void SortByQualityUnrelated() // Check stable sort + { + MediaType audioBasic = new MediaType("audio", "basic"); + MediaType audioWave = new MediaType("audio", "wave"); + MediaType textHtml = new MediaType("text", "html"); + + List expected = new List(); + expected.Add(textHtml); + expected.Add(audioBasic); + expected.Add(audioWave); + + List result = new List(expected); + MediaType.SortByQualityValue(result); + + for (int i = 0; i < result.Count; i++) + { + Assert.AreSame(expected[i], result[i], "Invalid media type at " + i); + } + } + + #region Utils + + public static void Shuffle(IList list) + { + Random rnd = new Random(); + + int i = list.Count; + while (i >= 1) + { + i--; + int nextIndex = rnd.Next(i, list.Count); + T val = list[nextIndex]; + list[nextIndex] = list[i]; + list[i] = val; + } + } + + private static IDictionary SingletonDictionary(TKey key, TValue value) + { + IDictionary dictionary = new Dictionary(1); + dictionary.Add(key, value); + return dictionary; + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/MockHttpInputMessage.cs b/netstandard/Spring.Rest.Tests/Http/MockHttpInputMessage.cs new file mode 100644 index 0000000..5367008 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/MockHttpInputMessage.cs @@ -0,0 +1,57 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; +using System.Text; + +namespace Spring.Http +{ + /// + /// Mocked IHttpInputMessage implementation. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class MockHttpInputMessage : IHttpInputMessage + { + private HttpHeaders headers; + private Stream body; + + public MockHttpInputMessage(byte[] body) + { + this.headers = new HttpHeaders(); + this.body = new MemoryStream(body); + } + + public MockHttpInputMessage(string body, Encoding charset) + : this(charset.GetBytes(body)) + { + } + + public HttpHeaders Headers + { + get { return this.headers; } + } + + public Stream Body + { + get { return this.body; } + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Http/MockHttpOuputMessage.cs b/netstandard/Spring.Rest.Tests/Http/MockHttpOuputMessage.cs new file mode 100644 index 0000000..83cee7a --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Http/MockHttpOuputMessage.cs @@ -0,0 +1,72 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; + +namespace Spring.Http +{ + /// + /// Mocked IHttpOutputMessage implementation. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class MockHttpOutputMessage : IHttpOutputMessage + { + private HttpHeaders headers; + private Action body; + private byte[] bodyAsBytes; + + public MockHttpOutputMessage() + { + this.headers = new HttpHeaders(); + } + + public HttpHeaders Headers + { + get { return this.headers; } + } + + public Action Body + { + set { this.body = value; } + } + + public byte[] GetBodyAsBytes() + { + if (bodyAsBytes == null) + { + using (MemoryStream requestStream = new MemoryStream()) + { + this.body(requestStream); + bodyAsBytes = requestStream.ToArray(); + } + } + return bodyAsBytes; + } + + public String GetBodyAsString(Encoding charset) + { + byte[] bytes = GetBodyAsBytes(); + return charset.GetString(bytes); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/IO/AssemblyResourceTests.cs b/netstandard/Spring.Rest.Tests/IO/AssemblyResourceTests.cs new file mode 100644 index 0000000..15a0105 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/IO/AssemblyResourceTests.cs @@ -0,0 +1,99 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +using NUnit.Framework; + +namespace Spring.IO +{ + // From Spring.Core.Tests + + /// + /// Unit tests for AssemblyResource + /// + /// Bruno Baia + [TestFixture] + public class AssemblyResourceTests + { + /// + /// Use incorrect format for an assembly resource. Using + /// comma delimited instead of '/'. + /// + [Test] + [ExpectedException(typeof(UriFormatException))] + public void CreateWithMalformedResourceName() + { + new AssemblyResource("assembly://Spring.Core.Tests,Spring.TestResource.txt"); + } + + /// + /// Use the correct format but with an invalid assembly name. + /// + [Test] + [ExpectedException(typeof(FileNotFoundException))] + public void CreateFromInvalidAssembly() + { + new AssemblyResource("assembly://Xyz.Invalid.Assembly/Spring/TestResource.txt"); + } + + /// + /// Sunny day scenario that creates IResources with full resource name + /// and ensures the correct contents can be read from them. + /// + [Test] + public void CreateValidAssemblyResourceWithFullResourceName() + { + AssemblyResource res = new AssemblyResource("assembly://Spring.Rest.Tests/Spring/TestResource.txt"); + Assert.IsFalse(res.IsOpen); + Assert.AreEqual(new Uri("assembly://Spring.Rest.Tests/Spring/TestResource.txt"), res.Uri); + AssertResourceContent(res, "Spring.TestResource.txt"); + IResource res2 = new AssemblyResource("assembly://Spring.Rest.Tests/Spring.IO/TestResource.txt"); + AssertResourceContent(res2, "Spring.IO.TestResource.txt"); + } + + /// + /// Sunny day scenario that creates IResources with relative resource name + /// and ensures the correct contents can be read from them. + /// + [Test] + public void CreateValidAssemblyResourceWithRelativeResourceName() + { + AssemblyResource res = new AssemblyResource("TestResource.txt", typeof(AssemblyResourceTests)); + Assert.IsFalse(res.IsOpen); + Assert.AreEqual(new Uri("assembly://Spring.Rest.Tests/Spring.IO/TestResource.txt"), res.Uri); + AssertResourceContent(res, "Spring.IO.TestResource.txt"); + } + + /// + /// Utility method to compare a resource that contains a single string with an exemplar. + /// + /// The resource to read a line from + /// the expected value of the line. + private void AssertResourceContent(IResource res, string expectedContent) + { + using (StreamReader reader = new StreamReader(res.GetStream())) + { + Assert.AreEqual(expectedContent, reader.ReadLine(), "Resource content is not as expected"); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/IO/ByteArrayResourceTests.cs b/netstandard/Spring.Rest.Tests/IO/ByteArrayResourceTests.cs new file mode 100644 index 0000000..6c21029 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/IO/ByteArrayResourceTests.cs @@ -0,0 +1,48 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; + +using NUnit.Framework; + +namespace Spring.IO +{ + /// + /// Unit tests for the ByteArrayResource class. + /// + /// Bruno Baia + [TestFixture] + public class ByteArrayResourceTests + { + [Test] + public void Instantiation () + { + byte[] bytes = Encoding.UTF8.GetBytes("A byte array resource."); + + ByteArrayResource res = new ByteArrayResource(bytes); + Assert.IsFalse(res.IsOpen); + Assert.IsNull(res.Uri); + Assert.IsNotNull(res.GetStream()); + Assert.IsNotNull(res.GetStream()); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/IO/FileResourceTests.cs b/netstandard/Spring.Rest.Tests/IO/FileResourceTests.cs new file mode 100644 index 0000000..3c7f362 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/IO/FileResourceTests.cs @@ -0,0 +1,116 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Reflection; + +using NUnit.Framework; + +namespace Spring.IO +{ + // From Spring.Core.Tests + + /// + /// Unit tests for the FileResource class. + /// + /// Bruno Baia + [TestFixture] + public class FileResourceTests + { + protected const string TemporaryFileName = "temp.file"; + + /// + /// Creates a FileInfo instance representing the original location of the given assembly + /// + /// + /// Use this instead of the "Assembly.Location" property to get the original location before shadow copying! + /// + protected static FileInfo GetAssemblyLocation(Assembly assembly) + { + return new FileInfo(new Uri(assembly.CodeBase).LocalPath); + } + + protected static FileInfo CreateFileForTheCurrentDirectory() + { + return new FileInfo(Path.GetFullPath( + Path.Combine(AppDomain.CurrentDomain.BaseDirectory, TemporaryFileName))); + } + + [Test] + public void CreateFileSystemResourceWithPathName() + { + FileResource res = new FileResource(TemporaryFileName); + Assert.AreEqual(TemporaryFileName, res.File.Name); + } + + [Test] + [ExpectedException(typeof(FileNotFoundException))] + public void FileSystemResourceOpenNonExistanceFile() + { + FileResource res = new FileResource(TemporaryFileName); + Stream stream = res.GetStream(); + } + + [Test] + public void FileResourceValidStream() + { + FileInfo file = GetAssemblyLocation(Assembly.GetExecutingAssembly()); + FileResource res = new FileResource(file.FullName); + using (Stream stream = res.GetStream()) + { + Assert.IsNotNull(stream); + Assert.IsTrue(stream.CanRead); + } + } + + [Test] + public void ReadStreamMultipleTimes() + { + FileInfo file = GetAssemblyLocation(Assembly.GetExecutingAssembly()); + FileResource res = new FileResource(file.FullName); + Assert.IsFalse(res.IsOpen); + using (Stream stream = res.GetStream()) + { + Assert.IsNotNull(stream); + } + using (Stream stream = res.GetStream()) + { + Assert.IsNotNull(stream); + } + } + + [Test] + public void GetUri() + { + FileResource res = new FileResource(TemporaryFileName); + Assert.IsNotNull(res.Uri); + } + + [Test] + public void FileInfo() + { + FileInfo file = CreateFileForTheCurrentDirectory(); + FileResource res = new FileResource(TemporaryFileName); + Assert.AreEqual(file.FullName.ToLowerInvariant(), res.File.FullName.ToLowerInvariant(), + "The bare file name all by itself must have resolved to a file in the current directory of the currently executing domain."); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/IO/StreamResourceTests.cs b/netstandard/Spring.Rest.Tests/IO/StreamResourceTests.cs new file mode 100644 index 0000000..bb8bb00 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/IO/StreamResourceTests.cs @@ -0,0 +1,71 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; + +using NUnit.Framework; + +namespace Spring.IO +{ + // From Spring.Core.Tests + + /// + /// Unit tests for the StreamResource class. + /// + /// Bruno Baia + [TestFixture] + public sealed class StreamResourceTests + { + [Test] + public void Instantiation () + { + using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes("A temporary resource."))) + { + StreamResource res = new StreamResource(stream); + Assert.IsTrue(res.IsOpen); + Assert.IsNull(res.Uri); + Assert.IsNotNull(res.GetStream()); + } + } + + [Test] + [ExpectedException (typeof (ArgumentNullException))] + public void InstantiationWithNull () + { + new StreamResource (null); + } + + [Test] + [ExpectedException (typeof(InvalidOperationException))] + public void ReadStreamMultipleTimes () + { + using (Stream stream = new MemoryStream(Encoding.UTF8.GetBytes("A temporary resource."))) + { + StreamResource res = new StreamResource(stream); + Assert.IsTrue(res.IsOpen); + Assert.IsNull(res.Uri); + res.GetStream(); + res.GetStream(); + } + } + } +} diff --git a/netstandard/Spring.Rest.Tests/IO/TestResource.txt b/netstandard/Spring.Rest.Tests/IO/TestResource.txt new file mode 100644 index 0000000..bcbe02d --- /dev/null +++ b/netstandard/Spring.Rest.Tests/IO/TestResource.txt @@ -0,0 +1 @@ +Spring.IO.TestResource.txt diff --git a/netstandard/Spring.Rest.Tests/Json/JsonMapperTests.cs b/netstandard/Spring.Rest.Tests/Json/JsonMapperTests.cs new file mode 100644 index 0000000..72ad91d --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Json/JsonMapperTests.cs @@ -0,0 +1,154 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using NUnit.Framework; + +namespace Spring.Json +{ + /// + /// Unit tests for the JsonMapper class. + /// + /// Bruno Baia + [TestFixture] + public class JsonMapperTests + { + [Test] + public void Config() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterDeserializer(typeof(Class1), new Class1Deserializer()); + mapper.RegisterSerializer(typeof(Class2), new Class2Serializer()); + + Assert.IsTrue(mapper.CanDeserialize(typeof(Class1))); + Assert.IsFalse(mapper.CanDeserialize(typeof(Class2))); + + Assert.IsFalse(mapper.CanSerialize(typeof(Class1))); + Assert.IsTrue(mapper.CanSerialize(typeof(Class2))); + } + + [Test] + public void JsonValueSpecialCase() + { + JsonMapper mapper = new JsonMapper(); + Assert.IsTrue(mapper.CanDeserialize(typeof(JsonValue))); + Assert.IsTrue(mapper.CanSerialize(typeof(JsonValue))); + } + + [Test] + public void DeserializeKnownType() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterDeserializer(typeof(Class1), new Class1Deserializer()); + + JsonObject value = new JsonObject(); + value.AddValue("ID", new JsonValue("007")); + Class1 obj1 = mapper.Deserialize(value); + + Assert.IsNotNull(obj1); + Assert.AreEqual("007", obj1.ID); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "Could not find deserializer for type 'Spring.Json.JsonMapperTests+Class2'.")] + public void DeserializeUnknownType() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterDeserializer(typeof(Class1), new Class1Deserializer()); + + mapper.Deserialize(new JsonValue()); + } + + [Test] + public void SerializeKnownType() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterSerializer(typeof(Class2), new Class2Serializer()); + + Class2 obj2 = new Class2(); + obj2.ID = 7; + JsonValue value2 = mapper.Serialize(obj2); + Assert.IsNotNull(value2); + Assert.AreEqual(7, value2.GetValue("ID")); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "Could not find serializer for type 'Spring.Json.JsonMapperTests+Class1'.")] + public void SerializeUnknownType() + { + JsonMapper mapper = new JsonMapper(); + mapper.RegisterSerializer(typeof(Class2), new Class2Serializer()); + + mapper.Serialize(new Class1()); + } + + #region Test classes + + public class Class1 + { + private string _id; + + public string ID + { + get { return _id; } + set { _id = value; } + } + } + + public class Class2 + { + private long _id; + + public long ID + { + get { return _id; } + set { _id = value; } + } + } + + public class Class1Deserializer : IJsonDeserializer + { + public object Deserialize(JsonValue value, JsonMapper mapper) + { + Class1 obj1 = new Class1(); + obj1.ID = value.GetValue("ID"); + return obj1; + } + } + + public class Class2Serializer : IJsonSerializer + { + public JsonValue Serialize(object obj, JsonMapper mapper) + { + Class2 obj1 = obj as Class2; + JsonObject value = new JsonObject(); + value.AddValue("ID", new JsonValue(obj1.ID)); + return value; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Json/JsonValueTests.cs b/netstandard/Spring.Rest.Tests/Json/JsonValueTests.cs new file mode 100644 index 0000000..a076fd7 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Json/JsonValueTests.cs @@ -0,0 +1,388 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using NUnit.Framework; + +namespace Spring.Json +{ + /// + /// Unit tests for the JsonValue class. + /// + /// Bruno Baia + [TestFixture] + public class JsonValueTests + { + [Test] + public void ParseAndGenerate() + { + string json = "{\"ID\":1,\"Name\":\"Bruno Baïa\",\"IsFrench\":true,\"SpeaksEnglish\":false,\"Description\":null,\"Childrens\":[\"Lisa\"]}"; + JsonValue value = JsonValue.Parse(json); + Assert.AreEqual(json, value.ToString()); + } + + [Test] + public void Generate() + { + JsonArray jsonArray = new JsonArray(); + jsonArray.AddValue(new JsonValue()); + jsonArray.AddValue(new JsonValue(false)); + jsonArray.AddValue(new JsonValue(true)); + jsonArray.AddValue(new JsonValue((byte)1)); + jsonArray.AddValue(new JsonValue((decimal)2.5)); + jsonArray.AddValue(new JsonValue((double)0.7)); + jsonArray.AddValue(new JsonValue((float)1.4)); + jsonArray.AddValue(new JsonValue((int)6)); + jsonArray.AddValue(new JsonValue((long)16)); + jsonArray.AddValue(new JsonValue((short)2)); + jsonArray.AddValue(new JsonValue("\tbla")); + jsonArray.AddValue(new JsonObject()); + jsonArray.AddValue(new JsonArray()); + + Assert.AreEqual("[null,false,true,1,2.5,0.7,1.4,6,16,2,\"\\tbla\",{},[]]", jsonArray.ToString()); + } + + [Test] + [ExpectedException(typeof(JsonException), + ExpectedMessage = "Could not parse JSON string '{\"ID\":}'.")] + public void ParseError() + { + JsonValue value = JsonValue.Parse("{\"ID\":}"); + } + + [Test] + public void TryParseFailed() + { + JsonValue value; + Assert.IsFalse(JsonValue.TryParse("[4}", out value)); + Assert.IsNull(value); + } + + [Test] + public void TryParseNull() + { + JsonValue value; + Assert.IsTrue(JsonValue.TryParse(null, out value)); + Assert.IsNull(value); + } + + [Test] + public void GetStringValue() + { + JsonValue value1 = JsonValue.Parse("\"Hello world!\""); + Assert.IsTrue(value1.IsString); + Assert.AreEqual("Hello world!", value1.GetValue()); + + JsonValue value2 = JsonValue.Parse("\"\\u0061\""); + Assert.IsTrue(value2.IsString); + Assert.AreEqual("a", value2.GetValue()); + + JsonValue value3 = JsonValue.Parse("\"\\uD845\\uDE1A\""); + Assert.IsTrue(value3.IsString); + Assert.AreEqual("𡘚", value3.GetValue()); + } + + [Test] + public void GetNumberValue() + { + JsonValue value1 = JsonValue.Parse("5.1878483143195925e+49"); + Assert.IsTrue(value1.IsNumber); + Assert.AreEqual(Math.Pow(Math.PI, 100), value1.GetValue()); + + JsonValue value2 = JsonValue.Parse("007"); + Assert.IsTrue(value2.IsNumber); + Assert.AreEqual(7, value2.GetValue()); + } + + [Test] + public void GetBooleanValue() + { + JsonValue value1 = JsonValue.Parse("true"); + Assert.IsTrue(value1.IsBoolean); + Assert.AreEqual(true, value1.GetValue()); + + JsonValue value2 = JsonValue.Parse("false"); + Assert.IsTrue(value2.IsBoolean); + Assert.AreEqual(false, value2.GetValue()); + } + + [Test] + public void GetNullValue() + { + JsonValue value = JsonValue.Parse("null"); + Assert.IsTrue(value.IsNull); + } + + [Test] + public void GetObjectValue() + { + JsonValue value = JsonValue.Parse( + "{\"ID\":1,\"Name\":\"Bruno Baïa\",\"IsFrench\":true,\"SpeaksEnglish\":false,\"Description\":null,\"Childrens\":[\"Lisa\"]}" + ); + + Assert.IsTrue(value.IsObject); + Assert.AreEqual(6, value.GetNames().Count); + Assert.AreEqual(6, value.GetValues().Count); + Assert.IsNotNull(value.GetValue("ID")); + Assert.IsTrue(value.GetValue("ID").IsNumber); + Assert.AreEqual(1, value.GetValue("ID").GetValue()); + Assert.AreEqual(1, value.GetValue("ID")); + Assert.IsNotNull(value.GetValue("Name")); + Assert.IsTrue(value.GetValue("Name").IsString); + Assert.AreEqual("Bruno Baïa", value.GetValue("Name").GetValue()); + Assert.AreEqual("Bruno Baïa", value.GetValue("Name")); + Assert.IsNotNull(value.GetValue("IsFrench")); + Assert.IsTrue(value.GetValue("IsFrench").IsBoolean); + Assert.AreEqual(true, value.GetValue("IsFrench").GetValue()); + Assert.AreEqual(true, value.GetValue("IsFrench")); + Assert.IsNotNull(value.GetValue("SpeaksEnglish")); + Assert.IsTrue(value.GetValue("SpeaksEnglish").IsBoolean); + Assert.AreEqual(false, value.GetValue("SpeaksEnglish").GetValue()); + Assert.AreEqual(false, value.GetValue("SpeaksEnglish")); + Assert.IsNotNull(value.GetValue("Description")); + Assert.IsTrue(value.GetValue("Description").IsNull); + Assert.IsNotNull(value.GetValue("Childrens")); + Assert.IsTrue(value.GetValue("Childrens").IsArray); + } + + [Test] + public void GetArrayValue() + { + JsonValue value = JsonValue.Parse( + "[1,\"Bruno Baïa\",true,false,null,{},[\"Lisa\"]]" + ); + + Assert.IsTrue(value.IsArray); + Assert.AreEqual(7, value.GetValues().Count); + Assert.IsNotNull(value.GetValue(0)); + Assert.IsTrue(value.GetValue(0).IsNumber); + Assert.AreEqual(1, value.GetValue(0)); + Assert.IsNotNull(value.GetValue(1)); + Assert.IsTrue(value.GetValue(1).IsString); + Assert.AreEqual("Bruno Baïa", value.GetValue(1).GetValue()); + Assert.AreEqual("Bruno Baïa", value.GetValue(1)); + Assert.IsNotNull(value.GetValue(2)); + Assert.IsTrue(value.GetValue(2).IsBoolean); + Assert.AreEqual(true, value.GetValue(2).GetValue()); + Assert.AreEqual(true, value.GetValue(2)); + Assert.IsNotNull(value.GetValue(3)); + Assert.IsTrue(value.GetValue(3).IsBoolean); + Assert.AreEqual(false, value.GetValue(3).GetValue()); + Assert.AreEqual(false, value.GetValue(3)); + Assert.IsNotNull(value.GetValue(4)); + Assert.IsTrue(value.GetValue(4).IsNull); + Assert.IsNotNull(value.GetValue(5)); + Assert.IsTrue(value.GetValue(5).IsObject); + Assert.IsNotNull(value.GetValue(6)); + Assert.IsTrue(value.GetValue(6).IsArray); + } + + [Test] + public void GetNullableValue() + { + JsonValue value1 = JsonValue.Parse("123"); + Assert.IsTrue(value1.IsNumber); + Assert.AreEqual(123, value1.GetValue()); + + JsonValue value2 = JsonValue.Parse("null"); + Assert.IsTrue(value2.IsNull); + Assert.AreEqual(null, value2.GetValue()); + } + + [Test] + [ExpectedException(typeof(JsonException), + ExpectedMessage = "Could not cast JSON string value to type 'System.Double'.")] + public void GetValueWithInvalidCastError() + { + JsonValue value = JsonValue.Parse("\"abc\""); + + Assert.IsTrue(value.IsString); + value.GetValue(); + } + + [Test] + public void GetObjectValueOnJsonObject() + { + JsonValue value = JsonValue.Parse("{\"Name\":\"Value\"}"); + + Assert.IsTrue(value.IsObject); + Assert.IsNull(value.GetValue("Bla")); + JsonException ex = Assert.Throws(delegate() { value.GetValue("Bla"); }); + Assert.AreEqual("The JSON object structure does not have an entry named 'Bla'.", ex.Message); + } + + [Test] + public void GetObjectValueOrDefaultValueOnJsonObject() + { + JsonValue value = JsonValue.Parse("{\"Name\":\"Value\"}"); + + Assert.IsTrue(value.IsObject); + Assert.IsNull(value.GetValue("Bla")); + Assert.AreEqual("Value", value.GetValueOrDefault("Name")); + Assert.AreEqual(null, value.GetValueOrDefault("Bla")); + Assert.AreEqual(String.Empty, value.GetValueOrDefault("Bla", String.Empty)); + Assert.AreEqual(0, value.GetValueOrDefault("Gla")); + Assert.AreEqual(-1, value.GetValueOrDefault("Gla", -1)); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "The value held by this instance is not a JSON object structure.")] + public void GetObjectValueOnNonJsonObject() + { + JsonValue value = JsonValue.Parse("[]"); + + Assert.IsFalse(value.IsObject); + value.GetValue("Bla"); + } + + [Test] + public void GetArrayValueOnJsonArray() + { + JsonValue value = JsonValue.Parse("[1, 2]"); + + Assert.IsTrue(value.IsArray); + Assert.IsNull(value.GetValue(7)); + JsonException ex = Assert.Throws(delegate() { value.GetValue(7); }); + Assert.AreEqual("The JSON array structure does not have an entry at index '7'.", ex.Message); + } + + [Test] + public void GetArrayValueOrDefaultValueOnJsonArray() + { + JsonValue value = JsonValue.Parse("[1, 2]"); + + Assert.IsTrue(value.IsArray); + Assert.IsNull(value.GetValue(7)); + Assert.AreEqual(2, value.GetValueOrDefault(1)); + Assert.AreEqual(null, value.GetValueOrDefault(3)); + Assert.AreEqual(String.Empty, value.GetValueOrDefault(3, String.Empty)); + Assert.AreEqual(0, value.GetValueOrDefault(4)); + Assert.AreEqual(-1, value.GetValueOrDefault(4, -1)); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "The value held by this instance is not a JSON array structure.")] + public void GetArrayValueOnNonJsonArray() + { + JsonValue value = JsonValue.Parse("{}"); + + Assert.IsFalse(value.IsArray); + value.GetValue(7); + } + + [Test] + public void GetValuesOnJsonObject() + { + JsonValue value = JsonValue.Parse("{\"Name\":{}, \"Age\":31}"); + + Assert.IsTrue(value.IsObject); + + Assert.AreEqual(2, value.GetValues().Count); + // TODO + + Assert.AreEqual(0, value.GetValues("Name").Count); + + JsonException ex1 = Assert.Throws(delegate() { value.GetValues("Bla"); }); + Assert.AreEqual("The JSON object structure does not have an entry named 'Bla'.", ex1.Message); + + JsonException ex2 = Assert.Throws(delegate() { value.GetValues("Age"); }); + Assert.AreEqual("The value held by this instance is not a JSON object or array structure.", ex2.Message); + } + + [Test] + public void GetValuesOnJsonArray() + { + JsonValue value = JsonValue.Parse("[7, {\"Age\":31}]"); + + Assert.IsTrue(value.IsArray); + + Assert.AreEqual(2, value.GetValues().Count); + // TODO + + Assert.AreEqual(1, value.GetValues(1).Count); + + JsonException ex1 = Assert.Throws(delegate() { value.GetValues(4); }); + Assert.AreEqual("The JSON array structure does not have an entry at index '4'.", ex1.Message); + + JsonException ex2 = Assert.Throws(delegate() { value.GetValues(0); }); + Assert.AreEqual("The value held by this instance is not a JSON object or array structure.", ex2.Message); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "The value held by this instance is not a JSON object or array structure.")] + public void GetValuesOnNonJsonObjectOrArray() + { + JsonValue value = JsonValue.Parse("123"); + + Assert.IsTrue(value.IsNumber); + value.GetValues(); + } + + [Test] + public void GetNamesOnJsonObject() + { + JsonValue value = JsonValue.Parse("{\"Name\":{}, \"Age\":31}"); + + Assert.IsTrue(value.IsObject); + Assert.AreEqual(2, value.GetNames().Count); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "The value held by this instance is not a JSON object structure.")] + public void GetNamesOnNonJsonObject() + { + JsonValue value = JsonValue.Parse("[]"); + + Assert.IsFalse(value.IsObject); + value.GetNames(); + } + + [Test] + public void ContainsNameOnJsonObject() + { + JsonValue value = JsonValue.Parse("{\"Name\":{}, \"Age\":31}"); + + Assert.IsTrue(value.IsObject); + Assert.IsTrue(value.ContainsName("Name")); + Assert.IsFalse(value.ContainsName("Location")); + } + + [Test] + [ExpectedException( + typeof(JsonException), + ExpectedMessage = "The value held by this instance is not a JSON object structure.")] + public void ContainsNameOnNonJsonObject() + { + JsonValue value = JsonValue.Parse("[]"); + + Assert.IsFalse(value.IsObject); + value.ContainsName("Name"); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Properties/launchSettings.json b/netstandard/Spring.Rest.Tests/Properties/launchSettings.json new file mode 100644 index 0000000..e824646 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Spring.Rest.Tests": { + "commandName": "Executable", + "executablePath": "D:\\tools\\NUnit 2.5.9\\bin\\net-2.0\\nunit.exe" + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Rest/Client/DTO/PagedList.cs b/netstandard/Spring.Rest.Tests/Rest/Client/DTO/PagedList.cs new file mode 100644 index 0000000..57e7649 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Rest/Client/DTO/PagedList.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Spring.Rest.Tests.Rest.Client.DTO +{/// + /// 分页数据列表 + /// + /// + public class PagedList + { + /// + /// 数据内容 + /// + public IList Content; + + /// + /// 当前页码 + /// + public int PageIndex; + + /// + /// 分页大小 + /// + public int PageSize; + + /// + /// 总记录数 + /// + public long TotalCount; + + /// + /// 授权状态 + /// + public bool AuthorizeStatus { get; set; } + + /// + /// 操作状态(false时将返回空数据) + /// + public bool OperationStatus { get; set; } + + /// + /// 错误消息 + /// + public string Msg { get; set; } + + /// + /// 消息详细列表 + /// + public List MsgDetails { get; set; } + + /// + /// 构造函数 + /// + public PagedList() + { + + } + + /// + /// 构造函数 + /// + /// + /// + /// + public PagedList(IQueryable source, int index, int pageSize) + { + this.Content = source.ToList(); + this.PageIndex = index; + this.PageSize = pageSize; + } + + /// + /// 构造函数 + /// + /// + /// + /// + /// + public PagedList(IEnumerable source, int index, int pageSize, long totalRecords) + { + this.Content = source.ToList(); + this.PageIndex = index; + this.PageSize = pageSize; + this.TotalCount = totalRecords; + } + } + + + /// + /// 业务平台登录日志Dto + /// + public class BusinessLoginLogDto + { + /// + ///登录账号 + /// + public virtual string Account { get; set; } + + /// + ///尝试密码 + /// + public virtual string TryPass { get; set; } + + /// + /// 登录结果 + /// + public virtual string LoginResultText { get; set; } + + /// + ///登录时间 + /// + public virtual DateTime OPTime { get; set; } + + /// + ///访问IP + /// + public virtual string AccessIP { get; set; } + + /// + ///备注 + /// + public virtual string Note { get; set; } + } +} diff --git a/netstandard/Spring.Rest.Tests/Rest/Client/HttpResponseExceptionTests.cs b/netstandard/Spring.Rest.Tests/Rest/Client/HttpResponseExceptionTests.cs new file mode 100644 index 0000000..8eb6191 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Rest/Client/HttpResponseExceptionTests.cs @@ -0,0 +1,64 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Text; + +using Spring.Http; +using Spring.Util; + +using NUnit.Framework; + +namespace Spring.Rest.Client +{ + /// + /// Unit tests for the HttpResponseException class. + /// + /// Bruno Baia + [TestFixture] + public class HttpResponseExceptionTests + { + [Test] + public void BinarySerialization() + { + Uri requestUri = new Uri("http://www.springframework.net"); + HttpMethod requestMethod = HttpMethod.POST; + byte[] body = new byte[2] { 0, 1 }; + HttpHeaders headers = new HttpHeaders(); + headers.ContentType = new MediaType("text", "plain"); + HttpStatusCode statusCode = HttpStatusCode.Accepted; + string statusDescription = "Accepted description"; + + HttpResponseException exBefore = new HttpResponseException(requestUri, requestMethod, + new HttpResponseMessage(body, headers, statusCode, statusDescription)); + + HttpResponseException exAfter = SerializationTestUtils.BinarySerializeAndDeserialize(exBefore) as HttpResponseException; + + Assert.IsNotNull(exAfter); + Assert.AreEqual(requestUri, exAfter.RequestUri, "Invalid request URI"); + Assert.AreEqual(requestMethod, exAfter.RequestMethod, "Invalid request method"); + Assert.AreEqual(body, exAfter.Response.Body, "Invalid response body"); + Assert.AreEqual(new MediaType("text", "plain"), exAfter.Response.Headers.ContentType, "Invalid response headers"); + Assert.AreEqual(statusCode, exAfter.Response.StatusCode, "Invalid status code"); + Assert.AreEqual(statusDescription, exAfter.Response.StatusDescription, "Invalid status description"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateIntegrationTests.cs b/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateIntegrationTests.cs new file mode 100644 index 0000000..a7c7c82 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateIntegrationTests.cs @@ -0,0 +1,1133 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.IO; +using System.Text; +using System.Threading; +#if NET_4_0 || SILVERLIGHT_5 +using System.Threading.Tasks; +#endif +using System.Collections.Generic; +using System.ServiceModel; +using System.ServiceModel.Web; + +using NUnit.Framework; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Client.Interceptor; + +namespace Spring.Rest.Client +{ + /// + /// Integration tests for the RestTemplate class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class RestTemplateIntegrationTests + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(RestTemplateIntegrationTests)); + + #endregion + + private WebServiceHost webServiceHost; + private string uri = "http://localhost:1337"; + private RestTemplate template; + private MediaType contentType; + + [SetUp] + public void SetUp() + { + template = new RestTemplate(uri); + contentType = new MediaType("text", "plain"); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDown() + { + webServiceHost.Close(); + } + + #region Sync + + [Test] + public void GetString() + { + string result = template.GetForObject("users"); + Assert.AreEqual("2", result, "Invalid content"); + } + + [Test(Description = "SPRNETREST-4")] + public void GetStringWithHeaders() + { + HttpEntity entity = new HttpEntity(); + entity.Headers["MyHeader"] = "MyValue"; + HttpResponseMessage result = template.Exchange("users", HttpMethod.GET, entity); + Assert.AreEqual("2", result.Body, "Invalid content"); + } + + [Test] + public void GetStringVarArgsTemplateVariables() + { + string result = template.GetForObject("user/{id}", 1); + Assert.AreEqual("Bruno Baïa", result, "Invalid content"); + } + + [Test] + public void GetStringDictionaryTemplateVariables() + { + IDictionary uriVariables = new Dictionary(1); + uriVariables.Add("id", 2); + string result = template.GetForObject("user/{id}", uriVariables); + Assert.AreEqual("Marie Baia", result, "Invalid content"); + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "GET request for 'http://localhost:1337/user/5' resulted in 404 - NotFound (User with id '5' not found).")] + public void GetStringError() + { + string result = template.GetForObject("user/{id}", 5); + } + + [Test] + public void GetStringForMessage() + { + HttpResponseMessage result = template.GetForMessage("user/{id}", 1); + Assert.AreEqual("Bruno Baïa", result.Body, "Invalid content"); + Assert.AreEqual(new MediaType("text", "plain", "utf-8"), result.Headers.ContentType, "Invalid content-type"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void GetStringNoResponse() + { + string result = template.GetForObject("nothing"); + Assert.IsNull(result); + } + + [Test] + public void GetNoContent() + { + string result = template.GetForObject("status/nocontent"); + Assert.IsNull(result, "Invalid content"); + + HttpResponseMessage response = template.GetForMessage("status/nocontent"); + Assert.AreEqual(HttpStatusCode.NoContent, response.StatusCode, "Invalid response code"); + Assert.IsNull(response.Body, "Invalid content"); + } + + [Test] + public void GetNoModified() + { + string result = template.GetForObject("status/notmodified"); + Assert.IsNull(result, "Invalid content"); + + HttpResponseMessage response = template.GetForMessage("status/notmodified"); + Assert.AreEqual(HttpStatusCode.NotModified, response.StatusCode, "Invalid response code"); + Assert.IsNull(response.Body, "Invalid content"); + } + + [Test] + public void HeadForHeaders() + { + HttpHeaders result = template.HeadForHeaders("head"); + Assert.AreEqual("MyValue", result["MyHeader"], "Invalid header"); + } + + [Test] + public void PostStringForLocation() + { + Uri result = template.PostForLocation("user", "Lisa Baia"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result, "Invalid location"); + } + + [Test] + public void PostStringForMessage() + { + HttpResponseMessage result = template.PostForMessage("user", "Lisa Baia"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", result.StatusDescription, "Invalid status description"); + Assert.AreEqual("3", result.Body, "Invalid content"); + } + + [Test] + public void PostStringForObject() + { + string result = template.PostForObject("user", "Lisa Baia"); + Assert.AreEqual("3", result, "Invalid content"); + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "POST request for 'http://localhost:1337/user' resulted in 400 - BadRequest (Content cannot be null or empty).")] + public void PostStringForObjectWithError() + { + string result = template.PostForObject("user", ""); + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "POST request for 'http://localhost:1337/user' resulted in 400 - BadRequest (Content cannot be null or empty).")] + public void PostStringNull() + { + template.PostForObject("user", null); + } + + [Test] + public void Put() + { + string result = template.GetForObject("user/1"); + Assert.AreEqual("Bruno Baïa", result, "Invalid content"); + + template.Put("user/1", "Bruno Baia"); + + result = template.GetForObject("user/1"); + Assert.AreEqual("Bruno Baia", result, "Invalid content"); + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "PUT request for 'http://localhost:1337/user/4' resulted in 400 - BadRequest (User id '4' does not exist).")] + public void PutWithError() + { + template.Put("user/4", "Dinora Baia"); + } + + [Test] + public void Delete() + { + string result = template.GetForObject("users"); + Assert.AreEqual("2", result, "Invalid content"); + + template.Delete("user/2"); + + result = template.GetForObject("users"); + Assert.AreEqual("1", result, "Invalid content"); + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "DELETE request for 'http://localhost:1337/user/10' resulted in 400 - BadRequest (User id '10' does not exist).")] + public void DeleteWithError() + { + template.Delete("user/10"); + } + + [Test] + public void OptionsForAllow() + { + IList result = template.OptionsForAllow("allow"); + Assert.AreEqual(3, result.Count, "Invalid response"); + Assert.IsTrue(result.Contains(HttpMethod.GET), "Invalid response"); + Assert.IsTrue(result.Contains(HttpMethod.HEAD), "Invalid response"); + Assert.IsTrue(result.Contains(HttpMethod.PUT), "Invalid response"); + } + + [Test] + public void ExchangeForResponse() + { + HttpResponseMessage result = template.Exchange( + "user", HttpMethod.POST, new HttpEntity("Maryse Baia")); + + Assert.AreEqual("3", result.Body, "Invalid content"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Maryse Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void ExchangeForMessage() + { + HttpResponseMessage result = template.Exchange( + "user/1", HttpMethod.PUT, new HttpEntity("Bruno Baia")); + + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '1' updated with 'Bruno Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void ExchangeWithHeaders() + { + HttpHeaders headers = new HttpHeaders(); + headers.ContentLength = 11; + HttpEntity entity = new HttpEntity("Maryse Baia", headers); + + HttpResponseMessage result = template.Exchange( + "user", HttpMethod.POST, entity); + + Assert.AreEqual("3", result.Body, "Invalid content"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Maryse Baia'", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void ClientError() + { + try + { + template.Execute("status/notfound", HttpMethod.GET, null, null); + Assert.Fail("RestTemplate should throw an exception"); + } + catch (Exception ex) + { + HttpClientErrorException clientErrorException = ex as HttpClientErrorException; + Assert.IsNotNull(clientErrorException, "Exception HttpClientErrorException expected"); + Assert.AreEqual("GET request for 'http://localhost:1337/status/notfound' resulted in 404 - NotFound (Not Found).", clientErrorException.Message); + Assert.IsTrue(clientErrorException.Response.Body.Length == 0); + Assert.IsTrue(clientErrorException.Response.Headers.ContentLength == 0); + Assert.AreEqual(String.Empty, clientErrorException.GetResponseBodyAsString()); + Assert.AreEqual(HttpStatusCode.NotFound, clientErrorException.Response.StatusCode); + Assert.AreEqual("Not Found", clientErrorException.Response.StatusDescription); + } + } + + [Test] + public void ServerError() + { + try + { + template.Execute("status/server", HttpMethod.GET, null, null); + Assert.Fail("RestTemplate should throw an exception"); + } + catch (Exception ex) + { + HttpServerErrorException serverErrorException = ex as HttpServerErrorException; + Assert.IsNotNull(serverErrorException, "Exception HttpServerErrorException expected"); + Assert.AreEqual("GET request for 'http://localhost:1337/status/server' resulted in 500 - InternalServerError (Internal Server Error).", serverErrorException.Message); + Assert.IsTrue(serverErrorException.Response.Body.Length == 0); + Assert.IsTrue(serverErrorException.Response.Headers.ContentLength == 0); + Assert.AreEqual(String.Empty, serverErrorException.GetResponseBodyAsString()); + Assert.AreEqual(HttpStatusCode.InternalServerError, serverErrorException.Response.StatusCode); + Assert.AreEqual("Internal Server Error", serverErrorException.Response.StatusDescription); + } + } + + [Test] + public void UsingInterceptors() + { + NoOpRequestSyncInterceptor.counter = 0; + NoOpRequestBeforeInterceptor.counter = 0; + NoOpRequestSyncInterceptor interceptor1 = new NoOpRequestSyncInterceptor(); + NoOpRequestBeforeInterceptor interceptor2 = new NoOpRequestBeforeInterceptor(); + NoOpRequestSyncInterceptor interceptor3 = new NoOpRequestSyncInterceptor(); + template.RequestInterceptors.Add(interceptor1); + template.RequestInterceptors.Add(interceptor2); + template.RequestInterceptors.Add(interceptor3); + + string result = template.PostForObject("user", "Lisa Baia"); + Assert.AreEqual("3", result, "Invalid content"); + + Assert.AreEqual(1, interceptor1.HandleRequestCounter); + Assert.AreEqual(2, interceptor3.HandleRequestCounter); + Assert.AreEqual(3, interceptor3.HandleResponseCounter); + Assert.AreEqual(4, interceptor1.HandleResponseCounter); + Assert.AreEqual(1, interceptor2.HandleCounter); + } + + #endregion + + #region Async + + [Test] + public void GetStringAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.GetForObjectAsync("users", + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual("2", args.Response, "Invalid content"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void GetStringForMessageAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.GetForMessageAsync("user/{id}", + delegate(RestOperationCompletedEventArgs> args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual("Bruno Baïa", args.Response.Body, "Invalid content"); + Assert.AreEqual(new MediaType("text", "plain", "utf-8"), args.Response.Headers.ContentType, "Invalid content-type"); + Assert.AreEqual(HttpStatusCode.OK, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", args.Response.StatusDescription, "Invalid status description"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }, 1); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void PostStringForMessageAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.PostForMessageAsync("user", "Lisa Baia", + delegate(RestOperationCompletedEventArgs> args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), args.Response.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", args.Response.StatusDescription, "Invalid status description"); + Assert.AreEqual("3", args.Response.Body, "Invalid content"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void DeleteAsyncWithNoAction() + { + string result = template.GetForObject("users"); + Assert.AreEqual("2", result, "Invalid content"); + + template.DeleteAsync("user/2", (Action>)null); + + Thread.Sleep(TimeSpan.FromSeconds(1)); + + result = template.GetForObject("users"); + Assert.AreEqual("1", result, "Invalid content"); + } + + [Test] + [ExpectedException(typeof(RestClientException), + ExpectedMessage = "Could not write request: no suitable IHttpMessageConverter found for request type [System.Int32]")] + public void ExceptionBeforeAsync() + { + // An exception occurs during request preparation, before async calls. + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.ExchangeAsync("user/1", HttpMethod.PUT, new HttpEntity(1), + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsFalse(args.Cancelled, "Invalid response"); + + Assert.IsNotNull(args.Error, "Invalid response"); + exception = args.Error; + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void ExchangeForMessageAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.ExchangeAsync("user/1", HttpMethod.PUT, new HttpEntity("Bruno Baia"), + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual(HttpStatusCode.OK, args.Response.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '1' updated with 'Bruno Baia'", args.Response.StatusDescription, "Invalid status description"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + [ExpectedException(typeof(HttpClientErrorException), + ExpectedMessage = "GET request for 'http://localhost:1337/status/notfound' resulted in 404 - NotFound (Not Found).")] + public void ClientErrorAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.ExecuteAsync("status/notfound", HttpMethod.GET, null, null, + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsFalse(args.Cancelled, "Invalid response"); + + Assert.IsNotNull(args.Error, "Invalid response"); + exception = args.Error; + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void ServerErrorAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + template.ExecuteAsync("status/server", HttpMethod.GET, null, null, + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.IsNotNull(args.Error, "Invalid response"); + HttpServerErrorException serverErrorException = args.Error as HttpServerErrorException; + Assert.IsNotNull(serverErrorException, "Exception HttpServerErrorException expected"); + Assert.AreEqual("GET request for 'http://localhost:1337/status/server' resulted in 500 - InternalServerError (Internal Server Error).", serverErrorException.Message); + Assert.IsTrue(serverErrorException.Response.Body.Length == 0); + Assert.IsTrue(serverErrorException.Response.Headers.ContentLength == 0); + Assert.AreEqual(String.Empty, serverErrorException.GetResponseBodyAsString()); + Assert.AreEqual(HttpStatusCode.InternalServerError, serverErrorException.Response.StatusCode); + Assert.AreEqual("Internal Server Error", serverErrorException.Response.StatusDescription); + + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void CancelAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + RestOperationCanceler canceler = template.ExchangeAsync("sleep/2", HttpMethod.GET, null, + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsTrue(args.Cancelled, "Invalid response"); + + WebException webEx = args.Error as WebException; + Assert.IsNotNull(webEx, "Invalid response exception"); + Assert.AreEqual(WebExceptionStatus.RequestCanceled, webEx.Status, "Invalid response exception status"); + + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + canceler.Cancel(); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + } + + [Test] + public void UsingInterceptorsAsync() + { + ManualResetEvent manualEvent = new ManualResetEvent(false); + Exception exception = null; + + NoOpRequestAsyncInterceptor.counter = 0; + NoOpRequestSyncInterceptor.counter = 0; + NoOpRequestBeforeInterceptor.counter = 0; + NoOpRequestAsyncInterceptor interceptor1 = new NoOpRequestAsyncInterceptor(); + NoOpRequestSyncInterceptor interceptor2 = new NoOpRequestSyncInterceptor(); + NoOpRequestBeforeInterceptor interceptor3 = new NoOpRequestBeforeInterceptor(); + NoOpRequestAsyncInterceptor interceptor4 = new NoOpRequestAsyncInterceptor(); + template.RequestInterceptors.Add(interceptor1); + template.RequestInterceptors.Add(interceptor2); + template.RequestInterceptors.Add(interceptor3); + template.RequestInterceptors.Add(interceptor4); + + template.PostForObjectAsync("user", "Lisa Baia", + delegate(RestOperationCompletedEventArgs args) + { + try + { + Assert.IsNull(args.Error, "Invalid response"); + Assert.IsFalse(args.Cancelled, "Invalid response"); + Assert.AreEqual("3", args.Response, "Invalid content"); + } + catch (Exception ex) + { + exception = ex; + } + finally + { + manualEvent.Set(); + } + }); + + manualEvent.WaitOne(); + if (exception != null) + { + throw exception; + } + + Assert.AreEqual(1, interceptor1.HandleRequestCounter); + Assert.AreEqual(2, interceptor4.HandleRequestCounter); + Assert.AreEqual(3, interceptor4.HandleResponseCounter); + Assert.AreEqual(4, interceptor1.HandleResponseCounter); + Assert.AreEqual(1, interceptor3.HandleCounter); + Assert.AreEqual(0, interceptor2.HandleRequestCounter); + Assert.AreEqual(0, interceptor2.HandleResponseCounter); + } + + #endregion + + #region Async (TPL) + +#if NET_4_0 || SILVERLIGHT_5 + [Test] + public void GetStringTaskAsync() + { + Assert.AreEqual("2", template.GetForObjectAsync("users").Result); + + template.GetForObjectAsync("users") + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsFalse(task.IsFaulted, "Invalid response"); + Assert.AreEqual("2", task.Result, "Invalid content"); + }) + .Wait(); + } + + [Test] + public void GetStringForMessageTaskAsync() + { + Assert.AreEqual("Bruno Baïa", template.GetForMessageAsync("user/{id}", 1).Result.Body); + + template.GetForMessageAsync("user/{id}", 1) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsFalse(task.IsFaulted, "Invalid response"); + Assert.AreEqual("Bruno Baïa", task.Result.Body, "Invalid content"); + Assert.AreEqual(new MediaType("text", "plain", "utf-8"), task.Result.Headers.ContentType, "Invalid content-type"); + Assert.AreEqual(HttpStatusCode.OK, task.Result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", task.Result.StatusDescription, "Invalid status description"); + }) + .Wait(); + } + + [Test] + public void PostStringForMessageTaskAsync() + { + template.PostForMessageAsync("user", "Lisa Baia") + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsFalse(task.IsFaulted, "Invalid response"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), task.Result.Headers.Location, "Invalid location"); + Assert.AreEqual(HttpStatusCode.Created, task.Result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '3' created with 'Lisa Baia'", task.Result.StatusDescription, "Invalid status description"); + Assert.AreEqual("3", task.Result.Body, "Invalid content"); + }) + .Wait(); + } + + [Test] + public void DeleteAsyncWithNoContinuation() + { + string result = template.GetForObject("users"); + Assert.AreEqual("2", result, "Invalid content"); + + template.DeleteAsync("user/2").Wait(); + + result = template.GetForObject("users"); + Assert.AreEqual("1", result, "Invalid content"); + } + + [Test] + public void ExceptionBeforeTaskAsync() + { + // An exception occurs during request preparation, before async calls. + template.ExchangeAsync("user/1", HttpMethod.PUT, new HttpEntity(1), CancellationToken.None) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsTrue(task.IsFaulted, "Invalid response"); + Assert.IsNotNull(task.Exception, "Invalid response"); + AssertAggregateException(task.Exception, typeof(RestClientException), "Could not write request: no suitable IHttpMessageConverter found for request type [System.Int32]"); + }) + .Wait(); + } + + [Test] + public void ExchangeForMessageTaskAsync() + { + template.ExchangeAsync("user/1", HttpMethod.PUT, new HttpEntity("Bruno Baia"), CancellationToken.None) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsFalse(task.IsFaulted, "Invalid response"); + Assert.AreEqual(HttpStatusCode.OK, task.Result.StatusCode, "Invalid status code"); + Assert.AreEqual("User id '1' updated with 'Bruno Baia'", task.Result.StatusDescription, "Invalid status description"); + }) + .Wait(); + } + + [Test] + public void ClientErrorTaskAsync() + { + template.ExecuteAsync("status/notfound", HttpMethod.GET, null, null, CancellationToken.None) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsTrue(task.IsFaulted, "Invalid response"); + Assert.IsNotNull(task.Exception, "Invalid response"); + AssertAggregateException(task.Exception, typeof(HttpClientErrorException), "GET request for 'http://localhost:1337/status/notfound' resulted in 404 - NotFound (Not Found)."); + }) + .Wait(); + } + + [Test] + public void ServerErrorTaskAsync() + { + template.ExecuteAsync("status/server", HttpMethod.GET, null, null, CancellationToken.None) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsTrue(task.IsFaulted, "Invalid response"); + Assert.IsNotNull(task.Exception, "Invalid response"); + AssertAggregateException(task.Exception, typeof(HttpServerErrorException), "GET request for 'http://localhost:1337/status/server' resulted in 500 - InternalServerError (Internal Server Error)."); + }) + .Wait(); + } + + [Test] + public void CancelTaskAsync() + { + CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + Task exchangeTask = template.ExchangeAsync("sleep/2", HttpMethod.GET, null, cancellationTokenSource.Token) + .ContinueWith(task => + { + Assert.IsTrue(task.IsCanceled, "Invalid response"); + }); + + cancellationTokenSource.Cancel(); + + exchangeTask.Wait(); + } + + [Test] + public void UsingInterceptorsTaskAsync() + { + NoOpRequestAsyncInterceptor.counter = 0; + NoOpRequestSyncInterceptor.counter = 0; + NoOpRequestBeforeInterceptor.counter = 0; + NoOpRequestAsyncInterceptor interceptor1 = new NoOpRequestAsyncInterceptor(); + NoOpRequestSyncInterceptor interceptor2 = new NoOpRequestSyncInterceptor(); + NoOpRequestBeforeInterceptor interceptor3 = new NoOpRequestBeforeInterceptor(); + NoOpRequestAsyncInterceptor interceptor4 = new NoOpRequestAsyncInterceptor(); + template.RequestInterceptors.Add(interceptor1); + template.RequestInterceptors.Add(interceptor2); + template.RequestInterceptors.Add(interceptor3); + template.RequestInterceptors.Add(interceptor4); + + template.ExchangeAsync("user", HttpMethod.POST, new HttpEntity("Lisa Baia"), CancellationToken.None) + .ContinueWith(task => + { + Assert.IsFalse(task.IsCanceled, "Invalid response"); + Assert.IsFalse(task.IsFaulted, "Invalid response"); + Assert.AreEqual("3", task.Result.Body, "Invalid content"); + }) + .Wait(); + + Assert.AreEqual(1, interceptor1.HandleRequestCounter); + Assert.AreEqual(2, interceptor4.HandleRequestCounter); + Assert.AreEqual(3, interceptor4.HandleResponseCounter); + Assert.AreEqual(4, interceptor1.HandleResponseCounter); + Assert.AreEqual(1, interceptor3.HandleCounter); + Assert.AreEqual(0, interceptor2.HandleRequestCounter); + Assert.AreEqual(0, interceptor2.HandleResponseCounter); + } + + private void AssertAggregateException(AggregateException ae, Type expectedException, string expectedMessage) + { + ae.Handle(ex => + { + if (expectedException.Equals(ex.GetType())) + { + Assert.AreEqual(expectedMessage, ex.Message); + return true; + } + return false; + }); + } +#endif + + #endregion + + #region Test classes + + private class NoOpRequestBeforeInterceptor : IClientHttpRequestBeforeInterceptor + { + public static int counter = 0; + public int HandleCounter { get; set; } + + public void BeforeExecute(IClientHttpRequestContext request) + { + HandleCounter = ++counter; + } + } + + private class NoOpRequestSyncInterceptor : IClientHttpRequestSyncInterceptor + { + public static int counter = 0; + public int HandleRequestCounter { get; set; } + public int HandleResponseCounter { get; set; } + + public IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution) + { + HandleRequestCounter = ++counter; + IClientHttpResponse response = execution.Execute(); + HandleResponseCounter = ++counter; + return response; + } + } + + private class NoOpRequestAsyncInterceptor : IClientHttpRequestAsyncInterceptor + { + public static int counter = 0; + public int HandleRequestCounter { get; set; } + public int HandleResponseCounter { get; set; } + + public void ExecuteAsync(IClientHttpRequestAsyncExecution execution) + { + HandleRequestCounter = ++counter; + execution.ExecuteAsync( + delegate(IClientHttpResponseAsyncContext ctx) + { + HandleResponseCounter = ++counter; + }); + } + } + + #endregion + + #region REST test service + + [ServiceContract] + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] + public class TestService + { + private IDictionary users; + + public TestService() + { + users = new Dictionary(); + users.Add("1", "Bruno Baïa"); + users.Add("2", "Marie Baia"); + } + + [OperationContract] + [WebGet(UriTemplate = "status/notfound")] + public void StatusNotFound() + { + WebOperationContext.Current.OutgoingResponse.SetStatusAsNotFound(); + } + + [OperationContract] + [WebGet(UriTemplate = "status/server")] + public void StatusServer() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.InternalServerError; + } + + [OperationContract] + [WebGet(UriTemplate = "status/nocontent")] + public void StatusNoContent() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NoContent; + } + + [OperationContract] + [WebGet(UriTemplate = "status/notmodified")] + public void StatusNotModified() + { + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotModified; + } + + [OperationContract] + [WebGet(UriTemplate = "nothing")] + public Stream GetNothing() + { + WebOperationContext context = WebOperationContext.Current; + + return CreateTextResponse(context, string.Empty); + } + + [OperationContract] + [WebInvoke(UriTemplate = "allow", Method = "OPTIONS")] + public void Allow() + { + WebOperationContext.Current.OutgoingResponse.Headers[HttpResponseHeader.Allow] = "GET, HEAD, PUT"; + } + + [OperationContract] + [WebInvoke(UriTemplate = "head", Method = "HEAD")] + public void Head() + { + WebOperationContext.Current.OutgoingResponse.Headers["MyHeader"] = "MyValue"; + } + + [OperationContract] + [WebGet(UriTemplate = "user/{id}")] + public Stream GetUser(string id) + { + WebOperationContext context = WebOperationContext.Current; + + if (!users.ContainsKey(id)) + { + context.OutgoingResponse.SetStatusAsNotFound(String.Format("User with id '{0}' not found", id)); + return null; + } + + return CreateTextResponse(context, users[id]); + } + + [OperationContract] + [WebGet(UriTemplate = "users")] + public Stream GetUsersCount() + { + WebOperationContext context = WebOperationContext.Current; + + return CreateTextResponse(context, users.Count.ToString()); + } + + [OperationContract] + [WebInvoke(UriTemplate = "user", Method = "POST")] + public Stream Post(Stream stream) + { + WebOperationContext context = WebOperationContext.Current; + + UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; + UriTemplate template = new UriTemplate("/user/{id}"); + + MediaType mediaType = MediaType.Parse(context.IncomingRequest.ContentType); + Encoding encoding = (mediaType == null) ? Encoding.UTF8 : mediaType.CharSet; + + string id = (users.Count + 1).ToString(); // generate new ID + string name; + using (StreamReader reader = new StreamReader(stream, encoding)) + { + name = reader.ReadToEnd(); + } + + if (String.IsNullOrEmpty(name)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = "Content cannot be null or empty"; + return CreateTextResponse(context, ""); + } + + users.Add(id, name); + + Uri uri = template.BindByPosition(match.BaseUri, id); + context.OutgoingResponse.SetStatusAsCreated(uri); + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' created with '{1}'", id, name); + + return CreateTextResponse(context, id); + } + + [OperationContract] + [WebInvoke(UriTemplate = "user/{id}", Method = "PUT")] + public void Update(string id, Stream stream) + { + WebOperationContext context = WebOperationContext.Current; + + if (!users.ContainsKey(id)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' does not exist", id); + return; + } + + MediaType mediaType = MediaType.Parse(context.IncomingRequest.ContentType); + + string name; + using (StreamReader reader = new StreamReader(stream, mediaType.CharSet)) + { + name = reader.ReadToEnd(); + } + users[id] = name; + + context.OutgoingResponse.StatusCode = HttpStatusCode.OK; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' updated with '{1}'", id, name); + } + + [OperationContract] + [WebInvoke(UriTemplate = "user/{id}", Method = "DELETE")] + public void Delete(string id) + { + WebOperationContext context = WebOperationContext.Current; + + if (!users.ContainsKey(id)) + { + context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' does not exist", id); + return; + } + + users.Remove(id); + + context.OutgoingResponse.StatusCode = HttpStatusCode.OK; + context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' have been removed", id); + } + + [OperationContract] + [WebGet(UriTemplate = "sleep/{seconds}")] + public void Sleep(string seconds) + { + Thread.Sleep(TimeSpan.FromSeconds(Int32.Parse(seconds))); + + WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK; + WebOperationContext.Current.OutgoingResponse.StatusDescription = "Status OK"; + } + + private Stream CreateTextResponse(WebOperationContext context, string text) + { + context.OutgoingResponse.ContentType = "text/plain; charset=utf-8"; + return new MemoryStream(Encoding.UTF8.GetBytes(text)); + } + } + + #endregion + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateTests.cs b/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateTests.cs new file mode 100644 index 0000000..579c343 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Rest/Client/RestTemplateTests.cs @@ -0,0 +1,742 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Converters; + +using NUnit.Framework; +using Rhino.Mocks; +using Spring.Http.Converters.Json; +using Spring.Rest.Tests.Rest.Client.DTO; + +namespace Spring.Rest.Client +{ + /// + /// Unit tests for the RestTemplate class. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [TestFixture] + public class RestTemplateTests + { + #region Logging + + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(RestTemplateTests)); + + #endregion + + private MockRepository mocks; + private RestTemplate template; + private IClientHttpRequestFactory requestFactory; + private IClientHttpRequest request; + private IClientHttpResponse response; + private IResponseErrorHandler errorHandler; + private IHttpMessageConverter converter; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + requestFactory = mocks.StrictMock(); + request = mocks.StrictMock(); + response = mocks.StrictMock(); + errorHandler = mocks.StrictMock(); + converter = mocks.StrictMock(); + + IList messageConverters = new List(1); + messageConverters.Add(converter); + + template = new RestTemplate(); + template.RequestFactory = requestFactory; + template.MessageConverters = messageConverters; + template.ErrorHandler = errorHandler; + } + + [TearDown] + public void TearDown() + { + mocks.VerifyAll(); + } + + [Test] + public void VarArgsTemplateVariables() + { + Uri requestUri = new Uri("http://example.com/hotels/42/bookings/21"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + template.Execute("http://example.com/hotels/{hotel}/bookings/{booking}", HttpMethod.GET, null, null, "42", "21"); + } + + [Test] + public void DictionaryTemplateVariables() + { + Uri requestUri = new Uri("http://example.com/hotels/42/bookings/21"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + IDictionary variables = new Dictionary(); + variables.Add("booking", "41"); + variables.Add("hotel", "42"); + template.Execute("http://example.com/hotels/{hotel}/bookings/{booking}", HttpMethod.GET, null, null, "42", "21"); + } + + [Test] + public void BaseAddressTemplate() + { + Uri requestUri = new Uri("http://example.com/hotels/42/bookings/21"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + template.BaseAddress = new Uri("http://example.com"); + template.Execute("hotels/{hotel}/bookings/{booking}", HttpMethod.GET, null, null, "42", "21"); + } + + [Test] + [ExpectedException(typeof(HttpServerErrorException), + ExpectedMessage = "GET request for 'http://example.com/' resulted in 500 - InternalServerError (Internal Server Error).")] + public void ErrorHandling() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(true); + Expect.Call(delegate() { errorHandler.HandleError(requestUri, requestMethod, response); }).Throw(new HttpServerErrorException( + requestUri, requestMethod, new HttpResponseMessage(new byte[0], HttpStatusCode.InternalServerError, "Internal Server Error"))); + + mocks.ReplayAll(); + + template.Execute("http://example.com", HttpMethod.GET, null, null); + } + + [Test] + public void GetForObject() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(converter.CanRead(typeof(string), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(string), textPlain)).Return(true); + String expected = "Hello World"; + Expect.Call(converter.Read(response)).Return(expected); + + mocks.ReplayAll(); + + string result = template.GetForObject("http://example.com"); + Assert.AreEqual(expected, result, "Invalid GET result"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + } + + [Test] + [ExpectedException(typeof(RestClientException), + ExpectedMessage = "Could not extract response: no suitable HttpMessageConverter found for response type [System.String] and content type [bar/baz]")] + public void GetUnsupportedMediaType() + { + Uri requestUri = new Uri("http://example.com/resource"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(converter.CanRead(typeof(string), null)).Return(true); + MediaType textPlain = new MediaType("foo", "bar"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + MediaType contentType = new MediaType("bar", "baz"); + responseHeaders.ContentType = contentType; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(string), contentType)).Return(false); + + mocks.ReplayAll(); + + template.GetForObject("http://example.com/{p}", "resource"); + } + + [Test] + public void GetNoContentType() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(converter.CanRead(typeof(string), null)).Return(true); + MediaType applicationOctetStream = new MediaType("application", "octet-stream"); + IList mediaTypes = new List(1); + mediaTypes.Add(applicationOctetStream); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + // No content-type + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(string), applicationOctetStream)).Return(true); + String expected = "Hello World"; + Expect.Call(converter.Read(response)).Return(expected); + + mocks.ReplayAll(); + + string result = template.GetForObject("http://example.com"); + Assert.AreEqual(expected, result, "Invalid GET result"); + Assert.AreEqual(applicationOctetStream.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + } + + [Test] + [ExpectedException(typeof(RestClientException), + ExpectedMessage = "Could not write request: no suitable IHttpMessageConverter found for request type [System.String] and content type [foo/bar]")] + public void PostUnsupportedMediaType() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + string helloWorld = "Hello World"; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + MediaType contentType = new MediaType("foo", "bar"); + Expect.Call(converter.CanWrite(typeof(string), contentType)).Return(false); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + HttpEntity entity = new HttpEntity(helloWorld); + entity.Headers.ContentType = contentType; + template.PostForLocation("http://example.com", entity); + } + + [Test] + public void GetForMessage() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(converter.CanRead(typeof(string), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(string), textPlain)).Return(true); + String expected = "Hello World"; + Expect.Call(converter.Read(response)).Return(expected); + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + Expect.Call(response.StatusDescription).Return("OK"); + + mocks.ReplayAll(); + + HttpResponseMessage result = template.GetForMessage("http://example.com"); + Assert.AreEqual(expected, result.Body, "Invalid GET result"); + Assert.AreEqual(textPlain, result.Headers.ContentType, "Invalid Content-Type"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); + + mocks.ReplayAll(); + } + + [Test] + public void HeadForHeaders() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.HEAD; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + HttpHeaders result = template.HeadForHeaders("http://example.com"); + + Assert.AreSame(responseHeaders, result, "Invalid headers returned"); + } + + [Test] + public void PostForLocation() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + string helloWorld = "Hello World"; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Uri expected = new Uri("http://example.com/hotels"); + responseHeaders.Location = expected; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + Uri result = template.PostForLocation("http://example.com", helloWorld); + Assert.AreEqual(expected, result, "Invalid POST result"); + } + + [Test] + public void PostForLocationMessageContentType() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + string helloWorld = "Hello World"; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + MediaType contentType = new MediaType("text", "plain"); + Expect.Call(converter.CanWrite(typeof(string), contentType)).Return(true); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + converter.Write(helloWorld, contentType, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Uri expected = new Uri("http://example.com/hotels"); + responseHeaders.Location = expected; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + HttpHeaders entityHeaders = new HttpHeaders(); + entityHeaders.ContentType = contentType; + HttpEntity entity = new HttpEntity(helloWorld, entityHeaders); + + Uri result = template.PostForLocation("http://example.com", entity); + Assert.AreEqual(expected, result, "Invalid POST result"); + } + + [Test] + public void PostForLocationMessageCustomHeader() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + string helloWorld = "Hello World"; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Uri expected = new Uri("http://example.com/hotels"); + responseHeaders.Location = expected; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + HttpHeaders entityHeaders = new HttpHeaders(); + entityHeaders.Add("MyHeader", "MyValue"); + HttpEntity entity = new HttpEntity(helloWorld, entityHeaders); + + Uri result = template.PostForLocation("http://example.com", entity); + Assert.AreEqual(expected, result, "Invalid POST result"); + Assert.AreEqual("MyValue", requestHeaders.Get("MyHeader"), "No custom header set"); + } + + [Test] + public void PostForLocationNoLocation() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + string helloWorld = "Hello World"; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + Uri result = template.PostForLocation("http://example.com", helloWorld); + Assert.IsNull(result, "Invalid POST result"); + + mocks.ReplayAll(); + } + + [Test] + public void PostForLocationNull() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + template.PostForLocation("http://example.com", null); + + Assert.AreEqual(0, requestHeaders.ContentLength, "Invalid content length"); + } + + [Test] + public void PostForObject() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + string helloWorld = "Hello World"; + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Version expected = new Version(1, 0); + Expect.Call(converter.CanRead(typeof(Version), textPlain)).Return(true); + Expect.Call(converter.Read(response)).Return(expected); + + mocks.ReplayAll(); + + Version result = template.PostForObject("http://example.com", helloWorld); + Assert.AreEqual(expected, result, "Invalid POST result"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + } + + [Test] + public void PostForMessage() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + string helloWorld = "Hello World"; + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Version expected = new Version(1, 0); + Expect.Call(converter.CanRead(typeof(Version), textPlain)).Return(true); + Expect.Call(converter.Read(response)).Return(expected); + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + Expect.Call(response.StatusDescription).Return("OK"); + + mocks.ReplayAll(); + + HttpResponseMessage result = template.PostForMessage("http://example.com", helloWorld); + Assert.AreEqual(expected, result.Body, "Invalid POST result"); + Assert.AreEqual(textPlain, result.Headers.ContentType, "Invalid Content-Type"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void PostForMessageNoBody() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + string helloWorld = "Hello World"; + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + Expect.Call(response.Headers).Return(responseHeaders); + Expect.Call(response.StatusCode).Return(HttpStatusCode.Created); + Expect.Call(response.StatusDescription).Return("CREATED"); + + mocks.ReplayAll(); + + HttpResponseMessage result = template.PostForMessage("http://example.com", helloWorld); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("CREATED", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void PostForObjectNull() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(Version), textPlain)).Return(true); + Expect.Call(converter.Read(response)).Return(null); + + mocks.ReplayAll(); + + Version result = template.PostForObject("http://example.com", null); + Assert.IsNull(result, "Invalid POST result"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + + Assert.AreEqual(0, requestHeaders.ContentLength, "Invalid content length"); + } + + [Test] + public void PostForEntityNull() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Expect.Call(converter.CanRead(typeof(Version), textPlain)).Return(true); + Expect.Call(converter.Read(response)).Return(null); + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + Expect.Call(response.StatusDescription).Return("OK"); + + mocks.ReplayAll(); + + HttpResponseMessage result = template.PostForMessage("http://example.com", null); + Assert.IsNull(result.Body, "Invalid POST result"); + Assert.AreEqual(textPlain, result.Headers.ContentType, "Invalid Content-Type"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + + Assert.AreEqual(0, requestHeaders.ContentLength, "Invalid content length"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); + } + + [Test] + public void Put() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.PUT; + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + string helloWorld = "Hello World"; + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + template.Put("http://example.com", helloWorld); + } + + [Test] + public void PutNull() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.PUT; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + template.Put("http://example.com", null); + + Assert.AreEqual(0, requestHeaders.ContentLength, "Invalid content length"); + } + + [Test] + public void Delete() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.DELETE; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + + mocks.ReplayAll(); + + template.Delete("http://example.com"); + } + + [Test] + public void OptionsForAllow() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.OPTIONS; + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.Add("Allow", "GET"); + responseHeaders.Add("Allow", "POST"); + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + + mocks.ReplayAll(); + + IList result = template.OptionsForAllow("http://example.com"); + Assert.AreEqual(2, result.Count, "Invalid OPTIONS result"); + Assert.IsTrue(result.Contains(HttpMethod.GET), "Invalid OPTIONS result"); + Assert.IsTrue(result.Contains(HttpMethod.POST), "Invalid OPTIONS result"); + } + + [Test] + public void Exchange() + { + Uri requestUri = new Uri("http://example.com"); + HttpMethod requestMethod = HttpMethod.POST; + Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); + MediaType textPlain = new MediaType("text", "plain"); + IList mediaTypes = new List(1); + mediaTypes.Add(textPlain); + Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); + Expect.Call(requestFactory.CreateRequest(requestUri, requestMethod)).Return(request); + HttpHeaders requestHeaders = new HttpHeaders(); + Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); + string helloWorld = "Hello World"; + Expect.Call(converter.CanWrite(typeof(string), null)).Return(true); + converter.Write(helloWorld, null, request); + ExpectGetResponse(); + Expect.Call(errorHandler.HasError(requestUri, requestMethod, response)).Return(false); + HttpHeaders responseHeaders = new HttpHeaders(); + responseHeaders.ContentType = textPlain; + Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); + ExpectHasMessageBody(responseHeaders); + Version expected = new Version(1, 0); + Expect.Call(converter.CanRead(typeof(Version), textPlain)).Return(true); + Expect.Call(converter.Read(response)).Return(expected); + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + Expect.Call(response.StatusDescription).Return("OK"); + + mocks.ReplayAll(); + + HttpHeaders requestMessageHeaders = new HttpHeaders(); + requestMessageHeaders.Add("MyHeader", "MyValue"); + HttpEntity requestEntity = new HttpEntity(helloWorld, requestMessageHeaders); + HttpResponseMessage result = template.Exchange("http://example.com", HttpMethod.POST, requestEntity); + Assert.AreEqual(expected, result.Body, "Invalid POST result"); + Assert.AreEqual(textPlain, result.Headers.ContentType, "Invalid Content-Type"); + Assert.AreEqual(textPlain.ToString(), requestHeaders.GetSingleValue("Accept"), "Invalid Accept header"); + Assert.AreEqual("MyValue", requestHeaders.Get("MyHeader"), "No custom header set"); + Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); + Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); + } + + //[Test] + //public void TestGetFormNetCore() + //{ + // var client = new RestTemplate("http://localhost:55622/"); + // client.MessageConverters = new List(); + // client.MessageConverters.Add(new NJsonHttpMessageConverter()); + + // var data = client.GetForObject>("BusinessLoginLog/GetList"); + //} + + #region Utility methods + + private void ExpectGetResponse() + { + Expect.Call(request.Execute()).Return(response); + #region Instrumentation + if (LOG.IsDebugEnabled) + { + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + Expect.Call(response.StatusDescription).Return("OK"); + } + #endregion + Expect.Call(response.Dispose); + } + + private void ExpectHasMessageBody(HttpHeaders responseHeaders) + { + responseHeaders.ContentLength = 1; + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK).Repeat.Twice(); + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Tests/Rest/Client/Support/DefaultResponseErrorHandlerTests.cs b/netstandard/Spring.Rest.Tests/Rest/Client/Support/DefaultResponseErrorHandlerTests.cs new file mode 100644 index 0000000..74d3217 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Rest/Client/Support/DefaultResponseErrorHandlerTests.cs @@ -0,0 +1,182 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; +using System.Text; + +using Spring.Http; +using Spring.Http.Client; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Rest.Client.Support +{ + /// + /// Unit tests for the DefaultResponseErrorHandler class. + /// + /// Bruno Baia + [TestFixture] + public class DefaultResponseErrorHandlerTests + { + private MockRepository mocks; + + private IClientHttpResponse response; + private DefaultResponseErrorHandler responseErrorHandler; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + response = mocks.StrictMock(); + + responseErrorHandler = new DefaultResponseErrorHandler(); + } + + [TearDown] + public void TearDown() + { + mocks.VerifyAll(); + } + + [Test] + public void ThrowsClientException() + { + Uri requestUri = new Uri("http://www.springframework.net"); + HttpMethod requestMethod = HttpMethod.GET; + ExpectResponse("ClientError", Encoding.UTF8, HttpStatusCode.NotFound, "NotFound"); + + mocks.ReplayAll(); + + Assert.IsTrue(responseErrorHandler.HasError(requestUri, requestMethod, response)); + try + { + responseErrorHandler.HandleError(requestUri, requestMethod, response); + Assert.Fail("DefaultResponseErrorHandler.HandleError should throw an exception"); + } + catch (Exception ex) + { + HttpClientErrorException clientErrorException = ex as HttpClientErrorException; + Assert.IsNotNull(clientErrorException, "Exception HttpClientErrorException expected"); + Assert.AreEqual(requestUri, clientErrorException.RequestUri); + Assert.AreEqual(requestMethod, clientErrorException.RequestMethod); + Assert.IsNotNull(clientErrorException.Response); + Assert.IsTrue(clientErrorException.Response.Body.Length > 0); + Assert.IsTrue(clientErrorException.Response.Headers.ContentLength > 0); + Assert.AreEqual(new MediaType("text", "plain", "utf-8"), clientErrorException.Response.Headers.ContentType); + Assert.AreEqual("ClientError", clientErrorException.GetResponseBodyAsString()); + Assert.AreEqual(HttpStatusCode.NotFound, clientErrorException.Response.StatusCode); + Assert.AreEqual("NotFound", clientErrorException.Response.StatusDescription); + } + } + + [Test] + public void ThrowsClientExceptionWithNoBody() + { + Uri requestUri = new Uri("http://www.springframework.net"); + HttpMethod requestMethod = HttpMethod.GET; + ExpectResponse(String.Empty, Encoding.UTF8, HttpStatusCode.NotFound, "NotFound"); + + mocks.ReplayAll(); + + Assert.IsTrue(responseErrorHandler.HasError(requestUri, requestMethod, response)); + try + { + responseErrorHandler.HandleError(requestUri, requestMethod, response); + Assert.Fail("DefaultResponseErrorHandler.HandleError should throw an exception"); + } + catch (Exception ex) + { + HttpClientErrorException clientErrorException = ex as HttpClientErrorException; + Assert.IsNotNull(clientErrorException, "Exception HttpClientErrorException expected"); + Assert.AreEqual(requestUri, clientErrorException.RequestUri); + Assert.AreEqual(requestMethod, clientErrorException.RequestMethod); + Assert.IsNotNull(clientErrorException.Response); + Assert.AreEqual(0, clientErrorException.Response.Headers.ContentLength); + Assert.AreEqual(0, clientErrorException.Response.Body.Length); + Assert.AreEqual(String.Empty, clientErrorException.GetResponseBodyAsString()); + } + } + + [Test] + public void ThrowsServerException() + { + Uri requestUri = new Uri("http://www.springframework.net"); + HttpMethod requestMethod = HttpMethod.GET; + ExpectResponse("ServerError", Encoding.UTF8, HttpStatusCode.InternalServerError, "Internal Server Error"); + + mocks.ReplayAll(); + + Assert.IsTrue(responseErrorHandler.HasError(requestUri, requestMethod, response)); + try + { + responseErrorHandler.HandleError(requestUri, requestMethod, response); + Assert.Fail("DefaultResponseErrorHandler.HandleError should throw an exception"); + } + catch (Exception ex) + { + HttpServerErrorException serverErrorException = ex as HttpServerErrorException; + Assert.IsNotNull(serverErrorException, "Exception HttpServerErrorException expected"); + Assert.AreEqual(requestUri, serverErrorException.RequestUri); + Assert.AreEqual(requestMethod, serverErrorException.RequestMethod); + Assert.IsNotNull(serverErrorException.Response); + Assert.IsTrue(serverErrorException.Response.Body.Length > 0); + Assert.IsTrue(serverErrorException.Response.Headers.ContentLength > 0); + Assert.AreEqual(new MediaType("text", "plain", "utf-8"), serverErrorException.Response.Headers.ContentType); + Assert.AreEqual("ServerError", serverErrorException.GetResponseBodyAsString()); + Assert.AreEqual(HttpStatusCode.InternalServerError, serverErrorException.Response.StatusCode); + Assert.AreEqual("Internal Server Error", serverErrorException.Response.StatusDescription); + } + } + + [Test] + public void DoesNotThrowException() + { + Uri requestUri = new Uri("http://www.springframework.net"); + HttpMethod requestMethod = HttpMethod.GET; + Expect.Call(response.StatusCode).Return(HttpStatusCode.OK); + + mocks.ReplayAll(); + + Assert.IsFalse(responseErrorHandler.HasError(requestUri, requestMethod, response)); + } + + #region Private methods + + private void ExpectResponse(string body, Encoding charSet, HttpStatusCode statusCode, string statusDescription) + { + MemoryStream mStream = new MemoryStream(); + HttpHeaders headers = new HttpHeaders(); + headers.ContentType = new MediaType("text", "plain", charSet); + byte[] bytes = charSet.GetBytes(body); + mStream = new MemoryStream(bytes); + headers.ContentLength = bytes.Length; + + Expect.Call(response.Body).Return(mStream).Repeat.Any(); + Expect.Call(response.Headers).Return(headers).Repeat.Any(); + Expect.Call(response.StatusCode).Return(statusCode).Repeat.Any(); + Expect.Call(response.StatusDescription).Return(statusDescription).Repeat.Any(); + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.csproj b/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.csproj new file mode 100644 index 0000000..e352a20 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.csproj @@ -0,0 +1,42 @@ + + + + $(TargetFullFrameworkVersion) + + + + + + + + + + + Never + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + + diff --git a/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.dll.config b/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.dll.config new file mode 100644 index 0000000..f80ce27 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Spring.Rest.Tests.dll.config @@ -0,0 +1,18 @@ + + + + + +
+ + + + + + + + + + + + diff --git a/netstandard/Spring.Rest.Tests/TestResource.txt b/netstandard/Spring.Rest.Tests/TestResource.txt new file mode 100644 index 0000000..185d1c4 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/TestResource.txt @@ -0,0 +1 @@ +Spring.TestResource.txt diff --git a/netstandard/Spring.Rest.Tests/Util/ArgumentUtilsTests.cs b/netstandard/Spring.Rest.Tests/Util/ArgumentUtilsTests.cs new file mode 100644 index 0000000..537967f --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Util/ArgumentUtilsTests.cs @@ -0,0 +1,63 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using NUnit.Framework; + +namespace Spring.Util +{ + // From Spring.Core.Tests + + /// + /// Unit tests for the ArgumentUtils class. + /// + /// Rick Evans + [TestFixture] + public sealed class ArgumentUtilsTests + { + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ArgumentNotNull() + { + ArgumentUtils.AssertNotNull(null, "foo"); + } + + [Test] + public void ArgumentHasTextWithValidText() + { + ArgumentUtils.AssertHasText("... and no-one's getting fat 'cept Mama Cas!", "foo"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ArgumentHasText() + { + ArgumentUtils.AssertHasText(null, "foo"); + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ArgumentHasTextWithWhiteSpaceCharacters() + { + ArgumentUtils.AssertHasText(" ", "foo"); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Util/IoUtilsTests.cs b/netstandard/Spring.Rest.Tests/Util/IoUtilsTests.cs new file mode 100644 index 0000000..6c0206c --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Util/IoUtilsTests.cs @@ -0,0 +1,58 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; + +using NUnit.Framework; + +namespace Spring.Util +{ + /// + /// Unit tests for the IoUtils class. + /// + /// Bruno Baia + [TestFixture] + public sealed class IoUtilsTests + { + [Test] + public void CopyStream() + { + string text = "Hello !"; + + byte[] bytes = Encoding.UTF8.GetBytes(text); + MemoryStream source = new MemoryStream(bytes); + + MemoryStream destination = new MemoryStream(); + + IoUtils.CopyStream(source, destination); + + Assert.AreEqual(text.Length, destination.Length, "Invalid copy !"); + Assert.AreEqual(text.Length, destination.Position, "Invalid copy !"); + + destination.Position = 0; + using (StreamReader reader = new StreamReader(destination, Encoding.UTF8)) + { + Assert.AreEqual(text, reader.ReadToEnd(), "Invalid copy !"); + } + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Util/SerializationTestUtils.cs b/netstandard/Spring.Rest.Tests/Util/SerializationTestUtils.cs new file mode 100644 index 0000000..01782bb --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Util/SerializationTestUtils.cs @@ -0,0 +1,80 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; + +namespace Spring.Util +{ + /// + /// Utilities for testing serializability of objects. + /// + /// + /// Exposes static methods for use in other test cases. + /// + /// Rod Johnson + /// Simon White (.NET) + public sealed class SerializationTestUtils + { + /// + /// Attempts to serialize the specified object to an in-memory stream. + /// + /// the object to serialize + public static void TryBinarySerialization(object o) + { + using (Stream stream = new MemoryStream()) + { + BinaryFormatter bformatter = new BinaryFormatter(); + bformatter.Serialize(stream, o); + } + } + + /// + /// Tests whether the specified object is serializable. + /// + /// the object to test. + /// true if the object is serializable, otherwise false. + public static bool IsBinarySerializable(object o) + { + return o == null ? true : o.GetType().IsSerializable; + } + + /// + /// Serializes the specified object to an in-memory stream, and returns + /// the result of deserializing the object stream. + /// + /// the object to use. + /// the deserialized object. + public static object BinarySerializeAndDeserialize(object o) + { + using (Stream stream = new MemoryStream()) + { + BinaryFormatter bformatter = new BinaryFormatter(); + bformatter.Serialize(stream, o); + stream.Flush(); + + stream.Seek(0, SeekOrigin.Begin); + object o2 = bformatter.Deserialize(stream); + return o2; + } + } + + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest.Tests/Util/StringUtilsTests.cs b/netstandard/Spring.Rest.Tests/Util/StringUtilsTests.cs new file mode 100644 index 0000000..ff03844 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Util/StringUtilsTests.cs @@ -0,0 +1,58 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Util +{ + // From Spring.Core + + /// + /// Unit tests for the StringUtils class. + /// + /// Aleksandar Seovic + /// Rick Evans + /// Griffin Caprio + [TestFixture] + public sealed class StringtUtilsTests + { + [Test] + public void HasLengthTests() + { + Assert.IsFalse(StringUtils.HasLength(null)); + Assert.IsFalse(StringUtils.HasLength("")); + Assert.IsTrue(StringUtils.HasLength(" ")); + Assert.IsTrue(StringUtils.HasLength("Hello")); + } + + [Test] + public void HasTextTests() + { + Assert.IsFalse(StringUtils.HasText(null)); + Assert.IsFalse(StringUtils.HasText("")); + Assert.IsFalse(StringUtils.HasText(" ")); + Assert.IsTrue(StringUtils.HasText("12345")); + Assert.IsTrue(StringUtils.HasText(" 12345 ")); + } + } +} diff --git a/netstandard/Spring.Rest.Tests/Util/UriTemplateTests.cs b/netstandard/Spring.Rest.Tests/Util/UriTemplateTests.cs new file mode 100644 index 0000000..8429ba2 --- /dev/null +++ b/netstandard/Spring.Rest.Tests/Util/UriTemplateTests.cs @@ -0,0 +1,227 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using NUnit.Framework; + +namespace Spring.Util +{ + /// + /// Unit tests for the UriTemplate class. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + [TestFixture] + public class UriTemplateTests + { + [Test] + public void GetVariableNames() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + string[] variableNames = template.VariableNames; + Assert.AreEqual(new string[] { "hotel", "booking" }, variableNames, "Invalid variable names"); + } + + [Test] + public void ExpandVarArgs() + { + // absolute + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + Uri result = template.Expand("1", "42"); + Assert.AreEqual(new Uri("http://example.com/hotels/1/bookings/42"), result, "Invalid expanded template"); + + // relative + template = new UriTemplate("/hotels/{hotel}/bookings/{booking}"); + result = template.Expand("1", "42"); + Assert.AreEqual(new Uri("/hotels/1/bookings/42", UriKind.Relative), result, "Invalid expanded template"); + } + + [Test] + public void MultipleExpandVarArgs() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + Uri result = template.Expand("2", 21); + result = template.Expand(1, "42"); + Assert.AreEqual(new Uri("http://example.com/hotels/1/bookings/42"), result, "Invalid expanded template"); + } + + [Test] + [ExpectedException( + typeof(ArgumentException), + ExpectedMessage = "Invalid amount of variables values in 'http://example.com/hotels/{hotel}/bookings/{booking}': expected 2; got 3")] + public void ExpandVarArgsInvalidAmountVariables() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + template.Expand("1", "42", 100); + } + + [Test] + public void ExpandVarArgsDuplicateVariables() + { + UriTemplate template = new UriTemplate("http://example.com/order/{c}/{c}/{c}"); + Assert.AreEqual(new string[] { "c" }, template.VariableNames, "Invalid variable names"); + Uri result = template.Expand("cheeseburger"); + Assert.AreEqual(new Uri("http://example.com/order/cheeseburger/cheeseburger/cheeseburger"), result, "Invalid expanded template"); + } + + [Test] + public void ExpandDictionary() + { + IDictionary uriVariables = new Dictionary(2); + uriVariables.Add("booking", "42"); + uriVariables.Add("hotel", 1); + + // absolute + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + Uri result = template.Expand(uriVariables); + Assert.AreEqual(new Uri("http://example.com/hotels/1/bookings/42"), result, "Invalid expanded template"); + + // relative + template = new UriTemplate("hotels/{hotel}/bookings/{booking}"); + result = template.Expand(uriVariables); + Assert.AreEqual(new Uri("hotels/1/bookings/42", UriKind.Relative), result, "Invalid expanded template"); + } + + [Test] + public void MultipleExpandDictionary() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + + IDictionary uriVariables = new Dictionary(2); + uriVariables.Add("booking", 21); + uriVariables.Add("hotel", "2"); + Uri result = template.Expand(uriVariables); + + uriVariables = new Dictionary(2); + uriVariables.Add("booking", "42"); + uriVariables.Add("hotel", "1"); + result = template.Expand(uriVariables); + + Assert.AreEqual(new Uri("http://example.com/hotels/1/bookings/42"), result, "Invalid expanded template"); + } + + [Test] + [ExpectedException( + typeof(ArgumentException), + ExpectedMessage = "Invalid amount of variables values in 'http://example.com/hotels/{hotel}/bookings/{booking}': expected 2; got 1")] + public void ExpandDictionaryInvalidAmountVariables() + { + IDictionary uriVariables = new Dictionary(2); + uriVariables.Add("hotel", 1); + + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + template.Expand(uriVariables); + } + + [Test] + [ExpectedException( + typeof(ArgumentException), + ExpectedMessage = "'uriVariables' dictionary has no value for 'hotel'")] + public void ExpandDictionaryUnboundVariables() + { + IDictionary uriVariables = new Dictionary(2); + uriVariables.Add("booking", "42"); + uriVariables.Add("bar", 1); + + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + template.Expand(uriVariables); + } + + [Test] + public void ExpandEncoded() + { + UriTemplate template = new UriTemplate("http://example.com/hotel list/{hotel}"); + Uri result = template.Expand("Z\u00fcrich"); + Assert.AreEqual(new Uri("http://example.com/hotel%20list/Z%C3%BCrich"), result, "Invalid expanded template"); + } + + [Test] + public void Matches() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}/"); + Assert.IsTrue(template.Matches("http://example.com/hotels/1/bookings/42/"), "UriTemplate does not match"); + Assert.IsFalse(template.Matches("hhhhttp://example.com/hotels/1/bookings/42/"), "UriTemplate matches"); + Assert.IsFalse(template.Matches("http://example.com/hotels/1/bookings/42/blabla"), "UriTemplate matches"); + Assert.IsFalse(template.Matches("http://example.com/hotels/bookings/"), "UriTemplate matches"); + Assert.IsFalse(template.Matches(""), "UriTemplate matches"); + Assert.IsFalse(template.Matches(null), "UriTemplate matches"); + } + + [Test] + public void Match() + { + UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + IDictionary result = template.Match("http://example.com/hotels/1/bookings/42"); + Assert.AreEqual(2, result.Count); + Assert.AreEqual("1", result["hotel"]); + Assert.AreEqual("42", result["booking"]); + + result = template.Match("http://example.com/hotels/1/bookings"); + Assert.AreEqual(0, result.Count); + } + + [Test] + public void matchDuplicate() + { + UriTemplate template = new UriTemplate("/order/{c}/{c}/{c}"); + IDictionary result = template.Match("/order/cheeseburger/cheeseburger/cheeseburger"); + Assert.AreEqual(1, result.Count); + Assert.AreEqual("cheeseburger", result["c"]); + } + + [Test] + public void MatchMultipleInOneSegment() + { + UriTemplate template = new UriTemplate("/{foo}-{bar}"); + IDictionary result = template.Match("/12-34"); + Assert.AreEqual(2, result.Count); + Assert.AreEqual("12", result["foo"]); + Assert.AreEqual("34", result["bar"]); + } + + [Test] + public void MatchesQueryVariables() + { + UriTemplate template = new UriTemplate("/search?q={query}"); + Assert.IsTrue(template.Matches("/search?q=foo")); + } + + [Test] + public void MatchesFragments() + { + UriTemplate template = new UriTemplate("/search#{fragment}"); + Assert.IsTrue(template.Matches("/search#foo")); + + template = new UriTemplate("/search?query={query}#{fragment}"); + Assert.IsTrue(template.Matches("/search?query=foo#bar")); + } + + [Test] + public void ExpandWithAtSign() + { + UriTemplate template = new UriTemplate("http://localhost/query={query}"); + Uri uri = template.Expand("foo@bar"); + Assert.AreEqual("http://localhost/query=foo@bar", uri.ToString()); + } + } +} diff --git a/netstandard/Spring.Rest/Http/Client/ClientHttpRequestCompletedEventArgs.cs b/netstandard/Spring.Rest/Http/Client/ClientHttpRequestCompletedEventArgs.cs new file mode 100644 index 0000000..ffffcea --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/ClientHttpRequestCompletedEventArgs.cs @@ -0,0 +1,64 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.ComponentModel; + +namespace Spring.Http.Client +{ + /// + /// Provides data when an asynchronous HTTP request execution completes. + /// + /// + public class ClientHttpRequestCompletedEventArgs : AsyncCompletedEventArgs + { + private IClientHttpResponse response; + + /// + /// Gets the response result of the execution. + /// + /// If the execution was canceled. + /// If the execution failed. + public IClientHttpResponse Response + { + get + { + // Raise an exception if the operation failed or was canceled. + base.RaiseExceptionIfNecessary(); + + // If the operation was successful, return the value. + return response; + } + } + + /// + /// Creates a new instance of . + /// + /// The response of the execution. + /// Any error that occurred during the asynchronous execution. + /// A value indicating whether the asynchronous execution was canceled. + /// The optional user-supplied state object. + public ClientHttpRequestCompletedEventArgs(IClientHttpResponse response, Exception exception, bool cancelled, object userState) + : base(exception, cancelled, userState) + { + this.response = response; + } + } +} diff --git a/netstandard/Spring.Rest/Http/Client/IClientHttpRequest.cs b/netstandard/Spring.Rest/Http/Client/IClientHttpRequest.cs new file mode 100644 index 0000000..fa7f7f2 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/IClientHttpRequest.cs @@ -0,0 +1,74 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client +{ + /// + /// Represents a client-side HTTP request. + /// + /// + /// + /// Created via an implementation of the . + /// + /// + /// A client HTTP request can be executed, + /// getting an which can be read from. + /// + /// + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IClientHttpRequest : IHttpOutputMessage + { + /// + /// Gets the HTTP method of the request. + /// + HttpMethod Method { get; } + + /// + /// Gets the URI of the request. + /// + Uri Uri { get; } + /// + /// Execute this request, resulting in a that can be read. + /// + /// The response result of the execution + IClientHttpResponse Execute(); + /// + /// Execute this request asynchronously. + /// + /// + /// An optional user-defined object that is passed to the method invoked + /// when the asynchronous operation completes. + /// + /// + /// The to perform when the asynchronous execution completes. + /// + void ExecuteAsync(object state, Action executeCompleted); + + /// + /// Cancels a pending asynchronous operation. + /// + void CancelAsync(); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/IClientHttpRequestFactory.cs b/netstandard/Spring.Rest/Http/Client/IClientHttpRequestFactory.cs new file mode 100644 index 0000000..a065854 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/IClientHttpRequestFactory.cs @@ -0,0 +1,41 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client +{ + /// + /// Factory for objects. + /// Requests are created by the method. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IClientHttpRequestFactory + { + /// + /// Create a new for the specified URI and HTTP method. + /// + /// The URI to create a request for. + /// The HTTP method to execute. + /// The created request + IClientHttpRequest CreateRequest(Uri uri, HttpMethod method); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/IClientHttpResponse.cs b/netstandard/Spring.Rest/Http/Client/IClientHttpResponse.cs new file mode 100644 index 0000000..3b46b55 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/IClientHttpResponse.cs @@ -0,0 +1,58 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; + +namespace Spring.Http.Client +{ + /// + /// Represents a client-side HTTP response. + /// + /// + /// + /// Obtained via an 'execution' of the . + /// + /// + /// A client HTTP response must be closed, + /// typically in a finally or via an using block. + /// + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IClientHttpResponse : IHttpInputMessage, IDisposable + { + /// + /// Gets the HTTP status code of the response. + /// + HttpStatusCode StatusCode { get; } + + /// + /// Gets the HTTP status description of the response. + /// + string StatusDescription { get; } + + /// + /// Closes this response, freeing any resources created. + /// + void Close(); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/BasicSigningRequestInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/BasicSigningRequestInterceptor.cs new file mode 100644 index 0000000..7acc0ba --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/BasicSigningRequestInterceptor.cs @@ -0,0 +1,69 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// implementation that forces + /// HTTP Basic authentication for the request. + /// + /// + /// HTTP Basic authentication can also be configured using , + /// but this implementation will not wait the challenge response from server + /// before to send the 'Authorization' header value. + /// + /// Bruno Baia + public class BasicSigningRequestInterceptor : IClientHttpRequestBeforeInterceptor + { + private string authorizationHeaderValue; + + /// + /// Creates a new instance of + /// with the given user name and password. + /// + /// The user name for HTTP authentication. + /// The password for HTTP authentication. + public BasicSigningRequestInterceptor(string userName, string password) + { + string authInfo = String.Format("{0}:{1}", userName, password); + authInfo = Convert.ToBase64String(Encoding.UTF8.GetBytes(authInfo)); + this.authorizationHeaderValue = "Basic " + authInfo; + } + + #region IClientHttpRequestBeforeInterceptor Members + + /// + /// The callback method before the given request is executed. + /// + /// + /// This implementation adds the 'Authorization' header to the created request. + /// + /// The request context. + public void BeforeExecute(IClientHttpRequestContext request) + { + request.Headers["Authorization"] = this.authorizationHeaderValue; + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncExecution.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncExecution.cs new file mode 100644 index 0000000..ce5f1c5 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncExecution.cs @@ -0,0 +1,64 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Represents the context of an asynchronous client-side HTTP request execution, + /// given to an interceptor. + /// + /// + /// Bruno Baia + public interface IClientHttpRequestAsyncExecution : IClientHttpRequestContext + { + /// + /// Gets or sets the optional user-defined object that is passed to the method invoked + /// when the asynchronous operation completes. + /// + object AsyncState { get; set; } + + /// + /// Execute the request asynchronously with the current context. + /// + /// + /// Used to invoke the next interceptor in the interceptor chain, + /// or - if the calling interceptor is last - execute the request itself. + /// + /// + void ExecuteAsync(); + + /// + /// Execute the request asynchronously with the current context and handle the response result. + /// + /// + /// Used to invoke the next interceptor in the interceptor chain, + /// or - if the calling interceptor is last - execute the request itself. + /// + /// + /// The to perform when the asynchronous execution completes. + /// + /// + void ExecuteAsync(Action executeCompleted); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncInterceptor.cs new file mode 100644 index 0000000..bf928b4 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestAsyncInterceptor.cs @@ -0,0 +1,58 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Intercepts asynchronous client-side HTTP requests execution. + /// + /// + /// Bruno Baia + public interface IClientHttpRequestAsyncInterceptor : IClientHttpRequestInterceptor + { + /// + /// Intercept the given asynchronous request execution. + /// The given allows the interceptor + /// to pass on the request and the response to the next entity in the chain. + /// + /// + /// A typical implementation of this method would follow the following pattern: + ///
    + ///
  • Examine the HTTP request.
  • + ///
  • Optionally modify the request headers.
  • + ///
  • Optionally modify the request body.
  • + ///
  • Either + ///
      + ///
    • execute the request asynchronously using the method,
    • + /// or + ///
    • execute the request asynchronously using the method + /// to examine, and optionally wrap the response,
    • + /// or + ///
    • do not execute the request to block the execution altogether.
    • + ///
    + ///
  • + ///
+ ///
+ /// The asynchronous request execution context. + void ExecuteAsync(IClientHttpRequestAsyncExecution execution); + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestBeforeInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestBeforeInterceptor.cs new file mode 100644 index 0000000..67e03d2 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestBeforeInterceptor.cs @@ -0,0 +1,56 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Intercepts client-side HTTP requests before their execution. + /// + /// + /// + /// This interceptor cannot prevent the request execution, + /// unless an exception is thrown. + /// + /// + /// The main advantage is that it can be used + /// for both synchronous and asynchronous request execution. + /// + /// + /// + /// Bruno Baia + public interface IClientHttpRequestBeforeInterceptor : IClientHttpRequestInterceptor + { + /// + /// The callback method before the given request is executed. + /// + /// + /// A typical implementation of this method would follow the following pattern: + ///
    + ///
  • Examine the HTTP request.
  • + ///
  • Optionally modify the request headers.
  • + ///
  • Optionally modify the request body.
  • + ///
+ ///
+ /// The request context. + void BeforeExecute(IClientHttpRequestContext request); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestContext.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestContext.cs new file mode 100644 index 0000000..c011e2c --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestContext.cs @@ -0,0 +1,54 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Represents the context of a client-side HTTP request, + /// given to an interceptor. + /// + /// + /// Bruno Baia + public interface IClientHttpRequestContext + { + /// + /// Gets the HTTP method of the request. + /// + HttpMethod Method { get; } + + /// + /// Gets the URI of the request. + /// + Uri Uri { get; } + + /// + /// Gets the request message headers. + /// + HttpHeaders Headers { get; } + + /// + /// Gets or sets the delegate that writes the request body message as a stream. + /// + Action Body { get; set; } + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryCreation.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryCreation.cs new file mode 100644 index 0000000..e40fe87 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryCreation.cs @@ -0,0 +1,53 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Represents the context of a client-side HTTP request creation, + /// given to an interceptor. + /// + /// + /// Bruno Baia + public interface IClientHttpRequestFactoryCreation + { + /// + /// Gets or sets the HTTP method of the request. + /// + HttpMethod Method { get; set; } + + /// + /// Gets or sets the URI of the request. + /// + Uri Uri { get; set; } + + /// + /// Create the request with the current context. + /// + /// + /// Used to invoke the next interceptor in the interceptor chain, + /// or - if the calling interceptor is last - create the request itself. + /// + /// The created request. + IClientHttpRequest Create(); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryInterceptor.cs new file mode 100644 index 0000000..a8e7a06 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestFactoryInterceptor.cs @@ -0,0 +1,57 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Intercepts client-side HTTP requests creation. + /// + /// + /// Bruno Baia + public interface IClientHttpRequestFactoryInterceptor : IClientHttpRequestInterceptor + { + /// + /// Intercept the given request creation, and return the created request. + /// The given allows the interceptor + /// to pass on the request to the next entity in the chain. + /// + /// + /// A typical implementation of this method would follow the following pattern: + ///
    + ///
  • Examine the HTTP uri and method.
  • + ///
  • Optionally modify the request uri.
  • + ///
  • Optionally modify the request method.
  • + ///
  • Either + ///
      + ///
    • create the request using the method,
    • + /// or + ///
    • create your own request.
    • + ///
    + ///
  • + ///
  • Optionally modify the created request.
  • + ///
+ ///
+ /// The request creation context. + /// The created request. + IClientHttpRequest Create(IClientHttpRequestFactoryCreation creation); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestInterceptor.cs new file mode 100644 index 0000000..775174a --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestInterceptor.cs @@ -0,0 +1,36 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Marker interfaces for client-side HTTP request interceptors. + /// + /// + /// This interface is not used directly. + /// Use the various derived interfaces to intercept specific events. + /// + /// Bruno Baia + public interface IClientHttpRequestInterceptor + { + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncExecution.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncExecution.cs new file mode 100644 index 0000000..7894ba8 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncExecution.cs @@ -0,0 +1,46 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Represents the context of a synchronous client-side HTTP request execution, + /// given to an interceptor. + /// + /// + /// Arjen Poutsma + /// Bruno Baia + public interface IClientHttpRequestSyncExecution : IClientHttpRequestContext + { + /// + /// Execute the request synchronously with the current context, and return the response. + /// + /// + /// Used to invoke the next interceptor in the interceptor chain, + /// or - if the calling interceptor is last - execute the request itself. + /// + /// The response result of the execution + IClientHttpResponse Execute(); + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncInterceptor.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncInterceptor.cs new file mode 100644 index 0000000..0214258 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpRequestSyncInterceptor.cs @@ -0,0 +1,59 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Intercepts synchronous client-side HTTP requests execution. + /// + /// + /// Arjen Poutsma + /// Bruno Baia + public interface IClientHttpRequestSyncInterceptor : IClientHttpRequestInterceptor + { + /// + /// Intercept the given synchronous request execution. + /// The given allows the interceptor + /// to pass on the request and the response to the next entity in the chain. + /// + /// + /// A typical implementation of this method would follow the following pattern: + ///
    + ///
  • Examine the HTTP request.
  • + ///
  • Optionally modify the request headers.
  • + ///
  • Optionally modify the request body.
  • + ///
  • Either + ///
      + ///
    • execute the request synchronous using the method,
    • + /// or + ///
    • do not execute the request to block the execution altogether.
    • + ///
    + ///
  • + ///
  • Optionally wrap the response.
  • + ///
+ ///
+ /// The request execution context. + /// The response result of the execution + IClientHttpResponse Execute(IClientHttpRequestSyncExecution execution); + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpResponseAsyncContext.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpResponseAsyncContext.cs new file mode 100644 index 0000000..10fbeec --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/IClientHttpResponseAsyncContext.cs @@ -0,0 +1,55 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Represents the asynchronous context of a client-side HTTP response, + /// given to an interceptor. + /// + /// + /// Bruno Baia + public interface IClientHttpResponseAsyncContext + { + /// + /// Gets or sets the response result of the execution. + /// + IClientHttpResponse Response { get; set; } + + /// + /// Gets or sets a value indicating whether the asynchronous request execution has been cancelled. + /// + bool Cancelled { get; set; } + + /// + /// Gets or sets a value indicating which error occurred during the asynchronous request execution. + /// + Exception Error { get; set; } + + /// + /// Gets or sets the state object passed to the asynchronous request execution. + /// + object UserState { get; set; } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequest.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequest.cs new file mode 100644 index 0000000..496cd78 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequest.cs @@ -0,0 +1,359 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Wrapper for an that has support + /// for s. + /// + /// Arjen Poutsma + /// Bruno Baia + public class InterceptingClientHttpRequest : IClientHttpRequest + { + private IClientHttpRequest targetRequest; + private IEnumerable interceptors; + + private Action body; + + /// + /// Gets the intercepted request. + /// + public IClientHttpRequest TargetRequest + { + get { return this.targetRequest; } + } + + /// + /// Creates a new instance of the with the given parameters. + /// + /// The request to wrap. + /// The interceptors that are to be applied. Can be null. + public InterceptingClientHttpRequest( + IClientHttpRequest request, + IEnumerable interceptors) + { + ArgumentUtils.AssertNotNull(request, "request"); + + this.targetRequest = request; + this.interceptors = interceptors != null ? interceptors : new IClientHttpRequestInterceptor[0]; + } + + #region IClientHttpRequest Members + + /// + /// Gets the HTTP method of the request. + /// + public HttpMethod Method + { + get { return this.targetRequest.Method; } + } + + /// + /// Gets the URI of the request. + /// + public Uri Uri + { + get { return this.targetRequest.Uri; } + } + + /// + /// Gets the message headers. + /// + public HttpHeaders Headers + { + get { return this.targetRequest.Headers; } + } + + /// + /// Gets or sets the delegate that writes the body message as a stream. + /// + public Action Body + { + get { return this.body; } + set + { + this.body = value; + this.targetRequest.Body = value; + } + } + /// + /// Execute this request, resulting in a that can be read. + /// + /// The response result of the execution + public IClientHttpResponse Execute() + { + RequestSyncExecution requestSyncExecution = new RequestSyncExecution(this.targetRequest, this.body, this.interceptors); + return requestSyncExecution.Execute(); + } + /// + /// Execute this request asynchronously. + /// + /// + /// An optional user-defined object that is passed to the method invoked + /// when the asynchronous operation completes. + /// + /// + /// The to perform when the asynchronous execution completes. + /// + public void ExecuteAsync(object state, Action executeCompleted) + { + RequestAsyncExecution requestAsyncExecution = new RequestAsyncExecution(this.targetRequest, this.body, this.interceptors, state, executeCompleted); + requestAsyncExecution.ExecuteAsync(); + } + + /// + /// Cancels a pending asynchronous operation. + /// + public void CancelAsync() + { + this.targetRequest.CancelAsync(); + } + + #endregion + + #region Inner class definitions + + private abstract class AbstractRequestContext : IClientHttpRequestContext + { + protected IClientHttpRequest delegateRequest; + protected Action body; + protected IEnumerator enumerator; + + protected AbstractRequestContext( + IClientHttpRequest delegateRequest, + Action body, + IEnumerable interceptors) + { + this.delegateRequest = delegateRequest; + this.body = body; + this.enumerator = interceptors.GetEnumerator(); + } + + public HttpMethod Method + { + get { return this.delegateRequest.Method; } + } + + public Uri Uri + { + get { return this.delegateRequest.Uri; } + } + + public HttpHeaders Headers + { + get { return this.delegateRequest.Headers; } + } + + public Action Body + { + get { return this.body; } + set { this.delegateRequest.Body = value; } + } + } + + private sealed class RequestSyncExecution : AbstractRequestContext, IClientHttpRequestSyncExecution + { + public RequestSyncExecution( + IClientHttpRequest delegateRequest, + Action body, + IEnumerable interceptors) + : base(delegateRequest, body, interceptors) + { + } + + public IClientHttpResponse Execute() + { + if (enumerator.MoveNext()) + { + if (enumerator.Current is IClientHttpRequestSyncInterceptor) + { + return ((IClientHttpRequestSyncInterceptor)enumerator.Current).Execute(this); + } + if (enumerator.Current is IClientHttpRequestBeforeInterceptor) + { + ((IClientHttpRequestBeforeInterceptor)enumerator.Current).BeforeExecute(this); + } + return this.Execute(); + } + else + { + return this.delegateRequest.Execute(); + } + } + } + + private sealed class RequestAsyncExecution : AbstractRequestContext, IClientHttpRequestAsyncExecution + { + private object asyncState; + private Action interceptedExecuteCompleted; + + private IList> executeCompletedDelegates; + + public RequestAsyncExecution( + IClientHttpRequest delegateRequest, + Action body, + IEnumerable interceptors, + object asyncState, + Action executeCompleted) + : base(delegateRequest, body, interceptors) + { + this.asyncState = asyncState; + this.interceptedExecuteCompleted = executeCompleted; + this.executeCompletedDelegates = new List>(); + } + + public object AsyncState + { + get { return this.asyncState; } + set { this.asyncState = value; } + } + + public void ExecuteAsync() + { + this.ExecuteAsync(null); + } + + public void ExecuteAsync(Action executeCompleted) + { + if (executeCompleted != null) + { + this.executeCompletedDelegates.Insert(0, executeCompleted); + } + if (enumerator.MoveNext()) + { + if (enumerator.Current is IClientHttpRequestAsyncInterceptor) + { + ((IClientHttpRequestAsyncInterceptor)enumerator.Current).ExecuteAsync(this); + } + else + { + if (enumerator.Current is IClientHttpRequestBeforeInterceptor) + { + ((IClientHttpRequestBeforeInterceptor)enumerator.Current).BeforeExecute(this); + } + this.ExecuteAsync(null); + } + } + else + { + this.delegateRequest.ExecuteAsync(this.asyncState, + delegate(ClientHttpRequestCompletedEventArgs args) + { + ResponseAsyncContext responseAsyncContext = new ResponseAsyncContext(args); + foreach (Action action in this.executeCompletedDelegates) + { + action(responseAsyncContext); + } + + if (this.interceptedExecuteCompleted != null) + { + interceptedExecuteCompleted(responseAsyncContext.GetCompletedEventArgs()); + } + }); + } + } + } + + private sealed class ResponseAsyncContext : IClientHttpResponseAsyncContext + { + private bool hasChanged; + private bool cancelled; + private Exception error; + private IClientHttpResponse response; + private object userState; + + private ClientHttpRequestCompletedEventArgs completedEventArgs; + + public bool Cancelled + { + get { return this.cancelled; } + set + { + this.hasChanged = true; + this.cancelled = value; + } + } + + public Exception Error + { + get { return this.error; } + set + { + this.hasChanged = true; + this.error = value; + } + } + + public IClientHttpResponse Response + { + get { return this.response; } + set + { + this.hasChanged = true; + this.response = value; + } + } + + public object UserState + { + get { return this.userState; } + set + { + this.hasChanged = true; + this.userState = value; + } + } + + public ResponseAsyncContext(ClientHttpRequestCompletedEventArgs completedEventArgs) + { + this.cancelled = completedEventArgs.Cancelled; + this.error = completedEventArgs.Error; + if (this.error == null) + { + this.response = completedEventArgs.Response; + } + this.userState = completedEventArgs.UserState; + + this.completedEventArgs = completedEventArgs; + } + + public ClientHttpRequestCompletedEventArgs GetCompletedEventArgs() + { + if (!this.hasChanged) + { + return this.completedEventArgs; + } + else + { + return new ClientHttpRequestCompletedEventArgs(this.response, this.error, this.cancelled, this.userState); + } + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequestFactory.cs b/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequestFactory.cs new file mode 100644 index 0000000..426a1f4 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/Interceptor/InterceptingClientHttpRequestFactory.cs @@ -0,0 +1,129 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Http.Client.Interceptor +{ + /// + /// Wrapper for an that has support + /// for s. + /// + /// Arjen Poutsma + /// Bruno Baia + public class InterceptingClientHttpRequestFactory : IClientHttpRequestFactory + { + private IClientHttpRequestFactory targetRequestFactory; + private IEnumerable interceptors; + + /// + /// Gets the intercepted request factory. + /// + public IClientHttpRequestFactory TargetRequestFactory + { + get { return this.targetRequestFactory; } + } + + /// + /// Creates a new instance of the with the given parameters. + /// + /// The request factory to wrap. + /// The interceptors that are to be applied. Can be null. + public InterceptingClientHttpRequestFactory( + IClientHttpRequestFactory requestFactory, + IEnumerable interceptors) + { + ArgumentUtils.AssertNotNull(requestFactory, "requestFactory"); + + this.targetRequestFactory = requestFactory; + this.interceptors = interceptors != null ? interceptors : new IClientHttpRequestInterceptor[0]; + } + + #region IClientHttpRequestFactory Members + + /// + /// Create a new for the specified URI and HTTP method. + /// + /// The URI to create a request for. + /// The HTTP method to execute. + /// The created request + public IClientHttpRequest CreateRequest(Uri uri, HttpMethod method) + { + RequestCreation requestCreation = new RequestCreation(uri, method, this.targetRequestFactory, this.interceptors); + IClientHttpRequest request = requestCreation.Create(); + return new InterceptingClientHttpRequest(request, this.interceptors); + } + + #endregion + + #region IClientHttpRequestFactoryCreation implementation + + private sealed class RequestCreation : IClientHttpRequestFactoryCreation + { + private Uri uri; + private HttpMethod method; + private IClientHttpRequestFactory delegateRequestFactory; + private IEnumerator enumerator; + + public RequestCreation(Uri uri, HttpMethod method, + IClientHttpRequestFactory delegateRequestFactory, + IEnumerable interceptors) + { + this.uri = uri; + this.method = method; + this.delegateRequestFactory = delegateRequestFactory; + this.enumerator = interceptors.GetEnumerator(); + } + + public HttpMethod Method + { + get { return this.method; } + set { this.method = value; } + } + + public Uri Uri + { + get { return this.uri; } + set { this.uri = value; } + } + + public IClientHttpRequest Create() + { + if (enumerator.MoveNext()) + { + if (enumerator.Current is IClientHttpRequestFactoryInterceptor) + { + return ((IClientHttpRequestFactoryInterceptor)enumerator.Current).Create(this); + } + return this.Create(); + } + else + { + return this.delegateRequestFactory.CreateRequest(this.uri, this.method); + } + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Http/Client/WebClientHttpRequest.cs b/netstandard/Spring.Rest/Http/Client/WebClientHttpRequest.cs new file mode 100644 index 0000000..2902868 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/WebClientHttpRequest.cs @@ -0,0 +1,500 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; +using System.Threading; +using System.ComponentModel; +using System.Globalization; + +using Spring.Util; + +namespace Spring.Http.Client +{ + /// + /// implementation that uses + /// .NET 's class to execute requests. + /// + /// + /// Bruno Baia + public class WebClientHttpRequest : IClientHttpRequest + { + private HttpHeaders headers; + private Action body; + private HttpWebRequest httpWebRequest; + + private bool isExecuted; + private bool isCancelled; + + /// + /// Gets the instance used by the request. + /// + public HttpWebRequest HttpWebRequest + { + get { return this.httpWebRequest; } + } + + /// + /// Creates a new instance of + /// with the given instance. + /// + /// The instance to use. + public WebClientHttpRequest(HttpWebRequest request) + { + ArgumentUtils.AssertNotNull(request, "request"); + + this.httpWebRequest = request; + this.headers = new HttpHeaders(); + } + + #region IClientHttpRequest Members + + /// + /// Gets the HTTP method of the request. + /// + public HttpMethod Method + { + get + { + return new HttpMethod(this.httpWebRequest.Method); + } + } + + /// + /// Gets the URI of the request. + /// + public Uri Uri + { + get + { + return this.httpWebRequest.RequestUri; + } + } + + /// + /// Gets the message headers. + /// + public HttpHeaders Headers + { + get { return headers; } + } + + /// + /// Sets the delegate that writes the body message as a stream. + /// + public Action Body + { + set { this.body = value; } + } + + /// + /// Execute this request, resulting in a that can be read. + /// + /// The response result of the execution + /// If the request is already executed or is currently executing. + public IClientHttpResponse Execute() + { + this.EnsureNotExecuted(); + + try + { + // Prepare + this.PrepareForExecution(); + + // Write + if (this.body != null) + { + using (Stream stream = this.httpWebRequest.GetRequestStream()) + { + this.body(stream); + } + } + + // Read + HttpWebResponse httpWebResponse = this.httpWebRequest.GetResponse() as HttpWebResponse; + if (this.httpWebRequest.HaveResponse && httpWebResponse != null) + { + return this.CreateClientHttpResponse(httpWebResponse); + } + } + catch (WebException ex) + { + // This exception can be raised with some status code + // Try to retrieve the response from the error + HttpWebResponse httpWebResponse = ex.Response as HttpWebResponse; + if (httpWebResponse != null) + { + return this.CreateClientHttpResponse(httpWebResponse); + } + throw; + } + finally + { + this.isExecuted = true; + } + + return null; + } + /// + /// Execute this request asynchronously. + /// + /// + /// An optional user-defined object that is passed to the method invoked + /// when the asynchronous operation completes. + /// + /// + /// The to perform when the asynchronous execution completes. + /// + /// If the request is already executed or is currently executing. + public void ExecuteAsync(object state, Action executeCompleted) + { + this.EnsureNotExecuted(); + + AsyncOperation asyncOperation = AsyncOperationManager.CreateOperation(state); + ExecuteState executeState = new ExecuteState(executeCompleted, asyncOperation); + + try + { + // Prepare + this.PrepareForExecution(); + + // Post request + if (this.body != null) + { + this.httpWebRequest.BeginGetRequestStream(ExecuteRequestCallback, executeState); + } + else + { + // Get request + this.HttpWebRequest.BeginGetResponse(ExecuteResponseCallback, executeState); + } + } + catch (Exception ex) + { + if (ex is ThreadAbortException || ex is StackOverflowException || ex is OutOfMemoryException) + { + throw; + } + ExecuteAsyncCallback(executeState, null, ex); + } + finally + { + this.isExecuted = true; + } + } + + /// + /// Cancels a pending asynchronous operation. + /// + public void CancelAsync() + { + this.isCancelled = true; + try + { + if (this.httpWebRequest != null) + { + this.httpWebRequest.Abort(); + } + } + catch (Exception exception) + { + if (((exception is OutOfMemoryException) || (exception is StackOverflowException)) || (exception is ThreadAbortException)) + { + throw; + } + } + } + + #endregion + + + #region Async methods/classes + + + private void ExecuteRequestCallback(IAsyncResult result) + { + ExecuteState state = (ExecuteState)result.AsyncState; + + try + { + // Write + using (Stream stream = this.httpWebRequest.EndGetRequestStream(result)) + { + this.body(stream); + } + + // Read + this.httpWebRequest.BeginGetResponse(ExecuteResponseCallback, state); + } + catch (Exception ex) + { + if (ex is ThreadAbortException || ex is StackOverflowException || ex is OutOfMemoryException) + { + throw; + } + ExecuteAsyncCallback(state, null, ex); + } + } + + private void ExecuteResponseCallback(IAsyncResult result) + { + ExecuteState state = (ExecuteState)result.AsyncState; + + IClientHttpResponse response = null; + Exception exception = null; + try + { + HttpWebResponse httpWebResponse = this.httpWebRequest.EndGetResponse(result) as HttpWebResponse; + if (this.httpWebRequest.HaveResponse == true && httpWebResponse != null) + { + response = this.CreateClientHttpResponse(httpWebResponse); + } + } + catch (Exception ex) + { + if (ex is ThreadAbortException || ex is StackOverflowException || ex is OutOfMemoryException) + { + throw; + } + exception = ex; + // This exception can be raised with some status code + // Try to retrieve the response from the error + if (ex is WebException) + { + HttpWebResponse httpWebResponse = ((WebException)ex).Response as HttpWebResponse; + if (httpWebResponse != null) + { + exception = null; + response = this.CreateClientHttpResponse(httpWebResponse); + } + } + } + + ExecuteAsyncCallback(state, response, exception); + } + + // This is the method that the underlying, free-threaded asynchronous behavior will invoke. + // This will happen on an arbitrary thread. + private void ExecuteAsyncCallback(ExecuteState state, IClientHttpResponse response, Exception exception) + { + // Package the results of the operation + ClientHttpRequestCompletedEventArgs eventArgs = new ClientHttpRequestCompletedEventArgs(response, exception, this.isCancelled, state.AsyncOperation.UserSuppliedState); + ExecuteCallbackArgs callbackArgs = new ExecuteCallbackArgs(eventArgs, state.ExecuteCompleted); + + // End the task. The asyncOp object is responsible for marshaling the call. + state.AsyncOperation.PostOperationCompleted(ExecuteResponseReceived, callbackArgs); + } + + private static void ExecuteResponseReceived(object arg) + { + ExecuteCallbackArgs callbackArgs = (ExecuteCallbackArgs)arg; + if (callbackArgs.Callback != null) + { + callbackArgs.Callback(callbackArgs.EventArgs); + } + } + + private class ExecuteCallbackArgs where T : class + { + public T EventArgs; + public Action Callback; + + public ExecuteCallbackArgs(T eventArgs, + Action callback) + { + this.EventArgs = eventArgs; + this.Callback = callback; + } + } + + private class ExecuteState + { + public Action ExecuteCompleted; + public AsyncOperation AsyncOperation; + + public ExecuteState( + Action executeCompleted, + AsyncOperation asyncOperation) + { + this.ExecuteCompleted = executeCompleted; + this.AsyncOperation = asyncOperation; + } + } + + #endregion + + /// + /// Ensures that the request can be executed. + /// + /// If the request is already executed or is currently executing. + protected void EnsureNotExecuted() + { + if (this.isExecuted) + { + throw new InvalidOperationException("Client HTTP request already executed or is currently executing."); + } + } + + /// + /// Creates and returns an implementation associated + /// with the request. + /// + /// The instance to use. + /// + /// An implementation associated with the request. + /// + protected virtual IClientHttpResponse CreateClientHttpResponse(HttpWebResponse response) + { + return new WebClientHttpResponse(response); + } + + /// + /// Prepare the request for execution. + /// + /// + /// Default implementation copies headers to the .NET request. Can be overridden in subclasses. + /// + protected virtual void PrepareForExecution() + { + // Copy headers + foreach (string header in this.headers) + { + // Special headers + switch (header.ToUpper(CultureInfo.InvariantCulture)) + { + case "ACCEPT": + { + this.httpWebRequest.Accept = this.headers[header]; + break; + } + + case "CONTENT-LENGTH": + { + this.httpWebRequest.ContentLength = this.headers.ContentLength; + break; + } + case "CONTENT-TYPE": + { + this.httpWebRequest.ContentType = this.headers[header]; + break; + } + case "DATE" : + { + DateTime? date = this.headers.Date; + if (date.HasValue) + { + this.httpWebRequest.Date = date.Value; + } + else + { + this.httpWebRequest.Date = DateTime.MinValue; + } + break; + } + case "HOST" : + { + this.httpWebRequest.Host = this.headers[header]; + break; + } + case "CONNECTION": + { + string headerValue = this.headers[header]; + if (headerValue.Equals("Keep-Alive", StringComparison.OrdinalIgnoreCase)) + { + this.httpWebRequest.KeepAlive = true; + } + else if (!headerValue.Equals("Close", StringComparison.OrdinalIgnoreCase)) + { + this.httpWebRequest.Connection = headerValue; + } + break; + } + case "EXPECT": + { + this.httpWebRequest.Expect = this.headers[header]; + break; + } + case "IF-MODIFIED-SINCE": + { + DateTime? date = this.headers.IfModifiedSince; + if (date.HasValue) + { + this.httpWebRequest.IfModifiedSince = date.Value; + } + else + { + this.httpWebRequest.IfModifiedSince = DateTime.MinValue; + } + break; + } + case "RANGE": + { + string headerValue = this.headers[header]; + try + { + // Supports '=-' format + string[] rangesSpecifiers = headerValue.Split('='); + string rangesSpecifier = rangesSpecifiers[0]; + string byteRangesSpecifier = rangesSpecifiers[1]; + int byteRangesSpecifierSeparator = byteRangesSpecifier.IndexOf('-'); + + long from = long.Parse(byteRangesSpecifier.Substring(0, byteRangesSpecifierSeparator)); + long to = long.Parse(byteRangesSpecifier.Substring(byteRangesSpecifierSeparator + 1)); + + this.httpWebRequest.AddRange(rangesSpecifier, from, to); + } + catch + { + this.httpWebRequest.Headers[header] = this.headers[header]; + } + break; + } + case "REFERER": + { + this.httpWebRequest.Referer = this.headers[header]; + break; + } + case "TRANSFER-ENCODING": + { + this.httpWebRequest.SendChunked = true; + string headerValue = this.headers[header]; + if (!headerValue.Equals("Chunked", StringComparison.OrdinalIgnoreCase)) + { + this.httpWebRequest.TransferEncoding = headerValue; + } + break; + } + case "USER-AGENT": + { + this.httpWebRequest.UserAgent = this.headers[header]; + break; + } + default: + { + // Other headers + this.httpWebRequest.Headers[header] = this.headers[header]; + break; + } + } + } + } + } +} diff --git a/netstandard/Spring.Rest/Http/Client/WebClientHttpRequestFactory.cs b/netstandard/Spring.Rest/Http/Client/WebClientHttpRequestFactory.cs new file mode 100644 index 0000000..d2b4b2f --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/WebClientHttpRequestFactory.cs @@ -0,0 +1,213 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using Spring.Util; + +namespace Spring.Http.Client +{ + /// + /// implementation that uses + /// .NET 's class to create requests. + /// + /// + /// Bruno Baia + public class WebClientHttpRequestFactory : IClientHttpRequestFactory + { + #region Properties + + private bool? _allowAutoRedirect; + private bool? _useDefaultCredentials; + private CookieContainer _cookieContainer; + private ICredentials _credentials; + /// + /// Gets or sets a boolean value that controls whether the request should follow redirection responses. + /// + public bool? AllowAutoRedirect + { + get { return this._allowAutoRedirect; } + set { this._allowAutoRedirect = value; } + } + /// + /// Gets or sets a boolean value that controls whether default credentials are sent with this request. + /// + public bool? UseDefaultCredentials + { + get { return this._useDefaultCredentials; } + set { this._useDefaultCredentials = value; } + } + /// + /// Gets or sets the cookies for the request. + /// + public CookieContainer CookieContainer + { + get { return this._cookieContainer; } + set { this._cookieContainer = value; } + } + /// + /// Gets or sets authentication information for the request. + /// + public ICredentials Credentials + { + get { return this._credentials; } + set { this._credentials = value; } + } + + private X509CertificateCollection _clientCertificates; + /// + /// Gets or sets the collection of security certificates that are associated with this request. + /// + public X509CertificateCollection ClientCertificates + { + get + { + if (this._clientCertificates == null) + { + this._clientCertificates = new X509CertificateCollection(); + } + return this._clientCertificates; + } + } + + private IWebProxy _proxy; + /// + /// Gets or sets proxy information for the request. + /// + /// + /// The default value is set by calling the property. + /// + public IWebProxy Proxy + { + get { return this._proxy; } + set { this._proxy = value; } + } + + private int? _timeout; + /// + /// Gets or sets the time-out value in milliseconds for synchronous request only. + /// + /// + /// The default is 100,000 milliseconds (100 seconds). + /// + public int? Timeout + { + get { return this._timeout; } + set { this._timeout = value; } + } + + private bool? _expect100Continue; + /// + /// Gets or sets a boolean value that determines whether 100-Continue behavior is used. + /// + /// + /// The default value is . + /// + public bool? Expect100Continue + { + get { return this._expect100Continue; } + set { this._expect100Continue = value; } + } + + private DecompressionMethods? _automaticDecompression; + /// + /// Gets or sets the type of decompression that is automatically used for the response result of this request. + /// + public DecompressionMethods? AutomaticDecompression + { + get { return this._automaticDecompression; } + set { this._automaticDecompression = value; } + } + + + + #endregion + + + + #region IClientHttpRequestFactory Members + + /// + /// Create a new for the specified URI and HTTP method. + /// + /// The URI to create a request for. + /// The HTTP method to execute. + /// The created request + public virtual IClientHttpRequest CreateRequest(Uri uri, HttpMethod method) + { + ArgumentUtils.AssertNotNull(uri, "uri"); + ArgumentUtils.AssertNotNull(method, "method"); + + HttpWebRequest httpWebRequest; + + httpWebRequest = WebRequest.Create(uri) as HttpWebRequest; + + httpWebRequest.Method = method.ToString(); + + if (this._allowAutoRedirect != null) + { + httpWebRequest.AllowAutoRedirect = this._allowAutoRedirect.Value; + } + if (this._useDefaultCredentials.HasValue) + { + httpWebRequest.UseDefaultCredentials = this._useDefaultCredentials.Value; + } + + if (this._cookieContainer != null) + { + httpWebRequest.CookieContainer = this._cookieContainer; + } + + if (this._credentials != null) + { + httpWebRequest.Credentials = this._credentials; + } + + if (this._clientCertificates != null) + { + foreach (X509Certificate2 certificate in this._clientCertificates) + { + httpWebRequest.ClientCertificates.Add(certificate); + } + } + if (this._proxy != null) + { + httpWebRequest.Proxy = this._proxy; + } + if (this._timeout != null) + { + httpWebRequest.Timeout = this._timeout.Value; + } + if (this._expect100Continue != null) + { + httpWebRequest.ServicePoint.Expect100Continue = this._expect100Continue.Value; + } + if (this._automaticDecompression.HasValue) + { + httpWebRequest.AutomaticDecompression = this._automaticDecompression.Value; + } + + return new WebClientHttpRequest(httpWebRequest); + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Http/Client/WebClientHttpResponse.cs b/netstandard/Spring.Rest/Http/Client/WebClientHttpResponse.cs new file mode 100644 index 0000000..52e942b --- /dev/null +++ b/netstandard/Spring.Rest/Http/Client/WebClientHttpResponse.cs @@ -0,0 +1,134 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; + +using Spring.Util; + +namespace Spring.Http.Client +{ + /// + /// implementation that uses + /// .NET 's class to read responses. + /// + /// Bruno Baia + public class WebClientHttpResponse : IClientHttpResponse + { + private HttpHeaders headers; + private HttpWebResponse httpWebResponse; + + /// + /// Gets the instance used by the response. + /// + public HttpWebResponse HttpWebResponse + { + get { return this.httpWebResponse; } + } + + /// + /// Creates a new instance of + /// with the given instance. + /// + /// The instance to use. + public WebClientHttpResponse(HttpWebResponse response) + { + ArgumentUtils.AssertNotNull(response, "response"); + + this.httpWebResponse = response; + this.headers = new HttpHeaders(); + + this.Initialize(); + } + + #region IClientHttpResponse Members + + /// + /// Gets the message headers. + /// + public HttpHeaders Headers + { + get { return this.headers; } + } + + /// + /// Gets the body of the message as a stream. + /// + public Stream Body + { + get + { + return this.httpWebResponse.GetResponseStream(); + } + } + + /// + /// Gets the HTTP status code of the response. + /// + public HttpStatusCode StatusCode + { + get + { + return this.httpWebResponse.StatusCode; + } + } + + /// + /// Gets the HTTP status description of the response. + /// + public string StatusDescription + { + get + { + return this.httpWebResponse.StatusDescription; + } + } + + /// + /// Closes this response, freeing any resources created. + /// + public void Close() + { + this.httpWebResponse.Close(); + } + + void IDisposable.Dispose() + { + ((IDisposable)this.httpWebResponse).Dispose(); + } + + #endregion + + /// + /// Initialize the response. + /// + /// + /// Default implementation copies headers from the .NET response. Can be overridden in subclasses. + /// + protected virtual void Initialize() + { + foreach (string header in this.httpWebResponse.Headers) + { + this.headers[header] = this.httpWebResponse.Headers[header]; + } + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/AbstractHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/AbstractHttpMessageConverter.cs new file mode 100644 index 0000000..14d3f7a --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/AbstractHttpMessageConverter.cs @@ -0,0 +1,279 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Collections.Generic; + +namespace Spring.Http.Converters +{ + /// + /// Base class for most implementations. + /// + /// + /// This base class adds support for setting supported s, through the + /// property. + /// It also adds support for 'Content-Type' when writing to the HTTP message. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + public abstract class AbstractHttpMessageConverter : IHttpMessageConverter + { + #region Logging + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(AbstractHttpMessageConverter)); + #endregion + + private IList _supportedMediaTypes = new List(); + + #region Constructor(s) + + /// + /// Creates a new instance of the + /// with no supported media types. + /// + protected AbstractHttpMessageConverter() + { + } + + /// + /// Creates a new instance of the + /// with multiple supported media type. + /// + /// The supported media types. + protected AbstractHttpMessageConverter(params MediaType[] supportedMediaTypes) + { + this._supportedMediaTypes = new List(supportedMediaTypes); + } + + #endregion + + #region IHttpMessageConverter Members + + /// + /// Indicates whether the given class can be read by this converter. + /// + /// + /// This implementation checks if the given class is supported, + /// and if the supported media types include + /// the given media type. + /// + /// The class to test for readability + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// if readable; otherwise + public virtual bool CanRead(Type type, MediaType mediaType) + { + return Supports(type) && CanRead(mediaType); + } + + /// + /// Indicates whether the given class can be written by this converter. + /// + /// + /// This implementation checks if the given class is supported, + /// and if the supported media types include + /// the given media type. + /// + /// The class to test for writability + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// if writable; otherwise + public virtual bool CanWrite(Type type, MediaType mediaType) + { + return Supports(type) && CanWrite(mediaType); + } + + /// + /// Gets or sets the list of objects supported by this converter. + /// + public virtual IList SupportedMediaTypes + { + get { return _supportedMediaTypes; } + set { _supportedMediaTypes = value; } + } + + /// + /// Read an object of the given type form the given HTTP message, and returns it. + /// + /// + /// This implementation simple delegates to method. + /// Future implementations might add some default behavior, however. + /// + /// + /// The type of object to return. This type must have previously been passed to the + /// method of this interface, which must have returned . + /// + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + public virtual T Read(IHttpInputMessage message) where T : class + { + return ReadInternal(message); + } + + /// + /// Write an given object to the given HTTP message. + /// + /// + /// This implementation delegates to method if a content + /// type was not provided, and calls . + /// + /// + /// The object to write to the HTTP message. The type of this object must have previously been + /// passed to the method of this interface, which must have returned . + /// + /// + /// The content type to use when writing. May be null to indicate that the default content type of the converter must be used. + /// If not null, this media type must have previously been passed to the method of this interface, + /// which must have returned . + /// + /// The HTTP message to write to. + /// In case of conversion errors + public virtual void Write(object content, MediaType contentType, IHttpOutputMessage message) + { + HttpHeaders headers = message.Headers; + if (headers.ContentType == null) + { + if (contentType == null || contentType.IsWildcardType || contentType.IsWildcardSubtype) + { + contentType = GetDefaultContentType(content); + } + if (contentType != null) + { + headers.ContentType = contentType; + } + } + WriteInternal(content, message); + } + + #endregion + + /// + /// Returns true if any of the supported media types include the given media type. + /// + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// + /// if the supported media types include the media type, or if the media type is null. + /// + protected virtual bool CanRead(MediaType mediaType) + { + if (mediaType == null) + { + return true; + } + foreach(MediaType supportedMediaType in this._supportedMediaTypes) + { + if (supportedMediaType.Includes(mediaType)) + { + return true; + } + } + return false; + } + + /// + /// Returns true if the given media type includes any of the supported media types. + /// + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// + /// if the supported media types are compatible with the media type, or if the media type is null. + /// + protected virtual bool CanWrite(MediaType mediaType) + { + if (mediaType == null || mediaType == MediaType.ALL) + { + return true; + } + foreach(MediaType supportedMediaType in this._supportedMediaTypes) + { + if (supportedMediaType.IsCompatibleWith(mediaType)) + { + return true; + } + } + return false; + } + + /// + /// Returns the default content type for the given object. + /// Called when is invoked without a specified content type parameter. + /// + /// + /// By default, this returns the first element of the property, if any. + /// + /// The object to return the content type for. + /// The content type, or null if not known. + protected virtual MediaType GetDefaultContentType(object content) + { + return (this._supportedMediaTypes.Count > 0 ? this._supportedMediaTypes[0] : null); + } + + /// + /// Returns the character set for the given Internet media type. + /// + /// The Internet media type. + /// + /// The default character to use if not specified by the media type. + /// + /// The character set. + protected virtual Encoding GetContentTypeCharset(MediaType contentType, Encoding defaultEncoding) + { + if (contentType != null && contentType.CharSet != null) + { + return contentType.CharSet; + } + else + { + return defaultEncoding; + } + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected abstract bool Supports(Type type); + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected abstract T ReadInternal(IHttpInputMessage message) where T : class; + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected abstract void WriteInternal(object content, IHttpOutputMessage message); + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/ByteArrayHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/ByteArrayHttpMessageConverter.cs new file mode 100644 index 0000000..42be326 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/ByteArrayHttpMessageConverter.cs @@ -0,0 +1,93 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +using Spring.Util; + +namespace Spring.Http.Converters +{ + /// + /// Implementation of that can read and write byte arrays. + /// + /// + /// By default, this converter supports all media types '*/*', and writes with a 'Content-Type' + /// of 'application/octet-stream'. + /// This can be overridden by setting the property. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class ByteArrayHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'application/octet-stream', and '*/*' media types. + /// + public ByteArrayHttpMessageConverter() : + base(new MediaType("application", "octet-stream"), MediaType.ALL) + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return type.Equals(typeof(byte[])); + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + // Read from the message stream + using (MemoryStream tempStream = new MemoryStream()) + { + IoUtils.CopyStream(message.Body, tempStream); + return tempStream.ToArray() as T; + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Create a byte array of the data we want to send + byte[] byteData = content as byte[]; + + message.Body = delegate(Stream stream) + { + stream.Write(byteData, 0, byteData.Length); + }; + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs new file mode 100644 index 0000000..63999f0 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs @@ -0,0 +1,96 @@ +#if NET_3_5 || SILVERLIGHT_FEED +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Xml; +using System.ServiceModel.Syndication; + +using Spring.Http.Converters.Xml; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Base class for Atom and RSS Feed message converters + /// using the class. + /// + /// Bruno Baia + public abstract class AbstractFeedHttpMessageConverter : AbstractXmlHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with multiple supported media type. + /// + /// The supported media types. + protected AbstractFeedHttpMessageConverter(params MediaType[] supportedMediaTypes) : + base(supportedMediaTypes) + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return type.Equals(typeof(SyndicationFeed)) || type.Equals(typeof(SyndicationItem)); + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader) + { + if (typeof(SyndicationFeed).Equals(typeof(T))) + { + return SyndicationFeed.Load(xmlReader) as T; + } + if (typeof(SyndicationItem).Equals(typeof(T))) + { + return SyndicationItem.Load(xmlReader) as T; + } + return null; + } + + /// + /// Returns the XmlReader settings + /// used by this converter to read from the HTTP message. + /// + /// The XmlReader settings. + protected override XmlReaderSettings GetXmlReaderSettings() + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.CloseInput = true; + settings.IgnoreProcessingInstructions = true; +#if NET_4_0 || SILVERLIGHT + settings.DtdProcessing = DtdProcessing.Ignore; +#else + settings.ProhibitDtd = false; +#endif + settings.XmlResolver = null; + return settings; + } + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs new file mode 100644 index 0000000..25dff0a --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs @@ -0,0 +1,67 @@ +#if NET_3_5 || SILVERLIGHT_FEED +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Xml; +using System.ServiceModel.Syndication; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Implementation of that can read and write Atom feeds + /// using the class. + /// + /// + /// By default, this converter reads and writes the media type 'application/atom+xml' media type. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class Atom10FeedHttpMessageConverter : AbstractFeedHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'application/atom+xml' media type. + /// + public Atom10FeedHttpMessageConverter() : + base(new MediaType("application", "atom+xml")) + { + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + if (content is SyndicationFeed) + { + SyndicationFeed atomFeed = content as SyndicationFeed; + atomFeed.SaveAsAtom10(xmlWriter); + } + else if (content is SyndicationItem) + { + SyndicationItem atomItem = content as SyndicationItem; + atomItem.SaveAsAtom10(xmlWriter); + } + } + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs new file mode 100644 index 0000000..3d2334a --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs @@ -0,0 +1,67 @@ +#if NET_3_5 || SILVERLIGHT_FEED +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Xml; +using System.ServiceModel.Syndication; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Implementation of that can read and write RSS feeds + /// using the class. + /// + /// + /// By default, this converter reads and writes the media type 'application/rss+xml' media type. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class Rss20FeedHttpMessageConverter : AbstractFeedHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'application/rss+xml' media type. + /// + public Rss20FeedHttpMessageConverter() : + base(new MediaType("application", "rss+xml")) + { + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + if (content is SyndicationFeed) + { + SyndicationFeed rssFeed = content as SyndicationFeed; + rssFeed.SaveAsRss20(xmlWriter); + } + else if (content is SyndicationItem) + { + SyndicationItem rssItem = content as SyndicationItem; + rssItem.SaveAsRss20(xmlWriter); + } + } + } +} +#endif \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/FileInfoHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/FileInfoHttpMessageConverter.cs new file mode 100644 index 0000000..0bc99f4 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/FileInfoHttpMessageConverter.cs @@ -0,0 +1,109 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Collections.Generic; + +using Spring.Util; +using Spring.IO; + +namespace Spring.Http.Converters +{ + /// + /// Implementation of that can write files. + /// + /// + /// A mapping between file extension and mime types is used to determine the Content-Type of written files. + /// If no Content-Type is available, 'application/octet-stream' is used. + /// + /// Bruno Baia + [Obsolete("This class is obsolete; use ResourceHttpMessageConverter instead.")] + public class FileInfoHttpMessageConverter : ResourceHttpMessageConverter + { + /// + /// Creates a new instance of the . + /// + public FileInfoHttpMessageConverter() + :base() + { + } + + /// + /// Indicates whether the given class can be read by this converter. + /// + /// The class to test for readability + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// if readable; otherwise + public override bool CanRead(Type type, MediaType mediaType) + { + return false; + } + + /// + /// Indicates whether the given class can be written by this converter. + /// + /// The class to test for writability + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// if writable; otherwise + public override bool CanWrite(Type type, MediaType mediaType) + { + return type.Equals(typeof(FileInfo)); + } + + /// + /// Read an object of the given type form the given HTTP message, and returns it. + /// + /// + /// The type of object to return. This type must have previously been passed to the + /// method of this interface, which must have returned . + /// + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + public override T Read(IHttpInputMessage message) + { + throw new NotSupportedException(); + } + + /// + /// Write an given object to the given HTTP message. + /// + /// + /// The object to write to the HTTP message. The type of this object must have previously been + /// passed to the method of this interface, which must have returned . + /// + /// + /// The content type to use when writing. May be null to indicate that the default content type of the converter must be used. + /// If not null, this media type must have previously been passed to the method of this interface, + /// which must have returned . + /// + /// The HTTP message to write to. + /// In case of conversion errors + public override void Write(object content, MediaType contentType, IHttpOutputMessage message) + { + base.Write(new FileResource(((FileInfo)content).FullName), contentType, message); + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/FormHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/FormHttpMessageConverter.cs new file mode 100644 index 0000000..f395634 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/FormHttpMessageConverter.cs @@ -0,0 +1,497 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; +using System.Collections.Specialized; + + +using Spring.IO; + +namespace Spring.Http.Converters +{ + /// + /// Implementation of that can handle form data, + /// including multipart form data (i.e. file uploads). + /// + /// + /// + /// This converter supports the 'application/x-www-form-urlencoded' and 'multipart/form-data' media + /// types, and read the 'application/x-www-form-urlencoded' media type (but not 'multipart/form-data'). + /// + /// + /// This converter uses UTF-8 to write form data as recommended by the W3C. + /// + /// + /// In other words, this converter can read and write 'normal' HTML forms (as ), + /// and it can write multipart form (as ). + /// When writing multipart, this converter uses other to write the respective MIME parts. + /// By default, basic converters are registered (supporting , + /// and , for instance); these can be overridden by setting property. + /// + /// + /// For example, the following snippet shows how to submit an HTML form: + /// + /// RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default + /// NameValueCollection form = new NameValueCollection(); + /// form.Add("field 1", "value 1"); + /// form.Add("field 2", "value 2"); + /// form.Add("field 2", "value 3"); + /// template.PostForLocation("http://example.com/myForm", form); + /// + /// + /// + /// The following snippet shows how to do a file upload: + /// + /// RestTemplate template = new RestTemplate(); + /// IDictionary<string, object> parts = new Dictionary<string, object>(); + /// parts.Add("field 1", "value 1"); + /// parts.Add("file", new FileResource(@"C:\myDir\myFile.jpg")); + /// template.PostForLocation("http://example.com/myFileUpload", parts); + /// + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class FormHttpMessageConverter : IHttpMessageConverter + { + /// + /// Default encoding for forms. + /// + protected static readonly Encoding DEFAULT_CHARSET = new UTF8Encoding(false); // Remove byte Order Mask (BOM) + + private static char[] BOUNDARY_CHARS = + new char[]{'-', '_', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', + 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', + 'V', 'W', 'X', 'Y', 'Z'}; + + private Random random; + private IList _supportedMediaTypes; + private IList _partConverters; + + /// + /// Gets or sets the message body converters to use. + /// These converters are used to convert objects to MIME parts. + /// + public IList PartConverters + { + get { return _partConverters; } + set { _partConverters = value; } + } + + /// + /// Creates a new instance of the . + /// + public FormHttpMessageConverter() + { + this.random = new Random(); + + this._supportedMediaTypes = new List(2); + this._supportedMediaTypes.Add(MediaType.APPLICATION_FORM_URLENCODED); + this._supportedMediaTypes.Add(MediaType.MULTIPART_FORM_DATA); + + this._partConverters = new List(3); + this._partConverters.Add(new ByteArrayHttpMessageConverter()); + this._partConverters.Add(new StringHttpMessageConverter()); +#pragma warning disable 618 + this._partConverters.Add(new FileInfoHttpMessageConverter()); +#pragma warning restore 618 + this._partConverters.Add(new ResourceHttpMessageConverter()); + } + + #region IHttpMessageConverter Members + + /// + /// Indicates whether the given class can be read by this converter. + /// + /// The class to test for readability + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// if readable; otherwise + public bool CanRead(Type type, MediaType mediaType) + { + if (typeof(NameValueCollection).IsAssignableFrom(type)) + { + if (mediaType == null) + { + return true; + } + foreach (MediaType supportedMediaType in this._supportedMediaTypes) + { + if (supportedMediaType != MediaType.MULTIPART_FORM_DATA && + supportedMediaType.Includes(mediaType)) + { + return true; + } + } + } + return false; + } + + /// + /// Indicates whether the given class can be written by this converter. + /// + /// The class to test for writability + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// if writable; otherwise + public bool CanWrite(Type type, MediaType mediaType) + { + if (typeof(NameValueCollection).IsAssignableFrom(type) || + typeof(IDictionary).IsAssignableFrom(type)) + { + if (mediaType == null) + { + return true; + } + foreach (MediaType supportedMediaType in this._supportedMediaTypes) + { + if (supportedMediaType.IsCompatibleWith(mediaType)) + { + return true; + } + } + } + return false; + } + + /// + /// Gets the list of objects supported by this converter. + /// + public IList SupportedMediaTypes + { + get { return _supportedMediaTypes; } + } + + /// + /// Read an object of the given type form the given HTTP message, and returns it. + /// + /// + /// The type of object to return. This type must have previously been passed to the + /// method of this interface, which must have returned . + /// + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + public T Read(IHttpInputMessage message) where T : class + { + // Get the message encoding + MediaType contentType = message.Headers.ContentType; + Encoding encoding = (contentType != null && contentType.CharSet != null) ? contentType.CharSet : DEFAULT_CHARSET; + + // Read from the message stream + string body; + using (StreamReader reader = new StreamReader(message.Body, encoding)) + { + body = reader.ReadToEnd(); + } + + string[] pairs = body.Split('&'); + NameValueCollection result = new NameValueCollection(pairs.Length); + foreach (string pair in pairs) + { + int idx = pair.IndexOf('='); + if (idx == -1) + { + result.Add(HttpUtils.FormDecode(pair), null); + } + else + { + string name = HttpUtils.FormDecode(pair.Substring(0, idx)); + string value = HttpUtils.FormDecode(pair.Substring(idx + 1)); + result.Add(name, value); + } + } + return result as T; + } + + /// + /// Write an given object to the given HTTP message. + /// + /// + /// The object to write to the HTTP message. The type of this object must have previously been + /// passed to the method of this interface, which must have returned . + /// + /// + /// The content type to use when writing. May be null to indicate that the default content type of the converter must be used. + /// If not null, this media type must have previously been passed to the method of this interface, + /// which must have returned . + /// + /// The HTTP message to write to. + /// In case of conversion errors + public void Write(object content, MediaType contentType, IHttpOutputMessage message) + { + if (content is NameValueCollection) + { + this.WriteForm((NameValueCollection) content, message); + } + else if (content is IDictionary) + { + this.WriteMultipart((IDictionary) content, message); + } + } + + #endregion + + #region Write Form + + private void WriteForm(NameValueCollection form, IHttpOutputMessage message) + { + message.Headers.ContentType = MediaType.APPLICATION_FORM_URLENCODED; + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < form.AllKeys.Length; i++) + { + string name = form.AllKeys[i]; + string[] values = form.GetValues(name); + if (values == null) + { + builder.Append(HttpUtils.FormEncode(name)); + } + else + { + for (int j = 0; j < values.Length; j++) + { + string value = values[j]; + builder.Append(HttpUtils.FormEncode(name)); + builder.Append('='); + builder.Append(HttpUtils.FormEncode(value)); + if (j != (values.Length - 1)) + { + builder.Append('&'); + } + } + } + if (i != (form.AllKeys.Length - 1)) + { + builder.Append('&'); + } + } + + // Create a byte array of the data we want to send + byte[] byteData = DEFAULT_CHARSET.GetBytes(builder.ToString()); + + // Write to the message stream + message.Body = delegate(Stream stream) + { + stream.Write(byteData, 0, byteData.Length); + }; + } + + #endregion + + #region Write Multipart + + private void WriteMultipart(IDictionary parts, IHttpOutputMessage message) + { + string boundary = this.GenerateMultipartBoundary(); + + IDictionary parameters = new Dictionary(1); + parameters.Add("boundary", boundary); + MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters); + message.Headers.ContentType = contentType; + + message.Body = delegate(Stream stream) + { + using (StreamWriter streamWriter = new StreamWriter(stream)) + { + streamWriter.NewLine = "\r\n"; + this.WriteParts(boundary, parts, streamWriter); + this.WriteEnd(boundary, streamWriter); + } + }; + } + + /// + /// Generates a multipart boundary. + /// + /// + /// Default implementation returns a random boundary. Can be overridden in subclasses. + /// + /// A multipart boundary + protected virtual string GenerateMultipartBoundary() + { + char[] boundary = new char[random.Next(11) + 30]; + for (int i = 0; i < boundary.Length; i++) + { + boundary[i] = BOUNDARY_CHARS[random.Next(BOUNDARY_CHARS.Length)]; + } + return new String(boundary); + } + + /// + /// Return the filename of the given multipart part to be used for the 'Content-Disposition' header. + /// + /// + /// Default implementation returns if the part is a , + /// extracts the file name from the URI if the part is a + /// and in other cases. Can be overridden in subclasses. + /// + /// The part to determine the file name for + /// The filename, or if not known + protected virtual string GetMultipartFilename(object part) + { + if (part is FileInfo) + { + return ((FileInfo)part).Name; + } + else if (part is IResource) + { + Uri resourceUri = ((IResource)part).Uri; + if (resourceUri != null) + { + return Path.GetFileName(resourceUri.ToString()); + } + } + return null; + } + + private void WriteParts(string boundary, IDictionary parts, StreamWriter streamWriter) + { + foreach(KeyValuePair entry in parts) + { + this.WriteBoundary(boundary, streamWriter); + HttpEntity entity = this.GetEntity(entry.Value); + this.WritePart(entry.Key, entity, streamWriter); + streamWriter.WriteLine(); + } + } + + private void WriteBoundary(string boundary, StreamWriter streamWriter) + { + streamWriter.Write("--"); + streamWriter.Write(boundary); + streamWriter.WriteLine(); + } + + private void WritePart(String name, HttpEntity partEntity, StreamWriter streamWriter) + { + object partBody = partEntity.Body; + Type partType = partBody.GetType(); + HttpHeaders partHeaders = partEntity.Headers; + MediaType partContentType = partHeaders.ContentType; + foreach (IHttpMessageConverter messageConverter in this._partConverters) + { + if (messageConverter.CanWrite(partType, partContentType)) + { + IHttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(streamWriter); + multipartMessage.Headers["Content-Disposition"] = this.GetContentDispositionFormData(name, this.GetMultipartFilename(partBody)); + foreach (string header in partHeaders) + { + multipartMessage.Headers[header] = partHeaders[header]; + } + messageConverter.Write(partBody, partContentType, multipartMessage); + return; + } + } + throw new HttpMessageNotWritableException(String.Format( + "Could not write request: no suitable HttpMessageConverter found for part type [{0}]", partType)); + } + + private void WriteEnd(string boundary, StreamWriter streamWriter) + { + streamWriter.Write("--"); + streamWriter.Write(boundary); + streamWriter.Write("--"); + streamWriter.WriteLine(); + } + + private HttpEntity GetEntity(object part) + { + if (part is HttpEntity) + { + return (HttpEntity)part; + } + return new HttpEntity(part); + } + + /// + /// Return the value of the 'Content-Disposition' header for 'form-data'. + /// + /// The field name + /// The filename, may be + /// The value of the 'Content-Disposition' header + private string GetContentDispositionFormData(string name, string filename) + { + StringBuilder builder = new StringBuilder(); + builder.AppendFormat("form-data; name=\"{0}\"", name); + if (filename != null) + { + builder.AppendFormat("; filename=\"{0}\"", filename); + } + return builder.ToString(); + } + + /// + /// Implementation of used for writing multipart data. + /// + private sealed class MultipartHttpOutputMessage : IHttpOutputMessage + { + private HttpHeaders headers; + + private StreamWriter bodyWriter; + + public MultipartHttpOutputMessage(StreamWriter bodyWriter) + { + this.headers = new HttpHeaders(); + this.bodyWriter = bodyWriter; + } + + #region IHttpMessage Members + + public HttpHeaders Headers + { + get { return this.headers; } + } + + public Action Body + { + get { throw new InvalidOperationException(); } + set { this.WritePartBody(value); } + } + + #endregion + + private void WritePartBody(Action body) + { + foreach (string header in this.headers) + { + bodyWriter.Write(header); + bodyWriter.Write(": "); + bodyWriter.Write(this.headers[header]); + bodyWriter.WriteLine(); + } + bodyWriter.WriteLine(); + bodyWriter.Flush(); + Stream stream = bodyWriter.BaseStream; + stream.Flush(); + body(stream); + stream.Flush(); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/HttpMessageConversionException.cs b/netstandard/Spring.Rest/Http/Converters/HttpMessageConversionException.cs new file mode 100644 index 0000000..0964040 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/HttpMessageConversionException.cs @@ -0,0 +1,82 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Http.Converters +{ + /// + /// Exception thrown by implementations when the conversion fails. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpMessageConversionException : Exception + { + /// + /// Creates a new instance of the class. + /// + public HttpMessageConversionException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public HttpMessageConversionException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + /// + /// The root exception that is being wrapped. + /// + public HttpMessageConversionException(string message, Exception rootCause) + : base(message, rootCause) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpMessageConversionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/HttpMessageNotReadableException.cs b/netstandard/Spring.Rest/Http/Converters/HttpMessageNotReadableException.cs new file mode 100644 index 0000000..86c82a5 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/HttpMessageNotReadableException.cs @@ -0,0 +1,83 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Http.Converters +{ + /// + /// Exception thrown by implementations + /// when reading from HTTP message fails. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpMessageNotReadableException : HttpMessageConversionException + { + /// + /// Creates a new instance of the class. + /// + public HttpMessageNotReadableException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public HttpMessageNotReadableException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + /// + /// The root exception that is being wrapped. + /// + public HttpMessageNotReadableException(string message, Exception rootCause) + : base(message, rootCause) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpMessageNotReadableException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/HttpMessageNotWritableException.cs b/netstandard/Spring.Rest/Http/Converters/HttpMessageNotWritableException.cs new file mode 100644 index 0000000..07f24b0 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/HttpMessageNotWritableException.cs @@ -0,0 +1,83 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Http.Converters +{ + /// + /// Exception thrown by implementations + /// when writing to HTTP message fails. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpMessageNotWritableException : HttpMessageConversionException + { + /// + /// Creates a new instance of the class. + /// + public HttpMessageNotWritableException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public HttpMessageNotWritableException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + /// + /// The root exception that is being wrapped. + /// + public HttpMessageNotWritableException(string message, Exception rootCause) + : base(message, rootCause) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpMessageNotWritableException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/IHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/IHttpMessageConverter.cs new file mode 100644 index 0000000..ebb2784 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/IHttpMessageConverter.cs @@ -0,0 +1,87 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +namespace Spring.Http.Converters +{ + /// + /// Strategy interface that specifies a converter that can convert from and to HTTP messages. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + public interface IHttpMessageConverter + { + /// + /// Indicates whether the given class can be read by this converter. + /// + /// The class to test for readability + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// if readable; otherwise + bool CanRead(Type type, MediaType mediaType); + + /// + /// Indicates whether the given class can be written by this converter. + /// + /// The class to test for writability + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// if writable; otherwise + bool CanWrite(Type type, MediaType mediaType); + + /// + /// Gets the list of objects supported by this converter. + /// + IList SupportedMediaTypes { get; } + + /// + /// Read an object of the given type form the given HTTP message, and returns it. + /// + /// + /// The type of object to return. This type must have previously been passed to the + /// method of this interface, which must have returned . + /// + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + T Read(IHttpInputMessage message) where T : class; + + /// + /// Write an given object to the given HTTP message. + /// + /// + /// The object to write to the HTTP message. The type of this object must have previously been + /// passed to the method of this interface, which must have returned . + /// + /// + /// The content type to use when writing. May be null to indicate that the default content type of the converter must be used. + /// If not null, this media type must have previously been passed to the method of this interface, + /// which must have returned . + /// + /// The HTTP message to write to. + /// In case of conversion errors + void Write(object content, MediaType contentType, IHttpOutputMessage message); + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/Json/DataContractJsonHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Json/DataContractJsonHttpMessageConverter.cs new file mode 100644 index 0000000..7770a49 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Json/DataContractJsonHttpMessageConverter.cs @@ -0,0 +1,166 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Xml; +using System.Text; +using System.Collections.Generic; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; + +namespace Spring.Http.Converters.Json +{ + /// + /// Implementation of that can read and write JSON + /// using . + /// + /// + /// By default, this converter supports 'application/json' media type. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class DataContractJsonHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Default encoding for JSON. + /// + protected static readonly Encoding DEFAULT_CHARSET = new UTF8Encoding(false); // Remove byte Order Mask (BOM) + + private IEnumerable _knownTypes; + private bool _requiresAttribute; + + /// + /// Gets or sets types that may be present in the object graph. + /// + public IEnumerable KnownTypes + { + get { return _knownTypes; } + set { _knownTypes = value; } + } + + /// + /// Indicates whether this converter supports only classes decorated with + /// and . + /// Default value is false. + /// + public bool RequiresAttribute + { + get { return _requiresAttribute; } + } + + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + public DataContractJsonHttpMessageConverter() : + base(new MediaType("application", "json")) + { + } + + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + /// + /// If true, supports only classes decorated with + /// and . + /// + public DataContractJsonHttpMessageConverter(bool requiresAttribute) : + base(new MediaType("application", "json")) + { + this._requiresAttribute = requiresAttribute; + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + if (this._requiresAttribute) + { + return ( + Attribute.GetCustomAttributes(type, typeof(DataContractAttribute), true).Length > 0 || + Attribute.GetCustomAttributes(type, typeof(CollectionDataContractAttribute), true).Length > 0 + ); + } + return true; + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + DataContractJsonSerializer serializer = this.GetSerializer(typeof(T)); + return (T)serializer.ReadObject(message.Body) as T; + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + + // Get the message encoding + Encoding encoding = this.GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + DataContractJsonSerializer serializer = this.GetSerializer(content.GetType()); + + // Write to the message stream + message.Body = delegate(Stream stream) + { + // Using JsonReaderWriterFactory directly to set encoding + using (XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(stream, encoding, false)) + { + serializer.WriteObject(jsonWriter, content); + } + }; + } + + /// + /// Creates an instance of to + /// serialize or deserialize an object of the specified type. + /// + /// The type of instances to serialize or deserialize. + /// The serializer to use. + protected virtual DataContractJsonSerializer GetSerializer(Type type) + { + if (this._knownTypes == null) + { + return new DataContractJsonSerializer(type); + } + else + { + return new DataContractJsonSerializer(type, this._knownTypes); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Json/JsonHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Json/JsonHttpMessageConverter.cs new file mode 100644 index 0000000..221181d --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Json/JsonHttpMessageConverter.cs @@ -0,0 +1,62 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; + +namespace Spring.Http.Converters.Json +{ + /// + /// Implementation of that can read and write JSON + /// using . + /// + /// + /// By default, this converter supports 'application/json' media type. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + [Obsolete("This class has been renamed to DataContractJsonHttpMessageConverter for better consistency.")] + public class JsonHttpMessageConverter : DataContractJsonHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + public JsonHttpMessageConverter() : + base() + { + } + + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + /// + /// If true, supports only classes decorated with + /// and . + /// + public JsonHttpMessageConverter(bool requiresAttribute) : + base(requiresAttribute) + { + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Json/SpringJsonHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Json/SpringJsonHttpMessageConverter.cs new file mode 100644 index 0000000..55dbd6d --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Json/SpringJsonHttpMessageConverter.cs @@ -0,0 +1,172 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +using Spring.Json; + +namespace Spring.Http.Converters.Json +{ + /// + /// Implementation of that can read and write JSON + /// using the Spring.Json library. + /// + /// + /// + /// This implementation supports getting/setting values from JSON directly, + /// without the need to deserialize/serialize to a .NET class. + /// + /// + /// By default, this converter supports 'application/json' media type. + /// This can be overridden by setting the property. + /// + /// + /// Bruno Baia + public class SpringJsonHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Default encoding for JSON strings. + /// + protected static readonly Encoding DEFAULT_CHARSET = new UTF8Encoding(false); // Remove byte Order Mask (BOM) + + private JsonMapper mapper; + + /// + /// Gets the underlying used for converting custom types. + /// + public JsonMapper JsonMapper + { + get { return mapper; } + } + + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + public SpringJsonHttpMessageConverter() : + this(new JsonMapper()) + { + } + + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// + /// A to use for converting custom types. + public SpringJsonHttpMessageConverter(JsonMapper mapper) : + base(new MediaType("application", "json")) + { + this.mapper = mapper; + } + + /// + /// Indicates whether the given class can be read by this converter. + /// + /// The class to test for readability + /// + /// The media type to read, can be null if not specified. Typically the value of a 'Content-Type' header. + /// + /// if readable; otherwise + public override bool CanRead(Type type, MediaType mediaType) + { + return base.CanRead(mediaType) && + (typeof(JsonValue).IsAssignableFrom(type) || this.mapper.CanDeserialize(type)); + } + + /// + /// Indicates whether the given class can be written by this converter. + /// + /// The class to test for writability + /// + /// The media type to write, can be null if not specified. Typically the value of an 'Accept' header. + /// + /// if writable; otherwise + public override bool CanWrite(Type type, MediaType mediaType) + { + return base.CanWrite(mediaType) && + (typeof(JsonValue).IsAssignableFrom(type) || this.mapper.CanSerialize(type)); + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + // Should not be called + throw new InvalidOperationException(); + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + // Get the message encoding + Encoding encoding = GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + // Read from the message stream + using (StreamReader reader = new StreamReader(message.Body, encoding)) + { + // Parse JSON + JsonValue jsonValue = JsonValue.Parse(reader.ReadToEnd()); + + if (typeof(T) == typeof(JsonValue)) + { + return jsonValue as T; + } + // Map from JsonValue to object + return this.mapper.Deserialize(jsonValue); + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Get the message encoding + Encoding encoding = GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + // Map from object to JsonValue + JsonValue jsonValue = (content is JsonValue) ? (JsonValue)content : this.mapper.Serialize(content); + + // Create a byte array of the data we want to send + byte[] byteData = encoding.GetBytes(jsonValue.ToString()); + + // Write to the message stream + message.Body = delegate(Stream stream) + { + stream.Write(byteData, 0, byteData.Length); + }; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/ResourceHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/ResourceHttpMessageConverter.cs new file mode 100644 index 0000000..969b6d8 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/ResourceHttpMessageConverter.cs @@ -0,0 +1,168 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Collections.Generic; + +using Spring.IO; +using Spring.Util; + +namespace Spring.Http.Converters +{ + /// + /// Implementation of that can read and write s. + /// + /// + /// + /// By default, this converter supports all media types. + /// + /// + /// A mapping between file extension and mime types is used to determine the Content-Type of written files. + /// If no Content-Type is available, 'application/octet-stream' is used. + /// + /// + /// This can be overridden by setting the property. + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class ResourceHttpMessageConverter : AbstractHttpMessageConverter + { + // Pre-defined mapping between file extension and mime types + private static IDictionary defaultMimeMapping; + + private IDictionary _mimeMapping; + + /// + /// Gets or sets the mapping between file extension and mime types. + /// + public IDictionary MimeMapping + { + get + { + if (this._mimeMapping == null) + { + this._mimeMapping = new Dictionary(defaultMimeMapping); + } + return _mimeMapping; + } + set { _mimeMapping = value; } + } + + static ResourceHttpMessageConverter() + { + defaultMimeMapping = new Dictionary(9, StringComparer.OrdinalIgnoreCase); + defaultMimeMapping.Add(".bmp", "image/bmp"); + defaultMimeMapping.Add(".gif", "image/gif"); + defaultMimeMapping.Add(".jpg", "image/jpeg"); + defaultMimeMapping.Add(".jpeg", "image/jpeg"); + defaultMimeMapping.Add(".pdf", "application/pdf"); + defaultMimeMapping.Add(".png", "image/png"); + defaultMimeMapping.Add(".tif", "image/tiff"); + defaultMimeMapping.Add(".txt", "text/plain"); + defaultMimeMapping.Add(".zip", "application/x-zip-compressed"); + } + + /// + /// Creates a new instance of the . + /// + public ResourceHttpMessageConverter() + : base(MediaType.ALL) + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return typeof(IResource).IsAssignableFrom(type); + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + // Read from the message stream + using (MemoryStream tempStream = new MemoryStream()) + { + IoUtils.CopyStream(message.Body, tempStream); + return new ByteArrayResource(tempStream.ToArray()) as T; + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Write to the message stream + message.Body = delegate(Stream stream) + { + using (Stream contentStream = ((IResource)content).GetStream()) + { + IoUtils.CopyStream(contentStream, stream); + } + }; + } + + + /// + /// Returns the default content type for the given object. + /// Called when is invoked without a specified content type parameter. + /// + /// + /// This implementation uses the mapping between file extension and mime types is used + /// to determine the Content-Type of written files. + /// If no Content-Type is available, 'application/octet-stream' is used. + /// + /// The object to return the content type for. + /// The content type, or null if not known. + protected override MediaType GetDefaultContentType(object content) + { + Uri resourceUri = ((IResource)content).Uri; + + if (resourceUri != null) + { + string fileExtension = Path.GetExtension(resourceUri.ToString()); + IDictionary mimeMapping = + (this._mimeMapping == null) ? defaultMimeMapping : this._mimeMapping; + + string mimeType; + if (mimeMapping.TryGetValue(fileExtension, out mimeType)) + { + return MediaType.Parse(mimeType); + } + } + return MediaType.APPLICATION_OCTET_STREAM; + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/StringHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/StringHttpMessageConverter.cs new file mode 100644 index 0000000..9f796e7 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/StringHttpMessageConverter.cs @@ -0,0 +1,107 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Text; + +namespace Spring.Http.Converters +{ + /// + /// Implementation of that can read and write strings. + /// + /// + /// By default, this converter supports all media types '*/*', and writes with a 'Content-Type' + /// of 'text/plain'. + /// This can be overridden by setting the property. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class StringHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Default encoding for strings. + /// + + protected static readonly Encoding DEFAULT_CHARSET = Encoding.GetEncoding("ISO-8859-1"); + + + + /// + /// Creates a new instance of the + /// with 'text/plain; charset=ISO-8859-1', and '*/*' media types. + /// + public StringHttpMessageConverter() : + base(new MediaType("text", "plain", "ISO-8859-1"), MediaType.ALL) + { + } + + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return type.Equals(typeof(string)); + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + // Get the message encoding + Encoding encoding = GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + // Read from the message stream + using (StreamReader reader = new StreamReader(message.Body, encoding)) + { + return reader.ReadToEnd() as T; + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Get the message encoding + Encoding encoding = GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + // Create a byte array of the data we want to send + byte[] byteData = encoding.GetBytes(content as string); + + // Write to the message stream + message.Body = delegate(Stream stream) + { + stream.Write(byteData, 0, byteData.Length); + }; + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs new file mode 100644 index 0000000..f5c104e --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs @@ -0,0 +1,148 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; +using System.Xml; +using System.Text; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Base class for that convert from/to XML. + /// + /// + /// By default, subclasses of this converter support 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public abstract class AbstractXmlHttpMessageConverter : AbstractHttpMessageConverter + { + /// + /// Default encoding for XML. + /// + protected static readonly Encoding DEFAULT_CHARSET = new UTF8Encoding(false); // Remove byte Order Mask (BOM) + + /// + /// Creates a new instance of the + /// with multiple supported media type. + /// + /// The supported media types. + protected AbstractXmlHttpMessageConverter(params MediaType[] supportedMediaTypes) : + base(supportedMediaTypes) + { + } + + /// + /// Creates a new instance of the that sets + /// the to 'text/xml' and 'application/xml', and 'application/*-xml'. + /// + protected AbstractXmlHttpMessageConverter() : + base(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml")) + { + } + + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP message to read from. + /// The converted object. + /// In case of conversion errors + protected override T ReadInternal(IHttpInputMessage message) + { + XmlReaderSettings settings = this.GetXmlReaderSettings(); + + // Read from the message stream + using (XmlReader xmlReader = XmlReader.Create(message.Body, settings)) + { + return ReadXml(xmlReader); + } + } + + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP message. + /// The HTTP message to write to. + /// In case of conversion errors + protected override void WriteInternal(object content, IHttpOutputMessage message) + { + // Get the message encoding + Encoding encoding = this.GetContentTypeCharset(message.Headers.ContentType, DEFAULT_CHARSET); + + XmlWriterSettings settings = this.GetXmlWriterSettings(); + settings.Encoding = encoding; + + // Write to the message stream + message.Body = delegate(Stream stream) + { + using (XmlWriter xmlWriter = XmlWriter.Create(stream, settings)) + { + WriteXml(xmlWriter, content); + } + }; + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected abstract T ReadXml(XmlReader xmlReader) where T : class; + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected abstract void WriteXml(XmlWriter xmlWriter, object content); + + /// + /// Returns the XmlReader settings + /// used by this converter to read from the HTTP message. + /// + /// The XmlReader settings. + protected virtual XmlReaderSettings GetXmlReaderSettings() + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.ConformanceLevel = ConformanceLevel.Auto; + settings.CloseInput = true; + settings.IgnoreProcessingInstructions = true; + settings.IgnoreWhitespace = true; + return settings; + } + + /// + /// Returns the XmlWriter settings + /// used by this converter to write to the HTTP message. + /// + /// The XmlWriter settings. + protected virtual XmlWriterSettings GetXmlWriterSettings() + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.CloseOutput = false; + settings.NewLineHandling = NewLineHandling.Entitize; + settings.OmitXmlDeclaration = true; + settings.CheckCharacters = false; + return settings; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Xml/DataContractHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Xml/DataContractHttpMessageConverter.cs new file mode 100644 index 0000000..d0563e2 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Xml/DataContractHttpMessageConverter.cs @@ -0,0 +1,145 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Xml; +using System.Runtime.Serialization; +using System.Collections.Generic; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Implementation of that can read and write XML + /// using . + /// + /// + /// + /// By default, this converter supports 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// This can be overridden by setting the property. + /// + /// + /// Bruno Baia + public class DataContractHttpMessageConverter : AbstractXmlHttpMessageConverter + { + private IEnumerable _knownTypes; + private bool _requiresAttribute; + + /// + /// Gets or sets types that may be present in the object graph. + /// + public IEnumerable KnownTypes + { + get { return _knownTypes; } + set { _knownTypes = value; } + } + + /// + /// Indicates whether this converter supports only classes decorated with + /// and . + /// Default value is false. + /// + public bool RequiresAttribute + { + get { return _requiresAttribute; } + } + + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// + public DataContractHttpMessageConverter() : + base() + { + } + + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// + /// + /// If true, supports only classes decorated with + /// and . + /// + public DataContractHttpMessageConverter(bool requiresAttribute) : + base() + { + this._requiresAttribute = requiresAttribute; + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + if (this._requiresAttribute) + { + return ( + Attribute.GetCustomAttributes(type, typeof(DataContractAttribute), true).Length > 0 || + Attribute.GetCustomAttributes(type, typeof(CollectionDataContractAttribute), true).Length > 0 + ); + } + return true; + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader) + { + DataContractSerializer serializer = this.GetSerializer(typeof(T)); + return serializer.ReadObject(xmlReader) as T; + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + DataContractSerializer serializer = this.GetSerializer(content.GetType()); + serializer.WriteObject(xmlWriter, content); + } + + /// + /// Creates an instance of to + /// serialize or deserialize an object of the specified type. + /// + /// The type of instances to serialize or deserialize. + /// The serializer to use. + protected virtual DataContractSerializer GetSerializer(Type type) + { + if (this._knownTypes == null) + { + return new DataContractSerializer(type); + } + else + { + return new DataContractSerializer(type, this._knownTypes); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Xml/XElementHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Xml/XElementHttpMessageConverter.cs new file mode 100644 index 0000000..8bc1b1b --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Xml/XElementHttpMessageConverter.cs @@ -0,0 +1,80 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Xml; +using System.Xml.Linq; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Implementation of that can read and write XML + /// from a (Linq to XML). + /// + /// + /// By default, this converter supports 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class XElementHttpMessageConverter : AbstractXmlHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// + public XElementHttpMessageConverter() : + base() + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return type.Equals(typeof(XElement)); + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader) + { + return XElement.Load(xmlReader) as T; + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + XElement xElement = content as XElement; + xElement.WriteTo(xmlWriter); + } + } +} diff --git a/netstandard/Spring.Rest/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs new file mode 100644 index 0000000..80d1545 --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs @@ -0,0 +1,81 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Xml; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Implementation of that can read and write XML + /// from a . + /// + /// + /// By default, this converter supports 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class XmlDocumentHttpMessageConverter : AbstractXmlHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// + public XmlDocumentHttpMessageConverter() : + base() + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return type.Equals(typeof(XmlDocument)); + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader) + { + XmlDocument document = new XmlDocument(); + document.Load(xmlReader); + return document as T; + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + XmlDocument document = content as XmlDocument; + document.WriteTo(xmlWriter); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs b/netstandard/Spring.Rest/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs new file mode 100644 index 0000000..ea7407b --- /dev/null +++ b/netstandard/Spring.Rest/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs @@ -0,0 +1,113 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Xml; +using System.Xml.Serialization; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Implementation of that can read and write XML + /// using . + /// + /// + /// By default, this converter supports 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// This can be overridden by setting the property. + /// + /// Bruno Baia + public class XmlSerializableHttpMessageConverter : AbstractXmlHttpMessageConverter + { + private Type[] _knownTypes; + + /// + /// Gets or sets types that may be present in the object graph. + /// + public Type[] KnownTypes + { + get { return _knownTypes; } + set { _knownTypes = value; } + } + + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// + public XmlSerializableHttpMessageConverter() : + base() + { + } + + /// + /// Indicates whether the given class is supported by this converter. + /// + /// The type to test for support. + /// if supported; otherwise + protected override bool Supports(Type type) + { + return true; + //return ( + // AttributeUtils.FindAttribute(type, typeof(XmlRootAttribute)) != null || + // AttributeUtils.FindAttribute(type, typeof(XmlTypeAttribute)) != null); + } + + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader) + { + XmlSerializer serializer = this.GetSerializer(typeof(T)); + return serializer.Deserialize(xmlReader) as T; + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP message. + protected override void WriteXml(XmlWriter xmlWriter, object content) + { + XmlSerializer serializer = this.GetSerializer(content.GetType()); + serializer.Serialize(xmlWriter, content); + } + + /// + /// Creates an instance of to + /// serialize or deserialize an object of the specified type. + /// + /// The type of instances to serialize or deserialize. + /// The serializer to use. + protected virtual XmlSerializer GetSerializer(Type type) + { + if (this._knownTypes == null) + { + return new XmlSerializer(type); + } + else + { + return new XmlSerializer(type, this._knownTypes); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/HttpEntity.cs b/netstandard/Spring.Rest/Http/HttpEntity.cs new file mode 100644 index 0000000..12f2f17 --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpEntity.cs @@ -0,0 +1,100 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Util; + +namespace Spring.Http +{ + /// + /// Represents an HTTP entity message, as defined in the HTTP specification. + /// HTTP 1.1, section 7 + /// + /// Arjen Poutsma + /// Bruno Baia + public class HttpEntity + { + private HttpHeaders headers; + private object body; + + /// + /// Gets the entity headers. + /// + public HttpHeaders Headers + { + get { return this.headers; } + } + + /// + /// Gets the entity body. May be null. + /// + public object Body + { + get { return this.body; } + } + + /// + /// Indicates whether this entity has a body. + /// + /// + public bool HasBody + { + get { return (this.body != null); } + } + + /// + /// Creates a new, empty instance of with no body or headers. + /// + public HttpEntity() + : this(null, new HttpHeaders()) + { + } + + /// + /// Creates a new instance of with the given body. + /// + /// The entity body. + public HttpEntity(object body) + : this(body, new HttpHeaders()) + { + } + + /// + /// Creates a new instance of with the given headers. + /// + /// The entity headers. + public HttpEntity(HttpHeaders headers) + : this(null, headers) + { + } + + /// + /// Creates a new instance of with the given body and headers. + /// + /// The entity body. + /// The entity headers. + public HttpEntity(object body, HttpHeaders headers) + { + ArgumentUtils.AssertNotNull(headers, "headers"); + + this.body = body; + this.headers = headers; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/HttpHeaders.cs b/netstandard/Spring.Rest/Http/HttpHeaders.cs new file mode 100644 index 0000000..cc9bca4 --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpHeaders.cs @@ -0,0 +1,465 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Globalization; +using System.Runtime.Serialization; +using System.Collections.Specialized; + + +namespace Spring.Http +{ + /// + /// Represents HTTP request and response headers, mapping string header names to list of string values. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpHeaders : NameValueCollection + { + private const string ACCEPT = "Accept"; + private const string ACCEPT_CHARSET = "Accept-Charset"; + private const string ALLOW = "Allow"; + private const string CACHE_CONTROL = "Cache-Control"; + private const string CONTENT_LENGTH = "Content-Length"; + private const string CONTENT_TYPE = "Content-Type"; + private const string DATE = "Date"; + private const string ETAG = "ETag"; + private const string EXPIRES = "Expires"; + private const string IF_MODIFIED_SINCE = "If-Modified-Since"; + private const string IF_NONE_MATCH = "If-None-Match"; + private const string LAST_MODIFIED = "Last-Modified"; + private const string LOCATION = "Location"; + private const string PRAGMA = "Pragma"; + + private static readonly DateTimeFormatInfo DateTimeFormatInfo = new DateTimeFormatInfo(); + + #region Constructor(s) + + /// + /// Creates a new, empty instance of the object. + /// + public HttpHeaders() : + base(8, StringComparer.OrdinalIgnoreCase) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The that holds the serialized object data + /// about the exception being thrown. + /// + /// + /// The that contains contextual information + /// about the source or destination. + /// + protected HttpHeaders(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + #endregion + + #region Properties + + /// + /// Gets or sets the array of acceptable media types, + /// as specified by the 'Accept' header. + /// + /// + /// Returns an empty array when the acceptable media types are unspecified. + /// + public MediaType[] Accept + { + get + { + string[] values = this.GetMultiValues(ACCEPT); + if (values == null || values.Length == 0) + { + return new MediaType[0]; + } + else + { + MediaType[] result = new MediaType[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = MediaType.Parse(values[i]); + } + return result; + } + } + set + { + foreach (MediaType mediaType in value) + { + this.Add(ACCEPT, mediaType.ToString()); + } + } + } + + /// + /// Gets or sets the array of allowed HTTP methods, + /// as specified by the 'Allow' header. + /// + /// + /// Returns an empty array when the allowed methods are unspecified. + /// + public HttpMethod[] Allow + { + get + { + string[] values = this.GetMultiValues(ALLOW); + if (values == null || values.Length == 0) + { + return new HttpMethod[0]; + } + else + { + HttpMethod[] result = new HttpMethod[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = new HttpMethod(values[i].Trim()); + } + return result; + } + } + set + { + foreach (HttpMethod method in value) + { + this.Add(ALLOW, method.ToString()); + } + } + } + + /// + /// Gets or sets the value of the 'Cache-Control' header. + /// + public string CacheControl + { + get + { + return this.Get(CACHE_CONTROL); + } + set + { + this.Set(CACHE_CONTROL, value); + } + } + + /// + /// Gets or sets the length of the body in bytes, + /// as specified by the 'Content-Length' header. + /// + /// + /// Returns -1 when the content-length is unknown. + /// + public long ContentLength + { + get + { + string value = this.GetSingleValue(CONTENT_LENGTH); + return (value != null ? long.Parse(value) : -1); + } + set + { + this.Set(CONTENT_LENGTH, value.ToString()); + } + } + + /// + /// Gets or sets the media type of the body, + /// as specified by the 'Content-Type' header. + /// + /// + /// Returns when the content type is unknown. + /// + public MediaType ContentType + { + get + { + string value = this.GetSingleValue(CONTENT_TYPE); + return (value != null ? MediaType.Parse(value) : null); + } + set + { + if (value.IsWildcardType) + { + throw new ArgumentException("'Content-Type' header cannot contain wildcard type '*'", "Content-Type"); + } + if (value.IsWildcardSubtype) + { + throw new ArgumentException("'Content-Type' header cannot contain wildcard subtype '*'", "Content-Type"); + } + this.Set(CONTENT_TYPE, value.ToString()); + } + } + + /// + /// Gets or sets the date and time at which the message was created, + /// as specified by the 'Date' header. + /// + /// + /// Returns when the date is unknown. + /// + public DateTime? Date + { + get + { + return this.GetSingleDate(DATE); + } + set + { + this.SetDate(DATE, value); + } + } + + /// + /// Gets or sets the entity tag of the body, as specified by the 'ETag' header. + /// + public string ETag + { + get + { + return this.Unquote(this.Get(ETAG)); + } + set + { + this.Set(ETAG, this.Quote(value)); + } + } + + /// + /// Gets or sets the date and time at which the message is no longer valid, + /// as specified by the 'Expires' header. + /// + public string Expires + { + get + { + return this.Get(EXPIRES); + } + set + { + this.Set(EXPIRES, value); + } + } + + /// + /// Gets or sets the date and time as specified by the 'If-Modified-Since' header. + /// + /// + /// Returns when the date is unknown. + /// + public DateTime? IfModifiedSince + { + get + { + return this.GetSingleDate(IF_MODIFIED_SINCE); + } + set + { + this.SetDate(IF_MODIFIED_SINCE, value); + } + } + + /// + /// Gets or sets the value of the 'If-None-Match' header. + /// + public string[] IfNoneMatch + { + get + { + string[] values = this.GetMultiValues(IF_NONE_MATCH); + if (values == null || values.Length == 0) + { + return new string[0]; + } + else + { + string[] result = new string[values.Length]; + for (int i = 0; i < values.Length; i++) + { + result[i] = Unquote(values[i].Trim()); + } + return result; + } + } + set + { + foreach (string str in value) + { + this.Add(IF_NONE_MATCH, this.Quote(str)); + } + } + } + + /// + /// Gets or sets the time the resource was last changed, + /// as specified by the 'Last-Modified' header. + /// + /// + /// Returns when the date is unknown. + /// + public DateTime? LastModified + { + get + { + return this.GetSingleDate(LAST_MODIFIED); + } + set + { + this.SetDate(LAST_MODIFIED, value); + } + } + + /// + /// Gets or sets the (new) location of a resource, + /// as specified by the 'Location' header. + /// + public Uri Location + { + get + { + string value = this.GetSingleValue(LOCATION); + return (value != null ? new Uri(value, UriKind.RelativeOrAbsolute) : null); + } + set + { + this.Set(LOCATION, value.ToString()); + } + } + + /// + /// Gets or sets the value of the 'Pragma' header. + /// + public string Pragma + { + get + { + return this.Get(PRAGMA); + } + set + { + this.Set(PRAGMA, value); + } + } + + #endregion + + #region Private methods + + private string Quote(string s) + { + if (s == null) + { + return null; + } + if (!s.StartsWith("\"") && !s.EndsWith("\"")) + { + s = "\"" + s + "\""; + } + return s; + } + + private string Unquote(string s) + { + if (s == null) + { + return null; + } + if (s.StartsWith("\"") && s.EndsWith("\"")) + { + s = s.Substring(1, s.Length - 2); + } + return s; + } + + private DateTime? GetSingleDate(string headerName) + { + string headerValue = GetSingleValue(headerName); + if (headerValue != null) + { + return DateTime.Parse(headerValue, DateTimeFormatInfo).ToUniversalTime(); + } + else + { + return null; + } + } + + private void SetDate(string headerName, DateTime? date) + { + if (date.HasValue) + { + this.Set(headerName, date.Value.ToUniversalTime().ToString("R", DateTimeFormatInfo)); + } + else + { + this.Remove(headerName); + } + } + + #endregion + + /// + /// Return the header value for the given header name, if any. + /// + /// The header name + /// The first header value; or + /// + /// If multiple values are stored for the given header name. + /// + public string GetSingleValue(string headerName) + { + string[] headerValues = this.GetValues(headerName); + if (headerValues == null || headerValues.Length == 0) + { + return null; + } + if (headerValues.Length == 1) + { + return headerValues[0]; + } + throw new NotSupportedException(String.Format( + "Multiple values not supported for header '{0}'", headerName)); + } + + /// + /// Return an array of header values for the given header name, if any. + /// + /// The header name + /// The array of header values; or + public string[] GetMultiValues(string headerName) + { + string headerValue = this.Get(headerName); + if (headerValue == null) + { + return null; + } + else + { + return headerValue.Split(','); + } + } + } +} diff --git a/netstandard/Spring.Rest/Http/HttpMethod.cs b/netstandard/Spring.Rest/Http/HttpMethod.cs new file mode 100644 index 0000000..613504a --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpMethod.cs @@ -0,0 +1,180 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Globalization; + +using Spring.Util; + +namespace Spring.Http +{ + /// + /// Represents an HTTP request method as defined in the HTTP specification. + /// HTTP 1.1, section 5.1.1 + /// + /// + /// HTTP method equality and hashing are case insensitive. + /// + /// Bruno Baia + [Serializable] + public class HttpMethod : IEquatable + { + /// + /// The OPTIONS method. + /// + public static readonly HttpMethod OPTIONS = new HttpMethod("OPTIONS"); + + /// + /// The GET method. + /// + public static readonly HttpMethod GET = new HttpMethod("GET"); + + /// + /// The HEAD method. + /// + public static readonly HttpMethod HEAD = new HttpMethod("HEAD"); + + /// + /// The POST method. + /// + public static readonly HttpMethod POST = new HttpMethod("POST"); + + /// + /// The PUT method. + /// + public static readonly HttpMethod PUT = new HttpMethod("PUT"); + + /// + /// The DELETE method. + /// + public static readonly HttpMethod DELETE = new HttpMethod("DELETE"); + + /// + /// The TRACE method. + /// + public static readonly HttpMethod TRACE = new HttpMethod("TRACE"); + + /// + /// The CONNECT method. + /// + public static readonly HttpMethod CONNECT = new HttpMethod("CONNECT"); + + private string method; + + /// + /// Creates a new instance of with the given HTTP method value. + /// + /// The HTTP method as a string value. + public HttpMethod(string method) + { + ArgumentUtils.AssertNotNull(method, "method"); + + // TODO: check method (http://tools.ietf.org/html/rfc2616#section-2.2) + this.method = method; + } + + /// + /// Indicates whether the current HTTP method is equal to another . + /// + /// An to compare with this HTTP method. + /// + /// if the specified is equal to the current HTTP method; otherwise, . + /// + public bool Equals(HttpMethod other) + { + if (Object.ReferenceEquals(other, null)) + { + return false; + } + if (Object.ReferenceEquals(this.method, other.method)) + { + return true; + } + return String.Equals(this.method, other.method, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Indicates whether the current HTTP method is equal to another . + /// + /// An to compare with this HTTP method. + /// + /// if the specified is equal to the current HTTP method; otherwise, . + /// + public override bool Equals(object obj) + { + return this.Equals(obj as HttpMethod); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// This method is suitable for use in hashing algorithms and data structures like a hash table. + /// + /// + /// A hash code for the current HTTP method. + /// + public override int GetHashCode() + { + return this.method.ToUpper(CultureInfo.InvariantCulture).GetHashCode(); + } + + /// + /// Returns a that represents the current HTTP method. + /// + /// + /// A that represents the current HTTP method. + /// + public override string ToString() + { + return this.method; + } + + /// + /// Determines whether two specified HTTP methods have the same value. + /// + /// The first HTTP method to compare, or . + /// The second HTTP method to compare, or . + /// + /// if values are the same; otherwise, . + /// + public static bool operator ==(HttpMethod method1, HttpMethod method2) + { + if (Object.ReferenceEquals(method1, null)) + { + return Object.ReferenceEquals(method2, null); + } + return method1.Equals(method2); + } + + /// + /// Determines whether two specified HTTP methods have different values. + /// + /// The first HTTP method to compare, or . + /// The second HTTP method to compare, or . + /// + /// if values are differents; otherwise, . + /// + public static bool operator !=(HttpMethod method1, HttpMethod method2) + { + return !(method1 == method2); + } + } +} diff --git a/netstandard/Spring.Rest/Http/HttpResponseMessage.cs b/netstandard/Spring.Rest/Http/HttpResponseMessage.cs new file mode 100644 index 0000000..40245bf --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpResponseMessage.cs @@ -0,0 +1,89 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; + +using Spring.Util; + +namespace Spring.Http +{ + /// + /// Represents an HTTP response message, with no body, as defined in the HTTP specification. + /// HTTP 1.1, section 6 + /// + /// Bruno Baia + [Serializable] + public class HttpResponseMessage + { + private HttpHeaders headers; + private HttpStatusCode statusCode; + private string statusDescription; + + /// + /// Gets the response headers. + /// + public HttpHeaders Headers + { + get { return this.headers; } + } + + /// + /// Gets the HTTP status code of the response. + /// + public HttpStatusCode StatusCode + { + get { return statusCode; } + } + + /// + /// Gets the HTTP status description of the response. + /// + public string StatusDescription + { + get { return statusDescription; } + } + + /// + /// Creates a new instance of with the given status code and status description. + /// + /// The HTTP status code. + /// The HTTP status description. + public HttpResponseMessage(HttpStatusCode statusCode, string statusDescription) : + this(new HttpHeaders(), statusCode, statusDescription) + { + } + + /// + /// Creates a new instance of with the given headers, status code and status description. + /// + /// The response headers. + /// The HTTP status code. + /// The HTTP status description. + public HttpResponseMessage(HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) + { + ArgumentUtils.AssertNotNull(headers, "headers"); + + this.headers = headers; + this.statusCode = statusCode; + this.statusDescription = statusDescription; + } + } +} diff --git a/netstandard/Spring.Rest/Http/HttpResponseMessage`1.cs b/netstandard/Spring.Rest/Http/HttpResponseMessage`1.cs new file mode 100644 index 0000000..559ccfe --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpResponseMessage`1.cs @@ -0,0 +1,69 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; + +namespace Spring.Http +{ + /// + /// Represents an HTTP response message, as defined in the HTTP specification. + /// HTTP 1.1, section 6 + /// + /// The type of the response body. + /// Bruno Baia + [Serializable] + public class HttpResponseMessage : HttpResponseMessage where T : class + { + private T body; + + /// + /// Gets the response body. May be null. + /// + public T Body + { + get { return this.body; } + } + + /// + /// Creates a new instance of with the given body, status code and status description. + /// + /// The response body. + /// The HTTP status code. + /// The HTTP status description. + public HttpResponseMessage(T body, HttpStatusCode statusCode, string statusDescription) : + this(body, new HttpHeaders(), statusCode, statusDescription) + { + } + + /// + /// Creates a new instance of with the given body, headers, status code and status description. + /// + /// The response body. + /// The response headers. + /// The HTTP status code. + /// The HTTP status description. + public HttpResponseMessage(T body, HttpHeaders headers, HttpStatusCode statusCode, string statusDescription) : + base(headers, statusCode, statusDescription) + { + this.body = body; + } + } +} diff --git a/netstandard/Spring.Rest/Http/HttpUtils.cs b/netstandard/Spring.Rest/Http/HttpUtils.cs new file mode 100644 index 0000000..57e75ef --- /dev/null +++ b/netstandard/Spring.Rest/Http/HttpUtils.cs @@ -0,0 +1,92 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Http +{ + /// + /// Miscellaneous HTTP utility methods. + /// + /// Bruno Baia + public static class HttpUtils + { + /// + /// Decodes 'application/x-www-form-urlencoded' data. + /// + /// The string to decode. + /// The decoded string. + public static string FormDecode(string s) + { + if (s == null) + { + return null; + } + return Uri.UnescapeDataString(s.Replace('+', ' ')); + } + + /// + /// Encodes 'application/x-www-form-urlencoded' data. + /// + /// The string to encode. + /// The encoded string. + public static string FormEncode(string s) + { + if (s == null) + { + return null; + } + return UrlEncode(s).Replace("%20", "+"); + } + + /// + /// Decodes URI data according to RFC 3986. + /// + /// The string to decode. + /// The decoded string. + public static string UrlDecode(string s) + { + if (s == null) + { + return null; + } + return Uri.UnescapeDataString(s); + } + + /// + /// Encodes URI data according to RFC 3986. + /// + /// The string to encode. + /// The encoded string. + public static string UrlEncode(string s) + { + if (s == null) + { + return null; + } + return Uri.EscapeDataString(s) + .Replace("!", "%21") + .Replace("'", "%27") + .Replace("(", "%28") + .Replace(")", "%29") + .Replace("*", "%2A"); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Http/IHttpInputMessage.cs b/netstandard/Spring.Rest/Http/IHttpInputMessage.cs new file mode 100644 index 0000000..6365df0 --- /dev/null +++ b/netstandard/Spring.Rest/Http/IHttpInputMessage.cs @@ -0,0 +1,46 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; + +namespace Spring.Http +{ + /// + /// Represents an HTTP message, consisting of headers + /// and a readable body. + /// + /// + /// Typically implemented by an HTTP request on the server-side, or a response on the client-side. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IHttpInputMessage + { + /// + /// Gets the message headers. + /// + HttpHeaders Headers { get; } + + /// + /// Gets the body of the message as a stream. + /// + Stream Body { get; } + } +} diff --git a/netstandard/Spring.Rest/Http/IHttpOutputMessage.cs b/netstandard/Spring.Rest/Http/IHttpOutputMessage.cs new file mode 100644 index 0000000..a18e6b1 --- /dev/null +++ b/netstandard/Spring.Rest/Http/IHttpOutputMessage.cs @@ -0,0 +1,47 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.Http +{ + /// + /// Represents an HTTP message, consisting of headers + /// and a writable body. + /// + /// + /// Typically implemented by an HTTP request on the client-side, or a response on the server-side. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IHttpOutputMessage + { + /// + /// Gets the message headers. + /// + HttpHeaders Headers { get; } + + /// + /// Sets the delegate that writes the body message as a stream. + /// + Action Body { set; } + } +} diff --git a/netstandard/Spring.Rest/Http/MediaType.cs b/netstandard/Spring.Rest/Http/MediaType.cs new file mode 100644 index 0000000..93764dd --- /dev/null +++ b/netstandard/Spring.Rest/Http/MediaType.cs @@ -0,0 +1,838 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Globalization; +using System.Collections; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Http +{ + /// + /// Represents an Internet Media Type, as defined in the HTTP specification. + /// HTTP 1.1, section 3.7 + /// + /// + /// Consists of a and a . + /// Also has functionality to parse media types from a string using . + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + [Serializable] + public class MediaType : IEquatable, IComparable + { + /// + /// The media type that includes all media ranges (i.e. '*/*'). + /// + public static readonly MediaType ALL = new MediaType("*", "*"); + + /// + /// The media type for 'application/atom+xml'. + /// + public static readonly MediaType APPLICATION_ATOM_XML = new MediaType("application", "atom+xml"); + + /// + /// The media type for 'application/x-www-form-urlencoded'. + /// + public static readonly MediaType APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); + + /// + /// The media type for 'application/json'. + /// + public static readonly MediaType APPLICATION_JSON = new MediaType("application", "json"); + + /// + /// The media type for 'application/octet-stream'. + /// + public static readonly MediaType APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream"); + + /// + /// The media type for 'application/xhtml+xml'. + /// + public static readonly MediaType APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml"); + + /// + /// The media type for 'image/gif'. + /// + public static readonly MediaType IMAGE_GIF = new MediaType("image", "gif"); + + /// + /// The media type for 'image/jpeg'. + /// + public static readonly MediaType IMAGE_JPEG = new MediaType("image", "jpeg"); + + /// + /// The media type for 'image/png'. + /// + public static readonly MediaType IMAGE_PNG = new MediaType("image", "png"); + + /// + /// The media type for 'image/xml'. + /// + public static readonly MediaType APPLICATION_XML = new MediaType("application", "xml"); + + /// + /// The media type for 'multipart/form-data'. + /// + public static readonly MediaType MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); + + /// + /// The media type for 'text/html'. + /// + public static readonly MediaType TEXT_HTML = new MediaType("text", "html"); + + /// + /// The media type for 'text/plain'. + /// + public static readonly MediaType TEXT_PLAIN = new MediaType("text", "plain"); + + /// + /// The media type for 'text/xml'. + /// + public static readonly MediaType TEXT_XML = new MediaType("text", "xml"); + + private const string WILDCARD_TYPE = "*"; + private const string PARAM_QUALITY_FACTOR = "q"; + private const string PARAM_CHARSET = "charset"; + + private string type; + private string subtype; + private IDictionary parameters; + + // Stable sort management + private int SortIndex = 0; + + /// + /// Gets the primary type. + /// + public string Type + { + get { return this.type; } + } + + /// + /// Gets the subtype. + /// + public string Subtype + { + get { return this.subtype; } + } + + /// + /// Indicate whether the type is the wildcard character '*', or not. + /// + public bool IsWildcardType + { + get { return WILDCARD_TYPE == type; } + } + + /// + /// Indicate whether the subtype is the wildcard character '*', or not. + /// + public bool IsWildcardSubtype + { + get { return WILDCARD_TYPE == subtype; } + } + + /// + /// Gets the character set, as indicated by a 'charset' parameter, if any. + /// + public Encoding CharSet + { + get + { + string charSet = null; + this.parameters.TryGetValue(PARAM_CHARSET, out charSet); + return charSet != null ? Encoding.GetEncoding(charSet) : null; + } + } + + /// + /// Gets the quality value, as indicated by a 'q' parameter, if any. + /// Defaults to '1.0'. + /// + public double QualityValue + { + get + { + string qualityFactory = null; + return this.parameters.TryGetValue(PARAM_QUALITY_FACTOR, out qualityFactory) + ? Double.Parse(qualityFactory, CultureInfo.InvariantCulture) + : 1D; + } + } + + /// + /// Creates a new instance of for the given primary type. + /// The subtype is set to '*', parameters are empty. + /// + /// The primary type. + public MediaType(string type) : + this(type, WILDCARD_TYPE) + { + } + + /// + /// Creates a new instance of for the given primary type and subtype. + /// The parameters are empty. + /// + /// The primary type. + /// The subtype. + public MediaType(string type, string subtype) : + this(type, subtype, new Dictionary(StringComparer.OrdinalIgnoreCase)) + { + } + + /// + /// Creates a new instance of for the given primary type, subtype and character set. + /// + /// The primary type. + /// The subtype. + /// The character set + public MediaType(string type, string subtype, string charSet) : + this(type, subtype) + { + this.parameters.Add(PARAM_CHARSET, charSet); + } + + /// + /// Creates a new instance of for the given primary type, subtype and character set. + /// + /// The primary type. + /// The subtype. + /// The character set + public MediaType(string type, string subtype, Encoding charSet) : + this(type, subtype) + { + this.parameters.Add(PARAM_CHARSET, charSet.WebName); + } + + /// + /// Creates a new instance of for the given primary type, subtype and quality value. + /// + /// The primary type. + /// The subtype. + /// The quality value + public MediaType(String type, String subtype, double qualityValue) : + this(type, subtype) + { + this.parameters.Add(PARAM_QUALITY_FACTOR, qualityValue.ToString(CultureInfo.InvariantCulture)); + } + + /// + /// Creates a new instance of by copying the type and subtype of the given MediaType, + /// and allows for different parameter. + /// + /// The other media type. + /// The parameters, may be null. + public MediaType(MediaType otherMediaType, IDictionary parameters) : + this(otherMediaType.Type, otherMediaType.Subtype, parameters) + { + } + + /// + /// Creates a new instance of for the given primary type, subtype and parameters. + /// + /// The primary type. + /// The subtype. + /// The parameters, may be null. + public MediaType(string type, string subtype, IDictionary parameters) + { + ArgumentUtils.AssertHasText(type, "type"); + ArgumentUtils.AssertHasText(subtype, "subtype"); + + // TODO: check type (http://tools.ietf.org/html/rfc2616#section-2.2) + this.type = type.ToLower(CultureInfo.InvariantCulture); + // TODO: check subtype (http://tools.ietf.org/html/rfc2616#section-2.2) + this.subtype = subtype.ToLower(CultureInfo.InvariantCulture); + // TODO: check parameters (http://tools.ietf.org/html/rfc2616#section-2.2) + this.parameters = new Dictionary(parameters, StringComparer.OrdinalIgnoreCase); + } + + #region IEquatable Members + + /// + /// Indicates whether the current media type is equal to another . + /// + /// A to compare with this media type. + /// + /// if the specified is equal to the current media type; otherwise, . + /// + public bool Equals(MediaType other) + { + if (Object.ReferenceEquals(other, null)) + { + return false; + } + if (Object.ReferenceEquals(this, other)) + { + return true; + } + if (this.type == other.type && this.subtype == other.subtype) + { + if (other.parameters.Count == this.parameters.Count) + { + foreach (string key in this.parameters.Keys) + { + if (!other.parameters.ContainsKey(key) || + !String.Equals(other.parameters[key], this.parameters[key], StringComparison.OrdinalIgnoreCase)) + { + return false; + } + } + return true; + } + } + return false; + } + + #endregion + + /// + /// Indicates whether the current media type is equal to another . + /// + /// An to compare with this media type. + /// + /// if the specified is equal to the current media type; otherwise, . + /// + public override bool Equals(object obj) + { + return this.Equals(obj as MediaType); + } + + /// + /// Serves as a hash function for a particular type. + /// + /// + /// This method is suitable for use in hashing algorithms and data structures like a hash table. + /// + /// + /// A hash code for the current media type. + /// + public override int GetHashCode() + { + return this.ToString().ToUpper(CultureInfo.InvariantCulture).GetHashCode(); + } + + /// + /// Returns a that represents the current media type. + /// + /// + /// A that represents the current media type. + /// + public override string ToString() + { + StringBuilder builder = new StringBuilder(); + builder.Append(this.type); + builder.Append('/'); + builder.Append(this.subtype); + foreach(string key in this.parameters.Keys) + { + builder.Append(';'); + builder.Append(key); + builder.Append('='); + builder.Append(this.parameters[key]); + } + return builder.ToString(); + } + + /// + /// Determines whether two specified media types have the same value. + /// + /// The first media type to compare, or . + /// The second media type to compare, or . + /// + /// if values are the same; otherwise, . + /// + public static bool operator ==(MediaType mediaType1, MediaType mediaType2) + { + if (Object.ReferenceEquals(mediaType1, null)) + { + return Object.ReferenceEquals(mediaType2, null); + } + return mediaType1.Equals(mediaType2); + } + + /// + /// Determines whether two specified media types have different values. + /// + /// The first media type to compare, or . + /// The second media type to compare, or . + /// + /// if values are differents; otherwise, . + /// + public static bool operator !=(MediaType mediaType1, MediaType mediaType2) + { + return !(mediaType1 == mediaType2); + } + + /// + /// Return a generic parameter value, given a parameter name. + /// + /// The parameter name. + /// The parameter value; or null if not present. + public string GetParameter(string name) + { + return this.parameters[name]; + } + + /// + /// Indicate whether this includes the given media type. + /// + /// + /// For instance, 'text/*' includes 'text/plain', 'text/html', and + /// 'application/*+xml' includes 'application/soap+xml', etc. + /// This method is non-symmetric. + /// + /// The reference media type with which to compare. + /// + /// if this media type includes the given media type; otherwise . + /// + public bool Includes(MediaType otherMediaType) + { + if (otherMediaType == null) + { + return false; + } + if (this.IsWildcardType) + { + // */* includes anything + return true; + } + else if (this.type == otherMediaType.type) + { + if (this.subtype == otherMediaType.subtype || this.IsWildcardSubtype) + { + return true; + } + // application/*+xml includes application/soap+xml + int thisPlusIdx = this.subtype.IndexOf('+'); + int otherPlusIdx = otherMediaType.subtype.IndexOf('+'); + if (thisPlusIdx != -1 && otherPlusIdx != -1) + { + string thisSubtypeNoSuffix = this.subtype.Substring(0, thisPlusIdx); + + string thisSubtypeSuffix = this.subtype.Substring(thisPlusIdx + 1); + string otherSubtypeSuffix = otherMediaType.subtype.Substring(otherPlusIdx + 1); + if (thisSubtypeSuffix == otherSubtypeSuffix && WILDCARD_TYPE == thisSubtypeNoSuffix) + { + return true; + } + } + } + return false; + } + + /// + /// Indicate whether this is compatible with the given media type. + /// + /// + /// For instance, 'text/*' is compatible 'text/plain', 'text/html', and vice versa. + /// In effect, this method is similar to , except that it's symmetric. + /// + /// The reference media type with which to compare. + /// + /// if this media type is compatible with the given media type; otherwise . + /// + public bool IsCompatibleWith(MediaType otherMediaType) + { + if (otherMediaType == null) + { + return false; + } + if (this.IsWildcardType || otherMediaType.IsWildcardType) + { + return true; + } + else if (this.type == otherMediaType.type) + { + if (this.subtype == otherMediaType.subtype || this.IsWildcardSubtype || otherMediaType.IsWildcardSubtype) + { + return true; + } + // application/*+xml is compatible with application/soap+xml, and vice-versa + int thisPlusIdx = this.subtype.IndexOf('+'); + int otherPlusIdx = otherMediaType.subtype.IndexOf('+'); + if (thisPlusIdx != -1 && otherPlusIdx != -1) + { + string thisSubtypeNoSuffix = this.subtype.Substring(0, thisPlusIdx); + string otherSubtypeNoSuffix = otherMediaType.subtype.Substring(0, otherPlusIdx); + + string thisSubtypeSuffix = this.subtype.Substring(thisPlusIdx + 1); + string otherSubtypeSuffix = otherMediaType.subtype.Substring(otherPlusIdx + 1); + + if (thisSubtypeSuffix == otherSubtypeSuffix && + (WILDCARD_TYPE == thisSubtypeNoSuffix || WILDCARD_TYPE == otherSubtypeNoSuffix)) + { + return true; + } + } + } + return false; + } + + #region IComparable Members + + /// + /// Compares this to another alphabetically. + /// + /// The media type to compare with this object. + /// + /// A 32-bit signed integer that indicates the relative order of the objects + /// being compared. The return value has the following meanings: Value Meaning + /// Less than zero This object is less than the other parameter. Zero This object + /// is equal to other. Greater than zero This object is greater than other. + /// + public int CompareTo(MediaType other) + { + int comp = this.type.CompareTo(other.type); + if (comp != 0) + { + return comp; + } + comp = this.subtype.CompareTo(other.subtype); + if (comp != 0) + { + return comp; + } + comp = this.parameters.Count - other.parameters.Count; + if (comp != 0) + { + return comp; + } + string[] thisKeys = new string[this.parameters.Keys.Count]; + this.parameters.Keys.CopyTo(thisKeys, 0); + Array.Sort(thisKeys, StringComparer.OrdinalIgnoreCase); + string[] otherKeys = new string[other.parameters.Keys.Count]; + other.parameters.Keys.CopyTo(otherKeys, 0); + Array.Sort(otherKeys, StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < this.parameters.Count; i++) + { + string thisKey = thisKeys[i]; + string otherKey = otherKeys[i]; + comp = String.Compare(thisKey, otherKey, StringComparison.OrdinalIgnoreCase); + if (comp != 0) + { + return comp; + } + comp = String.Compare(this.parameters[thisKey], other.parameters[otherKey]); + if (comp != 0) + { + return comp; + } + } + return this.SortIndex.CompareTo(other.SortIndex); // Stable sort + } + + #endregion + + /// + /// Parse the given String into a single . + /// + /// + /// This method can be used to parse a 'Content-Type' header. + /// + /// The string to parse. + /// The media type. + public static MediaType Parse(string mediaType) + { + if (!StringUtils.HasText(mediaType)) + { + return null; + } + + string[] parts = mediaType.Split(';'); + string fullType = parts[0].Trim(); + if (fullType == WILDCARD_TYPE) + { + fullType = "*/*"; + } + int subIndex = fullType.IndexOf('/'); + if (subIndex == -1) + { + throw new ArgumentException( + String.Format("'{0}' does not contain '/'", mediaType), + "mediaType"); + } + if (subIndex == fullType.Length - 1) + { + throw new ArgumentException( + String.Format("'{0}' does not contain subtype after '/'", mediaType), + "mediaType"); + } + string type = fullType.Substring(0, subIndex); + string subtype = fullType.Substring(subIndex + 1); + + IDictionary parameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + if (parts.Length > 1) + { + for (int i = 1; i < parts.Length; i++) + { + string parameter = parts[i].Trim(); + int eqIndex = parameter.IndexOf('='); + if (eqIndex != -1) + { + string attribute = parameter.Substring(0, eqIndex); + string value = parameter.Substring(eqIndex + 1); + parameters.Add(attribute, value); + } + } + } + + return new MediaType(type, subtype, parameters); + } + + /// + /// Return a string representation of the given list of objects. + /// + /// + /// This method can be used to for an 'Accept' or 'Content-Type' header. + /// + /// The list of media types to convert. + /// The string representation of the given list. + public static string ToString(IEnumerable mediaTypes) + { + StringBuilder builder = new StringBuilder(); + foreach(MediaType mediaType in mediaTypes) + { + if (builder.Length > 0) + { + builder.Append(','); + } + builder.Append(mediaType); + } + return builder.ToString(); + } + + /// + /// Sorts the given list of objects by specificity. + /// HTTP 1.1, section 14.1 + /// + /// + /// + /// Given two media types: + ///
    + ///
  1. if either media type has a wildcard type, then the media type without the + /// wildcard is ordered before the other.
  2. + ///
  3. if the two media types have different types, then they are considered equal and + /// remain their current order.
  4. + ///
  5. if either media type has a wildcard subtype, then the media type without + /// the wildcard is sorted before the other.
  6. + ///
  7. if the two media types have different subtypes, then they are considered equal + /// and remain their current order.
  8. + ///
  9. if the two media types have different quality value, then the media type + /// with the highest quality value is ordered before the other.
  10. + ///
  11. if the two media types have a different amount of parameters, then the + /// media type with the most parameters is ordered before the other.
  12. + ///
+ ///
+ /// + /// For example: + ///
audio/basic < audio/* < */*
+ ///
audio/* < audio/*;q=0.7; audio/*;q=0.3
+ ///
audio/basic;level=1 < audio/basic
+ ///
audio/basic == text/html
+ ///
audio/basic == audio/wave
+ ///
+ ///
+ /// The list of media types to be sorted. + public static void SortBySpecificity(List mediaTypes) + { + ArgumentUtils.AssertNotNull(mediaTypes, "mediaTypes"); + + if (mediaTypes.Count > 1) + { + // Stable sort + for(int i = 0; i < mediaTypes.Count; i++) + { + mediaTypes[i].SortIndex = i; + } + mediaTypes.Sort(SPECIFICITY_COMPARER); + } + } + + /// + /// Sorts the given list of objects by quality value. + /// + /// + /// + /// Given two media types: + ///
    + ///
  1. if the two media types have different quality value, then the media type + /// with the highest quality value is ordered before the other.
  2. + ///
  3. if either media type has a wildcard type, then the media type without the + /// wildcard is ordered before the other.
  4. + ///
  5. if the two media types have different types, then they are considered equal and + /// remain their current order.
  6. + ///
  7. if either media type has a wildcard subtype, then the media type without + /// the wildcard is sorted before the other.
  8. + ///
  9. if the two media types have different subtypes, then they are considered equal + /// and remain their current order.
  10. + ///
  11. if the two media types have a different amount of parameters, then the + /// media type with the most parameters is ordered before the other.
  12. + ///
+ ///
+ ///
+ /// The list of media types to be sorted + public static void SortByQualityValue(List mediaTypes) + { + ArgumentUtils.AssertNotNull(mediaTypes, "mediaTypes"); + + if (mediaTypes.Count > 1) + { + // Stable sort + for (int i = 0; i < mediaTypes.Count; i++) + { + mediaTypes[i].SortIndex = i; + } + mediaTypes.Sort(QUALITY_VALUE_COMPARER); + } + } + + /// + /// implementation by specificity value. + /// + public static IComparer SPECIFICITY_COMPARER = new SpecificityComparer(); + + /// + /// implementation by quality value. + /// + public static IComparer QUALITY_VALUE_COMPARER = new QualityValueComparer(); + + #region SpecificityComparer + + private class SpecificityComparer : IComparer + { + public int Compare(MediaType x, MediaType y) + { + if (x.IsWildcardType && !y.IsWildcardType) + { // */* < audio/* + return 1; + } + else if (y.IsWildcardType && !x.IsWildcardType) + { // audio/* > */* + return -1; + } + else if (x.type != y.type) + { // audio/basic == text/html + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + else + { // mediaType1.type == mediaType2.type + if (x.IsWildcardSubtype && !y.IsWildcardSubtype) + { // audio/* < audio/basic + return 1; + } + else if (y.IsWildcardSubtype && !x.IsWildcardSubtype) + { // audio/basic > audio/* + return -1; + } + else if (x.subtype != y.subtype) + { // audio/basic == audio/wave + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + else + { // mediaType2.subtype == mediaType2.subtype + double quality1 = x.QualityValue; + double quality2 = y.QualityValue; + int qualityComparison = quality2.CompareTo(quality1); + if (qualityComparison != 0) + { + return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 + } + else + { + int paramsSize1 = x.parameters.Count; + int paramsSize2 = y.parameters.Count; + int comp = (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic + if (comp != 0) + { + return comp; + } + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + } + } + } + } + + #endregion + + #region QualityValueComparer + + private class QualityValueComparer : IComparer + { + public int Compare(MediaType x, MediaType y) + { + double quality1 = x.QualityValue; + double quality2 = y.QualityValue; + int qualityComparison = quality2.CompareTo(quality1); + if (qualityComparison != 0) + { + return qualityComparison; // audio/*;q=0.7 < audio/*;q=0.3 + } + else if (x.IsWildcardType && !y.IsWildcardType) + { // */* < audio/* + return 1; + } + else if (y.IsWildcardType && !x.IsWildcardType) + { // audio/* > */* + return -1; + } + else if (x.type != y.type) + { // audio/basic == text/html + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + else + { // mediaType1.type == mediaType2.type + if (x.IsWildcardSubtype && !y.IsWildcardSubtype) + { // audio/* < audio/basic + return 1; + } + else if (y.IsWildcardSubtype && !x.IsWildcardSubtype) + { // audio/basic > audio/* + return -1; + } + else if (x.subtype != y.subtype) + { // audio/basic == audio/wave + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + else + { + int paramsSize1 = x.parameters.Count; + int paramsSize2 = y.parameters.Count; + int comp = (paramsSize2 < paramsSize1 ? -1 : (paramsSize2 == paramsSize1 ? 0 : 1)); // audio/basic;level=1 < audio/basic + if (comp != 0) + { + return comp; + } + return x.SortIndex.CompareTo(y.SortIndex); // Stable sort + } + } + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/IO/AbstractResource.cs b/netstandard/Spring.Rest/IO/AbstractResource.cs new file mode 100644 index 0000000..cd68d35 --- /dev/null +++ b/netstandard/Spring.Rest/IO/AbstractResource.cs @@ -0,0 +1,107 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Globalization; + +using Spring.Util; + +namespace Spring.IO +{ + /// + /// Convenience base class for implementations. + /// + /// Bruno Baia + /// + public abstract class AbstractResource : IResource + { + /// + /// Creates a new instance of the class. + /// + protected AbstractResource() + { + } + + #region IResource Members + + /// + /// Gets a value indicating whether or not this resource represents a handle with an open stream? + /// Returns if the source cannot be read multiple times. + /// + /// + /// This implementation always returns . + /// + public virtual bool IsOpen + { + get { return false; } + } + + /// + /// Gets the handle for this resource, + /// or if the source cannot be represented by an . + /// + public abstract Uri Uri { get; } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + public abstract Stream GetStream(); + + #endregion + + /// + /// Strips any protocol name from the supplied . + /// + /// + /// If the supplied does not have any protocol associated with it, + /// then the supplied will be returned as-is. + /// + /// + /// + /// GetResourceNameWithoutProtocol("http://www.mycompany.com/resource.txt"); + /// // returns www.mycompany.com/resource.txt + /// + /// + /// The name of the resource. + /// The name of the resource without the protocol name. + protected static string GetResourceNameWithoutProtocol(string resourceName) + { + int pos = resourceName.IndexOf(Uri.SchemeDelimiter); + if (pos == -1) + { + return resourceName; + } + else + { + return resourceName.Substring(pos + Uri.SchemeDelimiter.Length); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/IO/AssemblyResource.cs b/netstandard/Spring.Rest/IO/AssemblyResource.cs new file mode 100644 index 0000000..e5aeda1 --- /dev/null +++ b/netstandard/Spring.Rest/IO/AssemblyResource.cs @@ -0,0 +1,157 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Globalization; +using System.Reflection; + +using Spring.Util; + +namespace Spring.IO +{ + /// + /// An implementation for resources stored within assemblies + /// (aka embedded resources). + /// + /// + /// This implementation expects any resource name passed to the + /// constructor to adhere to the following format: + /// + /// assembly://assemblyName/namespace/resourceName + /// + /// + /// Bruno Baia + /// + public class AssemblyResource : AbstractResource + { + private string resourceName; + private Assembly assembly; + private Uri resourceUri; + + /// + /// Creates a new instance of the class + /// with the specified resource name. + /// + /// + /// The resource name that must follow the format 'assembly://assemblyName/namespace/resourceName'. + /// + /// + /// If the supplied did not conform to the expected format. + /// + /// + /// If the assembly specified in the supplied could not be found. + /// + public AssemblyResource(string resourceName) + { + ArgumentUtils.AssertHasText(resourceName, "resourceName"); + + string[] info = GetResourceNameWithoutProtocol(resourceName).Split('/'); + if (info.Length != 3) + { + throw new UriFormatException(String.Format( + "Invalid resource name. Name has to be in 'assembly:////' format.", resourceName)); + } + Assembly assembly = Assembly.Load(info[0]); + if (assembly == null) + { + throw new FileNotFoundException(String.Format( + "Unable to load assembly [{0}].", info[0])); + } + + this.Initialize(info[2], info[1], assembly); + } + + /// + /// Creates a new instance of the class. + /// + /// Uses the specified to obtain the assembly and namespace for the resource. + /// + /// The name of the file in the assembly. + /// The type to determine the assembly and the namespace. + public AssemblyResource(string fileName, Type type) + { + ArgumentUtils.AssertHasText(fileName, "resourceName"); + ArgumentUtils.AssertNotNull(type, "type"); + + this.Initialize(fileName, type.Namespace, type.Assembly); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name of the file in the assembly. + /// The namespace to use to generate the full resource name. + /// The assembly containing the resource. + protected void Initialize(string fileName, string ns, Assembly assembly) + { + this.resourceName = ns + "." + fileName; + this.assembly = assembly; + + string assemblyName = assembly.FullName.Split(',')[0]; + this.resourceUri = new Uri(String.Format("assembly://{0}/{1}/{2}", assemblyName, ns, fileName)); + } + + /// + /// Gets the handle for this resource. + /// + /// The URI follows the format 'assembly://assemblyName/namespace/resourceName'. + /// + public override Uri Uri + { + get { return this.resourceUri; } + } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + public override Stream GetStream() + { + Stream stream = this.assembly.GetManifestResourceStream(this.resourceName); + if (stream == null) + { + throw new FileNotFoundException(String.Format( + "Could not load resource [{0}] from assembly [{1}]. Spring.NET URI syntax is 'assembly://MyAssembly/MyNamespace/MyResource.ext'", this.resourceName, this.assembly)); + } + return stream; + } + + /// + /// Returns a that represents the current resource. + /// + /// + /// A that represents the current resource. + /// + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, + "Resource [{0}] from assembly [{1}]", this.resourceName, this.assembly.FullName); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/IO/ByteArrayResource.cs b/netstandard/Spring.Rest/IO/ByteArrayResource.cs new file mode 100644 index 0000000..3382087 --- /dev/null +++ b/netstandard/Spring.Rest/IO/ByteArrayResource.cs @@ -0,0 +1,110 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Globalization; + +namespace Spring.IO +{ + /// + /// A byte array backed resource. + /// + /// Bruno Baia + /// + public class ByteArrayResource : IResource + { + private byte[] bytes; + + /// + /// Creates a new instance of the class. + /// + /// The byte array for this resource. + public ByteArrayResource(byte[] bytes) + { + this.bytes = bytes ?? new byte[0]; + } + + /// + /// Gets the byte array encapsulated by this resource. + /// + public byte[] Bytes + { + get { return this.bytes; } + } + + #region IResource Members + + /// + /// Gets a value indicating whether or not this resource represents a handle with an open stream? + /// Returns if the source cannot be read multiple times. + /// + /// + /// This implementation always returns . + /// + public virtual bool IsOpen + { + get { return false; } + } + + /// + /// Gets the handle for this resource, + /// or if the source cannot be represented by an . + /// + /// + /// This implementation always returns , + /// assuming that the resource cannot be represented as an . + /// + public virtual Uri Uri + { + get { return null; } + } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + public virtual Stream GetStream() + { + return new MemoryStream(this.bytes); + } + + #endregion + + /// + /// Returns a that represents the current resource. + /// + /// + /// A that represents the current resource. + /// + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "Byte array resource [Length:{0}]", this.bytes.Length); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/IO/FileResource.cs b/netstandard/Spring.Rest/IO/FileResource.cs new file mode 100644 index 0000000..064ea8f --- /dev/null +++ b/netstandard/Spring.Rest/IO/FileResource.cs @@ -0,0 +1,103 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Globalization; + +using Spring.Util; + +namespace Spring.IO +{ + /// + /// A backed resource. + /// + /// Bruno Baia + /// + public class FileResource : AbstractResource + { + private FileInfo fileInfo; + private Uri fileUri; + + /// + /// Creates a new instance of the class. + /// + /// The name of the file system resource. + public FileResource(string resourceName) + { + ArgumentUtils.AssertHasText(resourceName, "resourceName"); + + // Remove protocol (if any) + string fileName = GetResourceNameWithoutProtocol(resourceName); + // Remove extra slashes used to indicate that resource is local (handle the case "/C:/path1/...") + if (fileName.Length > 2 && fileName[0] == '/' && fileName[2] == ':') + { + fileName = fileName.Substring(1); + } + + this.fileInfo = new FileInfo(fileName); + this.fileUri = new Uri(this.fileInfo.FullName); + } + + /// + /// Gets the underlying handle for this resource. + /// + public FileInfo File + { + get { return this.fileInfo; } + } + + /// + /// Gets the handle for this resource. + /// + public override Uri Uri + { + get { return this.fileUri; } + } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + public override Stream GetStream() + { + return new FileStream(this.fileUri.LocalPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + } + + /// + /// Returns a that represents the current resource. + /// + /// + /// A that represents the current resource. + /// + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "File resource [{0}]", this.fileInfo.FullName); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/IO/IResource.cs b/netstandard/Spring.Rest/IO/IResource.cs new file mode 100644 index 0000000..8b92cbf --- /dev/null +++ b/netstandard/Spring.Rest/IO/IResource.cs @@ -0,0 +1,76 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; + +namespace Spring.IO +{ + // Light version of the Spring.NET Core IResource abstraction + + /// + /// The central abstraction for access to resources such as s. + /// + /// + /// + /// This interface encapsulates a resource descriptor that abstracts away from the underlying type of resource; + /// possible resource types include files, memory streams, and databases (this list is not exhaustive). + /// + /// + /// A can definitely be opened and accessed for every such resource; + /// if the resource exists in a physical form (for example, the resource is not an in-memory stream + /// or one that has been extracted from an assembly or ZIP file), a + /// can also be accessed. The actual behavior is implementation-specific. + /// + /// + /// Third party extensions or libraries that want to integrate external resources + /// are encouraged expose such resources via this abstraction. + /// + /// + /// Bruno Baia + public interface IResource + { + /// + /// Gets a value indicating whether or not this resource represents a handle with an open stream? + /// Returns if the source cannot be read multiple times. + /// + bool IsOpen { get; } + + /// + /// Gets the handle for this resource, + /// or if the source cannot be represented by an . + /// + Uri Uri { get; } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + Stream GetStream(); + } +} diff --git a/netstandard/Spring.Rest/IO/StreamResource.cs b/netstandard/Spring.Rest/IO/StreamResource.cs new file mode 100644 index 0000000..100f7ba --- /dev/null +++ b/netstandard/Spring.Rest/IO/StreamResource.cs @@ -0,0 +1,127 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Globalization; + +using Spring.Util; + +namespace Spring.IO +{ + /// + /// adapter implementation for a . + /// + /// + /// + /// In contrast to other implementations, + /// this is an adapter for an already opened resource - the + /// therefore always returns . + /// + /// + /// Do not use this class if you need to keep the resource descriptor somewhere, + /// or if you need to read a stream multiple times. + /// + /// + /// Bruno Baia + /// + public class StreamResource : IResource + { + private Stream stream; + + /// + /// Creates a new instance of the class. + /// + /// The to use. + public StreamResource(Stream stream) + { + ArgumentUtils.AssertNotNull(stream, "stream"); + + this.stream = stream; + } + + #region IResource Members + + /// + /// Gets a value indicating whether or not this resource represents a handle with an open stream? + /// Returns if the source cannot be read multiple times. + /// + /// + /// This implementation always returns . + /// + public virtual bool IsOpen + { + get { return true; } + } + + /// + /// Gets the handle for this resource, + /// or if the source cannot be represented by an . + /// + /// + /// This implementation always returns , + /// assuming that the resource cannot be represented as an . + /// + public virtual Uri Uri + { + get { return null; } + } + + /// + /// Returns a for this resource. + /// + /// + /// Clients of this interface must be aware that every access of this + /// method will create a fresh ; + /// it is the responsibility of the calling code to close any such . + /// + /// + /// An . + /// + /// + /// If the underlying has already been read. + /// + /// + public virtual Stream GetStream() + { + if (this.stream == null) + { + throw new InvalidOperationException( + "Stream has already been read - do not use StreamSource if a stream needs to be read multiple times."); + } + Stream result = this.stream; + this.stream = null; + return result; + } + + #endregion + + /// + /// Returns a that represents the current resource. + /// + /// + /// A that represents the current resource. + /// + public override string ToString() + { + return String.Format(CultureInfo.InvariantCulture, "Stream resource [{0}]", this.stream.ToString()); + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Json/IJsonDeserializer.cs b/netstandard/Spring.Rest/Json/IJsonDeserializer.cs new file mode 100644 index 0000000..ac7a17e --- /dev/null +++ b/netstandard/Spring.Rest/Json/IJsonDeserializer.cs @@ -0,0 +1,39 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Json +{ + /// + /// An interface to deserialize object of arbitrary type from JSON. + /// + /// Bruno Baia + public interface IJsonDeserializer + { + /// + /// Deserialize the specified to a custom object. + /// + /// The to deserialize. + /// The to use. + /// An object created from the passed . + object Deserialize(JsonValue value, JsonMapper mapper); + } +} diff --git a/netstandard/Spring.Rest/Json/IJsonSerializer.cs b/netstandard/Spring.Rest/Json/IJsonSerializer.cs new file mode 100644 index 0000000..82341dc --- /dev/null +++ b/netstandard/Spring.Rest/Json/IJsonSerializer.cs @@ -0,0 +1,39 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Json +{ + /// + /// An interface to serialize object of arbitrary type into JSON. + /// + /// Bruno Baia + public interface IJsonSerializer + { + /// + /// Serialize the specified to a . + /// + /// The object to serialize. + /// The to use. + /// A object created from the passed objects. + JsonValue Serialize(object obj, JsonMapper mapper); + } +} diff --git a/netstandard/Spring.Rest/Json/JsonArray.cs b/netstandard/Spring.Rest/Json/JsonArray.cs new file mode 100644 index 0000000..2a2d202 --- /dev/null +++ b/netstandard/Spring.Rest/Json/JsonArray.cs @@ -0,0 +1,89 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Collections; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Json +{ + /// + /// Defines a JSON array structure. + /// A JSON array is a list of s. + /// + /// + /// Specification details, see http://www.json.org/ + /// + /// + /// Bruno Baia + public class JsonArray : JsonValue + { + /// + /// Creates a new instance of . + /// + public JsonArray(params JsonValue[] values) : + base(JsonValueType.Array, new List(values)) + { + } + + #region Public methods + + /// + /// Returns the at the specified entry index. + /// + /// The zero-based index of the entry that contains the value to get. + /// + /// The at the specified index + /// or if the entry does not exists. + /// + public override JsonValue GetValue(int index) + { + IList jsonArray = (IList)this.value; + if (index < jsonArray.Count) + { + return jsonArray[index]; + } + return null; + } + + /// + /// Returns all s of the JSON array structure. + /// + /// The collection of s. + public override ICollection GetValues() + { + return (IList)this.value; + } + + /// + /// Adds a to the end of the JSON array structure. + /// + /// The to add. + public void AddValue(JsonValue value) + { + ArgumentUtils.AssertNotNull(value, "value"); + + ((IList)this.value).Add(value); + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Json/JsonException.cs b/netstandard/Spring.Rest/Json/JsonException.cs new file mode 100644 index 0000000..6d0cc1c --- /dev/null +++ b/netstandard/Spring.Rest/Json/JsonException.cs @@ -0,0 +1,82 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Json +{ + /// + /// The exception that is thrown while processing (parsing, generating or mapping) JSON content. + /// + /// Bruno Baia + [Serializable] + public class JsonException : Exception + { + /// + /// Creates a new instance of the class. + /// + public JsonException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public JsonException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + /// + /// The root exception that is being wrapped. + /// + public JsonException(string message, Exception rootCause) + : base(message, rootCause) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected JsonException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} + diff --git a/netstandard/Spring.Rest/Json/JsonMapper.cs b/netstandard/Spring.Rest/Json/JsonMapper.cs new file mode 100644 index 0000000..7244870 --- /dev/null +++ b/netstandard/Spring.Rest/Json/JsonMapper.cs @@ -0,0 +1,132 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +namespace Spring.Json +{ + /// + /// Data binder that provides functionality for converting between + /// objects of arbitrary types and instances. + /// + /// Bruno Baia + public class JsonMapper + { + private IDictionary deserializers; + private IDictionary serializers; + + /// + /// Creates a new empty instance of . + /// + public JsonMapper() + { + this.deserializers = new Dictionary(); + this.serializers = new Dictionary(); + } + + /// + /// Registers a deserializer for the specified . + /// + /// The type to deserialize to. + /// The deserializer to use. + public void RegisterDeserializer(Type type, IJsonDeserializer deserializer) + { + this.deserializers[type] = deserializer; + } + + /// + /// Registers a serializer for the specified . + /// + /// The type to serialize from. + /// The serializer to use. + public void RegisterSerializer(Type type, IJsonSerializer serializer) + { + this.serializers[type] = serializer; + } + + /// + /// Indicates whether a can be deserialized to a given type. + /// + /// The type to use. + /// + /// if a can be deserialized to the given type; otherwise . + /// + public bool CanDeserialize(Type type) + { + if (type == typeof(JsonValue) || + this.deserializers.ContainsKey(type)) + { + return true; + } + return false; + } + + /// + /// Indicates whether a given type can be serialized to a . + /// + /// The type to use. + /// + /// if the given type can be serialized to a ; otherwise . + /// + public bool CanSerialize(Type type) + { + if (type == typeof(JsonValue) || + this.serializers.ContainsKey(type)) + { + return true; + } + return false; + } + + /// + /// Deserialize the specified to the type . + /// + /// The type to deserialize to. + /// The to deserialize. + /// An object created from the passed . + public T Deserialize(JsonValue value) + { + IJsonDeserializer deserializer; + if (this.deserializers.TryGetValue(typeof(T), out deserializer)) + { + return (T)deserializer.Deserialize(value, this); + } + throw new JsonException(String.Format("Could not find deserializer for type '{0}'.", typeof(T))); + } + + /// + /// Serialize the specified to a . + /// + /// The object to serialize. + /// A object created from the passed objects. + public JsonValue Serialize(object obj) + { + Type objType = obj.GetType(); + IJsonSerializer serializer; + if (this.serializers.TryGetValue(objType, out serializer)) + { + return serializer.Serialize(obj, this); + } + throw new JsonException(String.Format("Could not find serializer for type '{0}'.", objType)); + } + } +} + diff --git a/netstandard/Spring.Rest/Json/JsonObject.cs b/netstandard/Spring.Rest/Json/JsonObject.cs new file mode 100644 index 0000000..8e23133 --- /dev/null +++ b/netstandard/Spring.Rest/Json/JsonObject.cs @@ -0,0 +1,136 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Json +{ + /// + /// Represents a JSON object structure. + /// A JSON object is a collection of name/value pairs. + /// + /// + /// Specification details, see http://www.json.org/ + /// + /// + /// Bruno Baia + public class JsonObject : JsonValue + { + /// + /// Creates a new instance of . + /// + public JsonObject() : + base(JsonValueType.Object, new Dictionary()) + { + } + + #region Public methods + + /// + /// Returns the associated with the specified entry name + /// if the value held by this instance is a JSON object structure. + /// + /// The name of the entry that contains the value to get. + /// + /// The associated with the specified name + /// or if the entry does not exists. + /// + /// If the value held by this instance is not a JSON object structure. + public override JsonValue GetValue(string name) + { + ArgumentUtils.AssertNotNull(name, "name"); + + IDictionary jsonObject = (IDictionary)this.value; + JsonValue result; + if (jsonObject.TryGetValue(name, out result)) + { + return result; + } + return null; + } + + /// + /// Returns all s + /// if the value held by this instance is a JSON object or array structure. + /// + /// The collection of s. + /// If the value held by this instance is not a JSON object or array structure. + public override ICollection GetValues() + { + return ((IDictionary)this.value).Values; + } + + /// + /// If the value held by this instance is a JSON object structure, + /// indicates whether or not it contains the specified entry name. + /// + /// The name of the entry to search for. + /// + /// if this JSON object contains the specified entry name; otherwise, . + /// + /// If the value held by this instance is not a JSON object structure. + public override bool ContainsName(string name) + { + return ((IDictionary)this.value).ContainsKey(name); + } + + /// + /// Returns all entry names + /// if the value held by this instance is a JSON object structure. + /// + /// The collection of entry names. + /// If the value held by this instance is not a JSON object structure. + public override ICollection GetNames() + { + return ((IDictionary)this.value).Keys; + } + + /// + /// Adds the specified name/ pair + /// to the JSON object structure held by this instance. + /// + /// The name of the entry to add. + /// The of the entry to add. + /// + /// If the value held by this instance is not a JSON object structure or + /// if an entry with the same name already exists. + /// + public void AddValue(string name, JsonValue value) + { + ArgumentUtils.AssertNotNull(name, "name"); + ArgumentUtils.AssertNotNull(value, "value"); + + IDictionary jsonObject = (IDictionary)this.value; + if (jsonObject.ContainsKey(name)) + { + throw new JsonException(String.Format( + "An entry with the name '{0}' already exists in the JSON object structure.", + name)); + } + jsonObject.Add(name, value); + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Json/JsonValue.cs b/netstandard/Spring.Rest/Json/JsonValue.cs new file mode 100644 index 0000000..2f14bce --- /dev/null +++ b/netstandard/Spring.Rest/Json/JsonValue.cs @@ -0,0 +1,1084 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +// Parsing code based on SimpleJson 0.8 http://simplejson.codeplex.com/ +// http://bit.ly/simplejson +// License: Apache License 2.0 (Apache) +// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html + +using System; +using System.Text; +using System.Globalization; +using System.Collections.Generic; + +using Spring.Util; + +namespace Spring.Json +{ + /// + /// A generic container for JSON content. + /// Every JSON value types (object, array, string, number, true, false or null) can be accessed via the provided functions. + /// + /// + /// Specification details, see http://www.json.org/ + /// + /// + /// + /// Bruno Baia + public class JsonValue + { + /// + /// Specifies the JSON value types. + /// + protected enum JsonValueType + { + /// + /// The JSON string. + /// + String, + /// + /// The JSON number. + /// + Number, + /// + /// The JSON object. + /// + Object, + /// + /// The JSON array. + /// + Array, + /// + /// The JSON boolean. + /// + Boolean, + /// + /// The JSON null. + /// + Null + } + + /// + /// The value held by this instance. + /// + protected object value; + + /// + /// The JSON type of the value held by this instance. + /// + protected JsonValueType type; + + #region Properties + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON object structure. + /// + public bool IsObject + { + get { return this.type == JsonValueType.Object; } + } + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON array structure. + /// + public bool IsArray + { + get { return this.type == JsonValueType.Array; } + } + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON string. + /// + public bool IsString + { + get { return this.type == JsonValueType.String; } + } + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON number. + /// + public bool IsNumber + { + get { return this.type == JsonValueType.Number; } + } + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON boolean. + /// + public bool IsBoolean + { + get { return this.type == JsonValueType.Boolean; } + } + + /// + /// Gets or sets a value indicating whether or not this instance holds a JSON null value. + /// + public bool IsNull + { + get { return this.type == JsonValueType.Null; } + } + + #endregion + + #region Constructor(s) + + /// + /// Creates a new instance of that holds a JSON null value. + /// + public JsonValue() + { + this.type = JsonValueType.Null; + this.value = null; + } + + /// + /// Creates a new instance of that holds a JSON string value. + /// + /// The string value. + public JsonValue(string value) + { + ArgumentUtils.AssertNotNull(value, "value"); + + this.type = JsonValueType.String; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON boolean value. + /// + /// The boolean value. + public JsonValue(bool value) + { + this.type = JsonValueType.Boolean; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(byte value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(decimal value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(double value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(float value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(int value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(long value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of that holds a JSON number value. + /// + /// The number value. + public JsonValue(short value) + { + this.type = JsonValueType.Number; + this.value = value; + } + + /// + /// Creates a new instance of with the the given value and type. + /// + /// The JSON type. + /// The JSON value. + protected JsonValue(JsonValueType type, object value) + { + this.type = type; + this.value = value; + } + + #endregion + + #region Public methods + + /// + /// Parses the JSON string representation. A return value indicates whether the conversion succeeded. + /// + /// A JSON string to parse. + /// + /// If the conversion succeed, contains the equivalent to the passed string, + /// or null if the conversion failed. + /// + /// + /// if the passed string was parsed successfully; + /// otherwise, . + /// + public static bool TryParse(string json, out JsonValue result) + { + result = null; + if (json == null) + { + return true; + } + char[] charArray = json.ToCharArray(); + int index = 0; + bool success = true; + JsonValue simpleJsonObject = JsonParser.ParseValue(charArray, ref index, ref success); + if (success) + { + result = simpleJsonObject; + return true; + } + return false; + } + + /// + /// Parses the JSON string representation. + /// + /// A JSON string to parse. + /// A equivalent to the passed string. + /// is not in the correct format. + public static JsonValue Parse(string json) + { + JsonValue result; + if (TryParse(json, out result)) + { + return result; + } + throw new JsonException(String.Format("Could not parse JSON string '{0}'.", json)); + } + + /// + /// Converts this instance to its JSON string representation. + /// + /// The JSON string representation of this instance. + public override string ToString() + { + StringBuilder builder = new StringBuilder(2000); + JsonGenerator.GenerateValue(this, builder); + return builder.ToString(); + } + + /// + /// Returns the value held by this instance converted to type . + /// + /// The type to convert the value to. + /// The value held by this instance. + /// If the value held by this instance could not be converted to type . + public T GetValue() + { + if (this.value == null) + { + return default(T); + } + if (this.value is T) + { + return (T)this.value; + } + Type conversionType = typeof(T); + // Manage Nullable types + if (conversionType.IsGenericType && + (conversionType.GetGenericTypeDefinition() == typeof(Nullable<>))) + { + conversionType = Nullable.GetUnderlyingType(conversionType); + } + try + { + return (T)Convert.ChangeType(this.value, conversionType, CultureInfo.InvariantCulture); + } + catch (Exception ex) + { + throw new JsonException(String.Format("Could not cast JSON {0} value to type '{1}'.", this.type.ToString().ToLower(), conversionType), ex); + } + } + + + /// + /// Returns the associated with the specified entry name + /// if the value held by this instance is a JSON object structure. + /// + /// The name of the entry that contains the value to get. + /// + /// The associated with the specified name + /// or if the entry does not exists. + /// + /// If the value held by this instance is not a JSON object structure. + public virtual JsonValue GetValue(string name) + { + throw new JsonException("The value held by this instance is not a JSON object structure."); + } + + /// + /// Returns the at the specified entry index + /// if the value held by this instance is a JSON array structure. + /// + /// The zero-based index of the entry that contains the value to get. + /// + /// The at the specified index + /// or if the entry does not exists. + /// + /// If the value held by this instance is not a JSON array structure. + public virtual JsonValue GetValue(int index) + { + throw new JsonException("The value held by this instance is not a JSON array structure."); + } + + /// + /// Returns the value associated with the specified entry name + /// if the value held by this instance is a JSON object structure. + /// + /// + /// Equivalent to: GetValue(name).Get<T>() + /// + /// The type to convert the value to. + /// The name of the entry that contains the value to get. + /// The value associated with the specified name. + /// + /// If the value held by this instance is not a JSON object structure or + /// if the entry with the specified name does not exists. + /// + public T GetValue(string name) + { + JsonValue jsonValue = this.GetValue(name); + if (jsonValue != null) + { + return jsonValue.GetValue(); + } + throw new JsonException(String.Format( + "The JSON object structure does not have an entry named '{0}'.", + name)); + } + + /// + /// Returns the value associated with the specified entry name, or the object's default value, + /// if the value held by this instance is a JSON object structure. + /// + /// The type to convert the value to. + /// The name of the entry that contains the value to get. + /// The value associated with the specified name. + /// + /// If the value held by this instance is not a JSON object structure. + /// + public T GetValueOrDefault(string name) + { + return this.GetValueOrDefault(name, default(T)); + } + + /// + /// Returns the value associated with the specified entry name, or the specified default value, + /// if the value held by this instance is a JSON object structure. + /// + /// The type to convert the value to. + /// The name of the entry that contains the value to get. + /// The default value to return if the entry does not exists. + /// The value associated with the specified name if the entry does not exists. + /// + /// If the value held by this instance is not a JSON object structure. + /// + public T GetValueOrDefault(string name, T defaultValue) + { + JsonValue jsonValue = this.GetValue(name); + if (jsonValue != null) + { + return jsonValue.GetValue(); + } + return defaultValue; + } + + /// + /// Returns the value at the specified entry index + /// if the value held by this instance is a JSON array structure. + /// + /// + /// Equivalent to: GetValue(index).Get<T>() + /// + /// The type to convert the value to. + /// The zero-based index of the entry that contains the value to get. + /// The value at the specified index. + /// + /// If the value held by this instance is not a JSON array structure or + /// if the entry at the specified index does not exists. + /// + public T GetValue(int index) + { + JsonValue jsonValue = this.GetValue(index); + if (jsonValue != null) + { + return jsonValue.GetValue(); + } + throw new JsonException(String.Format( + "The JSON array structure does not have an entry at index '{0}'.", + index)); + } + + /// + /// Returns the value at the specified entry index, or the object's default value, + /// if the value held by this instance is a JSON array structure. + /// + /// The type to convert the value to. + /// The zero-based index of the entry that contains the value to get. + /// The value at the specified index. + /// + /// If the value held by this instance is not a JSON array structure. + /// + public T GetValueOrDefault(int index) + { + return this.GetValueOrDefault(index, default(T)); + } + + /// + /// Returns the value at the specified entry index, or the specified default value, + /// if the value held by this instance is a JSON array structure. + /// + /// The type to convert the value to. + /// The zero-based index of the entry that contains the value to get. + /// The default value to return if the entry does not exists. + /// The value at the specified index. + /// + /// If the value held by this instance is not a JSON array structure. + /// + public T GetValueOrDefault(int index, T defaultValue) + { + JsonValue jsonValue = this.GetValue(index); + if (jsonValue != null) + { + return jsonValue.GetValue(); + } + return defaultValue; + } + + /// + /// Returns all s + /// if the value held by this instance is a JSON object or array structure. + /// + /// The collection of s. + /// If the value held by this instance is not a JSON object or array structure. + public virtual ICollection GetValues() + { + throw new JsonException("The value held by this instance is not a JSON object or array structure."); + } + + /// + /// Returns all s associated with the specified entry name + /// if the value held by this instance is a JSON object structure. + /// + /// + /// Equivalent to: GetValue(name).GetValues() + /// + /// The name of the entry that contains the values to get. + /// The collection of s. + /// + /// If the value held by this instance is not a JSON object structure or + /// if the entry with the specified name does not exists. + /// + public ICollection GetValues(string name) + { + JsonValue jsonValue = this.GetValue(name); + if (jsonValue != null) + { + return jsonValue.GetValues(); + } + throw new JsonException(String.Format( + "The JSON object structure does not have an entry named '{0}'.", + name)); + } + + /// + /// Returns all s at the specified entry index + /// if the value held by this instance is a JSON array structure. + /// + /// + /// Equivalent to: GetValue(index).GetValues() + /// + /// The zero-based index of the entry that contains the values to get. + /// The collection of s. + /// + /// If the value held by this instance is not a JSON array structure or + /// if the entry at the specified index does not exists. + /// + public ICollection GetValues(int index) + { + JsonValue jsonValue = this.GetValue(index); + if (jsonValue != null) + { + return jsonValue.GetValues(); + } + throw new JsonException(String.Format( + "The JSON array structure does not have an entry at index '{0}'.", + index)); + } + + /// + /// If the value held by this instance is a JSON object structure, + /// indicates whether or not it contains the specified entry name. + /// + /// The name of the entry to search for. + /// + /// if this JSON object contains the specified entry name; otherwise, . + /// + /// If the value held by this instance is not a JSON object structure. + public virtual bool ContainsName(string name) + { + throw new JsonException("The value held by this instance is not a JSON object structure."); + } + + /// + /// Returns all entry names + /// if the value held by this instance is a JSON object structure. + /// + /// The collection of entry names. + /// If the value held by this instance is not a JSON object structure. + public virtual ICollection GetNames() + { + throw new JsonException("The value held by this instance is not a JSON object structure."); + } + + #endregion + + #region JSON parsing & generation + + static class JsonParser + { + private const int TOKEN_NONE = 0; + private const int TOKEN_CURLY_OPEN = 1; + private const int TOKEN_CURLY_CLOSE = 2; + private const int TOKEN_SQUARED_OPEN = 3; + private const int TOKEN_SQUARED_CLOSE = 4; + private const int TOKEN_COLON = 5; + private const int TOKEN_COMMA = 6; + private const int TOKEN_STRING = 7; + private const int TOKEN_NUMBER = 8; + private const int TOKEN_TRUE = 9; + private const int TOKEN_FALSE = 10; + private const int TOKEN_NULL = 11; + + public static JsonValue ParseValue(char[] json, ref int index, ref bool success) + { + switch (LookAhead(json, index)) + { + case TOKEN_STRING: + return new JsonValue(ParseString(json, ref index, ref success)); + case TOKEN_NUMBER: + return new JsonValue(JsonValueType.Number, ParseNumber(json, ref index, ref success)); + case TOKEN_CURLY_OPEN: + return ParseObject(json, ref index, ref success); + case TOKEN_SQUARED_OPEN: + return ParseArray(json, ref index, ref success); + case TOKEN_TRUE: + NextToken(json, ref index); + return new JsonValue(true); + case TOKEN_FALSE: + NextToken(json, ref index); + return new JsonValue(false); + case TOKEN_NULL: + NextToken(json, ref index); + return new JsonValue(); + case TOKEN_NONE: + break; + } + + success = false; + return null; + } + + private static JsonObject ParseObject(char[] json, ref int index, ref bool success) + { + JsonObject jsonObject = new JsonObject(); + int token; + + // { + NextToken(json, ref index); + + bool done = false; + while (!done) + { + token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_CURLY_CLOSE) + { + NextToken(json, ref index); + return jsonObject; + } + else + { + // name + string name = ParseString(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + + // : + token = NextToken(json, ref index); + if (token != TOKEN_COLON) + { + success = false; + return null; + } + + // value + JsonValue value = ParseValue(json, ref index, ref success); + if (!success) + { + success = false; + return null; + } + + jsonObject.AddValue(name, value); + } + } + + return jsonObject; + } + + private static JsonArray ParseArray(char[] json, ref int index, ref bool success) + { + JsonArray jsonArray = new JsonArray(); + + // [ + NextToken(json, ref index); + + bool done = false; + while (!done) + { + int token = LookAhead(json, index); + if (token == TOKEN_NONE) + { + success = false; + return null; + } + else if (token == TOKEN_COMMA) + NextToken(json, ref index); + else if (token == TOKEN_SQUARED_CLOSE) + { + NextToken(json, ref index); + break; + } + else + { + JsonValue value = ParseValue(json, ref index, ref success); + if (!success) + return null; + jsonArray.AddValue(value); + } + } + + return jsonArray; + } + + private static string ParseString(char[] json, ref int index, ref bool success) + { + StringBuilder s = new StringBuilder(); + char c; + + EatWhitespace(json, ref index); + + // " + c = json[index++]; + + bool complete = false; + while (!complete) + { + if (index == json.Length) + { + break; + } + + c = json[index++]; + if (c == '"') + { + complete = true; + break; + } + else if (c == '\\') + { + if (index == json.Length) + break; + c = json[index++]; + if (c == '"') + s.Append('"'); + else if (c == '\\') + s.Append('\\'); + else if (c == '/') + s.Append('/'); + else if (c == 'b') + s.Append('\b'); + else if (c == 'f') + s.Append('\f'); + else if (c == 'n') + s.Append('\n'); + else if (c == 'r') + s.Append('\r'); + else if (c == 't') + s.Append('\t'); + else if (c == 'u') + { + int remainingLength = json.Length - index; + if (remainingLength >= 4) + { + // parse the 32 bit hex into an integer codepoint + uint codePoint; + if ( + !(success = + TryParseUInt32(new string(json, index, 4), NumberStyles.HexNumber, + CultureInfo.InvariantCulture, out codePoint))) + return ""; + + // convert the integer codepoint to a unicode char and add to string + if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate + { + index += 4; // skip 4 chars + remainingLength = json.Length - index; + if (remainingLength >= 6) + { + uint lowCodePoint; + if (new string(json, index, 2) == "\\u" && + TryParseUInt32(new string(json, index + 2, 4), NumberStyles.HexNumber, + CultureInfo.InvariantCulture, out lowCodePoint)) + { + if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate + { + s.Append((char)codePoint); + s.Append((char)lowCodePoint); + index += 6; // skip 6 chars + continue; + } + } + } + success = false; // invalid surrogate pair + return ""; + } + + s.Append(char.ConvertFromUtf32((int)codePoint)); + // skip 4 chars + index += 4; + } + else + break; + } + } + else + s.Append(c); + } + + if (!complete) + { + success = false; + return null; + } + + return s.ToString(); + } + + private static bool TryParseUInt32(string s, NumberStyles style, IFormatProvider provider, out uint result) + { + return uint.TryParse(s, style, provider, out result); + } + + + private static object ParseNumber(char[] json, ref int index, ref bool success) + { + EatWhitespace(json, ref index); + + int lastIndex = GetLastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + + string returnNumber = new string(json, index, charLength); + index = lastIndex + 1; + return returnNumber; + } + + private static int GetLastIndexOfNumber(char[] json, int index) + { + int lastIndex; + for (lastIndex = index; lastIndex < json.Length; lastIndex++) + { + if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) + { + break; + } + } + return lastIndex - 1; + } + + private static void EatWhitespace(char[] json, ref int index) + { + for (; index < json.Length; index++) + { + if (" \t\n\r\b\f".IndexOf(json[index]) == -1) + { + break; + } + } + } + + private static int LookAhead(char[] json, int index) + { + int saveIndex = index; + return NextToken(json, ref saveIndex); + } + + private static int NextToken(char[] json, ref int index) + { + EatWhitespace(json, ref index); + + if (index == json.Length) + { + return TOKEN_NONE; + } + + char c = json[index]; + index++; + switch (c) + { + case '{': + return TOKEN_CURLY_OPEN; + case '}': + return TOKEN_CURLY_CLOSE; + case '[': + return TOKEN_SQUARED_OPEN; + case ']': + return TOKEN_SQUARED_CLOSE; + case ',': + return TOKEN_COMMA; + case '"': + return TOKEN_STRING; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + return TOKEN_NUMBER; + case ':': + return TOKEN_COLON; + } + index--; + + int remainingLength = json.Length - index; + + // false + if (remainingLength >= 5) + { + if (json[index] == 'f' && + json[index + 1] == 'a' && + json[index + 2] == 'l' && + json[index + 3] == 's' && + json[index + 4] == 'e') + { + index += 5; + return TOKEN_FALSE; + } + } + + // true + if (remainingLength >= 4) + { + if (json[index] == 't' && + json[index + 1] == 'r' && + json[index + 2] == 'u' && + json[index + 3] == 'e') + { + index += 4; + return TOKEN_TRUE; + } + } + + // null + if (remainingLength >= 4) + { + if (json[index] == 'n' && + json[index + 1] == 'u' && + json[index + 2] == 'l' && + json[index + 3] == 'l') + { + index += 4; + return TOKEN_NULL; + } + } + + return TOKEN_NONE; + } + } + + static class JsonGenerator + { + public static void GenerateValue(JsonValue jsonValue, StringBuilder builder) + { + switch (jsonValue.type) + { + case JsonValueType.String: + { + GenerateString((string)jsonValue.value, builder); + break; + } + case JsonValueType.Object: + { + GenerateObject((IDictionary)jsonValue.value, builder); + break; + } + case JsonValueType.Array: + { + GenerateArray((IList)jsonValue.value, builder); + break; + } + case JsonValueType.Number: + { + GenerateNumber(jsonValue.value, builder); + break; + } + case JsonValueType.Boolean: + { + builder.Append((bool)jsonValue.value ? "true" : "false"); + break; + } + case JsonValueType.Null: + { + builder.Append("null"); + break; + } + } + } + + private static void GenerateObject(IDictionary jsonObject, StringBuilder builder) + { + builder.Append("{"); + + bool first = true; + foreach (KeyValuePair keyValuePair in jsonObject) + { + if (!first) + { + builder.Append(","); + } + GenerateString(keyValuePair.Key, builder); + builder.Append(":"); + GenerateValue(keyValuePair.Value, builder); + first = false; + } + + builder.Append("}"); + } + + private static void GenerateArray(IList jsonArray, StringBuilder builder) + { + builder.Append("["); + + bool first = true; + foreach (JsonValue value in jsonArray) + { + if (!first) + { + builder.Append(","); + } + GenerateValue(value, builder); + first = false; + } + + builder.Append("]"); + } + + private static void GenerateString(string jsonString, StringBuilder builder) + { + builder.Append("\""); + + char[] charArray = jsonString.ToCharArray(); + for (int i = 0; i < charArray.Length; i++) + { + char c = charArray[i]; + if (c == '"') + builder.Append("\\\""); + else if (c == '\\') + builder.Append("\\\\"); + else if (c == '\b') + builder.Append("\\b"); + else if (c == '\f') + builder.Append("\\f"); + else if (c == '\n') + builder.Append("\\n"); + else if (c == '\r') + builder.Append("\\r"); + else if (c == '\t') + builder.Append("\\t"); + else + builder.Append(c); + } + + builder.Append("\""); + } + + private static void GenerateNumber(object jsonNumber, StringBuilder builder) + { + builder.Append(Convert.ToString(jsonNumber, CultureInfo.InvariantCulture)); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Rest/Client/HttpClientErrorException.cs b/netstandard/Spring.Rest/Rest/Client/HttpClientErrorException.cs new file mode 100644 index 0000000..3d20cbb --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/HttpClientErrorException.cs @@ -0,0 +1,64 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +using Spring.Http; + +namespace Spring.Rest.Client +{ + /// + /// Exception thrown when an HTTP 4xx is received. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpClientErrorException : HttpResponseException + { + /// + /// Creates a new instance of + /// based on an HTTP response message. + /// + /// The HTTP request URI. + /// The HTTP request method. + /// The HTTP response message. + public HttpClientErrorException(Uri requestUri, HttpMethod requestMethod, HttpResponseMessage response) + : base (requestUri, requestMethod, response) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpClientErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/HttpResponseException.cs b/netstandard/Spring.Rest/Rest/Client/HttpResponseException.cs new file mode 100644 index 0000000..f59e4b5 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/HttpResponseException.cs @@ -0,0 +1,145 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Runtime.Serialization; +using System.Security.Permissions; + +using Spring.Http; + +namespace Spring.Rest.Client +{ + /// + /// Base class for exceptions based on a response message. + /// + /// Bruno Baia + [Serializable] + public class HttpResponseException : RestClientException + { + /// + /// Default encoding for responses as string. + /// + protected static readonly Encoding DEFAULT_CHARSET = Encoding.GetEncoding("ISO-8859-1"); + private Uri requestUri; + private HttpMethod requestMethod; + private HttpResponseMessage response; + + /// + /// Gets the HTTP request URI. + /// + public Uri RequestUri + { + get { return this.requestUri; } + } + + /// + /// Gets the HTTP request method. + /// + public HttpMethod RequestMethod + { + get { return this.requestMethod; } + } + + /// + /// Gets the HTTP response message. + /// + public HttpResponseMessage Response + { + get { return this.response; } + } + + /// + /// Creates a new instance of + /// based on an HTTP response message. + /// + /// The HTTP request URI. + /// The HTTP request method. + /// The HTTP response message. + public HttpResponseException(Uri requestUri, HttpMethod requestMethod, HttpResponseMessage response) + : base(string.Format("{0} request for '{1}' resulted in {2:d} - {2} ({3}).", requestMethod, requestUri, response.StatusCode, response.StatusDescription)) + { + this.requestUri = requestUri; + this.requestMethod = requestMethod; + this.response = response; + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpResponseException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + if (info != null) + { + this.requestUri = (Uri)info.GetValue("RequestUri", typeof(Uri)); + this.requestMethod = (HttpMethod)info.GetValue("RequestMethod", typeof(HttpMethod)); + this.response = (HttpResponseMessage)info.GetValue("Response", typeof(HttpResponseMessage)); + } + } + + /// + /// Populates the with + /// information about the exception. + /// + /// + /// The that holds + /// the serialized object data about the exception being thrown. + /// + /// + /// The that contains contextual + /// information about the source or destination. + /// + [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)] + public override void GetObjectData( + SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + if (info != null) + { + info.AddValue("RequestUri", this.requestUri); + info.AddValue("RequestMethod", this.requestMethod); + info.AddValue("Response", this.response); + } + } + /// + /// Returns the response body as a string. + /// + /// The response body. + public string GetResponseBodyAsString() + { + if (this.response.Body == null) + { + return null; + } + MediaType contentType = this.response.Headers.ContentType; + Encoding charset = (contentType != null && contentType.CharSet != null) ? contentType.CharSet : DEFAULT_CHARSET; + return charset.GetString(this.response.Body, 0, this.response.Body.Length); + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/HttpServerErrorException.cs b/netstandard/Spring.Rest/Rest/Client/HttpServerErrorException.cs new file mode 100644 index 0000000..1d5eab5 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/HttpServerErrorException.cs @@ -0,0 +1,64 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +using Spring.Http; + +namespace Spring.Rest.Client +{ + /// + /// Exception thrown when an HTTP 5xx is received. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class HttpServerErrorException : HttpResponseException + { + /// + /// Creates a new instance of + /// based on an HTTP response message. + /// + /// The HTTP request URI. + /// The HTTP request method. + /// The HTTP response message. + public HttpServerErrorException(Uri requestUri, HttpMethod requestMethod, HttpResponseMessage response) + : base(requestUri, requestMethod, response) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected HttpServerErrorException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/IRequestCallback.cs b/netstandard/Spring.Rest/Rest/Client/IRequestCallback.cs new file mode 100644 index 0000000..ff0dab0 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/IRequestCallback.cs @@ -0,0 +1,54 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http.Client; + +namespace Spring.Rest.Client +{ + /// + /// Callback interface for code that operates on a . + /// Allows to manipulate the request headers, and write to the request body. + /// + /// + /// + /// Callback interface used by 's senders methods. + /// Implementations of this interface perform the actual work of writing data + /// to a , but don't need to worry about exception + /// handling or closing resources. + /// + /// + /// Used internally by the , but also useful for application code. + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IRequestCallback + { + /// + /// Gets called by with an to write data. + /// + /// + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + void DoWithRequest(IClientHttpRequest request); + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/IResponseErrorHandler.cs b/netstandard/Spring.Rest/Rest/Client/IResponseErrorHandler.cs new file mode 100644 index 0000000..839df6b --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/IResponseErrorHandler.cs @@ -0,0 +1,59 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client +{ + /// + /// Strategy interface used by the to determine + /// whether a particular response has an error or not. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IResponseErrorHandler + { + /// + /// Indicates whether the given response has any errors. + /// + /// Implementations will typically inspect the status code of the response. + /// + /// The request URI. + /// The request method. + /// The response to inspect. + /// + /// if the response has an error; otherwise . + /// + bool HasError(Uri requestUri, HttpMethod requestMethod, IClientHttpResponse response); + + /// + /// Handles the error in the given response. + /// + /// This method is only called when HasError() method has returned . + /// + /// The request URI. + /// The request method. + /// The response with the error + void HandleError(Uri requestUri, HttpMethod requestMethod, IClientHttpResponse response); + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/IResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/IResponseExtractor.cs new file mode 100644 index 0000000..841fc3c --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/IResponseExtractor.cs @@ -0,0 +1,52 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http.Client; + +namespace Spring.Rest.Client +{ + /// + /// Callback interface for code that operates on a . + /// Allows to manipulate the response headers, and extract the response body. + /// + /// + /// + /// Generic callback interface used by 's retrieval methods. + /// Implementations of this interface perform the actual work of extracting data + /// from a , but don't need to worry about exception + /// handling or closing resources. + /// + /// + /// Used internally by the , but also useful for application code. + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public interface IResponseExtractor where T : class + { + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + T ExtractData(IClientHttpResponse response); + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/IRestOperations.cs b/netstandard/Spring.Rest/Rest/Client/IRestOperations.cs new file mode 100644 index 0000000..8d16d80 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/IRestOperations.cs @@ -0,0 +1,1169 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + + +using Spring.Http; + +namespace Spring.Rest.Client +{ + /// + /// Interface specifying a basic set of RESTful operations. + /// + /// + /// Not often used directly, but a useful option to enhance testability, + /// as it can easily be mocked or stubbed. + /// + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + public partial interface IRestOperations + { + #region Synchronous operations + + #region GET + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// The converted object + T GetForObject(string url, params object[] uriVariables) where T : class; + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// The converted object + T GetForObject(string url, IDictionary uriVariables) where T : class; + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// The type of the response value. + /// The URL. + /// The converted object + T GetForObject(Uri url) where T : class; + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// The HTTP response message. + HttpResponseMessage GetForMessage(string url, params object[] uriVariables) where T : class; + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + HttpResponseMessage GetForMessage(string url, IDictionary uriVariables) where T : class; + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP response message. + HttpResponseMessage GetForMessage(Uri url) where T : class; + + #endregion + + #region HEAD + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// All HTTP headers of that resource + HttpHeaders HeadForHeaders(string url, params object[] uriVariables); + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// All HTTP headers of that resource + HttpHeaders HeadForHeaders(string url, IDictionary uriVariables); + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// All HTTP headers of that resource + HttpHeaders HeadForHeaders(Uri url); + + #endregion + + #region POST + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The value for the Location header. + Uri PostForLocation(string url, object request, params object[] uriVariables); + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The value for the Location header. + Uri PostForLocation(string url, object request, IDictionary uriVariables); + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The value for the Location header. + Uri PostForLocation(Uri url, object request); + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The converted object. + T PostForObject(string url, object request, params object[] uriVariables) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The converted object. + T PostForObject(string url, object request, IDictionary uriVariables) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The converted object. + T PostForObject(Uri url, object request) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The HTTP response message. + HttpResponseMessage PostForMessage(string url, object request, params object[] uriVariables) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The HTTP response message. + HttpResponseMessage PostForMessage(Uri url, object request) where T : class; + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The HTTP response message with no entity. + HttpResponseMessage PostForMessage(string url, object request, params object[] uriVariables); + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables); + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The HTTP response message with no entity. + HttpResponseMessage PostForMessage(Uri url, object request); + + #endregion + + #region PUT + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + void Put(string url, object request, params object[] uriVariables); + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + void Put(string url, object request, IDictionary uriVariables); + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + void Put(Uri url, object request); + + #endregion + + #region DELETE + + /// + /// Delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + void Delete(string url, params object[] uriVariables); + + /// + /// Delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + void Delete(string url, IDictionary uriVariables); + + /// + /// Delete the resources at the specified URI. + /// + /// The URL. + void Delete(Uri url); + + #endregion + + #region OPTIONS + + /// + /// Return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// The value of the allow header. + IList OptionsForAllow(string url, params object[] uriVariables); + + /// + /// Return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// The value of the allow header. + IList OptionsForAllow(string url, IDictionary uriVariables); + + /// + /// Return the value of the Allow header for the given URI. + /// + /// The URL. + /// The value of the allow header. + IList OptionsForAllow(Uri url); + + #endregion + + + #region Exchange + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The variables to expand the template. + /// The HTTP response message. + HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, params object[] uriVariables) where T : class; + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables) where T : class; + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The HTTP response message. + HttpResponseMessage Exchange(Uri url, HttpMethod method, HttpEntity requestEntity) where T : class; + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The variables to expand the template. + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, params object[] uriVariables); + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables); + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(Uri url, HttpMethod method, HttpEntity requestEntity); + + #endregion + + #region General execution + + /// + /// Execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The variables to expand the template. + /// An arbitrary object, as returned by the . + T Execute(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, params object[] uriVariables) where T : class; + + /// + /// Execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The dictionary containing variables for the URI template. + /// An arbitrary object, as returned by the . + T Execute(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables) where T : class; + + /// + /// Execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// An arbitrary object, as returned by the . + T Execute(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class; + + #endregion + + #endregion + + + + #region Asynchronous operations (TPL) + + #region GET + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task GetForObjectAsync(string url, params object[] uriVariables) where T : class; + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task GetForObjectAsync(string url, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// The type of the response value. + /// The URL. + /// A Task<T> that represents the asynchronous operation. + Task GetForObjectAsync(Uri url) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task> GetForMessageAsync(string url, params object[] uriVariables) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task> GetForMessageAsync(string url, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// A Task<T> that represents the asynchronous operation. + Task> GetForMessageAsync(Uri url) where T : class; + + #endregion + + #region HEAD + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task HeadForHeadersAsync(string url, params object[] uriVariables); + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task HeadForHeadersAsync(string url, IDictionary uriVariables); + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + Task HeadForHeadersAsync(Uri url); + + #endregion + + #region POST + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task PostForLocationAsync(string url, object request, params object[] uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task PostForLocationAsync(string url, object request, IDictionary uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + Task PostForLocationAsync(Uri url, object request); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task PostForObjectAsync(string url, object request, params object[] uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task PostForObjectAsync(string url, object request, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + Task PostForObjectAsync(Uri url, object request) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task> PostForMessageAsync(string url, object request, params object[] uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task> PostForMessageAsync(string url, object request, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + Task> PostForMessageAsync(Uri url, object request) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task PostForMessageAsync(string url, object request, params object[] uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task PostForMessageAsync(string url, object request, IDictionary uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + Task PostForMessageAsync(Uri url, object request); + + #endregion + + #region PUT + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task PutAsync(string url, object request, params object[] uriVariables); + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task PutAsync(string url, object request, IDictionary uriVariables); + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + Task PutAsync(Uri url, object request); + + #endregion + + #region DELETE + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task DeleteAsync(string url, params object[] uriVariables); + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task DeleteAsync(string url, IDictionary uriVariables); + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + Task DeleteAsync(Uri url); + + #endregion + + #region OPTIONS + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task> OptionsForAllowAsync(string url, params object[] uriVariables); + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task> OptionsForAllowAsync(string url, IDictionary uriVariables); + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + Task> OptionsForAllowAsync(Uri url); + + #endregion + + + #region Exchange + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task> ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, params object[] uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task> ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + Task> ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, params object[] uriVariables); + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, IDictionary uriVariables); + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + Task ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken); + + #endregion + + #region General execution + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + Task ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken, params object[] uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + Task ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken, IDictionary uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + Task ExecuteAsync(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken) where T : class; + + #endregion + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Obsolete/IRestOperations.ObsoleteAsync.cs b/netstandard/Spring.Rest/Rest/Client/Obsolete/IRestOperations.ObsoleteAsync.cs new file mode 100644 index 0000000..e861128 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Obsolete/IRestOperations.ObsoleteAsync.cs @@ -0,0 +1,765 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Spring.Http; + +namespace Spring.Rest.Client +{ + public partial interface IRestOperations + { + #region Asynchronous operations + + #region GET + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForObjectAsync(string url, Action> getCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForObjectAsync(string url, IDictionary uriVariables, Action> getCompleted) where T : class; + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForObjectAsync(Uri url, Action> getCompleted) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForMessageAsync(string url, Action>> getCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForMessageAsync(string url, IDictionary uriVariables, Action>> getCompleted) where T : class; + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler GetForMessageAsync(Uri url, Action>> getCompleted) where T : class; + + #endregion + + #region HEAD + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler HeadForHeadersAsync(string url, Action> headCompleted, params object[] uriVariables); + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler HeadForHeadersAsync(string url, IDictionary uriVariables, Action> headCompleted); + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler HeadForHeadersAsync(Uri url, Action> headCompleted); + + #endregion + + #region POST + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForLocationAsync(string url, object request, Action> postCompleted, params object[] uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForLocationAsync(string url, object request, IDictionary uriVariables, Action> postCompleted); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForLocationAsync(Uri url, object request, Action> postCompleted); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForObjectAsync(string url, object request, Action> postCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForObjectAsync(string url, object request, IDictionary uriVariables, Action> postCompleted) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForObjectAsync(Uri url, object request, Action> postCompleted) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(string url, object request, Action>> postCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(string url, object request, IDictionary uriVariables, Action>> postCompleted) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(Uri url, object request, Action>> postCompleted) where T : class; + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(string url, object request, Action> postCompleted, params object[] uriVariables); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(string url, object request, IDictionary uriVariables, Action> postCompleted); + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PostForMessageAsync(Uri url, object request, Action> postCompleted); + + #endregion + + #region PUT + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PutAsync(string url, object request, Action> putCompleted, params object[] uriVariables); + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PutAsync(string url, object request, IDictionary uriVariables, Action> putCompleted); + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler PutAsync(Uri url, object request, Action> putCompleted); + + #endregion + + #region DELETE + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler DeleteAsync(string url, Action> deleteCompleted, params object[] uriVariables); + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler DeleteAsync(string url, IDictionary uriVariables, Action> deleteCompleted); + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler DeleteAsync(Uri url, Action> deleteCompleted); + + #endregion + + #region OPTIONS + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler OptionsForAllowAsync(string url, Action>> optionsCompleted, params object[] uriVariables); + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler OptionsForAllowAsync(string url, IDictionary uriVariables, Action>> optionsCompleted); + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler OptionsForAllowAsync(Uri url, Action>> optionsCompleted); + + #endregion + + + #region Exchange + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, Action>> methodCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables, Action>> methodCompleted) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, Action>> methodCompleted) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, Action> methodCompleted, params object[] uriVariables); + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables, Action> methodCompleted); + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, Action> methodCompleted); + + #endregion + + #region General execution + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, Action> methodCompleted, params object[] uriVariables) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables, Action> methodCompleted) where T : class; + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + RestOperationCanceler ExecuteAsync(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, Action> methodCompleted) where T : class; + + #endregion + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Obsolete/RestTemplate.ObsoleteAsync.cs b/netstandard/Spring.Rest/Rest/Client/Obsolete/RestTemplate.ObsoleteAsync.cs new file mode 100644 index 0000000..e7beeb0 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Obsolete/RestTemplate.ObsoleteAsync.cs @@ -0,0 +1,943 @@ +using System; +using System.Collections.Generic; +using Spring.Http; +using Spring.Rest.Client.Support; + + +namespace Spring.Rest.Client +{ + public partial class RestTemplate + { + #region Asynchronous operations + + #region GET + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForObjectAsync(string url, Action> getCompleted, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, getCompleted, uriVariables); + } + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForObjectAsync(string url, IDictionary uriVariables, Action> getCompleted) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables, getCompleted); + } + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForObjectAsync(Uri url, Action> getCompleted) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, getCompleted); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForMessageAsync(string url, Action>> getCompleted, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, getCompleted, uriVariables); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForMessageAsync(string url, IDictionary uriVariables, Action>> getCompleted) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables, getCompleted); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// + /// The Action<T> to perform when the asynchronous GET method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler GetForMessageAsync(Uri url, Action>> getCompleted) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, getCompleted); + } + + #endregion + + #region HEAD + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler HeadForHeadersAsync(string url, Action> headCompleted, params object[] uriVariables) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, headCompleted, uriVariables); + } + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler HeadForHeadersAsync(string url, IDictionary uriVariables, Action> headCompleted) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, uriVariables, headCompleted); + } + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous HEAD method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler HeadForHeadersAsync(Uri url, Action> headCompleted) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, headCompleted); + } + + #endregion + + #region POST + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForLocationAsync(string url, object request, Action> postCompleted, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForLocationAsync(string url, object request, IDictionary uriVariables, Action> postCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForLocationAsync(Uri url, object request, Action> postCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForObjectAsync(string url, object request, Action> postCompleted, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForObjectAsync(string url, object request, IDictionary uriVariables, Action> postCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForObjectAsync(Uri url, object request, Action> postCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(string url, object request, Action>> postCompleted, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(string url, object request, IDictionary uriVariables, Action>> postCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(Uri url, object request, Action>> postCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(string url, object request, Action> postCompleted, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(string url, object request, IDictionary uriVariables, Action> postCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables, postCompleted); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous POST method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PostForMessageAsync(Uri url, object request, Action> postCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, postCompleted); + } + + #endregion + + #region PUT + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PutAsync(string url, object request, Action> putCompleted, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, putCompleted, uriVariables); + } + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PutAsync(string url, object request, IDictionary uriVariables, Action> putCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, uriVariables, putCompleted); + } + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler PutAsync(Uri url, object request, Action> putCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, putCompleted); + } + + #endregion + + #region DELETE + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler DeleteAsync(string url, Action> deleteCompleted, params object[] uriVariables) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, deleteCompleted, uriVariables); + } + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler DeleteAsync(string url, IDictionary uriVariables, Action> deleteCompleted) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, uriVariables, deleteCompleted); + } + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous PUT method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler DeleteAsync(Uri url, Action> deleteCompleted) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, deleteCompleted); + } + + #endregion + + #region OPTIONS + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler OptionsForAllowAsync(string url, Action>> optionsCompleted, params object[] uriVariables) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, optionsCompleted, uriVariables); + } + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler OptionsForAllowAsync(string url, IDictionary uriVariables, Action>> optionsCompleted) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, uriVariables, optionsCompleted); + } + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// The URL. + /// + /// The Action<T> to perform when the asynchronous OPTIONS method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler OptionsForAllowAsync(Uri url, Action>> optionsCompleted) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, optionsCompleted); + } + + #endregion + + + #region Exchange + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, Action>> methodCompleted, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, methodCompleted, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables, Action>> methodCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, uriVariables, methodCompleted); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, Action>> methodCompleted) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, methodCompleted); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, Action> methodCompleted, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, methodCompleted, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables, Action> methodCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, uriVariables, methodCompleted); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, Action> methodCompleted) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, methodCompleted); + } + + #endregion + + #region General execution + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// The variables to expand the template. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, Action> methodCompleted, params object[] uriVariables) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url, uriVariables), method, + requestCallback, responseExtractor, methodCompleted); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The dictionary containing variables for the URI template. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables, Action> methodCompleted) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url, uriVariables), method, + requestCallback, responseExtractor, methodCompleted); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public RestOperationCanceler ExecuteAsync(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, Action> methodCompleted) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url), method, + requestCallback, responseExtractor, methodCompleted); + } + + #endregion + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/RestClientException.cs b/netstandard/Spring.Rest/Rest/Client/RestClientException.cs new file mode 100644 index 0000000..76500a4 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/RestClientException.cs @@ -0,0 +1,82 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Runtime.Serialization; + +namespace Spring.Rest.Client +{ + /// + /// Base class for exceptions thrown by whenever it encounters client-side HTTP errors. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + [Serializable] + public class RestClientException : Exception + { + /// + /// Creates a new instance of the class. + /// + public RestClientException() + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + public RestClientException(string message) + : base(message) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// A message about the exception. + /// + /// + /// The root exception that is being wrapped. + /// + public RestClientException(string message, Exception rootCause) + : base(message, rootCause) + { + } + + /// + /// Creates a new instance of the class. + /// + /// + /// The + /// that holds the serialized object data about the exception being thrown. + /// + /// + /// The + /// that contains contextual information about the source or destination. + /// + protected RestClientException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/RestOperationCanceler.cs b/netstandard/Spring.Rest/Rest/Client/RestOperationCanceler.cs new file mode 100644 index 0000000..07e6889 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/RestOperationCanceler.cs @@ -0,0 +1,82 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client +{ + /// + /// Cancels a pending REST asynchronous operation. + /// + public class RestOperationCanceler + { + private Uri uri; + private HttpMethod method; + private IClientHttpRequest request; + + internal RestOperationCanceler(Uri uri, HttpMethod method) + { + this.uri = uri; + this.method = method; + } + + internal RestOperationCanceler(IClientHttpRequest request) + { + this.request = request; + } + + /// + /// Gets the HTTP method of the request. + /// + public HttpMethod Method + { + get + { + return this.request != null ? this.request.Method : this.method; + } + } + + /// + /// Gets the URI of the request. + /// + public Uri Uri + { + get + { + return this.request != null ? this.request.Uri : this.uri; + } + } + + /// + /// Cancels a pending asynchronous operation. + /// + public void Cancel() + { + if (this.request != null) + { + this.request.CancelAsync(); + } + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/RestOperationCompletedEventArgs.cs b/netstandard/Spring.Rest/Rest/Client/RestOperationCompletedEventArgs.cs new file mode 100644 index 0000000..f6037fd --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/RestOperationCompletedEventArgs.cs @@ -0,0 +1,67 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.ComponentModel; + +namespace Spring.Rest.Client +{ + /// + /// Provides data when an asynchronous REST operation completes. + /// + /// The type of the response value. + /// + /// + public class RestOperationCompletedEventArgs : AsyncCompletedEventArgs + { + private T response; + + /// + /// Gest the response of the REST operation. + /// + /// If the operation was canceled. + /// If the operation failed. + public T Response + { + get + { + // Raise an exception if the operation failed or was canceled. + base.RaiseExceptionIfNecessary(); + + // If the operation was successful, return the value. + return response; + } + } + + /// + /// Creates a new instance of . + /// + /// The response of the REST operation. + /// Any error that occurred during the asynchronous operation. + /// A value indicating whether the asynchronous operation was canceled. + /// The optional user-supplied state object. + public RestOperationCompletedEventArgs(T response, Exception exception, bool cancelled, object userState) + : base(exception, cancelled, userState) + { + this.response = response; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Rest/Client/RestTemplate.cs b/netstandard/Spring.Rest/Rest/Client/RestTemplate.cs new file mode 100644 index 0000000..2c0db56 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/RestTemplate.cs @@ -0,0 +1,2004 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Client.Interceptor; +using Spring.Http.Converters; +using Spring.Http.Converters.Xml; + +using Spring.Http.Converters.Json; + +using Spring.Rest.Client.Support; +using UriTemplate = Spring.Util.UriTemplate; // UriTemplate exists in .NET Framework since 3.5 +using ArgumentUtils = Spring.Util.ArgumentUtils; + +namespace Spring.Rest.Client +{ + /// + /// The central class for client-side HTTP access. + /// It simplifies communication with HTTP servers, and enforces RESTful principles. + /// It handles HTTP connections, leaving application code to provide URLs (with possible template variables) and extract results. + /// + /// + /// + /// The main entry points of this template are the methods named after the six main HTTP methods: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + ///
HTTP methodRestTemplate methods
DELETE
GET
HEAD
OPTIONS
POST
PUT
Any
+ ///
+ /// + /// For each of these HTTP methods, there are three corresponding Java methods in the . + /// Two variant take a string URI as first argument and are capable of substituting any URI templates in + /// that URL using either a string variable arguments array, or a string dictionary. + /// The string varargs variant expands the given template variables in order, so that + /// + /// string result = restTemplate.GetForObject<string>("http://example.com/hotels/{hotel}/bookings/{booking}", 42, 21); + /// + /// will perform a GET on 'http://example.com/hotels/42/bookings/21'. The map variant expands the template based on + /// variable name, and is therefore more useful when using many variables, or when a single variable is used multiple + /// times. For example: + /// + /// IDictionary<string, object> vars = new Dictionary<string, object>(); + /// vars.Add("hotel", 42); + /// string result = restTemplate.GetForObject<string>("http://example.com/hotels/{hotel}/rooms/{hotel}", vars); + /// + /// will perform a GET on 'http://example.com/hotels/42/rooms/42'. Alternatively, there are URI variant + /// methods, which do not allow for URI templates, but allow you to reuse a single, expanded URI multiple times. + /// + /// + /// Furthermore, the string-argument methods assume that the URL String is unencoded. This means that + /// + /// restTemplate.GetForObject<string>("http://example.com/hotel list"); + /// + /// will perform a GET on 'http://example.com/hotel%20list'. + /// + /// + /// Objects passed to and returned from these methods are converted to and from HTTP messages by + /// instances. Converters for the main mime types are registered by default, + /// but you can also write your own converter and register it via the property. + /// + /// + /// This template uses a and a + /// as default strategies for creating HTTP connections or handling HTTP errors, respectively. + /// These defaults can be overridden through the and properties. + /// + ///
+ /// + /// + /// + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public partial class RestTemplate : IRestOperations + { + #region Logging +#if !SILVERLIGHT && !CF_3_5 + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(RestTemplate)); +#endif + #endregion + + #region Fields / Properties + + private Uri _baseAddress; + private IList _messageConverters; + private IClientHttpRequestFactory _requestFactory; + private IResponseErrorHandler _errorHandler; + private IList _requestInterceptors; + + /// + /// Gets or sets the base URL for the request. + /// + public Uri BaseAddress + { + get + { + return this._baseAddress; + } + set + { + ArgumentUtils.AssertNotNull(value, "BaseAddress"); + if (!value.IsAbsoluteUri) + { + throw new ArgumentException(String.Format("'{0}' is not an absolute URI", value), "BaseAddress"); + } + this._baseAddress = value; + } + } + + /// + /// Gets or sets the message converters. + /// These converters are used to convert from and to HTTP request and response messages. + /// + public IList MessageConverters + { + get { return this._messageConverters; } + set { this._messageConverters = value; } + } + + /// + /// Gets or sets the request factory that this class uses for obtaining for objects. + /// + /// + /// Default value is . + /// + public IClientHttpRequestFactory RequestFactory + { + get { return this._requestFactory; } + set { this._requestFactory = value; } + } + + /// + /// Gets or sets the error handler. + /// + /// + /// Default value is . + /// + public IResponseErrorHandler ErrorHandler + { + get { return this._errorHandler; } + set { this._errorHandler = value; } + } + + /// + /// Gets or sets the request interceptors. + /// + public IList RequestInterceptors + { + get { return this._requestInterceptors; } + set { this._requestInterceptors = value; } + } + + #endregion + + #region Constructor(s) + + /// + /// Creates a new instance of . + /// + /// The base address to use. + public RestTemplate(Uri baseAddress) : + this() + { + this.BaseAddress = baseAddress; + } + + /// + /// Creates a new instance of . + /// + /// The base address to use. + public RestTemplate(string baseAddress) : + this() + { + this.BaseAddress = new Uri(baseAddress, UriKind.Absolute); + } + + /// + /// Creates a new instance of . + /// + public RestTemplate() + { + this._requestFactory = new WebClientHttpRequestFactory(); + this._errorHandler = new DefaultResponseErrorHandler(); + this._requestInterceptors = new List(); + + this._messageConverters = new List(); + + this._messageConverters.Add(new ByteArrayHttpMessageConverter()); + this._messageConverters.Add(new StringHttpMessageConverter()); + this._messageConverters.Add(new FormHttpMessageConverter()); + this._messageConverters.Add(new XmlDocumentHttpMessageConverter()); + } + + #endregion + + #region IRestOperations Members + + #region Synchronous operations + + #region GET + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// The converted object + public T GetForObject(string url, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// The converted object + public T GetForObject(string url, IDictionary uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted and returned. + /// + /// The type of the response value. + /// The URL. + /// The converted object + public T GetForObject(Uri url) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.GET, requestCallback, responseExtractor); + } + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// The HTTP response message. + public HttpResponseMessage GetForMessage(string url, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + public HttpResponseMessage GetForMessage(string url, IDictionary uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.GET, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP response message. + public HttpResponseMessage GetForMessage(Uri url) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.GET, requestCallback, responseExtractor); + } + + #endregion + + #region HEAD + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// All HTTP headers of that resource + public HttpHeaders HeadForHeaders(string url, params object[] uriVariables) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.Execute(url, HttpMethod.HEAD, null, responseExtractor, uriVariables); + } + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// All HTTP headers of that resource + public HttpHeaders HeadForHeaders(string url, IDictionary uriVariables) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.Execute(url, HttpMethod.HEAD, null, responseExtractor, uriVariables); + } + + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// All HTTP headers of that resource + public HttpHeaders HeadForHeaders(Uri url) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.Execute(url, HttpMethod.HEAD, null, responseExtractor); + } + + #endregion + + #region POST + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The value for the Location header. + public Uri PostForLocation(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The value for the Location header. + public Uri PostForLocation(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The value for the Location header. + public Uri PostForLocation(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The converted object. + public T PostForObject(string url, object request, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The converted object. + public T PostForObject(string url, object request, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The converted object. + public T PostForObject(Uri url, object request) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The HTTP response message. + public HttpResponseMessage PostForMessage(string url, object request, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + public HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The HTTP response message. + public HttpResponseMessage PostForMessage(Uri url, object request) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, HttpMethod.POST, requestCallback, responseExtractor); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, HttpMethod.POST, requestCallback, responseExtractor); + } + + #endregion + + #region PUT + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + public void Put(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + this.Execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); + } + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + public void Put(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + this.Execute(url, HttpMethod.PUT, requestCallback, null, uriVariables); + } + + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + public void Put(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + this.Execute(url, HttpMethod.PUT, requestCallback, null); + } + + #endregion + + #region DELETE + + /// + /// Delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + public void Delete(string url, params object[] uriVariables) + { + this.Execute(url, HttpMethod.DELETE, null, null, uriVariables); + } + + /// + /// Delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + public void Delete(string url, IDictionary uriVariables) + { + this.Execute(url, HttpMethod.DELETE, null, null, uriVariables); + } + + /// + /// Delete the resources at the specified URI. + /// + /// The URL. + public void Delete(Uri url) + { + this.Execute(url, HttpMethod.DELETE, null, null); + } + + #endregion + + #region OPTIONS + + /// + /// Return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// The value of the allow header. + public IList OptionsForAllow(string url, params object[] uriVariables) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.Execute>(url, HttpMethod.OPTIONS, null, responseExtractor, uriVariables); + } + + /// + /// Return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// The value of the allow header. + public IList OptionsForAllow(string url, IDictionary uriVariables) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.Execute>(url, HttpMethod.OPTIONS, null, responseExtractor, uriVariables); + } + + /// + /// Return the value of the Allow header for the given URI. + /// + /// The URL. + /// The value of the allow header. + public IList OptionsForAllow(Uri url) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.Execute>(url, HttpMethod.OPTIONS, null, responseExtractor); + } + + #endregion + + + #region Exchange + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The variables to expand the template. + /// The HTTP response message. + public HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, method, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + public HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, method, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The HTTP response message. + public HttpResponseMessage Exchange(Uri url, HttpMethod method, HttpEntity requestEntity) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.Execute>(url, method, requestCallback, responseExtractor); + } + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The variables to expand the template. + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, method, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(string url, HttpMethod method, HttpEntity requestEntity, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, method, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(Uri url, HttpMethod method, HttpEntity requestEntity) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, method, requestCallback, responseExtractor); + } + + #endregion + + #region General execution + + /// + /// Execute the HTTP request to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The variables to expand the template. + /// An arbitrary object, as returned by the . + public T Execute(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, params object[] uriVariables) where T : class + { + return this.DoExecute(this.BuildUri(this._baseAddress, url, uriVariables), method, requestCallback, responseExtractor); + } + + /// + /// Execute the HTTP request to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The dictionary containing variables for the URI template. + /// An arbitrary object, as returned by the . + public T Execute(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables) where T : class + { + return this.DoExecute(this.BuildUri(this._baseAddress, url, uriVariables), method, requestCallback, responseExtractor); + } + + /// + /// Execute the HTTP request to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// An arbitrary object, as returned by the . + public T Execute(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class + { + return this.DoExecute(this.BuildUri(this._baseAddress, url), method, requestCallback, responseExtractor); + } + + #endregion + + + #endregion + + + + + #region Asynchronous operations (TPL) + + + #region GET + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task GetForObjectAsync(string url, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task GetForObjectAsync(string url, IDictionary uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve a representation by doing a GET on the specified URL. + /// The response (if any) is converted. + /// + /// The type of the response value. + /// The URL. + /// A Task<T> that represents the asynchronous operation. + public Task GetForObjectAsync(Uri url) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task> GetForMessageAsync(string url, params object[] uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task> GetForMessageAsync(string url, IDictionary uriVariables) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve an entity by doing a GET on the specified URL. + /// The response is converted and stored in an . + /// + /// The type of the response value. + /// The URL. + /// A Task<T> that represents the asynchronous operation. + public Task> GetForMessageAsync(Uri url) where T : class + { + AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.GET, requestCallback, responseExtractor, CancellationToken.None); + } + + #endregion + + #region HEAD + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task HeadForHeadersAsync(string url, params object[] uriVariables) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task HeadForHeadersAsync(string url, IDictionary uriVariables) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + public Task HeadForHeadersAsync(Uri url) + { + HeadersResponseExtractor responseExtractor = new HeadersResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.HEAD, null, responseExtractor, CancellationToken.None); + } + + #endregion + + #region POST + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForLocationAsync(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForLocationAsync(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the value of the 'Location' header. + /// This header typically indicates where the new resource is stored. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + public Task PostForLocationAsync(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + LocationHeaderResponseExtractor responseExtractor = new LocationHeaderResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForObjectAsync(string url, object request, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForObjectAsync(string url, object request, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + public Task PostForObjectAsync(Uri url, object request) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + MessageConverterResponseExtractor responseExtractor = new MessageConverterResponseExtractor(this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task> PostForMessageAsync(string url, object request, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task> PostForMessageAsync(string url, object request, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + public Task> PostForMessageAsync(Uri url, object request) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForMessageAsync(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task PostForMessageAsync(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create a new resource by POSTing the given object to the URI template, + /// and returns the response with no entity as . + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + public Task PostForMessageAsync(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, HttpMethod.POST, requestCallback, responseExtractor, CancellationToken.None); + } + + #endregion + + #region PUT + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task PutAsync(string url, object request, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task PutAsync(string url, object request, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously create or update a resource by PUTting the given object to the URI. + /// + /// The URL. + /// + /// The object to be POSTed, may be a in order to add additional HTTP headers. + /// + /// A Task<T> that represents the asynchronous operation. + public Task PutAsync(Uri url, object request) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, this._messageConverters); + return this.ExecuteAsync(url, HttpMethod.PUT, requestCallback, null, CancellationToken.None); + } + + #endregion + + #region DELETE + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task DeleteAsync(string url, params object[] uriVariables) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task DeleteAsync(string url, IDictionary uriVariables) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously delete the resources at the specified URI. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + public Task DeleteAsync(Uri url) + { + return this.ExecuteAsync(url, HttpMethod.DELETE, null, null, CancellationToken.None); + } + + #endregion + + #region OPTIONS + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task> OptionsForAllowAsync(string url, params object[] uriVariables) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task> OptionsForAllowAsync(string url, IDictionary uriVariables) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, CancellationToken.None, uriVariables); + } + + /// + /// Asynchronously return the value of the Allow header for the given URI. + /// + /// The URL. + /// A Task<T> that represents the asynchronous operation. + public Task> OptionsForAllowAsync(Uri url) + { + AllowHeaderResponseExtractor responseExtractor = new AllowHeaderResponseExtractor(); + return this.ExecuteAsync>(url, HttpMethod.OPTIONS, null, responseExtractor, CancellationToken.None); + } + + #endregion + + + #region Exchange + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task> ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, params object[] uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, cancellationToken, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + public Task> ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, IDictionary uriVariables) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, cancellationToken, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response as . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + public Task> ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken) where T : class + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, typeof(T), this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(this._messageConverters); + return this.ExecuteAsync>(url, method, requestCallback, responseExtractor, cancellationToken); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, params object[] uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, cancellationToken, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The dictionary containing variables for the URI template. + /// The that will be assigned to the task. + /// + /// A instance that allows to cancel the asynchronous operation. + /// + public Task ExchangeAsync(string url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken, IDictionary uriVariables) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, cancellationToken, uriVariables); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, writing the given request message to the request, + /// and returns the response with no entity as . + /// + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// + /// The HTTP entity (headers and/or body) to write to the request, may be . + /// + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + public Task ExchangeAsync(Uri url, HttpMethod method, HttpEntity requestEntity, CancellationToken cancellationToken) + { + HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.ExecuteAsync(url, method, requestCallback, responseExtractor, cancellationToken); + } + + #endregion + + #region General execution + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// The variables to expand the template. + /// A Task<T> that represents the asynchronous operation. + public Task ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken, params object[] uriVariables) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url, uriVariables), method, + requestCallback, responseExtractor, cancellationToken); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// The dictionary containing variables for the URI template. + /// A Task<T> that represents the asynchronous operation. + public Task ExecuteAsync(string url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken, IDictionary uriVariables) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url, uriVariables), method, + requestCallback, responseExtractor, cancellationToken); + } + + /// + /// Asynchronously execute the HTTP method to the given URI template, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The URL. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + public Task ExecuteAsync(Uri url, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor, CancellationToken cancellationToken) where T : class + { + return this.DoExecuteAsync(BuildUri(this._baseAddress, url), method, + requestCallback, responseExtractor, cancellationToken); + } + + #endregion + + #endregion + + #endregion + + #region DoExecute + + /// + /// Execute the HTTP request to the given URI, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The fully-expanded URI to connect to. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// An arbitrary object, as returned by the . + protected virtual T DoExecute(Uri uri, HttpMethod method, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class + { + IClientHttpRequest request = this.GetClientHttpRequestFactory().CreateRequest(uri, method); + + if (requestCallback != null) + { + requestCallback.DoWithRequest(request); + } + + using (IClientHttpResponse response = request.Execute()) + { + if (response != null) + { + if (this._errorHandler != null && this._errorHandler.HasError(uri, method, response)) + { + HandleResponseError(uri, method, response, this._errorHandler); + } + else + { + LogResponseStatus(uri, method, response); + } + + if (responseExtractor != null) + { + return responseExtractor.ExtractData(response); + } + } + } + + return null; + } + + #endregion + + #region DoExecuteAsync + + /// + /// Asynchronously execute the HTTP request to the given URI, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The fully-expanded URI to connect to. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// + /// The Action<T> to perform when the asynchronous method completes. + /// + /// + /// A instance that allows to cancel the asynchronous operation. + /// + protected virtual RestOperationCanceler DoExecuteAsync(Uri uri, HttpMethod method, + IRequestCallback requestCallback, IResponseExtractor responseExtractor, + Action> methodCompleted) where T : class + { + try + { + IClientHttpRequest request = this.GetClientHttpRequestFactory().CreateRequest(uri, method); + + RestAsyncOperationState state = new RestAsyncOperationState(uri, method, responseExtractor, this._errorHandler, methodCompleted); + + if (requestCallback != null) + { + requestCallback.DoWithRequest(request); + } + + request.ExecuteAsync(state, ResponseReceivedCallback); + + return new RestOperationCanceler(request); + } + // An exception occurs before becoming async + catch (Exception ex) + { + if (methodCompleted != null) + { + methodCompleted(new RestOperationCompletedEventArgs(null, ex, false, null)); + } + return new RestOperationCanceler(uri, method); + } + } + + private static void ResponseReceivedCallback(ClientHttpRequestCompletedEventArgs responseReceived) where T : class + { + RestAsyncOperationState state = (RestAsyncOperationState)responseReceived.UserState; + + T value = null; + Exception exception = responseReceived.Error; + bool cancelled = responseReceived.Cancelled; + if (exception == null && !cancelled) + { + using (IClientHttpResponse response = responseReceived.Response) + { + if (response != null) + { + try + { + if (state.ResponseErrorHandler != null && + state.ResponseErrorHandler.HasError(state.Uri, state.Method, response)) + { + HandleResponseError(state.Uri, state.Method, response, state.ResponseErrorHandler); + } + else + { + LogResponseStatus(state.Uri, state.Method, response); + } + + if (state.ResponseExtractor != null) + { + value = state.ResponseExtractor.ExtractData(response); + } + } + catch (Exception ex) + { + exception = ex; + } + } + } + } + if (state.MethodCompleted != null) + { + state.MethodCompleted(new RestOperationCompletedEventArgs(value, exception, cancelled, null)); + } + } + + #endregion + + #region DoExecuteAsync (TPL) + + /// + /// Asynchronously execute the HTTP request to the given URI, preparing the request with the + /// , and reading the response with an . + /// + /// The type of the response value. + /// The fully-expanded URI to connect to. + /// The HTTP method (GET, POST, etc.) + /// Object that prepares the request. + /// Object that extracts the return value from the response. + /// The that will be assigned to the task. + /// A Task<T> that represents the asynchronous operation. + protected virtual Task DoExecuteAsync(Uri uri, HttpMethod method, + IRequestCallback requestCallback, IResponseExtractor responseExtractor, + CancellationToken cancellationToken) where T : class + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + + Task.Factory.StartNew(() => + { + RestOperationCanceler canceler = this.DoExecuteAsync(uri, method, requestCallback, responseExtractor, + args => + { + if (args.Cancelled) + { + taskCompletionSource.TrySetCanceled(); + } + else if (args.Error != null) + { + taskCompletionSource.TrySetException(args.Error); + } + else + { + taskCompletionSource.TrySetResult(args.Response); + } + }); + + cancellationToken.Register(() => + { + canceler.Cancel(); + }); + }); + + return taskCompletionSource.Task; + } + + #endregion + + #region BuildUri + + // TODO : Add IRequestUriBuilder interface to support .NET 3.5 UriTemplate class ? + // Problems with silverlight implementation : + // - UriTemplate.BindByPosition is not supported in Silverlight + // - UriTemplate class is not included in the Silverlight core dlls + + /// + /// Builds an uri using the given parameters. + /// + /// The base address to use, may be . + /// An absolute or relative URI template to expand. + /// The variables to expand the template. + /// The absolute build URI. + /// If an absolute URI can't be build. + protected virtual Uri BuildUri(Uri baseAddress, string url, params object[] uriVariables) + { + UriTemplate uriTemplate = new UriTemplate(url); + Uri uri = uriTemplate.Expand(uriVariables); + return BuildUri(baseAddress, uri); + } + + /// + /// Builds an uri using the given parameters. + /// + /// The base address to use, may be . + /// An absolute or relative URI template to expand. + /// The dictionary containing variables for the URI template. + /// The absolute build URI. + /// If an absolute URI can't be build. + protected virtual Uri BuildUri(Uri baseAddress, string url, IDictionary uriVariables) + { + UriTemplate uriTemplate = new UriTemplate(url); + Uri uri = uriTemplate.Expand(uriVariables); + return BuildUri(baseAddress, uri); + } + + /// + /// Builds an uri using the given parameters. + /// + /// The base address to use, may be . + /// An absolute or relative URI. + /// The absolute build URI. + /// If an absolute URI can't be build. + protected virtual Uri BuildUri(Uri baseAddress, Uri uri) + { + if (!uri.IsAbsoluteUri) + { + if (baseAddress != null) + { + return new Uri(baseAddress, uri); + } + else + { + throw new ArgumentException(String.Format("'{0}' is not an absolute URI", uri), "uri"); + } + } + return uri; + } + + #endregion + + private IClientHttpRequestFactory GetClientHttpRequestFactory() + { + if (this._requestInterceptors != null && this._requestInterceptors.Count > 0) + { + return new InterceptingClientHttpRequestFactory(this._requestFactory, this._requestInterceptors); + } + else + { + return this._requestFactory; + } + } + + private static void LogResponseStatus(Uri uri, HttpMethod method, IClientHttpResponse response) + { + #region Instrumentation + if (LOG.IsDebugEnabled) + { + LOG.Debug(string.Format( + "{0} request for '{1}' resulted in {2:d} - {2} ({3})", + method, uri, response.StatusCode, response.StatusDescription)); + } + #endregion + } + + private static void HandleResponseError(Uri uri, HttpMethod method, IClientHttpResponse response, + IResponseErrorHandler errorHandler) + { + #region Instrumentation + if (LOG.IsWarnEnabled) + { + LOG.Warn(String.Format( + "{0} request for '{1}' resulted in {2:d} - {2} ({3}); invoking error handler", + method, uri, response.StatusCode, response.StatusDescription)); + } + #endregion + + errorHandler.HandleError(uri, method, response); + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/AcceptHeaderRequestCallback.cs b/netstandard/Spring.Rest/Rest/Client/Support/AcceptHeaderRequestCallback.cs new file mode 100644 index 0000000..78f4c4e --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/AcceptHeaderRequestCallback.cs @@ -0,0 +1,115 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Converters; + +namespace Spring.Rest.Client.Support +{ + /// + /// Request callback implementation that prepares the request's accept headers. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class AcceptHeaderRequestCallback : IRequestCallback + { + #region Logging + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(AcceptHeaderRequestCallback)); + #endregion + + /// + /// The expected response body type. + /// + protected Type responseType; + + /// + /// The list of to use. + /// + protected IList messageConverters; + + /// + /// Creates a new instance of . + /// + /// The expected response body type. + /// The list of to use. + public AcceptHeaderRequestCallback(Type responseType, IList messageConverters) + { + this.responseType = responseType; + this.messageConverters = messageConverters; + } + + #region IRequestCallback Members + + /// + /// Gets called by with an to write data. + /// + /// + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public virtual void DoWithRequest(IClientHttpRequest request) + { + if (responseType != null) + { + List allSupportedMediaTypes = new List(); + foreach (IHttpMessageConverter messageConverter in this.messageConverters) + { + if (messageConverter.CanRead(responseType, null)) + { + foreach (MediaType supportedMediaType in messageConverter.SupportedMediaTypes) + { + if (supportedMediaType.CharSet != null) + { + allSupportedMediaTypes.Add(new MediaType( + supportedMediaType.Type, supportedMediaType.Subtype)); + } + else + { + allSupportedMediaTypes.Add(supportedMediaType); + } + } + } + } + if (allSupportedMediaTypes.Count > 0) + { + MediaType.SortBySpecificity(allSupportedMediaTypes); + + #region Instrumentation + if (LOG.IsDebugEnabled) + { + LOG.Debug(String.Format( + "Setting request Accept header to '{0}'", + MediaType.ToString(allSupportedMediaTypes))); + } + #endregion + + request.Headers.Accept = allSupportedMediaTypes.ToArray(); + } + } + } + + #endregion + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/AllowHeaderResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/Support/AllowHeaderResponseExtractor.cs new file mode 100644 index 0000000..0b0d973 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/AllowHeaderResponseExtractor.cs @@ -0,0 +1,45 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that extracts the response HTTP headers 'Allow'. + /// + /// Bruno Baia + public class AllowHeaderResponseExtractor : IResponseExtractor> + { + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public IList ExtractData(IClientHttpResponse response) + { + return new List(response.Headers.Allow); + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/DefaultResponseErrorHandler.cs b/netstandard/Spring.Rest/Rest/Client/Support/DefaultResponseErrorHandler.cs new file mode 100644 index 0000000..f91a258 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/DefaultResponseErrorHandler.cs @@ -0,0 +1,127 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.IO; +using System.Net; + +using Spring.Util; +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Support +{ + /// + /// Default implementation of the interface. + /// + /// + /// + /// This error handler checks for the status code on the : + /// any client code error (4xx) or server code error (5xx) is considered to be an error. + /// + /// + /// This behavior can be changed by overriding the method. + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class DefaultResponseErrorHandler : IResponseErrorHandler + { + #region IResponseErrorHandler Members + + /// + /// Indicates whether the given response has any errors. + /// + /// This implementation delegates to HasError(HttpStatusCode) method with the response status code. + /// + /// The request URI. + /// The request method. + /// The response to inspect. + /// + /// if the response has an error; otherwise . + /// + public virtual bool HasError(Uri requestUri, HttpMethod requestMethod, IClientHttpResponse response) + { + return this.HasError(response.StatusCode); + } + + /// + /// Handles the error in the given response. + /// + /// This method is only called when HasError() method has returned . + /// + /// The request URI. + /// The request method. + /// The response with the error + public virtual void HandleError(Uri requestUri, HttpMethod requestMethod, IClientHttpResponse response) + { + byte[] body = null; + Stream bodyStream = response.Body; + if (bodyStream != null) + { + using (MemoryStream tempStream = new MemoryStream()) + { + IoUtils.CopyStream(bodyStream, tempStream); + body = tempStream.ToArray(); + } + } + this.HandleError(requestUri, requestMethod, new HttpResponseMessage(body, response.Headers, response.StatusCode, response.StatusDescription)); + } + + #endregion + + /// + /// Checks if the given status code is a client code error (4xx) or a server code error (5xx). + /// + /// The HTTP status code. + /// + /// if the response has an error; otherwise . + /// + protected virtual bool HasError(HttpStatusCode statusCode) + { + int type = (int)statusCode / 100; + return type == 4 || type == 5; + } + + /// + /// Throws an exception if the response status code is a client code error (4xx) + /// or a server code error (5xx). + /// This method is only called when has returned . + /// + /// The request URI. + /// The request method. + /// The response message with the error + /// If the response status code is a client error (4xx). + /// If the response status code is a server error (4xx). + public virtual void HandleError(Uri requestUri, HttpMethod requestMethod, HttpResponseMessage response) + { + int type = (int)response.StatusCode / 100; + switch (type) + { + case 4: + throw new HttpClientErrorException(requestUri, requestMethod, response); + case 5: + throw new HttpServerErrorException(requestUri, requestMethod, response); + default: + throw new HttpResponseException(requestUri, requestMethod, response); + } + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/HeadersResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/Support/HeadersResponseExtractor.cs new file mode 100644 index 0000000..a5bdbb1 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/HeadersResponseExtractor.cs @@ -0,0 +1,44 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that extracts the response HTTP headers. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class HeadersResponseExtractor : IResponseExtractor + { + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public HttpHeaders ExtractData(IClientHttpResponse response) + { + return response.Headers; + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/HttpEntityRequestCallback.cs b/netstandard/Spring.Rest/Rest/Client/Support/HttpEntityRequestCallback.cs new file mode 100644 index 0000000..6018904 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/HttpEntityRequestCallback.cs @@ -0,0 +1,145 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Converters; + +namespace Spring.Rest.Client.Support +{ + /// + /// Request callback implementation that writes the given object to the request stream. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class HttpEntityRequestCallback : AcceptHeaderRequestCallback + { + #region Logging + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(HttpEntityRequestCallback)); + #endregion + + /// + /// The underlying used by the request. + /// + protected HttpEntity requestEntity; + + /// + /// Creates a new instance of . + /// + /// + /// The object to write to the request. + /// Can be a in order to add additional HTTP headers to the request. + /// + /// The list of to use. + public HttpEntityRequestCallback(object requestBody, IList messageConverters) : + this(requestBody, null, messageConverters) + { + } + + /// + /// Creates a new instance of . + /// + /// The object to write to the request. + /// The expected response body type. + /// The list of to use. + public HttpEntityRequestCallback(object requestBody, Type responseType, IList messageConverters) : + base(responseType, messageConverters) + { + if (requestBody is HttpEntity) + { + this.requestEntity = (HttpEntity)requestBody; + } + else + { + this.requestEntity = new HttpEntity(requestBody); + } + } + + /// + /// Gets called by with an to write data. + /// + /// + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public override void DoWithRequest(IClientHttpRequest request) + { + base.DoWithRequest(request); + + // headers + foreach (string header in requestEntity.Headers) + { + request.Headers[header] = requestEntity.Headers[header]; + } + + // body + if (requestEntity.HasBody) + { + object requestBody = requestEntity.Body; + MediaType requestContentType = requestEntity.Headers.ContentType; + foreach (IHttpMessageConverter messageConverter in base.messageConverters) + { + if (messageConverter.CanWrite(requestBody.GetType(), requestContentType)) + { + #region Instrumentation + if (LOG.IsDebugEnabled) + { + if (requestContentType != null) + { + LOG.Debug(string.Format( + "Writing [{0}] as '{1}' using [{2}]", + requestBody, requestContentType, messageConverter)); + } + else + { + LOG.Debug(string.Format( + "Writing [{0}] using [{1}]", + requestBody, messageConverter)); + } + } + #endregion + + messageConverter.Write(requestBody, requestContentType, request); + return; + } + } + string message = string.Format( + "Could not write request: no suitable IHttpMessageConverter found for request type [{0}]", + requestBody.GetType().FullName); + if (requestContentType != null) + { + message = string.Format("{0} and content type [{1}]", message, requestContentType); + } + throw new RestClientException(message); + } + else + { + if (request.Headers.ContentLength == -1) + { + request.Headers.ContentLength = 0; + } + } + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor.cs new file mode 100644 index 0000000..d6e25cd --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor.cs @@ -0,0 +1,43 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that extracts the HTTP response message with no body. + /// + /// Bruno Baia + public class HttpMessageResponseExtractor : IResponseExtractor + { + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public HttpResponseMessage ExtractData(IClientHttpResponse response) + { + return new HttpResponseMessage(response.Headers, response.StatusCode, response.StatusDescription); + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor`1.cs b/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor`1.cs new file mode 100644 index 0000000..b962a67 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/HttpMessageResponseExtractor`1.cs @@ -0,0 +1,59 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Converters; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that extracts the HTTP response message with no body. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class HttpMessageResponseExtractor : IResponseExtractor> where T : class + { + private MessageConverterResponseExtractor httpMessageConverterExtractor; + + /// + /// Creates a new instance of the class. + /// + /// The list of to use. + public HttpMessageResponseExtractor(IList messageConverters) + { + httpMessageConverterExtractor = new MessageConverterResponseExtractor(messageConverters); + } + + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public HttpResponseMessage ExtractData(IClientHttpResponse response) + { + T body = httpMessageConverterExtractor.ExtractData(response); + return new HttpResponseMessage(body, response.Headers, response.StatusCode, response.StatusDescription); + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/LocationHeaderResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/Support/LocationHeaderResponseExtractor.cs new file mode 100644 index 0000000..db69f70 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/LocationHeaderResponseExtractor.cs @@ -0,0 +1,45 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using Spring.Http; +using Spring.Http.Client; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that extracts the response HTTP headers 'Location'. + /// + /// Bruno Baia + public class LocationHeaderResponseExtractor : IResponseExtractor + { + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public Uri ExtractData(IClientHttpResponse response) + { + return response.Headers.Location; + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/MessageConverterResponseExtractor.cs b/netstandard/Spring.Rest/Rest/Client/Support/MessageConverterResponseExtractor.cs new file mode 100644 index 0000000..d2da24a --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/MessageConverterResponseExtractor.cs @@ -0,0 +1,120 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Net; +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Client; +using Spring.Http.Converters; + +namespace Spring.Rest.Client.Support +{ + /// + /// Response extractor that uses the given HTTP message converters to convert the response into a type. + /// + /// The response body type. + /// Arjen Poutsma + /// Bruno Baia (.NET) + public class MessageConverterResponseExtractor : IResponseExtractor where T : class + { + #region Logging + private static readonly Common.Logging.ILog LOG = Common.Logging.LogManager.GetLogger(typeof(MessageConverterResponseExtractor)); + #endregion + + /// + /// The underlying list of used to extract data from the response. + /// + protected IList messageConverters; + + /// + /// Creates a new instance of the class. + /// + /// The list of to use. + public MessageConverterResponseExtractor(IList messageConverters) + { + this.messageConverters = messageConverters; + } + + /// + /// Gets called by with an opened to extract data. + /// Does not need to care about closing the request or about handling errors: + /// this will all be handled by the class. + /// + /// The active HTTP request. + public virtual T ExtractData(IClientHttpResponse response) + { + if (!this.HasMessageBody(response)) + { + return null; + } + MediaType mediaType = response.Headers.ContentType; + if (mediaType == null) + { + #region Instrumentation + if (LOG.IsWarnEnabled) + { + LOG.Warn("No Content-Type header found, defaulting to 'application/octet-stream'"); + } + #endregion + + mediaType = MediaType.APPLICATION_OCTET_STREAM; + } + foreach(IHttpMessageConverter messageConverter in messageConverters) + { + if (messageConverter.CanRead(typeof(T), mediaType)) + { + #region Instrumentation + if (LOG.IsDebugEnabled) + { + LOG.Debug(string.Format( + "Reading [{0}] as '{1}' using [{2}]", + typeof(T).FullName, mediaType, messageConverter)); + } + #endregion + + return messageConverter.Read(response); + } + } + throw new RestClientException(string.Format( + "Could not extract response: no suitable HttpMessageConverter found for response type [{0}] and content type [{1}]", + typeof(T).FullName, mediaType)); + } + + /// + /// Indicates whether or not the given response has a message body. + /// + /// + /// Default implementation returns false for a response status of 204 or 304, or a 'Content-Length' of 0. + /// + /// The response to check for a message body. + /// if the response has a body; otherwise . + protected virtual bool HasMessageBody(IClientHttpResponse response) + { + if (response.StatusCode == HttpStatusCode.NoContent || + response.StatusCode == HttpStatusCode.NotModified) + { + return false; + } + return response.Headers.ContentLength != 0; + } + } +} diff --git a/netstandard/Spring.Rest/Rest/Client/Support/RestAsyncOperationState.cs b/netstandard/Spring.Rest/Rest/Client/Support/RestAsyncOperationState.cs new file mode 100644 index 0000000..2e27980 --- /dev/null +++ b/netstandard/Spring.Rest/Rest/Client/Support/RestAsyncOperationState.cs @@ -0,0 +1,116 @@ + +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +using Spring.Http; + +namespace Spring.Rest.Client.Support +{ + /// + /// Represents the state of an asynchronous REST operation + /// that is passed to the request asynchronous execution method. + /// + /// The type of the response value. + /// Bruno Baia + public class RestAsyncOperationState where T : class + { + private Uri _uri; + private HttpMethod _method; + private IResponseExtractor _responseExtractor; + private IResponseErrorHandler _responseErrorHandler; + private Action> _methodCompleted; + + /// + /// Gets or sets the HTTP URI. + /// + public Uri Uri + { + get { return _uri; } + set { _uri = value; } + } + + /// + /// Gets or sets the HTTP method. + /// + public HttpMethod Method + { + get { return _method; } + set { _method = value; } + } + + /// + /// Gets or sets the + /// that extracts the return value from the response. + /// + public IResponseExtractor ResponseExtractor + { + get { return _responseExtractor; } + set { _responseExtractor = value; } + } + + /// + /// Gets or sets the + /// that handles error in the response. + /// + public IResponseErrorHandler ResponseErrorHandler + { + get { return _responseErrorHandler; } + set { _responseErrorHandler = value; } + } + + /// + /// Gets or sets the Action<RestOperationCompletedEventArgs<T>> + /// to perform when the REST operation completes. + /// + public Action> MethodCompleted + { + get { return _methodCompleted; } + set { _methodCompleted = value; } + } + + /// + /// Creates a new instance of . + /// + /// The HTTP URI. + /// The HTTP method. + /// + /// The object that extracts the return value from the response. + /// + /// + /// The object that handles error in the response. + /// + /// + /// The callback method when the REST operation completes. + /// + public RestAsyncOperationState(Uri uri, HttpMethod method, + IResponseExtractor responseExtractor, + IResponseErrorHandler responseErrorHandler, + Action> methodCompleted) + { + this._uri = uri; + this._method = method; + this._responseExtractor = responseExtractor; + this._responseErrorHandler = responseErrorHandler; + this._methodCompleted = methodCompleted; + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Spring.Rest.csproj b/netstandard/Spring.Rest/Spring.Rest.csproj new file mode 100644 index 0000000..2eae0ce --- /dev/null +++ b/netstandard/Spring.Rest/Spring.Rest.csproj @@ -0,0 +1,33 @@ + + + + $(TargetNetStandardVersion);$(TargetFullFrameworkVersion) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/netstandard/Spring.Rest/Util/ArgumentUtils.cs b/netstandard/Spring.Rest/Util/ArgumentUtils.cs new file mode 100644 index 0000000..45efec2 --- /dev/null +++ b/netstandard/Spring.Rest/Util/ArgumentUtils.cs @@ -0,0 +1,86 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Globalization; + +namespace Spring.Util +{ + // From Spring.Core.AssertUtils (Renamed to differentiate it from Core Spring.NET) + + /// + /// Assertion utility methods that assists in validating arguments. + /// Useful for identifying programmer errors early and clearly at runtime. + /// + /// + /// + /// For example, if the contract of a public method states it does not allow null arguments, + /// ArgumentUtils can be used to validate that contract. + /// Doing this clearly indicates a contract violation when it occurs and protects the class's invariants. + /// + /// + /// Mainly for internal use within the framework. + /// + /// + /// Aleksandar Seovic + /// Erich Eichinger + public sealed class ArgumentUtils + { + /// + /// Checks the value of the supplied and throws an + /// if it is . + /// + /// The object to check. + /// The argument name. + /// + /// If the supplied is . + /// + public static void AssertNotNull(object argument, string name) + { + if (argument == null) + { + throw new ArgumentNullException (name, + String.Format(CultureInfo.InvariantCulture, + "Argument '{0}' must not be null.", name)); + } + } + + /// + /// Checks the value of the supplied string and throws an + /// if it is or + /// contains only whitespace character(s). + /// + /// The string to check. + /// The argument name. + /// + /// If the supplied is or + /// contains only whitespace character(s). + /// + public static void AssertHasText(string argument, string name) + { + if (!StringUtils.HasText(argument)) + { + throw new ArgumentNullException(name, + String.Format (CultureInfo.InvariantCulture, + "String argument '{0}' must have text; it must not be null, empty, or blank.", name)); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Util/IoUtils.cs b/netstandard/Spring.Rest/Util/IoUtils.cs new file mode 100644 index 0000000..edbb30b --- /dev/null +++ b/netstandard/Spring.Rest/Util/IoUtils.cs @@ -0,0 +1,51 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System.IO; + +namespace Spring.Util +{ + /// + /// Utility methods for IO handling. + /// + /// Bruno Baia + public sealed class IoUtils + { + /// + /// Copies one stream into another. + /// + public static void CopyStream(Stream source, Stream destination) + { + source.CopyTo(destination); + /* +#if NET_4_0 + source.CopyTo(destination); +#else + int bytesCount; + byte[] buffer = new byte[0x1000]; + while ((bytesCount = source.Read(buffer, 0, buffer.Length)) != 0) + { + destination.Write(buffer, 0, bytesCount); + } +#endif + */ + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Util/StringUtils.cs b/netstandard/Spring.Rest/Util/StringUtils.cs new file mode 100644 index 0000000..109f9bb --- /dev/null +++ b/netstandard/Spring.Rest/Util/StringUtils.cs @@ -0,0 +1,103 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Util +{ + // From Spring.Core + + /// + /// Miscellaneous utility methods. + /// + /// + /// Mainly for internal use within the framework. + /// + /// Rod Johnson + /// Juergen Hoeller + /// Keith Donald + /// Aleksandar Seovic (.NET) + /// Mark Pollack (.NET) + /// Rick Evans (.NET) + /// Erich Eichinger (.NET) + public sealed class StringUtils + { + /// Checks if a string has length. + /// + /// The string to check, may be . + /// + /// + /// if the string has length and is not + /// . + /// + /// + /// + /// StringUtils.HasLength(null) = false + /// StringUtils.HasLength("") = false + /// StringUtils.HasLength(" ") = true + /// StringUtils.HasLength("Hello") = true + /// + /// + public static bool HasLength(string target) + { + return (target != null && target.Length > 0); + } + + /// + /// Checks if a has text. + /// + /// + ///

+ /// More specifically, returns if the string is + /// not , it's is > + /// zero (0), and it has at least one non-whitespace character. + ///

+ ///
+ /// + /// The string to check, may be . + /// + /// + /// if the is not + /// , + /// > zero (0), and does not consist + /// solely of whitespace. + /// + /// + /// + /// StringUtils.HasText(null) = false + /// StringUtils.HasText("") = false + /// StringUtils.HasText(" ") = false + /// StringUtils.HasText("12345") = true + /// StringUtils.HasText(" 12345 ") = true + /// + /// + public static bool HasText(string target) + { + if (target == null) + { + return false; + } + else + { + return HasLength(target.Trim()); + } + } + } +} \ No newline at end of file diff --git a/netstandard/Spring.Rest/Util/UriTemplate.cs b/netstandard/Spring.Rest/Util/UriTemplate.cs new file mode 100644 index 0000000..a7475f1 --- /dev/null +++ b/netstandard/Spring.Rest/Util/UriTemplate.cs @@ -0,0 +1,246 @@ +#region License + +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; + +namespace Spring.Util +{ + /// + /// Represents a URI template. An URI template is a URI-like String that contained variables + /// marked of in braces {}, which can be expanded to produce a URI. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) + public class UriTemplate + { + // Captures URI template variable names. + private static Regex VARIABLENAMES_REGEX = new Regex(@"\{([^/]+?)\}", RegexOptions.Compiled); + // Replaces template variables in the URI template. + private static string VARIABLEVALUE_PATTERN = "(?<{0}>.*)"; + + private const string BRACE_LEFT = "{"; + private const string BRACE_RIGHT = "}"; + + private string uriTemplate; + private string[] variableNames; + private Regex matchRegex; + + /// + /// Gets the names of the variables in the template, in order. + /// + public string[] VariableNames + { + get { return this.variableNames; } + } + + /// + /// Creates a new instance of with the given URI String. + /// + /// The URI template string. + public UriTemplate(string uriTemplate) + { + this.uriTemplate = uriTemplate; + Parser parser = new Parser(uriTemplate); + this.variableNames = parser.GetVariableNames(); + this.matchRegex = parser.GetMatchRegex(); + } + + /// + /// Given the dictionary of variables, expands this template into a full URI. + /// The dictionary keys represent variable names, the dicitonary values variable values. + /// The order of variables is not significant. + /// + /// + /// + /// UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + /// IDictionary<string, object> uriVariables = new Dictionary<string, object>(); + /// uriVariables.Add("booking", "42"); + /// uriVariables.Add("hotel", 1); + /// Console.Out.WriteLine(template.Expand(uriVariables)); + /// + /// will print:
http://example.com/hotels/1/bookings/42
+ ///
+ /// The dictionary of URI variables. + /// The expanded URI + public Uri Expand(IDictionary uriVariables) + { + if (uriVariables.Count != this.variableNames.Length) + { + throw new ArgumentException(String.Format( + "Invalid amount of variables values in '{0}': expected {1}; got {2}", + this.uriTemplate, this.variableNames.Length, uriVariables.Count)); + } + + string uri = this.uriTemplate; + foreach (string variableName in this.variableNames) + { + if (!uriVariables.ContainsKey(variableName)) + { + throw new ArgumentException(String.Format( + "'uriVariables' dictionary has no value for '{0}'", + variableName)); + } + uri = Replace(uri, variableName, uriVariables[variableName]); + } + + return new Uri(uri, UriKind.RelativeOrAbsolute); + } + + /// + /// Given an array of variables, expands this template into a full URI. + /// The array represent variable values. The order of variables is significant. + /// + /// + /// + /// UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + /// Console.Out.WriteLine(template.Expand(1, "42")); + /// + /// will print:
http://example.com/hotels/1/bookings/42
+ ///
+ /// The array of URI variables. + /// The expanded URI + public Uri Expand(params object[] uriVariableValues) + { + if (uriVariableValues.Length != this.variableNames.Length) + { + throw new ArgumentException(String.Format( + "Invalid amount of variables values in '{0}': expected {1}; got {2}", + this.uriTemplate, this.variableNames.Length, uriVariableValues.Length)); + } + + string uri = this.uriTemplate; + for (int i = 0; i < this.variableNames.Length; i++) + { + uri = Replace(uri, this.variableNames[i], uriVariableValues[i]); + } + + return new Uri(uri, UriKind.RelativeOrAbsolute); + } + + /// + /// Indicates whether the given URI matches this template. + /// + /// The URI to match to. + /// if it matches; otherwise + public bool Matches(string uri) + { + if (uri == null) + { + return false; + } + return this.matchRegex.IsMatch(uri); + } + + /// + /// Match the given URI to a dictionary of variable values. Keys in the returned map are variable names, + /// values are variable values, as occurred in the given URI + /// + /// + /// + /// UriTemplate template = new UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); + /// Console.Out.WriteLine(template.Match("http://example.com/hotels/1/bookings/42")); + /// + /// will print:
{hotel=1, booking=42}
+ ///
+ /// The URI to match to. + /// A dictionary of variable values. + public IDictionary Match(string uri) + { + ArgumentUtils.AssertNotNull(uri, "uri"); + + IDictionary result = new Dictionary(); + Match match = this.matchRegex.Match(uri); + for (int i = 1; i < match.Groups.Count; i++ ) + { + result.Add(this.matchRegex.GroupNameFromNumber(i), match.Groups[i].Value); + } + return result; + } + + /// + /// Returns a that represents the current + /// + /// + /// A that represents the current . + /// + public override string ToString() + { + return this.uriTemplate; + } + + private static string Replace(string uriTemplate, string token, object value) + { + string quotedToken = BRACE_LEFT + token + BRACE_RIGHT; + return uriTemplate.Replace(quotedToken, (value == null) ? String.Empty : value.ToString()); + } + + // Static inner class to parse uri template strings into a matching regular expression. + private class Parser + { + private List variableNames = new List(); + private StringBuilder patternBuilder = new StringBuilder(); + + public Parser(string uriTemplate) + { + ArgumentUtils.AssertNotNull(uriTemplate, "uriTemplate"); + + int index = 0; + this.patternBuilder.Append("^"); + foreach (Match match in VARIABLENAMES_REGEX.Matches(uriTemplate)) + { + string variableName = match.Groups[1].Value; + if (!variableNames.Contains(variableName)) + { + variableNames.Add(variableName); + } + + this.patternBuilder.Append(Escape(uriTemplate, index, match.Index - index)); + this.patternBuilder.Append(String.Format(VARIABLEVALUE_PATTERN, variableName)); + index = match.Index + match.Length; + } + this.patternBuilder.Append(Escape(uriTemplate, index, uriTemplate.Length - index)); + this.patternBuilder.Append("$"); + } + + private static string Escape(String fullPath, int start, int end) + { + if (start == end) + { + return ""; + } + return Regex.Escape(fullPath.Substring(start, end)); + } + + public string[] GetVariableNames() + { + return this.variableNames.ToArray(); + } + + public Regex GetMatchRegex() + { + return new Regex(this.patternBuilder.ToString()); + } + } + } +} diff --git a/nuget-packages/push-all-to-nuget-yj.cmd b/nuget-packages/push-all-to-nuget-yj.cmd new file mode 100644 index 0000000..9764b64 --- /dev/null +++ b/nuget-packages/push-all-to-nuget-yj.cmd @@ -0,0 +1,19 @@ +@echo off +echo *** Pushing all packages in this folder to NuGet.org *** +echo Note: this assumes the APIKey has already been set on this computer. +echo If not, run the following command to set the ApiKey: +echo Nuget.exe setApiKey [API_KEY] +echo . +set ApiKey= 1BxXhC4ebnbs +set SourceUrl= http://nuget.yjdev.net/nuget +for /f %%F in ('dir /b *.nupkg') DO CALL :INVOKE %%F +GOTO :EOF + +:INVOKE +echo . +echo Processing file %1 ... +echo . +NuGet.exe push %1 -ApiKey %ApiKey% -Source %SourceUrl%" +echo . +echo ...complete! +GOTO :EOF diff --git a/setup/NuGet/src/Spring.Http.Converters.NJson.nuspec b/setup/NuGet/src/Spring.Http.Converters.NJson.nuspec index b9cd98c..e696555 100644 --- a/setup/NuGet/src/Spring.Http.Converters.NJson.nuspec +++ b/setup/NuGet/src/Spring.Http.Converters.NJson.nuspec @@ -4,7 +4,7 @@ Spring.Http.Converters.NJson Spring.Rest (Json.NET support) - 1.2.0 + 3.0.0 http://springframework.net/images/SpringSource_Leaves32x32.png http://www.springframework.net/rest/ http://www.springframework.net/license.html diff --git a/setup/NuGet/src/Spring.Rest.Testing.nuspec b/setup/NuGet/src/Spring.Rest.Testing.nuspec index a745c22..2f91cb4 100644 --- a/setup/NuGet/src/Spring.Rest.Testing.nuspec +++ b/setup/NuGet/src/Spring.Rest.Testing.nuspec @@ -4,7 +4,7 @@ Spring.Rest.Testing Spring.Rest Testing - 1.2.0 + 3.0.0 http://springframework.net/images/SpringSource_Leaves32x32.png http://www.springframework.net/rest/ http://www.springframework.net/license.html diff --git a/setup/NuGet/src/Spring.Rest.nuspec b/setup/NuGet/src/Spring.Rest.nuspec index b6b07bc..e58bc1a 100644 --- a/setup/NuGet/src/Spring.Rest.nuspec +++ b/setup/NuGet/src/Spring.Rest.nuspec @@ -4,7 +4,7 @@ Spring.Rest Spring.Rest - 1.2.0 + 3.0.0 http://springframework.net/images/SpringSource_Leaves32x32.png http://www.springframework.net/rest/ http://www.springframework.net/license.html diff --git a/src/CommonAssemblyInfo.cs b/src/CommonAssemblyInfo.cs index 3f9db6b..57824f7 100644 --- a/src/CommonAssemblyInfo.cs +++ b/src/CommonAssemblyInfo.cs @@ -4,21 +4,21 @@ //------------------------------------------------------------------------------ // -// Ce code a t gnr par un outil. -// Version du runtime :4.0.30319.1 +// ˴ɹɡ +// ʱ汾:4.0.30319.42000 // -// Les modifications apportes ce fichier peuvent provoquer un comportement incorrect et seront perdues si -// le code est rgnr. +// ԴļĸĿܻᵼ²ȷΪ +// ɴ룬ЩĽᶪʧ // //------------------------------------------------------------------------------ [assembly: CLSCompliantAttribute(true)] [assembly: ComVisibleAttribute(false)] -[assembly: AssemblyProductAttribute("Spring.NET REST Client Framework 1.2.0")] +[assembly: AssemblyProductAttribute("Spring.NET REST Client Framework 2.0.0 for .NET Standard 2.0")] [assembly: AssemblyCompanyAttribute("http://www.springframework.net/")] -[assembly: AssemblyCopyrightAttribute("Copyright 2012 SpringSource")] +[assembly: AssemblyCopyrightAttribute("Copyright 2020 SpringSource")] [assembly: AssemblyTrademarkAttribute("Apache License, Version 2.0")] [assembly: AssemblyCultureAttribute("")] -[assembly: AssemblyVersionAttribute("1.2.0.0")] -[assembly: AssemblyConfigurationAttribute("1.2.0.0; dev")] +[assembly: AssemblyVersionAttribute("2.0.0.522")] +[assembly: AssemblyConfigurationAttribute("net-standard-2.0; 2.0.0.522; dev")] diff --git a/src/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs b/src/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs index 39a0003..f5cacda 100644 --- a/src/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs +++ b/src/Spring.Http.Converters.NJson/Http/Converters/Json/NJsonHttpMessageConverter.cs @@ -75,7 +75,7 @@ protected override T ReadInternal(IHttpInputMessage message) using (JsonTextReader jsonReader = new JsonTextReader(reader)) { JsonSerializer jsonSerializer = new JsonSerializer(); - return jsonSerializer.Deserialize(jsonReader); + return jsonSerializer.Deserialize(jsonReader); } } diff --git a/src/Spring.Http.Converters.NJson/packages.config b/src/Spring.Http.Converters.NJson/packages.config new file mode 100644 index 0000000..e1fae9c --- /dev/null +++ b/src/Spring.Http.Converters.NJson/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Spring.Rest/AssemblyInfo.cs b/src/Spring.Rest/AssemblyInfo.cs index 5afaa50..4d49413 100644 --- a/src/Spring.Rest/AssemblyInfo.cs +++ b/src/Spring.Rest/AssemblyInfo.cs @@ -4,11 +4,4 @@ [assembly: AssemblyTitle("Spring.Rest")] [assembly: AssemblyDescription("Interfaces and classes that provide REST client API in Spring.NET")] - -#if !SILVERLIGHT && !CF_3_5 -#if STRONG -[assembly: InternalsVisibleTo("Spring.Rest.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5af6fc0c5a1a93adbee2cb424fe0a23ca6430fe0620dd9a00393be236e5d8ab481c8ad4a68c35a62e474695d63658313e4f35a2f29cd38c072f73227eaec5f0b6fb6f9e0ed6ab4f105a393f709eae6cfd010febebb004dd230d51b5e8aec839b6832c21ec7ac3b6cadba8d9b8870b1ab1507cabea54dcacd2c74ea45231a3bd")] -#else -[assembly: InternalsVisibleTo("Spring.Rest.Tests")] -#endif -#endif \ No newline at end of file +[assembly: InternalsVisibleTo("Spring.Rest.Tests")] \ No newline at end of file diff --git a/src/Spring.Rest/Spring.Rest.2010-NET40.csproj b/src/Spring.Rest/Spring.Rest.2010-NET40.csproj index 055217b..3a8dc8c 100644 --- a/src/Spring.Rest/Spring.Rest.2010-NET40.csproj +++ b/src/Spring.Rest/Spring.Rest.2010-NET40.csproj @@ -94,6 +94,9 @@ ..\..\lib\net\2.0\Common.Logging.dll + + ..\..\lib\net\2.0\Common.Logging.Core.dll + System diff --git a/src/Spring.Rest/Spring.Rest.2012-NET45.csproj b/src/Spring.Rest/Spring.Rest.2012-NET45.csproj index 83d2171..26c48ca 100644 --- a/src/Spring.Rest/Spring.Rest.2012-NET45.csproj +++ b/src/Spring.Rest/Spring.Rest.2012-NET45.csproj @@ -95,8 +95,13 @@ false - - ..\..\lib\net\2.0\Common.Logging.dll + + ..\..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll + True + + + ..\..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll + True System @@ -243,6 +248,9 @@ + + + diff --git a/src/Spring.Rest/packages.config b/src/Spring.Rest/packages.config new file mode 100644 index 0000000..5ea23e0 --- /dev/null +++ b/src/Spring.Rest/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/Spring.Rest.Tests/Spring.Rest.Tests.2010-NET40.csproj b/test/Spring.Rest.Tests/Spring.Rest.Tests.2010-NET40.csproj index 62ce4dd..9f3ed36 100644 --- a/test/Spring.Rest.Tests/Spring.Rest.Tests.2010-NET40.csproj +++ b/test/Spring.Rest.Tests/Spring.Rest.Tests.2010-NET40.csproj @@ -96,6 +96,9 @@ ..\..\lib\net\2.0\Common.Logging.dll + + ..\..\lib\net\2.0\Common.Logging.Core.dll + ..\..\lib\net\4.0\Newtonsoft.Json.dll diff --git a/test/Spring.Rest.Tests/Spring.Rest.Tests.2012-NET45.csproj b/test/Spring.Rest.Tests/Spring.Rest.Tests.2012-NET45.csproj index 7788399..b1c6d00 100644 --- a/test/Spring.Rest.Tests/Spring.Rest.Tests.2012-NET45.csproj +++ b/test/Spring.Rest.Tests/Spring.Rest.Tests.2012-NET45.csproj @@ -96,8 +96,13 @@ false - - ..\..\lib\net\2.0\Common.Logging.dll + + ..\..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll + True + + + ..\..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll + True ..\..\lib\net\4.0\Newtonsoft.Json.dll @@ -182,6 +187,8 @@ + + Always diff --git a/test/Spring.Rest.Tests/app.config b/test/Spring.Rest.Tests/app.config new file mode 100644 index 0000000..d88c2d5 --- /dev/null +++ b/test/Spring.Rest.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/Spring.Rest.Tests/packages.config b/test/Spring.Rest.Tests/packages.config new file mode 100644 index 0000000..4a8fef6 --- /dev/null +++ b/test/Spring.Rest.Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file