From 9998999a956c0ff8f10befb47cf8c4db38b8ee32 Mon Sep 17 00:00:00 2001 From: bbaia Date: Tue, 3 Aug 2010 15:03:38 +0000 Subject: [PATCH] REST client API: Dev (SPRNET-1345) --- .../Spring.RestQuickStart.2008.sln | 6 - .../Spring.RestQuickStart.2010.sln | 6 - .../src/Spring.RestQuickStart/Program.cs | 16 +- .../Spring.RestQuickStart.2008.csproj | 8 +- .../Spring.RestQuickStart.2010.csproj | 6 +- .../AbstractHttpMessageConverter.cs | 251 ++++-- .../ByteArrayHttpMessageConverter.cs | 43 +- .../Feed/AbstractFeedHttpMessageConverter.cs | 45 +- .../Feed/Atom10FeedHttpMessageConverter.cs | 33 +- .../Feed/Rss20FeedHttpMessageConverter.cs | 70 ++ .../Feed/RssFeedHttpMessageConverter.cs | 43 - .../Http/Converters/IHttpMessageConverter.cs | 99 ++- .../Json/JsonHttpMessageConverter.cs | 50 +- .../Converters/StringHttpMessageConverter.cs | 53 +- .../Xml/AbstractXmlHttpMessageConverter.cs | 87 +- .../Xml/DataContractHttpMessageConverter.cs | 91 +- .../Xml/XElementHttpMessageConverter.cs | 34 +- .../Xml/XmlDocumentHttpMessageConverter.cs | 33 +- .../XmlSerializableHttpMessageConverter.cs | 36 +- .../Http/DefaultHttpWebRequestFactory.cs | 45 +- src/Spring/Spring.Http/Http/HttpMethod.cs | 81 +- .../Spring.Http/Http/HttpRequestMessage.cs | 83 +- .../Spring.Http/Http/HttpResponseMessage.cs | 91 +- .../Spring.Http/Http/HttpResponseMessage`1.cs | 117 +++ .../Http/IHttpWebRequestFactory.cs | 26 +- src/Spring/Spring.Http/Http/MediaType.cs | 510 ++++++----- .../Spring.Http/Http/Rest/IRequestCallback.cs | 40 +- .../Http/Rest/IResponseExtractor.cs | 43 +- .../Spring.Http/Http/Rest/IRestOperations.cs | 826 +++++++++++------- .../Http/Rest/RestClientException.cs | 14 +- .../Spring.Http/Http/Rest/RestTemplate.cs | 681 +++++++++++++-- .../Support/AcceptHeaderRequestCallback.cs | 30 +- .../Rest/Support/HeadersResponseExtractor.cs | 14 +- .../Support/HttpMessageRequestCallback.cs | 59 +- .../Support/HttpMessageResponseExtractor.cs | 39 +- .../Support/HttpMessageResponseExtractor`1.cs | 59 ++ .../MessageConverterResponseExtractor.cs | 39 +- .../Rest/Support/MethodRequestCallback.cs | 20 +- .../Spring.Http/Spring.Http.2008.csproj | 12 +- .../Spring.Http/Spring.Http.2010.csproj | 14 +- src/Spring/Spring.Http/Util/AssertUtils.cs | 121 +++ src/Spring/Spring.Http/Util/StringUtils.cs | 103 +++ src/Spring/Spring.Http/Util/UriTemplate.cs | 91 +- .../ByteArrayHttpMessageConverterTests.cs | 34 +- .../Atom10FeedHttpMessageConverterTests.cs | 132 +++ ...eedHttpMessageConverterIntegrationTests.cs | 171 ++++ .../Rss20FeedHttpMessageConverterTests.cs | 131 +++ ...sonHttpMessageConverterIntegrationTests.cs | 171 ++++ .../Json/JsonHttpMessageConverterTests.cs | 125 +++ .../StringHttpMessageConverterTests.cs | 51 +- ...actHttpMessageConverterIntegrationTests.cs | 328 ------- .../DataContractHttpMessageConverterTests.cs | 117 ++- .../Xml/XElementHttpMessageConverterTests.cs | 124 +++ .../XmlDocumentHttpMessageConverterTests.cs | 44 +- ...XmlHttpMessageConverterIntegrationTests.cs | 240 +++++ ...mlSerializableHttpMessageConverterTests.cs | 137 +++ .../Http/Rest/RestTemplateIntegrationTests.cs | 3 +- .../Http/Rest/RestTemplateTests.cs | 33 +- .../Spring.Http.Tests.2008.csproj | 25 +- .../Spring.Http.Tests.2010.csproj | 17 +- 60 files changed, 4337 insertions(+), 1714 deletions(-) create mode 100644 src/Spring/Spring.Http/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs delete mode 100644 src/Spring/Spring.Http/Http/Converters/Feed/RssFeedHttpMessageConverter.cs create mode 100644 src/Spring/Spring.Http/Http/HttpResponseMessage`1.cs create mode 100644 src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor`1.cs create mode 100644 src/Spring/Spring.Http/Util/AssertUtils.cs create mode 100644 src/Spring/Spring.Http/Util/StringUtils.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterTests.cs delete mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterIntegrationTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs create mode 100644 test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs diff --git a/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2008.sln b/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2008.sln index 7ff336dd..f59b4a84 100644 --- a/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2008.sln +++ b/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2008.sln @@ -1,7 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Core.2008", "..\..\..\src\Spring\Spring.Core\Spring.Core.2008.csproj", "{710961A3-0DF4-49E4-A26E-F5B9C044AC84}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Http.2008", "..\..\..\src\Spring\Spring.Http\Spring.Http.2008.csproj", "{FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Http.Tests.2008", "..\..\..\test\Spring\Spring.Http.Tests\Spring.Http.Tests.2008.csproj", "{F04CEE18-3897-A399-46BF-459437475B21}" @@ -14,10 +12,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Debug|Any CPU.Build.0 = Debug|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Release|Any CPU.ActiveCfg = Release|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Release|Any CPU.Build.0 = Release|Any CPU {FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2010.sln b/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2010.sln index 0bef0f04..c7576af9 100644 --- a/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2010.sln +++ b/examples/Spring/Spring.RestQuickStart/Spring.RestQuickStart.2010.sln @@ -1,7 +1,5 @@ Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Core.2010", "..\..\..\src\Spring\Spring.Core\Spring.Core.2010.csproj", "{710961A3-0DF4-49E4-A26E-F5B9C044AC84}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Http.2010", "..\..\..\src\Spring\Spring.Http\Spring.Http.2010.csproj", "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spring.Http.Tests.2010", "..\..\..\test\Spring\Spring.Http.Tests\Spring.Http.Tests.2010.csproj", "{4594CEE7-3897-A3BF-9946-5B4374F01821}" @@ -14,10 +12,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Debug|Any CPU.Build.0 = Debug|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Release|Any CPU.ActiveCfg = Release|Any CPU - {710961A3-0DF4-49E4-A26E-F5B9C044AC84}.Release|Any CPU.Build.0 = Release|Any CPU {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}.Debug|Any CPU.Build.0 = Debug|Any CPU {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Program.cs b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Program.cs index 6acf2663..fdf64974 100644 --- a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Program.cs +++ b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Program.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Xml.Linq; using Spring.Http.Rest; @@ -11,9 +13,19 @@ namespace Spring.RestQuickStart try { RestTemplate rt = new RestTemplate("http://twitter.com"); - string result = rt.GetForObject("/statuses/user_timeline.xml?id={id}", "lancearmstrong"); - Console.WriteLine(result); + //string result = rt.GetForObject("/statuses/user_timeline.xml?id={id}&count={2}", "SpringForNet", "10"); + //Console.WriteLine(result); + + XElement result = rt.GetForObject("/statuses/user_timeline.xml?id={id}&count={2}", "SpringForNet", "10"); + var tweets = from el in result.Elements("status") + select el.Element("text").Value; + foreach (string tweet in tweets) + { + Console.WriteLine(String.Format("* {0}", tweet)); + Console.WriteLine(); + } + } catch (Exception ex) { diff --git a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2008.csproj b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2008.csproj index d1f5b7e6..10809169 100644 --- a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2008.csproj +++ b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2008.csproj @@ -32,6 +32,10 @@ + + 3.5 + + 3.5 @@ -40,10 +44,6 @@ - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2008 - {FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC} Spring.Http.2008 diff --git a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2010.csproj b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2010.csproj index 62e73c12..3693c86a 100644 --- a/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2010.csproj +++ b/examples/Spring/Spring.RestQuickStart/src/Spring.RestQuickStart/Spring.RestQuickStart.2010.csproj @@ -52,6 +52,8 @@ + + 3.5 @@ -60,10 +62,6 @@ - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2010 - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Spring.Http.2010 diff --git a/src/Spring/Spring.Http/Http/Converters/AbstractHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/AbstractHttpMessageConverter.cs index 98ee761e..f4e253dc 100644 --- a/src/Spring/Spring.Http/Http/Converters/AbstractHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/AbstractHttpMessageConverter.cs @@ -19,6 +19,7 @@ #endregion using System; +using System.IO; using System.Net; using System.Collections.Generic; @@ -26,17 +27,17 @@ using Spring.Util; namespace Spring.Http.Converters { - /** - * Abstract base class for most {@link HttpMessageConverter} implementations. - * - *

This base class adds support for setting supported {@code MediaTypes}, through the - * {@link #setSupportedMediaTypes(List) supportedMediaTypes} bean property. It also adds - * support for {@code Content-Type} and {@code Content-Length} when writing to output messages. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - */ + ///

+ /// 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 request. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) public abstract class AbstractHttpMessageConverter : IHttpMessageConverter { #region Logging @@ -47,9 +48,9 @@ namespace Spring.Http.Converters private IList _supportedMediaTypes = new List(); - /** - * Set the list of {@link MediaType} objects supported by this converter. - */ + /// + /// Gets or sets the list of objects supported by this converter. + /// public IList SupportedMediaTypes { get { return _supportedMediaTypes; } @@ -58,18 +59,19 @@ namespace Spring.Http.Converters #region Constructor(s) - /** - * Construct an {@code AbstractHttpMessageConverter} with no supported media types. - * @see #setSupportedMediaTypes - */ + /// + /// Creates a new instance of the + /// with no supported media types. + /// protected AbstractHttpMessageConverter() { } - /** - * Construct an {@code AbstractHttpMessageConverter} with multiple supported media type. - * @param supportedMediaTypes the supported media types - */ + /// + /// 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); @@ -79,47 +81,80 @@ namespace Spring.Http.Converters #region IHttpMessageConverter Membres - /** - * {@inheritDoc} - *

This implementation checks if the given class is {@linkplain #supports(Class) supported}, - * and if the {@linkplain #getSupportedMediaTypes() supported media types} - * {@linkplain MediaType#includes(MediaType) include} the given media type. - */ - public bool CanRead(Type type, MediaType mediaType) + ///

+ /// 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 bool CanRead(Type type, MediaType mediaType) { return Supports(type) && CanRead(mediaType); } - /** - * {@inheritDoc} - *

This implementation checks if the given class is {@linkplain #supports(Class) supported}, - * and if the {@linkplain #getSupportedMediaTypes() supported media types} - * {@linkplain MediaType#includes(MediaType) include} the given media type. - */ + ///

+ /// 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 bool CanWrite(Type type, MediaType mediaType) { return Supports(type) && CanWrite(mediaType); } - /** - * {@inheritDoc} - *

This implementation simple delegates to {@link #readInternal(Class, HttpInputMessage)}. - * Future implementations might add some default behavior, however. - */ + ///

+ /// Read an object of the given type form the given HTTP response, 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 response to read from. + /// The converted object. public T Read(HttpWebResponse response) where T : class { return ReadInternal(response); } - /** - * {@inheritDoc} - *

This implementation delegates to {@link #getDefaultContentType(Object)} if a content - * type was not provided, calls {@link #getContentLength}, and sets the corresponding headers - * on the output message. It then calls {@link #writeInternal}. - */ + ///

+ /// Write an given object to the given HTTP request. + /// + /// + /// This implementation delegates to method if a content + /// type was not provided, and calls . + /// + /// + /// The object to write to the HTTP request. 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 request to write to. public void Write(object content, MediaType mediaType, HttpWebRequest request) { - if (!StringUtils.HasText(request.Headers[HttpRequestHeader.ContentType])) + if (!StringUtils.HasText(request.ContentType)) { if (mediaType == null || mediaType.IsWildcardType || mediaType.IsWildcardSubtype) { @@ -135,14 +170,16 @@ namespace Spring.Http.Converters #endregion - /** - * Returns true if any of the {@linkplain #setSupportedMediaTypes(List) supported media types} - * include the given media type. - * @param mediaType the media type to read, can be {@code null} if not specified. Typically the value of a - * {@code Content-Type} header. - * @return true if the supported media types include the media type, or if the media type is {@code null} - */ - protected bool CanRead(MediaType mediaType) + /// + /// 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 bool CanRead(MediaType mediaType) { if (mediaType == null) { @@ -158,13 +195,15 @@ namespace Spring.Http.Converters return false; } - /** - * Returns true if the given media type includes any of the - * {@linkplain #setSupportedMediaTypes(List) supported media types}. - * @param mediaType the media type to write, can be {@code null} if not specified. Typically the value of an - * {@code Accept} header. - * @return true if the supported media types are compatible with the media type, or if the media type is {@code null} - */ + /// + /// Returns true if the given media type includes any of the supported media types. + /// + /// + /// The media type to write, can be {@code 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 bool CanWrite(MediaType mediaType) { if (mediaType == null || mediaType.Equals(MediaType.ALL)) @@ -181,44 +220,74 @@ namespace Spring.Http.Converters return false; } - /** - * Returns the default content type for the given type. Called when {@link #write} - * is invoked without a specified content type parameter. - *

By default, this returns the first element of the - * {@link #setSupportedMediaTypes(List) supportedMediaTypes} property, if any. - * Can be overridden in subclasses. - * @param t the type to return the content type for - * @return the content type, or null if not known - */ + ///

+ /// Returns the default content type for the given type. + /// Called when is invoked without a specified content type parameter. + /// + /// + /// By default, this returns the first element of the property, if any. + /// + /// The type to return the content type for. + /// The content type, or null if not known. protected virtual MediaType GetDefaultContentType(Type type) { return (this._supportedMediaTypes.Count > 0 ? this._supportedMediaTypes[0] : null); } - /** - * Indicates whether the given class is supported by this converter. - * @param clazz the class to test for support - * @return true if supported; false otherwise - */ + /// + /// 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 {@link #read}. - * @param clazz the type of object to return - * @param inputMessage the HTTP input message to read from - * @return the converted object - * @throws IOException in case of I/O errors - * @throws HttpMessageNotReadableException in case of conversion errors - */ + /// + /// Abstract template method that reads the actualy object. Invoked from . + /// + /// The type of object to return. + /// The HTTP response to read from. + /// The converted object. protected abstract T ReadInternal(HttpWebResponse response) where T : class; - /** - * Abstract template method that writes the actual body. Invoked from {@link #write}. - * @param t the object to write to the output message - * @param outputMessage the message to write to - * @throws IOException in case of I/O errors - * @throws HttpMessageNotWritableException in case of conversion errors - */ + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected abstract void WriteInternal(object content, HttpWebRequest request); + + #region Inner class definitions + + // TODO : Move this class + internal class IgnoreCloseMemoryStream : MemoryStream + { + public IgnoreCloseMemoryStream() + : base() + { + } + + public override void Close() + { + } + + public void CopyToAndClose(Stream dest) + { + this.Position = 0; + + int bufferSize = 65536; + byte[] buffer = new byte[bufferSize]; + + int bytesCount; + while ((bytesCount = this.Read(buffer, 0, buffer.Length)) > 0) + { + dest.Write(buffer, 0, bytesCount); + } + dest.Flush(); + + base.Close(); + } + } + + #endregion } } diff --git a/src/Spring/Spring.Http/Http/Converters/ByteArrayHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/ByteArrayHttpMessageConverter.cs index 6b32d410..90c5d9a4 100644 --- a/src/Spring/Spring.Http/Http/Converters/ByteArrayHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/ByteArrayHttpMessageConverter.cs @@ -25,29 +25,43 @@ using System.Text; namespace Spring.Http.Converters { - /** - * Implementation of {@link HttpMessageConverter} that can read and write byte arrays. - * - *

By default, this converter supports all media types (*/*), and writes with a {@code - * Content-Type} of {@code application/octet-stream}. This can be overridden by setting the {@link - * #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property. - * - * @author Arjen Poutsma - * @since 3.0 - */ + ///

+ /// 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 {@code ByteArrayHttpMessageConverter}. */ + /// + /// 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 response to read from. + /// The converted object. protected override T ReadInternal(HttpWebResponse response) { // Get the response stream @@ -57,6 +71,11 @@ namespace Spring.Http.Converters } } + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected override void WriteInternal(object content, HttpWebRequest request) { // Create a byte array of the data we want to send @@ -65,7 +84,7 @@ namespace Spring.Http.Converters // Set the content length in the request headers request.ContentLength = byteData.Length; - // Write data + // Write to the request using (Stream postStream = request.GetRequestStream()) { postStream.Write(byteData, 0, byteData.Length); diff --git a/src/Spring/Spring.Http/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs index fdf4770a..ace94f8d 100644 --- a/src/Spring/Spring.Http/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Feed/AbstractFeedHttpMessageConverter.cs @@ -28,27 +28,58 @@ 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 - { - /** - * Construct an {@code AbstractHttpMessageConverter} with multiple supported media type. - * @param supportedMediaTypes the supported media types - */ + { + /// + /// 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)); + 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 HTTP response to read from. + /// The converted object. protected override T ReadXml(XmlReader xmlReader, HttpWebResponse response) { - return SyndicationFeed.Load(xmlReader) as T; + 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 default XmlReader settings + /// used by this converter to read from the HTTP response. + /// + /// The XmlReader settings. protected override XmlReaderSettings GetDefaultXmlReaderSettings() { XmlReaderSettings settings = new XmlReaderSettings(); diff --git a/src/Spring/Spring.Http/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs index da075e77..ee8246b6 100644 --- a/src/Spring/Spring.Http/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Feed/Atom10FeedHttpMessageConverter.cs @@ -26,17 +26,44 @@ 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', 'application/xml' and 'text/xml' media types. + /// public Atom10FeedHttpMessageConverter() : - base(new MediaType("application", "atom+xml")) + base(new MediaType("application", "atom+xml"), new MediaType("application", "xml"), new MediaType("text", "xml")) { } + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) { - SyndicationFeed rssFeed = content as SyndicationFeed; - rssFeed.SaveAsAtom10(xmlWriter); + 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); + } } } } diff --git a/src/Spring/Spring.Http/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs new file mode 100644 index 00000000..8cb356ab --- /dev/null +++ b/src/Spring/Spring.Http/Http/Converters/Feed/Rss20FeedHttpMessageConverter.cs @@ -0,0 +1,70 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2010 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; +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', 'application/xml' and 'text/xml' media types. + /// + public Rss20FeedHttpMessageConverter() : + base(new MediaType("application", "rss+xml"), new MediaType("application", "xml"), new MediaType("text", "xml")) + { + } + + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP request. + /// The HTTP request to write to. + protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) + { + 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/src/Spring/Spring.Http/Http/Converters/Feed/RssFeedHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Feed/RssFeedHttpMessageConverter.cs deleted file mode 100644 index 9f691a7a..00000000 --- a/src/Spring/Spring.Http/Http/Converters/Feed/RssFeedHttpMessageConverter.cs +++ /dev/null @@ -1,43 +0,0 @@ -#if NET_3_5 -#region License - -/* - * Copyright 2002-2010 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; -using System.ServiceModel.Syndication; - -namespace Spring.Http.Converters.Feed -{ - public class Rss20FeedHttpMessageConverter : AbstractFeedHttpMessageConverter - { - public Rss20FeedHttpMessageConverter() : - base(new MediaType("application", "rss+xml")) - { - } - - protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) - { - SyndicationFeed rssFeed = content as SyndicationFeed; - rssFeed.SaveAsRss20(xmlWriter); - } - } -} -#endif \ No newline at end of file diff --git a/src/Spring/Spring.Http/Http/Converters/IHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/IHttpMessageConverter.cs index 5b48699a..dfba7cf9 100644 --- a/src/Spring/Spring.Http/Http/Converters/IHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/IHttpMessageConverter.cs @@ -24,62 +24,65 @@ using System.Collections.Generic; namespace Spring.Http.Converters { - /** - * Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - */ + // TODO: HttpMessageNotReadableException & HttpMessageNotWritableException exceptions ? + + /// + /// Strategy interface that specifies a converter that can convert from and to HTTP requests and responses. + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) public interface IHttpMessageConverter { - /** - * Indicates whether the given class can be read by this converter. - * @param clazz the class to test for readability - * @param mediaType the media type to read, can be {@code null} if not specified. Typically the value of a - * {@code Content-Type} header. - * @return {@code true} if readable; {@code false} otherwise - */ + /// + /// 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. - * @param clazz the class to test for writability - * @param mediaType the media type to write, can be {@code null} if not specified. Typically the value of an - * {@code Accept} header. - * @return {@code true} if writable; {@code false} otherwise - */ + /// + /// 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); - - /** - * Return the list of {@link MediaType} objects supported by this converter. - * @return the list of supported media types - */ + + /// + /// Gets the list of objects supported by this converter. + /// IList SupportedMediaTypes { get; } - - /** - * Read an object of the given type form the given input message, and returns it. - * @param clazz the type of object to return. This type must have previously been passed to the - * {@link #canRead canRead} method of this interface, which must have returned {@code true}. - * @param inputMessage the HTTP input message to read from - * @return the converted object - * @throws IOException in case of I/O errors - * @throws HttpMessageNotReadableException in case of conversion errors - */ + + /// + /// Read an object of the given type form the given HTTP response, 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 response to read from. + /// The converted object. T Read(HttpWebResponse response) where T : class; - /** - * Write an given object to the given output message. - * @param t the object to write to the output message. The type of this object must have previously been - * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}. - * @param contentType the content type to use when writing. May be {@code null} to indicate that the - * default content type of the converter must be used. If not {@code null}, this media type must have - * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have - * returned {@code true}. - * @param outputMessage the message to write to - * @throws IOException in case of I/O errors - * @throws HttpMessageNotWritableException in case of conversion errors - */ + /// + /// Write an given object to the given HTTP request. + /// + /// + /// The object to write to the HTTP request. 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 request to write to. void Write(object content, MediaType mediaType, HttpWebRequest request); } } diff --git a/src/Spring/Spring.Http/Http/Converters/Json/JsonHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Json/JsonHttpMessageConverter.cs index b0050c57..ccdae667 100644 --- a/src/Spring/Spring.Http/Http/Converters/Json/JsonHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Json/JsonHttpMessageConverter.cs @@ -31,20 +31,48 @@ using Spring.Util; namespace Spring.Http.Converters.Json { // TODO : Support for known types, etc... + // TODO : Fix Write method + + /// + /// Implementation of that can read and write JSON. + /// + /// + /// By default, this converter supports 'application/json' media type. + /// This can be overridden by setting the property. + /// + /// Bruno Baia public class JsonHttpMessageConverter : AbstractHttpMessageConverter { + /// + /// Default encoding for JSON. + /// public static readonly Encoding DEFAULT_CHARSET = Encoding.UTF8; + /// + /// Creates a new instance of the + /// with the media type 'application/json'. + /// public JsonHttpMessageConverter() : 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 response to read from. + /// The converted object. protected override T ReadInternal(HttpWebResponse response) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T)); @@ -54,12 +82,17 @@ namespace Spring.Http.Converters.Json } } + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected override void WriteInternal(object content, HttpWebRequest request) { // Get the request encoding - MediaType mediaType = MediaType.ParseMediaType(request.Headers[HttpRequestHeader.ContentType]); + MediaType mediaType = MediaType.ParseMediaType(request.ContentType); Encoding encoding; - if (mediaType == null || String.IsNullOrEmpty(mediaType.CharSet)) + if (mediaType == null || !StringUtils.HasText(mediaType.CharSet)) { encoding = DEFAULT_CHARSET; } @@ -70,18 +103,19 @@ namespace Spring.Http.Converters.Json DataContractJsonSerializer serializer = new DataContractJsonSerializer(content.GetType()); - // Write data - using (Stream postStream = request.GetRequestStream()) + // Write to the request + using (IgnoreCloseMemoryStream requestStream = new IgnoreCloseMemoryStream()) { - using (XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(postStream, encoding, false)) + using (XmlDictionaryWriter jsonWriter = JsonReaderWriterFactory.CreateJsonWriter(requestStream, encoding, false)) { serializer.WriteObject(jsonWriter, content); jsonWriter.Flush(); } - postStream.Flush(); - + // Set the content length in the request headers - request.ContentLength = postStream.Length; + request.ContentLength = requestStream.Length; + + requestStream.CopyToAndClose(request.GetRequestStream()); } } } diff --git a/src/Spring/Spring.Http/Http/Converters/StringHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/StringHttpMessageConverter.cs index 23ba33eb..1c423a14 100644 --- a/src/Spring/Spring.Http/Http/Converters/StringHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/StringHttpMessageConverter.cs @@ -23,37 +23,57 @@ using System.IO; using System.Net; using System.Text; +using Spring.Util; + namespace Spring.Http.Converters { - /** - * Implementation of {@link HttpMessageConverter} that can read and write strings. - * - *

By default, this converter supports all media types (*/*), and writes with a {@code - * Content-Type} of {@code text/plain}. This can be overridden by setting the {@link - * #setSupportedMediaTypes(java.util.List) supportedMediaTypes} property. - * - * @author Arjen Poutsma - * @since 3.0 - */ + ///

+ /// 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. + /// public 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 response to read from. + /// The converted object. protected override T ReadInternal(HttpWebResponse response) { // Get the response encoding Encoding encoding; - if (String.IsNullOrEmpty(response.CharacterSet)) + if (!StringUtils.HasText(response.CharacterSet)) { encoding = DEFAULT_CHARSET; } @@ -69,12 +89,17 @@ namespace Spring.Http.Converters } } + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected override void WriteInternal(object content, HttpWebRequest request) { // Get the request encoding - MediaType mediaType = MediaType.ParseMediaType(request.Headers[HttpRequestHeader.ContentType]); Encoding encoding; - if (mediaType == null || String.IsNullOrEmpty(mediaType.CharSet)) + MediaType mediaType = MediaType.ParseMediaType(request.ContentType); + if (mediaType == null || !StringUtils.HasText(mediaType.CharSet)) { encoding = DEFAULT_CHARSET; } @@ -89,7 +114,7 @@ namespace Spring.Http.Converters // Set the content length in the request headers request.ContentLength = byteData.Length; - // Write data + // Write to the request using (Stream postStream = request.GetRequestStream()) { postStream.Write(byteData, 0, byteData.Length); diff --git a/src/Spring/Spring.Http/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs index c961d841..0bdbf57a 100644 --- a/src/Spring/Spring.Http/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Xml/AbstractXmlHttpMessageConverter.cs @@ -18,20 +18,36 @@ #endregion -using System; using System.IO; using System.Xml; using System.Net; using System.Text; +using Spring.Util; + 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. + /// public static readonly Encoding DEFAULT_CHARSET = Encoding.UTF8; private XmlReaderSettings _xmlReaderSettings; + /// + /// Gets or sets the XmlReader settings + /// used by this converter to read from the HTTP response. + /// public XmlReaderSettings XmlReaderSettings { get @@ -45,15 +61,31 @@ namespace Spring.Http.Converters.Xml set { _xmlReaderSettings = value; } } - /** - * Construct an {@code AbstractHttpMessageConverter} with multiple supported media type. - * @param supportedMediaTypes the supported media types - */ + /// + /// 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 response to read from. + /// The converted object. protected override T ReadInternal(HttpWebResponse response) { using (Stream stream = response.GetResponseStream()) @@ -65,12 +97,17 @@ namespace Spring.Http.Converters.Xml } } + /// + /// Abstract template method that writes the actual body. Invoked from . + /// + /// The object to write to the HTTP request. + /// The HTTP request to write to. protected override void WriteInternal(object content, HttpWebRequest request) { // Get the request encoding - MediaType mediaType = MediaType.ParseMediaType(request.Headers[HttpRequestHeader.ContentType]); Encoding encoding; - if (mediaType == null || String.IsNullOrEmpty(mediaType.CharSet)) + MediaType mediaType = MediaType.ParseMediaType(request.ContentType); + if (mediaType == null || !StringUtils.HasText(mediaType.CharSet)) { encoding = DEFAULT_CHARSET; } @@ -79,20 +116,44 @@ namespace Spring.Http.Converters.Xml encoding = Encoding.GetEncoding(mediaType.CharSet); } - using (Stream postStream = request.GetRequestStream()) + // Write to the request + using (IgnoreCloseMemoryStream requestStream = new IgnoreCloseMemoryStream()) { - using (XmlTextWriter xmlWriter = new XmlTextWriter(postStream, encoding)) + using (XmlTextWriter xmlWriter = new XmlTextWriter(requestStream, encoding)) { WriteXml(xmlWriter, content, request); xmlWriter.Flush(); } - // TODO : Don't work // Set the content length in the request headers - request.ContentLength = postStream.Length; + request.ContentLength = requestStream.Length; + + requestStream.CopyToAndClose(request.GetRequestStream()); } } + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The HTTP response to read from. + /// The converted object. + protected abstract T ReadXml(XmlReader xmlReader, HttpWebResponse response) 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 request. + /// The HTTP request to write to. + protected abstract void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request); + + /// + /// Returns the default XmlReader settings + /// used by this converter to read from the HTTP response. + /// + /// The XmlReader settings. protected virtual XmlReaderSettings GetDefaultXmlReaderSettings() { XmlReaderSettings settings = new XmlReaderSettings(); @@ -102,9 +163,5 @@ namespace Spring.Http.Converters.Xml settings.IgnoreWhitespace = true; return settings; } - - protected abstract T ReadXml(XmlReader xmlReader, HttpWebResponse response) where T : class; - - protected abstract void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request); } } \ No newline at end of file diff --git a/src/Spring/Spring.Http/Http/Converters/Xml/DataContractHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Xml/DataContractHttpMessageConverter.cs index 5c0fec3d..00516275 100644 --- a/src/Spring/Spring.Http/Http/Converters/Xml/DataContractHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Xml/DataContractHttpMessageConverter.cs @@ -26,67 +26,72 @@ using System.Xml; using System.Text; using System.Runtime.Serialization; -using Spring.Util; - namespace Spring.Http.Converters.Xml { - // TODO : Derive from AbstractXmlHttpMessageConverter ? // TODO : Support for known types, etc... - public class DataContractHttpMessageConverter : AbstractHttpMessageConverter - { - public static readonly Encoding DEFAULT_CHARSET = Encoding.UTF8; + /// + /// 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. + /// + /// + /// This converter can read classes annotated with and , and write classes + /// annotated with with {@link XmlRootElement}, or subclasses thereof. + /// + /// + /// Bruno Baia + public class DataContractHttpMessageConverter : AbstractXmlHttpMessageConverter + { + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// public DataContractHttpMessageConverter() : - base(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml")) + 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(DataContractAttribute)) != null || - // AttributeUtils.FindAttribute(type, typeof(SerializableAttribute)) != null || - // typeof(ISerializable).IsAssignableFrom(type)); + return ( + Attribute.GetCustomAttributes(type, typeof(DataContractAttribute), true).Length > 0 || + Attribute.GetCustomAttributes(type, typeof(CollectionDataContractAttribute), true).Length > 0 + ); } - protected override T ReadInternal(HttpWebResponse response) + /// + /// Abstract template method that reads the actualy object using a . Invoked from . + /// + /// The type of object to return. + /// The XmlReader to use. + /// The HTTP response to read from. + /// The converted object. + protected override T ReadXml(XmlReader xmlReader, HttpWebResponse response) { DataContractSerializer serializer = new DataContractSerializer(typeof(T)); - using (Stream stream = response.GetResponseStream()) - { - return (T)serializer.ReadObject(stream) as T; - } + return serializer.ReadObject(xmlReader) as T; } - protected override void WriteInternal(object content, HttpWebRequest request) + /// + /// Abstract template method that writes the actual body using a . Invoked from . + /// + /// The XmlWriter to use. + /// The object to write to the HTTP request. + /// The HTTP request to write to. + protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) { - // Get the request encoding - MediaType mediaType = MediaType.ParseMediaType(request.Headers[HttpRequestHeader.ContentType]); - Encoding encoding; - if (mediaType == null || String.IsNullOrEmpty(mediaType.CharSet)) - { - encoding = DEFAULT_CHARSET; - } - else - { - encoding = Encoding.GetEncoding(mediaType.CharSet); - } - DataContractSerializer serializer = new DataContractSerializer(content.GetType()); - - // Write data - using (Stream postStream = request.GetRequestStream()) - { - using (XmlTextWriter xmlWriter = new XmlTextWriter(postStream, encoding)) - { - serializer.WriteObject(xmlWriter, content); - xmlWriter.Flush(); - } - - // Set the content length in the request headers - request.ContentLength = postStream.Length; - } + serializer.WriteObject(xmlWriter, content); } } } diff --git a/src/Spring/Spring.Http/Http/Converters/Xml/XElementHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Xml/XElementHttpMessageConverter.cs index e47278d9..950101e8 100644 --- a/src/Spring/Spring.Http/Http/Converters/Xml/XElementHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Xml/XElementHttpMessageConverter.cs @@ -27,23 +27,55 @@ using System.Xml.Linq; namespace Spring.Http.Converters.Xml { // TODO : Support XElement.Load options + + /// + /// 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(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml")) + 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 HTTP response to read from. + /// The converted object. protected override T ReadXml(XmlReader xmlReader, HttpWebResponse response) { 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 request. + /// The HTTP request to write to. protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) { XElement xElement = content as XElement; diff --git a/src/Spring/Spring.Http/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs index cac254be..2121d71e 100644 --- a/src/Spring/Spring.Http/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Xml/XmlDocumentHttpMessageConverter.cs @@ -24,18 +24,43 @@ using System.Net; 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(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml")) + 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 HTTP response to read from. + /// The converted object. protected override T ReadXml(XmlReader xmlReader, HttpWebResponse response) { XmlDocument document = new XmlDocument(); @@ -43,6 +68,12 @@ namespace Spring.Http.Converters.Xml 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 request. + /// The HTTP request to write to. protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) { XmlDocument document = content as XmlDocument; diff --git a/src/Spring/Spring.Http/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs b/src/Spring/Spring.Http/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs index 37eb38ac..6b1ef2d7 100644 --- a/src/Spring/Spring.Http/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs +++ b/src/Spring/Spring.Http/Http/Converters/Xml/XmlSerializableHttpMessageConverter.cs @@ -23,18 +23,35 @@ using System.Xml; using System.Net; using System.Xml.Serialization; -using Spring.Util; - namespace Spring.Http.Converters.Xml { // TODO : Support for known types, etc... + + /// + /// 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 { + /// + /// Creates a new instance of the + /// with 'text/xml', 'application/xml', and 'application/*-xml' media types. + /// public XmlSerializableHttpMessageConverter() : - base(new MediaType("application", "xml"), new MediaType("text", "xml"), new MediaType("application", "*+xml")) + 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; @@ -43,12 +60,25 @@ namespace Spring.Http.Converters.Xml // 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 HTTP response to read from. + /// The converted object. protected override T ReadXml(XmlReader xmlReader, HttpWebResponse response) { XmlSerializer serializer = new XmlSerializer(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 request. + /// The HTTP request to write to. protected override void WriteXml(XmlWriter xmlWriter, object content, HttpWebRequest request) { XmlSerializer serializer = new XmlSerializer(content.GetType()); diff --git a/src/Spring/Spring.Http/Http/DefaultHttpWebRequestFactory.cs b/src/Spring/Spring.Http/Http/DefaultHttpWebRequestFactory.cs index a73604fb..0641b067 100644 --- a/src/Spring/Spring.Http/Http/DefaultHttpWebRequestFactory.cs +++ b/src/Spring/Spring.Http/Http/DefaultHttpWebRequestFactory.cs @@ -24,13 +24,11 @@ using System.Security.Cryptography.X509Certificates; namespace Spring.Http { - /** - * Factory for {@link ClientHttpRequest} objects. - * Requests are created by the {@link #createRequest(URI, HttpMethod)} method. - * - * @author Arjen Poutsma - * @since 3.0 - */ + /// + /// Factory for objects. Requests are created by the method. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) public class DefaultHttpWebRequestFactory : IHttpWebRequestFactory { // TODO : Add other properties @@ -40,6 +38,9 @@ namespace Spring.Http private IWebProxy _proxy; private int? _timeout; + /// + /// Gets or sets the collection of security certificates that are associated with this request. + /// public X509CertificateCollection ClientCertificates { get @@ -52,18 +53,34 @@ namespace Spring.Http } } + /// + /// Gets or sets authentication information for the request. + /// public ICredentials Credentials { get { return _credentials; } set { _credentials = value; } } + /// + /// Gets or sets proxy information for the request. + /// + /// + /// The default value is set by calling the property. + /// public IWebProxy Proxy { get { return _proxy; } set { _proxy = value; } } + /// + /// Gets or sets the time-out value in milliseconds for the + /// and methods. + /// + /// + /// The default is 100,000 milliseconds (100 seconds). + /// public int? Timeout { get { return _timeout; } @@ -72,15 +89,11 @@ namespace Spring.Http #region IHttpWebRequestFactory Membres - /** - * Create a new {@link ClientHttpRequest} for the specified URI and HTTP method. - *

The returned request can be written to, and then executed by calling - * {@link ClientHttpRequest#execute()}. - * @param uri the URI to create a request for - * @param httpMethod the HTTP method to execute - * @return the created request - * @throws IOException in case of I/O errors - */ + ///

+ /// Create a new for the specified URI. + /// + /// The URI to create a request for. + /// The created request public HttpWebRequest CreateRequest(Uri uri) { HttpWebRequest request = WebRequest.Create(uri) as HttpWebRequest; diff --git a/src/Spring/Spring.Http/Http/HttpMethod.cs b/src/Spring/Spring.Http/Http/HttpMethod.cs index e2be1362..88fc0346 100644 --- a/src/Spring/Spring.Http/Http/HttpMethod.cs +++ b/src/Spring/Spring.Http/Http/HttpMethod.cs @@ -1,19 +1,19 @@ #region License -/* - * Copyright 2002-2010 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. +/* + * Copyright 2002-2010 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 @@ -22,23 +22,52 @@ using System; namespace Spring.Http { - /** - * Java 5 enumeration of HTTP request methods. Intended for use - * with {@link org.springframework.http.client.ClientHttpRequest} - * and {@link org.springframework.web.client.RestTemplate}. - * - * @author Arjen Poutsma - * @since 3.0 - */ + /// + /// Enumeration of HTTP request methods as defined in the HTTP specification. + /// HTTP 1.1, section 6 + /// + /// Arjen Poutsma + /// Bruno Baia public enum HttpMethod { + /// + /// The OPTIONS method. + /// OPTIONS, + + /// + /// The GET method. + /// GET, - HEAD, - POST, - PUT, - DELETE, + + /// + /// The HEAD method. + /// + HEAD, + + /// + /// The POST method. + /// + POST, + + /// + /// The PUT method. + /// + PUT, + + /// + /// The DELETE method. + /// + DELETE, + + /// + /// The TRACE method. + /// TRACE, + + /// + /// The CONNECT method. + /// CONNECT } } diff --git a/src/Spring/Spring.Http/Http/HttpRequestMessage.cs b/src/Spring/Spring.Http/Http/HttpRequestMessage.cs index f4a4bcbf..6039aadc 100644 --- a/src/Spring/Spring.Http/Http/HttpRequestMessage.cs +++ b/src/Spring/Spring.Http/Http/HttpRequestMessage.cs @@ -22,7 +22,11 @@ using System.Net; namespace Spring.Http { - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5 + /// + /// Represents a HTTP request message, as defined in the HTTP specification. + /// HTTP 1.1, section 5 + /// + /// Bruno Baia public class HttpRequestMessage { //private string requestUri; @@ -43,87 +47,94 @@ namespace Spring.Http // set { httpVersion = value; } //} + /// + /// Gets the HTTP method. + /// public HttpMethod Method { get { return this.method; } set { this.method = value; } } - /** - * Returns the headers of this message. - */ + /// + /// Gets the request headers. + /// public WebHeaderCollection Headers { get { return this.headers; } } - /** - * Returns the body of this message. - */ + /// + /// Gets the response body. + /// public object Body { get { return this.body; } } - /** - * Create a new {@code HttpRequestMessage} with no body and no headers. - */ + /// + /// Creates a new instance of with the given Http method. + /// + /// The HTTP method. public HttpRequestMessage(HttpMethod method) : this(null, new WebHeaderCollection(), method) { } - /** - * Create a new {@code HttpRequestMessage} with the given headers and no body. - * @param headers the message headers - */ + /// + /// Creates a new instance of with the given headers. + /// + /// The request headers. public HttpRequestMessage(WebHeaderCollection headers) : this(null, headers, HttpMethod.GET) { } - /** - * Create a new {@code HttpRequestMessage} with the given headers and no body. - * @param headers the message headers - */ + /// + /// Creates a new instance of with the given headers and HTTP method. + /// + /// The request headers. + /// The HTTP method. public HttpRequestMessage(WebHeaderCollection headers, HttpMethod method) : this(null, headers, method) { } - /** - * Create a new {@code HttpRequestMessage} with the given body and no headers. - * @param body the message body - */ + /// + /// Creates a new instance of with the given body. + /// + /// The response body. public HttpRequestMessage(object body) : this(body, new WebHeaderCollection(), HttpMethod.GET) { } - /** - * Create a new {@code HttpRequestMessage} with the given body and no headers. - * @param body the message body - */ + /// + /// Creates a new instance of with the given body and HTTP method. + /// + /// The response body. + /// The HTTP method. public HttpRequestMessage(object body, HttpMethod method) : this(body, new WebHeaderCollection(), method) { } - /** - * Create a new {@code HttpRequestMessage} with the given body and headers. - * @param body the messagae body - * @param headers the message headers - */ + /// + /// Creates a new instance of with the given body and headers. + /// + /// The response body. + /// The response headers. public HttpRequestMessage(object body, WebHeaderCollection headers) : this(body, headers, HttpMethod.GET) { } - /** - * Create a new {@code HttpRequestMessage} with the given body and headers. - * @param body the messagae body - * @param headers the message headers - */ + /// + /// Creates a new instance of with the given body, headers and HTTP method. + /// + /// The response body. + /// The response headers. + /// The HTTP method. public HttpRequestMessage(object body, WebHeaderCollection headers, HttpMethod method) { this.method = method; diff --git a/src/Spring/Spring.Http/Http/HttpResponseMessage.cs b/src/Spring/Spring.Http/Http/HttpResponseMessage.cs index 6f33bc6d..21f8e944 100644 --- a/src/Spring/Spring.Http/Http/HttpResponseMessage.cs +++ b/src/Spring/Spring.Http/Http/HttpResponseMessage.cs @@ -22,86 +22,31 @@ using System.Net; namespace Spring.Http { - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6 - public class HttpResponseMessage where T : class + /// + /// Represents a HTTP response message with no entity. + /// + /// Bruno Baia + public class HttpResponseMessage : HttpResponseMessage { - private WebHeaderCollection headers; - private T body; - private HttpStatusCode statusCode; - private string statusDescription; - - /** - * Returns the headers of this entity. - */ - public WebHeaderCollection Headers - { - get { return this.headers; } - } - - /** - * Returns the body of this entity. - */ - public T Body - { - get { return this.body; } - } - - /** - * Return the HTTP status code of the response. - * @return the HTTP status as an HttpStatus enum value - */ - public HttpStatusCode StatusCode - { - get { return statusCode; } - } - - public string StatusDescription - { - get { return statusDescription; } - } - - /** - * Create a new {@code ResponseEntity} with the given status code, and no body, no headers. - * @param body the entity body - * @param statusCode the status code - */ + /// + /// 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(null, null, statusCode, statusDescription) + base(null, null, statusCode, statusDescription) { } - /** - * Create a new {@code ResponseEntity} with the given body and status code, and no headers. - * @param body the entity body - * @param statusCode the status code - */ - public HttpResponseMessage(T body, HttpStatusCode statusCode, string statusDescription) : - this(body, null, statusCode, statusDescription) - { - } - - /** - * Create a new {@code HttpEntity} with the given headers and status code, and no body. - * @param headers the entity headers - * @param statusCode the status code - */ + /// + /// 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(WebHeaderCollection headers, HttpStatusCode statusCode, string statusDescription) : - this(null, headers, statusCode, statusDescription) + base(null, headers, statusCode, statusDescription) { } - - /** - * Create a new {@code HttpEntity} with the given body, headers, and status code. - * @param body the entity body - * @param headers the entity headers - * @param statusCode the status code - */ - public HttpResponseMessage(T body, WebHeaderCollection headers, HttpStatusCode statusCode, string statusDescription) - { - this.statusCode = statusCode; - this.statusDescription = statusDescription; - this.body = body; - this.headers = headers; - } } } diff --git a/src/Spring/Spring.Http/Http/HttpResponseMessage`1.cs b/src/Spring/Spring.Http/Http/HttpResponseMessage`1.cs new file mode 100644 index 00000000..3c718cc0 --- /dev/null +++ b/src/Spring/Spring.Http/Http/HttpResponseMessage`1.cs @@ -0,0 +1,117 @@ +#region License + +/* + * Copyright 2002-2010 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.Net; + +namespace Spring.Http +{ + /// + /// Represents a HTTP response message, as defined in the HTTP specification. + /// HTTP 1.1, section 6 + /// + /// The type of the response body. + /// Bruno Baia + public class HttpResponseMessage where T : class + { + private WebHeaderCollection headers; + private T body; + private HttpStatusCode statusCode; + private string statusDescription; + + /// + /// Gets the response headers. + /// + public WebHeaderCollection Headers + { + get { return this.headers; } + } + + /// + /// Gets the response body. May be null. + /// + public T Body + { + get { return this.body; } + } + + /// + /// 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(null, null, statusCode, statusDescription) + { + } + + /// + /// 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, null, 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(WebHeaderCollection headers, HttpStatusCode statusCode, string statusDescription) : + this(null, headers, 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, WebHeaderCollection headers, HttpStatusCode statusCode, string statusDescription) + { + this.statusCode = statusCode; + this.statusDescription = statusDescription; + this.body = body; + this.headers = headers; + } + } +} diff --git a/src/Spring/Spring.Http/Http/IHttpWebRequestFactory.cs b/src/Spring/Spring.Http/Http/IHttpWebRequestFactory.cs index 4a34e38e..d8e9c965 100644 --- a/src/Spring/Spring.Http/Http/IHttpWebRequestFactory.cs +++ b/src/Spring/Spring.Http/Http/IHttpWebRequestFactory.cs @@ -23,24 +23,18 @@ using System.Net; namespace Spring.Http { - /** - * Factory for {@link ClientHttpRequest} objects. - * Requests are created by the {@link #createRequest(URI, HttpMethod)} method. - * - * @author Arjen Poutsma - * @since 3.0 - */ + /// + /// Factory for objects. Requests are created by the method. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) public interface IHttpWebRequestFactory { - /** - * Create a new {@link ClientHttpRequest} for the specified URI and HTTP method. - *

The returned request can be written to, and then executed by calling - * {@link ClientHttpRequest#execute()}. - * @param uri the URI to create a request for - * @param httpMethod the HTTP method to execute - * @return the created request - * @throws IOException in case of I/O errors - */ + ///

+ /// Create a new for the specified URI. + /// + /// The URI to create a request for. + /// The created request HttpWebRequest CreateRequest(Uri uri); } } diff --git a/src/Spring/Spring.Http/Http/MediaType.cs b/src/Spring/Spring.Http/Http/MediaType.cs index 07b0ee64..225569dc 100644 --- a/src/Spring/Spring.Http/Http/MediaType.cs +++ b/src/Spring/Spring.Http/Http/MediaType.cs @@ -28,88 +28,88 @@ using Spring.Util; namespace Spring.Http { - /** - * Represents an Internet Media Type, as defined in the HTTP specification. - * - *

Consists of a {@linkplain #getType() type} and a {@linkplain #getSubtype() subtype}. - * Also has functionality to parse media types from a string using {@link #parseMediaType(String)}, - * or multiple comma-separated media types using {@link #parseMediaTypes(String)}. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - * @see HTTP 1.1, section 3.7 - */ + ///

+ /// 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 , + /// or multiple comma-separated media types using . + /// + /// Arjen Poutsma + /// Juergen Hoeller + /// Bruno Baia (.NET) public class MediaType : IComparable { - /** - * Public constant media type that includes all media ranges (i.e. */*). - */ + /// + /// Public constant media type that includes all media ranges (i.e. '*/*'). + /// public static readonly MediaType ALL = new MediaType("*", "*"); - /** - * Public constant media type for {@code application/atom+xml}. - */ + /// + /// Public constant media type for 'application/atom+xml'. + /// public static readonly MediaType APPLICATION_ATOM_XML = new MediaType("application", "atom+xml"); - /** - * Public constant media type for {@code application/x-www-form-urlencoded}. - * */ + /// + /// Public constant media type for 'application/x-www-form-urlencoded'. + /// public static readonly MediaType APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); - /** - * Public constant media type for {@code application/json}. - * */ + /// + /// Public constant media type for 'application/json'. + /// public static readonly MediaType APPLICATION_JSON = new MediaType("application", "json"); - /** - * Public constant media type for {@code application/octet-stream}. - * */ + /// + /// Public constant media type for 'application/octet-stream'. + /// public static readonly MediaType APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream"); - /** - * Public constant media type for {@code application/xhtml+xml}. - * */ + /// + /// Public constant media type for 'application/xhtml+xml'. + /// public static readonly MediaType APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml"); - /** - * Public constant media type for {@code image/gif}. - */ + /// + /// Public constant media type for 'image/gif'. + /// public static readonly MediaType IMAGE_GIF = new MediaType("image", "gif"); - /** - * Public constant media type for {@code image/jpeg}. - */ + /// + /// Public constant media type for 'image/jpeg'. + /// public static readonly MediaType IMAGE_JPEG = new MediaType("image", "jpeg"); - /** - * Public constant media type for {@code image/png}. - */ + /// + /// Public constant media type for 'image/png'. + /// public static readonly MediaType IMAGE_PNG = new MediaType("image", "png"); - /** - * Public constant media type for {@code image/xml}. - */ + /// + /// Public constant media type for 'image/xml'. + /// public static readonly MediaType APPLICATION_XML = new MediaType("application", "xml"); - /** - * Public constant media type for {@code multipart/form-data}. - * */ + /// + /// Public constant media type for 'multipart/form-data'. + /// public static readonly MediaType MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); - /** - * Public constant media type for {@code text/html}. - * */ + /// + /// Public constant media type for 'text/html'. + /// public static readonly MediaType TEXT_HTML = new MediaType("text", "html"); - /** - * Public constant media type for {@code text/plain}. - * */ + /// + /// Public constant media type for 'text/plain'. + /// public static readonly MediaType TEXT_PLAIN = new MediaType("text", "plain"); - /** - * Public constant media type for {@code text/xml}. - * */ + /// + /// Public constant media type for 'text/xml'. + /// public static readonly MediaType TEXT_XML = new MediaType("text", "xml"); @@ -125,43 +125,41 @@ namespace Spring.Http private IDictionary parameters; - /** - * Return the primary type. - */ + /// + /// Gets the primary type. + /// public string Type { get { return this.type; } } - /** - * Return the subtype. - */ + /// + /// Gets the subtype. + /// public string Subtype { get { return this.subtype; } } - /** - * Indicate whether the {@linkplain #getType() type} is the wildcard character * or not. - */ + /// + /// Indicate whether the type is the wildcard character '*', or not. + /// public bool IsWildcardType { get { return WILDCARD_TYPE == type; } } - /** - * Indicate whether the {@linkplain #getSubtype() subtype} is the wildcard character * or not. - * @return whether the subtype is * - */ + /// + /// Indicate whether the subtype is the wildcard character '*', or not. + /// public bool IsWildcardSubtype { get { return WILDCARD_TYPE == subtype; } } - /** - * Return the character set, as indicated by a charset parameter, if any. - * @return the character set; or null if not available - */ + /// + /// Gets the character set, as indicated by a 'charset' parameter, if any. + /// public string CharSet { get @@ -174,11 +172,10 @@ namespace Spring.Http } } - /** - * Return the quality value, as indicated by a q parameter, if any. - * Defaults to 1.0. - * @return the quality factory - */ + /// + /// Gets the quality value, as indicated by a 'q' parameter, if any. + /// Defaults to '1.0'. + /// public double QualityValue { get @@ -190,75 +187,68 @@ namespace Spring.Http } } - /** - * Create a new {@link MediaType} for the given primary type. - *

The {@linkplain #getSubtype() subtype} is set to *, parameters empty. - * @param type the primary type - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ + ///

+ /// 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) { } - /** - * Create a new {@link MediaType} for the given primary type and subtype. - *

The parameters are empty. - * @param type the primary type - * @param subtype the subtype - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ + ///

+ /// 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.InvariantCultureIgnoreCase)) { } - /** - * Create a new {@link MediaType} for the given type, subtype, and character set. - * @param type the primary type - * @param subtype the subtype - * @param charSet the character set - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ + /// + /// 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); } - /** - * Create a new {@link MediaType} for the given type, subtype, and quality value. - * - * @param type the primary type - * @param subtype the subtype - * @param qualityValue the quality value - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ + /// + /// 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)); } - /** - * Copy-constructor that copies the type and subtype of the given {@link MediaType}, - * and allows for different parameter. - * @param other the other media type - * @param parameters the parameters, may be null - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ - public MediaType(MediaType other, IDictionary parameters) : - this(other.Type, other.Subtype, parameters) + /// + /// 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) { } - /** - * Create a new {@link MediaType} for the given type, subtype, and parameters. - * @param type the primary type - * @param subtype the subtype - * @param parameters the parameters, may be null - * @throws IllegalArgumentException if any of the parameters contain illegal characters - */ + /// + /// 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) { AssertUtils.ArgumentHasText(type, "'type' must not be empty"); @@ -285,6 +275,15 @@ namespace Spring.Http //} } + /// + /// Determines whether the specified is equal to the current . + /// + /// + /// The to compare with the current . + /// + /// + /// true if the specified is equal to the current ; otherwise, false. + /// public override bool Equals(object obj) { if (this == obj) @@ -314,6 +313,15 @@ namespace Spring.Http return false; } + /// + /// Serves as a hash function for a particular type. + /// + /// + /// is suitable for use in hashing algorithms and data structures like a hash table. + /// + /// + /// A hash code for the current . + /// public override int GetHashCode() { int result = this.type.GetHashCode(); @@ -322,6 +330,12 @@ namespace Spring.Http return result; } + /// + /// Returns a that represents the current + /// + /// + /// A that represents the current . + /// public override string ToString() { StringBuilder builder = new StringBuilder(); @@ -338,11 +352,11 @@ namespace Spring.Http return builder.ToString(); } - /** - * Checks the given token string for illegal characters, as defined in RFC 2616, section 2.2. - * @throws IllegalArgumentException in case of illegal characters - * @see HTTP 1.1, section 2.2 - */ + // ** + // * Checks the given token string for illegal characters, as defined in RFC 2616, section 2.2. + // * @throws IllegalArgumentException in case of illegal characters + // * @see HTTP 1.1, section 2.2 + // */ //private void checkToken(String s) { // for (int i=0; i < s.length(); i++ ) { // char ch = s.charAt(i); @@ -382,26 +396,31 @@ namespace Spring.Http // return isQuotedString(s) ? s.substring(1, s.length() - 1) : s; //} - /** - * Return a generic parameter value, given a parameter name. - * @param name the parameter name - * @return the parameter value; or null if not present - */ + /// + /// 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 {@link MediaType} includes the given media type. - *

For instance, {@code text/*} includes {@code text/plain}, {@code text/html}, and {@code application/*+xml} - * includes {@code application/soap+xml}, etc. This method is non-symmetic. - * @param other the reference media type with which to compare - * @return true if this media type includes the given media type; false otherwise - */ - public bool Includes(MediaType other) + ///

+ /// 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 (other == null) + if (otherMediaType == null) { return false; } @@ -410,21 +429,21 @@ namespace Spring.Http // */* includes anything return true; } - else if (this.type == other.type) + else if (this.type == otherMediaType.type) { - if (this.subtype == other.subtype || this.IsWildcardSubtype) + if (this.subtype == otherMediaType.subtype || this.IsWildcardSubtype) { return true; } // application/*+xml includes application/soap+xml int thisPlusIdx = this.subtype.IndexOf('+'); - int otherPlusIdx = other.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 = other.subtype.Substring(otherPlusIdx + 1); + string otherSubtypeSuffix = otherMediaType.subtype.Substring(otherPlusIdx + 1); if (thisSubtypeSuffix == otherSubtypeSuffix && WILDCARD_TYPE == thisSubtypeNoSuffix) { return true; @@ -434,39 +453,43 @@ namespace Spring.Http return false; } - /** - * Indicate whether this {@link MediaType} is compatible with the given media type. - *

For instance, {@code text/*} is compatible with {@code text/plain}, {@code text/html}, and vice versa. - * In effect, this method is similar to {@link #includes(MediaType)}, except that it's symmetric. - * @param other the reference media type with which to compare - * @return true if this media type is compatible with the given media type; false otherwise - */ - public bool IsCompatibleWith(MediaType other) + ///

+ /// 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 (other == null) + if (otherMediaType == null) { return false; } - if (this.IsWildcardType || other.IsWildcardType) + if (this.IsWildcardType || otherMediaType.IsWildcardType) { return true; } - else if (this.type == other.type) + else if (this.type == otherMediaType.type) { - if (this.subtype == other.subtype || this.IsWildcardSubtype || other.IsWildcardSubtype) + 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 = other.subtype.IndexOf('+'); + int otherPlusIdx = otherMediaType.subtype.IndexOf('+'); if (thisPlusIdx != -1 && otherPlusIdx != -1) { string thisSubtypeNoSuffix = this.subtype.Substring(0, thisPlusIdx); - string otherSubtypeNoSuffix = other.subtype.Substring(0, otherPlusIdx); + string otherSubtypeNoSuffix = otherMediaType.subtype.Substring(0, otherPlusIdx); string thisSubtypeSuffix = this.subtype.Substring(thisPlusIdx + 1); - string otherSubtypeSuffix = other.subtype.Substring(otherPlusIdx + 1); + string otherSubtypeSuffix = otherMediaType.subtype.Substring(otherPlusIdx + 1); if (thisSubtypeSuffix == otherSubtypeSuffix && (WILDCARD_TYPE == thisSubtypeNoSuffix || WILDCARD_TYPE == otherSubtypeNoSuffix)) @@ -480,11 +503,16 @@ namespace Spring.Http #region IComparable Membres - /** - * Compares this {@link MediaType} to another alphabetically. - * @param other media type to compare to - * @see #sortBySpecificity(List) - */ + /// + /// 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); @@ -519,15 +547,17 @@ namespace Spring.Http #endregion - /** - * Parse the given String into a single {@link MediaType}. - * @param mediaType the string to parse - * @return the media type - * @throws IllegalArgumentException if the string cannot be parsed - */ + /// + /// Parse the given String into a single . + /// + /// The string to parse. + /// The media type. public static MediaType ParseMediaType(string mediaType) { - AssertUtils.ArgumentHasText(mediaType, "'mediaType' must not be empty"); + if (!StringUtils.HasText(mediaType)) + { + return null; + } string[] parts = mediaType.Split(';'); string fullType = parts[0].Trim(); @@ -570,17 +600,18 @@ namespace Spring.Http return new MediaType(type, subtype, parameters); } - /** - * Parse the given, comma-seperated string into a list of {@link MediaType} objects. - *

This method can be used to parse an Accept or Content-Type header. - * @param mediaTypes the string to parse - * @return the list of media types - * @throws IllegalArgumentException if the string cannot be parsed - */ + ///

+ /// Parse the given, comma-seperated string into a list of objects. + /// + /// + /// This method can be used to parse an 'Accept' or 'Content-Type' header. + /// + /// The string to parse. + /// The list of media types. public static List ParseMediaTypes(string mediaTypes) { List mediaTypeList = new List(); - if (!StringUtils.HasLength(mediaTypes)) + if (!StringUtils.HasText(mediaTypes)) { return mediaTypeList; } @@ -592,13 +623,14 @@ namespace Spring.Http return mediaTypeList; } - /** - * Return a string representation of the given list of {@link MediaType} objects. - *

This method can be used to for an {@code Accept} or {@code Content-Type} header. - * @param mediaTypes the string to parse - * @return the list of media types - * @throws IllegalArgumentException if the String cannot be parsed - */ + ///

+ /// 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(); @@ -613,32 +645,38 @@ namespace Spring.Http return builder.ToString(); } - /** - * Sorts the given list of {@link MediaType} objects by specificity. - *

Given two media types: - *

    - *
  1. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the - * wildcard is ordered before the other.
  2. - *
  3. if the two media types have different {@linkplain #getType() types}, then they are considered equal and - * remain their current order.
  4. - *
  5. if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without - * the wildcard is sorted before the other.
  6. - *
  7. if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal - * and remain their current order.
  8. - *
  9. if the two media types have different {@linkplain #getQualityValue() 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 {@linkplain #getParameter(String) 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
- * @param mediaTypes the list of media types to be sorted - * @see HTTP 1.1, section 14.1 - */ + /// + /// 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) { AssertUtils.ArgumentNotNull(mediaTypes, "mediaTypes"); @@ -649,26 +687,29 @@ namespace Spring.Http } } - /** - * Sorts the given list of {@link MediaType} objects by quality value. - *

Given two media types: - *

    - *
  1. if the two media types have different {@linkplain #getQualityValue() quality value}, then the media type - * with the highest quality value is ordered before the other.
  2. - *
  3. if either media type has a {@linkplain #isWildcardType() wildcard type}, then the media type without the - * wildcard is ordered before the other.
  4. - *
  5. if the two media types have different {@linkplain #getType() types}, then they are considered equal and - * remain their current order.
  6. - *
  7. if either media type has a {@linkplain #isWildcardSubtype() wildcard subtype}, then the media type without - * the wildcard is sorted before the other.
  8. - *
  9. if the two media types have different {@linkplain #getSubtype() subtypes}, then they are considered equal - * and remain their current order.
  10. - *
  11. if the two media types have a different amount of {@linkplain #getParameter(String) parameters}, then the - * media type with the most parameters is ordered before the other.
  12. - *
- * @param mediaTypes the list of media types to be sorted - * @see #getQualityValue() - */ + /// + /// 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) { AssertUtils.ArgumentNotNull(mediaTypes, "mediaTypes"); @@ -679,7 +720,14 @@ namespace Spring.Http } } + /// + /// 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 diff --git a/src/Spring/Spring.Http/Http/Rest/IRequestCallback.cs b/src/Spring/Spring.Http/Http/Rest/IRequestCallback.cs index 11c2cc58..b1e71352 100644 --- a/src/Spring/Spring.Http/Http/Rest/IRequestCallback.cs +++ b/src/Spring/Spring.Http/Http/Rest/IRequestCallback.cs @@ -23,25 +23,31 @@ using System.Net; namespace Spring.Http.Rest { - /** - * Callback interface for code that operates on a {@link ClientHttpRequest}. Allows to manipulate the request - * headers, and write to the request body. - * - *

Used internally by the {@link RestTemplate}, but also useful for application code. - * - * @author Arjen Poutsma - * @see RestTemplate#execute - * @since 3.0 - */ + ///

+ /// 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 {@link RestTemplate#execute} with an opened {@code ClientHttpRequest}. - * Does not need to care about closing the request or about handling errors: - * this will all be handled by the {@code RestTemplate}. - * @param request the active HTTP request - * @throws IOException in case of I/O errors - */ + /// + /// Gets called by with an opened 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(HttpWebRequest request); } } diff --git a/src/Spring/Spring.Http/Http/Rest/IResponseExtractor.cs b/src/Spring/Spring.Http/Http/Rest/IResponseExtractor.cs index 3aa609ce..98f568b9 100644 --- a/src/Spring/Spring.Http/Http/Rest/IResponseExtractor.cs +++ b/src/Spring/Spring.Http/Http/Rest/IResponseExtractor.cs @@ -23,26 +23,31 @@ using System.Net; namespace Spring.Http.Rest { - /** - * Generic callback interface used by {@link RestTemplate}'s retrieval methods - * Implementations of this interface perform the actual work of extracting data - * from a {@link ClientHttpResponse}, but don't need to worry about exception - * handling or closing resources. - * - *

Used internally by the {@link RestTemplate}, but also useful for application code. - * - * @author Arjen Poutsma - * @since 3.0 - * @see RestTemplate#execute - */ + ///

+ /// 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 { - /** - * Extract data from the given {@code ClientHttpResponse} and return it. - * @param response the HTTP response - * @return the extracted data - * @throws IOException in case of I/O errors - */ - T ExtractData(HttpWebResponse response); // throws IOException; + /// + /// 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(HttpWebResponse response); } } diff --git a/src/Spring/Spring.Http/Http/Rest/IRestOperations.cs b/src/Spring/Spring.Http/Http/Rest/IRestOperations.cs index 82b32130..abb3d2ad 100644 --- a/src/Spring/Spring.Http/Http/Rest/IRestOperations.cs +++ b/src/Spring/Spring.Http/Http/Rest/IRestOperations.cs @@ -26,413 +26,559 @@ using Spring.Http; namespace Spring.Http.Rest { + /// + /// 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 interface IRestOperations { #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. - * @param url the URL - * @param responseType the type of the return value - * @param uriVariables the variables to expand the template - * @return the converted object - */ - T GetForObject(string url, params string[] uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 string[] uriVariables) where T : class; - /** - * Retrieve a representation by doing a GET on the URI template. - * The response (if any) is converted and returned. - *

URI Template variables are expanded using the given map. - * @param url the URL - * @param responseType the type of the return value - * @param uriVariables the map containing variables for the URI template - * @return the converted object - */ - T GetForObject(string url, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 URL . - * The response (if any) is converted and returned. - * @param url the URL - * @param responseType the type of the return value - * @return the converted object - */ - T GetForObject(Uri url) where T : class; //throws RestClientException; + /// + /// 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 {@link ResponseEntity}. - *

URI Template variables are expanded using the given URI variables, if any. - * @param url the URL - * @param responseType the type of the return value - * @param uriVariables the variables to expand the template - * @return the entity - * @since 3.0.2 - */ - HttpResponseMessage GetForMessage(string url, params string[] uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 string[] uriVariables) where T : class; - /** - * Retrieve a representation by doing a GET on the URI template. - * The response is converted and stored in an {@link ResponseEntity}. - *

URI Template variables are expanded using the given map. - * @param url the URL - * @param responseType the type of the return value - * @param uriVariables the map containing variables for the URI template - * @return the converted object - * @since 3.0.2 - */ - HttpResponseMessage GetForMessage(string url, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 a representation by doing a GET on the URL . - * The response is converted and stored in an {@link ResponseEntity}. - * @param url the URL - * @param responseType the type of the return value - * @return the converted object - * @since 3.0.2 - */ - HttpResponseMessage GetForMessage(Uri url) where T : class; //throws RestClientException; + /// + /// 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. - * @param url the URL - * @param uriVariables the variables to expand the template - * @return all HTTP headers of that resource - */ - WebHeaderCollection HeadForHeaders(string url, params string[] uriVariables); //throws RestClientException; + ///

+ /// 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 + WebHeaderCollection HeadForHeaders(string url, params string[] uriVariables); - /** - * Retrieve all headers of the resource specified by the URI template. - *

URI Template variables are expanded using the given map. - * @param url the URL - * @param uriVariables the map containing variables for the URI template - * @return all HTTP headers of that resource - */ - WebHeaderCollection HeadForHeaders(string url, IDictionary uriVariables); //throws RestClientException; + ///

+ /// 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 + WebHeaderCollection HeadForHeaders(string url, IDictionary uriVariables); - /** - * Retrieve all headers of the resource specified by the URL. - * @param url the URL - * @return all HTTP headers of that resource - */ + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// All HTTP headers of that resource WebHeaderCollection HeadForHeaders(Uri url); //throws RestClientException; #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 {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param uriVariables the variables to expand the template - * @return the value for the Location header - * @see HttpEntity - */ - Uri PostForLocation(string url, object request, params string[] uriVariables); //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The value for the Location header. + Uri PostForLocation(string url, object request, params string[] 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 map. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param uriVariables the variables to expand the template - * @return the value for the Location header - * @see HttpEntity - */ - Uri PostForLocation(string url, object request, IDictionary uriVariables); //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 URL, and returns the value of the - * Location header. This header typically indicates where the new resource is stored. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @return the value for the Location header - * @see HttpEntity - */ - Uri PostForLocation(Uri url, object request); //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param responseType the type of the return value - * @param uriVariables the variables to expand the template - * @return the converted object - * @see HttpEntity - */ - T PostForObject(string url, object request, params string[] uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The converted object. + T PostForObject(string url, object request, params string[] 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 map. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param responseType the type of the return value - * @param uriVariables the variables to expand the template - * @return the converted object - * @see HttpEntity - */ - T PostForObject(string url, object request, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 URL, - * and returns the representation found in the response. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param responseType the type of the return value - * @return the converted object - * @see HttpEntity - */ - T PostForObject(Uri url, object request) where T : class; //throws RestClientException; + ///

+ /// Create a new resource by POSTing the given object to the URI template, + /// and returns the representation found in the response. + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 {@link ResponseEntity}. - *

URI Template variables are expanded using the given URI variables, if any. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param uriVariables the variables to expand the template - * @return the converted object - * @see HttpEntity - * @since 3.0.2 - */ - HttpResponseMessage PostForMessage(string url, object request, params string[] uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The HTTP response message. + HttpResponseMessage PostForMessage(string url, object request, params string[] uriVariables) where T : class; - /** - * Create a new resource by POSTing the given object to the URI template, - * and returns the response as {@link HttpEntity}. - *

URI Template variables are expanded using the given map. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @param uriVariables the variables to expand the template - * @return the converted object - * @see HttpEntity - * @since 3.0.2 - */ - HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 URL, - * and returns the response as {@link ResponseEntity}. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be POSTed, may be null - * @return the converted object - * @see HttpEntity - * @since 3.0.2 - */ - HttpResponseMessage PostForMessage(Uri url, object request) where T : class; //throws RestClientException; + ///

+ /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The HTTP response message with no entity. + HttpResponseMessage PostForMessage(string url, object request, params string[] 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// 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 {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be PUT, may be null - * @param uriVariables the variables to expand the template - * @see HttpEntity - */ - void Put(string url, object request, params string[] uriVariables); //throws RestClientException; + ///

+ /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be PUT, may be null. + /// The variables to expand the template. + void Put(string url, object request, params string[] uriVariables); - /** - * Creates a new resource by PUTting the given object to URI template. - *

URI Template variables are expanded using the given map. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be PUT, may be null - * @param uriVariables the variables to expand the template - * @see HttpEntity - */ - void Put(string url, object request, IDictionary uriVariables); //throws RestClientException; + ///

+ /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// + /// URI Template variables are expanded using the given dictionary. + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be PUT, may be null. + /// The dictionary containing variables for the URI template. + void Put(string url, object request, IDictionary uriVariables); - /** - * Creates a new resource by PUTting the given object to URL. - *

The {@code request} parameter can be a {@link HttpEntity} in order to - * add additional HTTP headers to the request. - * @param url the URL - * @param request the Object to be PUT, may be null - * @see HttpEntity - */ - void Put(Uri url, object request); //throws RestClientException; + ///

+ /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be PUT, may be null. + 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. - * @param url the URL - * @param uriVariables the variables to expand in the template - */ - void Delete(string url, params string[] uriVariables); //throws RestClientException; + ///

+ /// 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 string[] uriVariables); - /** - * Delete the resources at the specified URI. - *

URI Template variables are expanded using the given map. - * - * @param url the URL - * @param uriVariables the variables to expand the template - */ - void Delete(string url, IDictionary uriVariables); //throws RestClientException; + ///

+ /// 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 URL. - * @param url the URL - */ - void Delete(Uri url); //throws RestClientException; + /// + /// 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. - * @param url the URL - * @param uriVariables the variables to expand in the template - * @return the value of the allow header - */ - IList OptionsForAllow(string url, params string[] uriVariables); //throws RestClientException; + ///

+ /// 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 string[] uriVariables); - /** - * Return the value of the Allow header for the given URI. - *

URI Template variables are expanded using the given map. - * @param url the URL - * @param uriVariables the variables to expand in the template - * @return the value of the allow header - */ - IList OptionsForAllow(string url, IDictionary uriVariables); //throws RestClientException; + ///

+ /// 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 URL. - * @param url the URL - * @return the value of the allow header - */ - IList OptionsForAllow(Uri url); //throws RestClientException; + /// + /// 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 entity to the request, and - * returns the response as {@link ResponseEntity}. - *

URI Template variables are expanded using the given URI variables, if any. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null} - * @param responseType the type of the return value - * @param uriVariables the variables to expand in the template - * @return the response as entity - * @since 3.0.2 - */ - HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, params string[] uriVariables) where T : class; //throws RestClientException; + ///

+ /// Execute the HTTP request 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 request message to write to the request. + /// The variables to expand the template. + /// The HTTP response message. + HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, params string[] uriVariables) where T : class; - /** - * Execute the HTTP method to the given URI template, writing the given request entity to the request, and - * returns the response as {@link ResponseEntity}. - *

URI Template variables are expanded using the given URI variables, if any. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null} - * @param responseType the type of the return value - * @param uriVariables the variables to expand in the template - * @return the response as entity - * @since 3.0.2 - */ - HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// Execute the HTTP request 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 request message to write to the request. + /// The dictionary containing variables for the URI template. + /// The HTTP response message. + HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, IDictionary uriVariables) where T : class; - /** - * Execute the HTTP method to the given URI template, writing the given request entity to the request, and - * returns the response as {@link ResponseEntity}. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null} - * @param responseType the type of the return value - * @return the response as entity - * @since 3.0.2 - */ - HttpResponseMessage Exchange(Uri url, HttpRequestMessage requestMessage) where T : class; //throws RestClientException; + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The HTTP response message. + HttpResponseMessage Exchange(Uri url, HttpRequestMessage requestMessage) where T : class; + + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The variables to expand the template. + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, params string[] uriVariables); + + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, IDictionary uriVariables); + + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The HTTP response message with no entity. + HttpResponseMessage Exchange(Uri url, HttpRequestMessage requestMessage); #endregion #region General execution - /** - * Execute the HTTP method to the given URI template, preparing the request with the - * {@link RequestCallback}, and reading the response with a {@link ResponseExtractor}. - *

URI Template variables are expanded using the given URI variables, if any. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestCallback object that prepares the request - * @param responseExtractor object that extracts the return value from the response - * @param uriVariables the variables to expand in the template - * @return an arbitrary object, as returned by the {@link 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 URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor, params string[] uriVariables) where T : class; //throws RestClientException; - /** - * Execute the HTTP method to the given URI template, preparing the request with the - * {@link RequestCallback}, and reading the response with a {@link ResponseExtractor}. - *

URI Template variables are expanded using the given URI variables map. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestCallback object that prepares the request - * @param responseExtractor object that extracts the return value from the response - * @param uriVariables the variables to expand in the template - * @return an arbitrary object, as returned by the {@link ResponseExtractor} - */ - T Execute(string url, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables) where T : class; //throws RestClientException; + ///

+ /// 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. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables) where T : class; - /** - * Execute the HTTP method to the given URL, preparing the request with the - * {@link RequestCallback}, and reading the response with a {@link ResponseExtractor}. - * @param url the URL - * @param method the HTTP method (GET, POST, etc) - * @param requestCallback object that prepares the request - * @param responseExtractor object that extracts the return value from the response - * @return an arbitrary object, as returned by the {@link ResponseExtractor} - */ - T Execute(Uri url, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class; //throws RestClientException; + /// + /// 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. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class; #endregion } diff --git a/src/Spring/Spring.Http/Http/Rest/RestClientException.cs b/src/Spring/Spring.Http/Http/Rest/RestClientException.cs index 52fdf86e..4d862e02 100644 --- a/src/Spring/Spring.Http/Http/Rest/RestClientException.cs +++ b/src/Spring/Spring.Http/Http/Rest/RestClientException.cs @@ -23,19 +23,23 @@ using System.Runtime.Serialization; namespace Spring.Http.Rest { + /// + /// 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 - /// RestClientException class. + /// Creates a new instance of the class. /// public RestClientException() { } /// - /// Creates a new instance of the RestClientException class. + /// Creates a new instance of the class. /// /// /// A message about the exception. @@ -46,7 +50,7 @@ namespace Spring.Http.Rest } /// - /// Creates a new instance of the RestClientException class. + /// Creates a new instance of the class. /// /// /// A message about the exception. @@ -60,7 +64,7 @@ namespace Spring.Http.Rest } /// - /// Creates a new instance of the RestClientException class. + /// Creates a new instance of the class. /// /// /// The diff --git a/src/Spring/Spring.Http/Http/Rest/RestTemplate.cs b/src/Spring/Spring.Http/Http/Rest/RestTemplate.cs index 43c39872..79ec4119 100644 --- a/src/Spring/Spring.Http/Http/Rest/RestTemplate.cs +++ b/src/Spring/Spring.Http/Http/Rest/RestTemplate.cs @@ -23,7 +23,6 @@ using System.IO; using System.Net; using System.Collections.Generic; -using Spring.Util; using Spring.Http; using Spring.Http.Converters; using Spring.Http.Converters.Xml; @@ -31,73 +30,73 @@ using Spring.Http.Converters.Json; using Spring.Http.Converters.Feed; using Spring.Http.Rest.Support; using UriTemplate = Spring.Util.UriTemplate; // UriTemplate in .NET Framework since 3.5 +using AssertUtils = Spring.Util.AssertUtils; +using StringUtils = Spring.Util.StringUtils; namespace Spring.Http.Rest { - /** - * 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{@link #delete}
GET{@link #getForObject}
{@link #getForEntity}
HEAD{@link #headForHeaders}
OPTIONS{@link #optionsForAllow}
POST{@link #postForLocation}
{@link #postForObject}
PUT{@link #put}
any{@link #exchange}
{@link #execute}
- * - *

For each of these HTTP methods, there are three corresponding Java methods in the {@code RestTemplate}. Two - * variant take a {@code String} URI as first argument (eg. {@link #getForObject(String, Class, Object[])}, {@link - * #getForObject(String, Class, Map)}), and are capable of substituting any {@linkplain UriTemplate URI templates} in - * that URL using either a {@code String} variable arguments array, or a {@code Map}. The string varargs - * variant expands the given template variables in order, so that - *

-     * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/bookings/{booking}", String.class,"42",
-     * "21");
-     * 
- * will perform a GET on {@code 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: - *
-     * Map<String, String> vars = Collections.singletonMap("hotel", "42");
-     * String result = restTemplate.getForObject("http://example.com/hotels/{hotel}/rooms/{hotel}", String.class, vars);
-     * 
- * will perform a GET on {@code http://example.com/hotels/42/rooms/42}. Alternatively, there are {@link URI} variant - * methods ({@link #getForObject(URI, Class)}), which do not allow for URI templates, but allow you to reuse a single, - * expanded URI multiple times. - * - *

Furthermore, the {@code String}-argument methods assume that the URL String is unencoded. This means that - *

-     * restTemplate.getForObject("http://example.com/hotel list");
-     * 
- * will perform a GET on {@code http://example.com/hotel%20list}. As a result, any URL passed that is already encoded - * will be encoded twice (i.e. {@code http://example.com/hotel%20list} will become {@code - * http://example.com/hotel%2520list}). If this behavior is undesirable, use the {@code URI}-argument methods, which - * will not perform any URL encoding. - * - *

Objects passed to and returned from these methods are converted to and from HTTP messages by {@link - * HttpMessageConverter} instances. Converters for the main mime types are registered by default, but you can also write - * your own converter and register it via the {@link #setMessageConverters messageConverters} bean property. - * - *

This template uses a {@link org.springframework.http.client.SimpleClientHttpRequestFactory} and a {@link - * DefaultResponseErrorHandler} as default strategies for creating HTTP connections or handling HTTP errors, - * respectively. These defaults can be overridden through the {@link #setRequestFactory(ClientHttpRequestFactory) - * requestFactory} and {@link #setErrorHandler(ResponseErrorHandler) errorHandler} bean properties. - * - * @author Arjen Poutsma - * @see HttpMessageConverter - * @see RequestCallback - * @see ResponseExtractor - * @see ResponseErrorHandler - * @since 3.0 - */ + ///

+ /// 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, String> vars = new Dictionary<String, String>(); + /// 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 as default strategy for creating HTTP connections. + /// + ///
+ /// + /// + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) public class RestTemplate : IRestOperations { #region Logging @@ -116,6 +115,9 @@ namespace Spring.Http.Rest private IResponseExtractor headersExtractor; + /// + /// Gets or sets the base URL for the request. + /// public Uri BaseAddress { get @@ -133,18 +135,29 @@ namespace Spring.Http.Rest } } + /// + /// Indicates if an exception is thrown when a HTTP client or server error happens (HTTP status code 3xx or 4xx). + /// By default, the value is to be consistent with .NET behavior in . + /// public bool ThrowExceptionOnError { get { return _throwExceptionOnError; } set { _throwExceptionOnError = 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. + /// public IHttpWebRequestFactory RequestFactory { get { return this._requestFactory; } @@ -155,19 +168,29 @@ namespace Spring.Http.Rest #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); } - /** Create a new instance of the {@link RestTemplate} using default settings. */ + /// + /// Creates a new instance of . + /// public RestTemplate() { this._throwExceptionOnError = true; @@ -175,22 +198,35 @@ namespace Spring.Http.Rest this._requestFactory = new DefaultHttpWebRequestFactory(); this._messageConverters = new List(); -#if NET_3_5 - //this._messageConverters.Add(new JsonHttpMessageConverter()); - this._messageConverters.Add(new Atom10FeedHttpMessageConverter()); - this._messageConverters.Add(new Rss20FeedHttpMessageConverter()); - this._messageConverters.Add(new XElementHttpMessageConverter()); -#endif - this._messageConverters.Add(new XmlDocumentHttpMessageConverter()); - //this._messageConverters.Add(new XmlSerializableHttpMessageConverter()); - this._messageConverters.Add(new StringHttpMessageConverter()); + this._messageConverters.Add(new ByteArrayHttpMessageConverter()); + this._messageConverters.Add(new StringHttpMessageConverter()); + //this._messageConverters.Add(new XmlSerializableHttpMessageConverter()); + this._messageConverters.Add(new XmlDocumentHttpMessageConverter()); +#if NET_3_5 + this._messageConverters.Add(new DataContractHttpMessageConverter()); + this._messageConverters.Add(new XElementHttpMessageConverter()); + this._messageConverters.Add(new Rss20FeedHttpMessageConverter()); + this._messageConverters.Add(new Atom10FeedHttpMessageConverter()); + //this._messageConverters.Add(new JsonHttpMessageConverter()); +#endif } #endregion #region IRestOperations Membres + /// + /// 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 string[] uriVariables) where T : class { AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(HttpMethod.GET, typeof(T), this._messageConverters); @@ -198,6 +234,17 @@ namespace Spring.Http.Rest return this.Execute(url, 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(HttpMethod.GET, typeof(T), this._messageConverters); @@ -205,6 +252,13 @@ namespace Spring.Http.Rest return this.Execute(url, 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(HttpMethod.GET, typeof(T), this._messageConverters); @@ -212,6 +266,17 @@ namespace Spring.Http.Rest return this.Execute(url, 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 string[] uriVariables) where T : class { AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(HttpMethod.GET, typeof(T), this._messageConverters); @@ -219,6 +284,17 @@ namespace Spring.Http.Rest return this.Execute>(url, 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(HttpMethod.GET, typeof(T), this._messageConverters); @@ -226,6 +302,13 @@ namespace Spring.Http.Rest return this.Execute>(url, 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(HttpMethod.GET, typeof(T), this._messageConverters); @@ -233,24 +316,64 @@ namespace Spring.Http.Rest return this.Execute>(url, requestCallback, responseExtractor); } + /// + /// 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 WebHeaderCollection HeadForHeaders(string url, params string[] uriVariables) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.HEAD); return this.Execute(url, requestCallback, this.headersExtractor, 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 WebHeaderCollection HeadForHeaders(string url, IDictionary uriVariables) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.HEAD); return this.Execute(url, requestCallback, this.headersExtractor, uriVariables); } + /// + /// Retrieve all headers of the resource specified by the URI template. + /// + /// The URL. + /// All HTTP headers of that resource public WebHeaderCollection HeadForHeaders(Uri url) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.HEAD); return this.Execute(url, requestCallback, this.headersExtractor); } + /// + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The value for the Location header. public Uri PostForLocation(string url, object request, params string[] uriVariables) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -261,6 +384,23 @@ namespace Spring.Http.Rest return StringUtils.HasText(location) ? new Uri(location) : null; } + /// + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The dictionary containing variables for the URI template. + /// The value for the Location header. public Uri PostForLocation(string url, object request, IDictionary uriVariables) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -271,6 +411,17 @@ namespace Spring.Http.Rest return StringUtils.HasText(location) ? new Uri(location) : null; } + /// + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The value for the Location header. public Uri PostForLocation(Uri url, object request) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -281,6 +432,23 @@ namespace Spring.Http.Rest return StringUtils.HasText(location) ? new Uri(location) : null; } + /// + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The converted object. public T PostForObject(string url, object request, params string[] uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -289,6 +457,23 @@ namespace Spring.Http.Rest return this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The dictionary containing variables for the URI template. + /// The converted object. public T PostForObject(string url, object request, IDictionary uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -297,6 +482,17 @@ namespace Spring.Http.Rest return this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The converted object. public T PostForObject(Uri url, object request) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -305,6 +501,23 @@ namespace Spring.Http.Rest return this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The HTTP response message. public HttpResponseMessage PostForMessage(string url, object request, params string[] uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -313,6 +526,23 @@ namespace Spring.Http.Rest return this.Execute>(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The dictionary containing variables for the URI template. + /// The HTTP response message. public HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -321,6 +551,17 @@ namespace Spring.Http.Rest return this.Execute>(url, requestCallback, responseExtractor, uriVariables); } + /// + /// Create a new resource by POSTing the given object to the URI template, + /// and returns the response as . + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The type of the response value. + /// The URL. + /// The Object to be POSTed, may be null. + /// The HTTP response message. public HttpResponseMessage PostForMessage(Uri url, object request) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -329,6 +570,86 @@ namespace Spring.Http.Rest return this.Execute>(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The variables to expand the template. + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(string url, object request, params string[] uriVariables) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( + HttpMethod.POST, request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(string url, object request, IDictionary uriVariables) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( + HttpMethod.POST, request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be POSTed, may be null. + /// The HTTP response message with no entity. + public HttpResponseMessage PostForMessage(Uri url, object request) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( + HttpMethod.POST, request, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, requestCallback, responseExtractor); + } + + /// + /// 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be PUT, may be null. + /// The variables to expand the template. public void Put(string url, object request, params string[] uriVariables) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -336,6 +657,20 @@ namespace Spring.Http.Rest this.Execute(url, 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 request parameter can be a in order to add additional HTTP headers to the request. + /// + /// + /// The URL. + /// The Object to be PUT, may be null. + /// The dictionary containing variables for the URI template. public void Put(string url, object request, IDictionary uriVariables) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -343,6 +678,14 @@ namespace Spring.Http.Rest this.Execute(url, requestCallback, null, uriVariables); } + /// + /// Create or update a resource by PUTting the given object to the URI. + /// + /// + /// The request parameter can be a in order to add additional HTTP headers to the request. + /// + /// The URL. + /// The Object to be PUT, may be null. public void Put(Uri url, object request) { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback( @@ -350,24 +693,53 @@ namespace Spring.Http.Rest this.Execute(url, requestCallback, null); } + /// + /// 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 string[] uriVariables) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.DELETE); this.Execute(url, requestCallback, 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) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.DELETE); this.Execute(url, requestCallback, null, uriVariables); } + /// + /// Delete the resources at the specified URI. + /// + /// The URL. public void Delete(Uri url) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.DELETE); this.Execute(url, requestCallback, null); } + /// + /// 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 string[] uriVariables) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.OPTIONS); @@ -378,6 +750,15 @@ namespace Spring.Http.Rest return ParseAllowHeader(allow); } + /// + /// 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) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.OPTIONS); @@ -387,6 +768,11 @@ namespace Spring.Http.Rest return ParseAllowHeader(allow); } + /// + /// Return the value of the Allow header for the given URI. + /// + /// The URL. + /// The value of the allow header. public IList OptionsForAllow(Uri url) { MethodRequestCallback requestCallback = new MethodRequestCallback(HttpMethod.OPTIONS); @@ -396,6 +782,18 @@ namespace Spring.Http.Rest return ParseAllowHeader(allow); } + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The variables to expand the template. + /// The HTTP response message. public HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, params string[] uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, typeof(T), this._messageConverters); @@ -403,6 +801,18 @@ namespace Spring.Http.Rest return this.Execute>(url, requestCallback, responseExtractor, uriVariables); } + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The dictionary containing variables for the URI template. + /// The HTTP response message. public HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, IDictionary uriVariables) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, typeof(T), this._messageConverters); @@ -410,6 +820,14 @@ namespace Spring.Http.Rest return this.Execute>(url, requestCallback, responseExtractor, uriVariables); } + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The HTTP response message. public HttpResponseMessage Exchange(Uri url, HttpRequestMessage requestMessage) where T : class { HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, typeof(T), this._messageConverters); @@ -417,6 +835,69 @@ namespace Spring.Http.Rest return this.Execute>(url, requestCallback, responseExtractor); } + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The variables to expand the template. + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, params string[] uriVariables) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The dictionary containing variables for the URI template. + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(string url, HttpRequestMessage requestMessage, IDictionary uriVariables) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, requestCallback, responseExtractor, uriVariables); + } + + /// + /// Execute the HTTP request 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 request message to write to the request. + /// The HTTP response message with no entity. + public HttpResponseMessage Exchange(Uri url, HttpRequestMessage requestMessage) + { + HttpMessageRequestCallback requestCallback = new HttpMessageRequestCallback(requestMessage, this._messageConverters); + HttpMessageResponseExtractor responseExtractor = new HttpMessageResponseExtractor(); + return this.Execute(url, 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 URI variables, if any. + /// + /// The type of the response value. + /// The URL. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor, params string[] uriVariables) where T : class { UriTemplate uriTemplate = new UriTemplate(url); @@ -424,6 +905,19 @@ namespace Spring.Http.Rest return this.DoExecute(uri, 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. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor, IDictionary uriVariables) where T : class { UriTemplate uriTemplate = new UriTemplate(url); @@ -431,6 +925,15 @@ namespace Spring.Http.Rest return this.DoExecute(uri, 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. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class { return this.DoExecute(url, requestCallback, responseExtractor); @@ -438,30 +941,30 @@ namespace Spring.Http.Rest #endregion - /** - * Execute the given method on the provided URI. The {@link ClientHttpRequest} is processed using the {@link - * RequestCallback}; the response with the {@link ResponseExtractor}. - * @param url the fully-expanded URL to connect to - * @param method the HTTP method to execute (GET, POST, etc.) - * @param requestCallback object that prepares the request (can be null) - * @param responseExtractor object that extracts the return value from the response (can be null) - * @return an arbitrary object, as returned by the {@link ResponseExtractor} - */ - protected virtual T DoExecute(Uri url, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class + /// + /// 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. + /// 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, IRequestCallback requestCallback, IResponseExtractor responseExtractor) where T : class { HttpWebRequest request; HttpWebResponse response = null; - Uri finalUri = url; - if (!url.IsAbsoluteUri) + Uri finalUri = uri; + if (!uri.IsAbsoluteUri) { if (this._baseAddress != null) { - finalUri = new Uri(this._baseAddress, url); + finalUri = new Uri(this._baseAddress, uri); } else { - throw new ArgumentException(String.Format("'{0}' is not an absolute URI", url), "url"); + throw new ArgumentException(String.Format("'{0}' is not an absolute URI", uri), "uri"); } } diff --git a/src/Spring/Spring.Http/Http/Rest/Support/AcceptHeaderRequestCallback.cs b/src/Spring/Spring.Http/Http/Rest/Support/AcceptHeaderRequestCallback.cs index afd08cb7..af4aa070 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/AcceptHeaderRequestCallback.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/AcceptHeaderRequestCallback.cs @@ -22,14 +22,17 @@ using System; using System.Net; using System.Collections.Generic; +using Spring.Util; using Spring.Http; using Spring.Http.Converters; namespace Spring.Http.Rest.Support { - /** - * Request callback implementation that prepares the request's accept headers. - */ + /// + /// Request callback implementation that prepares the request's accept headers. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) public class AcceptHeaderRequestCallback : MethodRequestCallback { #region Logging @@ -38,9 +41,22 @@ namespace Spring.Http.Rest.Support #endregion + /// + /// The expected response body type. + /// protected Type responseType; + + /// + /// The list of to use. + /// protected IList messageConverters; + /// + /// Creates a new instance of . + /// + /// The HTTP method. + /// The expected response body type. + /// The list of to use. public AcceptHeaderRequestCallback(HttpMethod method, Type responseType, IList messageConverters) : base(method) { @@ -48,6 +64,12 @@ namespace Spring.Http.Rest.Support this.messageConverters = messageConverters; } + /// + /// Gets called by with an opened 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(HttpWebRequest request) { base.DoWithRequest(request); @@ -61,7 +83,7 @@ namespace Spring.Http.Rest.Support { foreach (MediaType supportedMediaType in messageConverter.SupportedMediaTypes) { - if (!String.IsNullOrEmpty(supportedMediaType.CharSet)) + if (StringUtils.HasText(supportedMediaType.CharSet)) { allSupportedMediaTypes.Add(new MediaType( supportedMediaType.Type, supportedMediaType.Subtype)); diff --git a/src/Spring/Spring.Http/Http/Rest/Support/HeadersResponseExtractor.cs b/src/Spring/Spring.Http/Http/Rest/Support/HeadersResponseExtractor.cs index 121e54e1..338c3133 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/HeadersResponseExtractor.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/HeadersResponseExtractor.cs @@ -22,11 +22,19 @@ using System.Net; namespace Spring.Http.Rest.Support { - /** - * Response extractor that extracts the response {@link HttpHeaders}. - */ + /// + /// 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 WebHeaderCollection ExtractData(HttpWebResponse response) { return response.Headers; diff --git a/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageRequestCallback.cs b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageRequestCallback.cs index f9f34893..8c87df7b 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageRequestCallback.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageRequestCallback.cs @@ -22,15 +22,16 @@ using System; using System.Net; using System.Collections.Generic; -using Spring.Util; using Spring.Http; using Spring.Http.Converters; namespace Spring.Http.Rest.Support { - /** - * Request callback implementation that writes the given object to the request stream. - */ + /// + /// Request callback implementation that writes the given object to the request stream. + /// + /// Arjen Poutsma + /// Bruno Baia (.NET) public class HttpMessageRequestCallback : AcceptHeaderRequestCallback { #region Logging @@ -41,11 +42,24 @@ namespace Spring.Http.Rest.Support private HttpRequestMessage requestMessage; + /// + /// Creates a new instance of . + /// + /// The HTTP method. + /// The object to write to the request. + /// The list of to use. public HttpMessageRequestCallback(HttpMethod method, object requestBody, IList messageConverters) : this(method, requestBody, null, messageConverters) { } + /// + /// Creates a new instance of . + /// + /// The HTTP method. + /// The object to write to the request. + /// The expected response body type. + /// The list of to use. public HttpMessageRequestCallback(HttpMethod method, object requestBody, Type responseType, IList messageConverters) : base(method, responseType, messageConverters) { @@ -60,17 +74,34 @@ namespace Spring.Http.Rest.Support } } + /// + /// Creates a new instance of . + /// + /// The HTTP request message. + /// The list of to use. public HttpMessageRequestCallback(HttpRequestMessage requestMessage, IList messageConverters) : this(requestMessage, null, messageConverters) { } + /// + /// Creates a new instance of . + /// + /// The HTTP request message. + /// The expected response body type. + /// The list of to use. public HttpMessageRequestCallback(HttpRequestMessage requestMessage, Type responseType, IList messageConverters) : base(requestMessage.Method, responseType, messageConverters) { this.requestMessage = requestMessage; } + /// + /// Gets called by with an opened 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(HttpWebRequest request) { base.DoWithRequest(request); @@ -78,18 +109,26 @@ namespace Spring.Http.Rest.Support // headers if (requestMessage.Headers.Count > 0) { - request.Headers.Add(requestMessage.Headers); + foreach(string headerName in requestMessage.Headers) + { + // TODO : Check other special cases or create a HttpHeaders class + // Special cases + if (headerName == "Content-Type") + { + request.ContentType = requestMessage.Headers[HttpRequestHeader.ContentType]; + } + else + { + request.Headers.Add(headerName, requestMessage.Headers[headerName]); + } + } } // body if (requestMessage.Body != null) { object requestBody = requestMessage.Body; - MediaType requestContentType = null; - if (StringUtils.HasText(requestMessage.Headers[HttpRequestHeader.ContentType])) - { - requestContentType = MediaType.ParseMediaType(requestMessage.Headers[HttpRequestHeader.ContentType]); - } + MediaType requestContentType = MediaType.ParseMediaType(requestMessage.Headers[HttpRequestHeader.ContentType]); foreach (IHttpMessageConverter messageConverter in base.messageConverters) { if (messageConverter.CanWrite(requestBody.GetType(), requestContentType)) diff --git a/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor.cs b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor.cs index e7b2f993..dcaa13dc 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor.cs @@ -19,37 +19,24 @@ #endregion using System.Net; -using System.Collections.Generic; - -using Spring.Util; -using Spring.Http; -using Spring.Http.Converters; namespace Spring.Http.Rest.Support { - /** - * Response extractor for {@link HttpEntity}. - */ - public class HttpMessageResponseExtractor : IResponseExtractor> where T : class + /// + /// Response extractor that extracts the HTTP response message with no body. + /// + /// Bruno Baia + public class HttpMessageResponseExtractor : IResponseExtractor { - private MessageConverterResponseExtractor httpMessageConverterExtractor; - - public HttpMessageResponseExtractor(IList 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(HttpWebResponse response) { - httpMessageConverterExtractor = new MessageConverterResponseExtractor(messageConverters); - } - - public HttpResponseMessage ExtractData(HttpWebResponse response) - { - if (StringUtils.HasText(response.Headers[HttpResponseHeader.ContentType])) - { - T body = httpMessageConverterExtractor.ExtractData(response); - return new HttpResponseMessage(body, response.Headers, response.StatusCode, response.StatusDescription); - } - else - { - return new HttpResponseMessage(response.Headers, response.StatusCode, response.StatusDescription); - } + return new HttpResponseMessage(response.Headers, response.StatusCode, response.StatusDescription); } } } diff --git a/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor`1.cs b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor`1.cs new file mode 100644 index 00000000..c8fd8ac0 --- /dev/null +++ b/src/Spring/Spring.Http/Http/Rest/Support/HttpMessageResponseExtractor`1.cs @@ -0,0 +1,59 @@ +#region License + +/* + * Copyright 2002-2010 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.Net; +using System.Collections.Generic; + +using Spring.Http; +using Spring.Http.Converters; + +namespace Spring.Http.Rest.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(HttpWebResponse response) + { + T body = httpMessageConverterExtractor.ExtractData(response); + return new HttpResponseMessage(body, response.Headers, response.StatusCode, response.StatusDescription); + } + } +} diff --git a/src/Spring/Spring.Http/Http/Rest/Support/MessageConverterResponseExtractor.cs b/src/Spring/Spring.Http/Http/Rest/Support/MessageConverterResponseExtractor.cs index 13d29b31..db04dca8 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/MessageConverterResponseExtractor.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/MessageConverterResponseExtractor.cs @@ -28,14 +28,12 @@ using Spring.Http.Converters; namespace Spring.Http.Rest.Support { - /** - * Response extractor that uses the given {@linkplain HttpMessageConverter entity converters} to convert the response - * into a type T. - * - * @author Arjen Poutsma - * @see RestTemplate - * @since 3.0 - */ + /// + /// 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 @@ -46,25 +44,32 @@ namespace Spring.Http.Rest.Support private IList messageConverters; - /** - * Creates a new instance of the {@code HttpMessageConverterExtractor} with the given response type and message - * converters. The given converters must support the response type. - */ + /// + /// 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 T ExtractData(HttpWebResponse response) { - if (!StringUtils.HasText(response.Headers[HttpResponseHeader.ContentType])) + string contentType = response.Headers[HttpResponseHeader.ContentType]; + if (!StringUtils.HasText(contentType)) { throw new RestClientException("Could not extract response: no Content-Type found"); } - MediaType contentType = MediaType.ParseMediaType(response.Headers[HttpResponseHeader.ContentType]); + MediaType mediaType = MediaType.ParseMediaType(contentType); foreach(IHttpMessageConverter messageConverter in messageConverters) { - if (messageConverter.CanRead(typeof(T), contentType)) + if (messageConverter.CanRead(typeof(T), mediaType)) { #region Instrumentation @@ -72,7 +77,7 @@ namespace Spring.Http.Rest.Support { LOG.Debug(String.Format( "Reading [{0}] as '{1}' using [{2}]", - typeof(T).FullName, contentType, messageConverter)); + typeof(T).FullName, mediaType, messageConverter)); } #endregion @@ -82,7 +87,7 @@ namespace Spring.Http.Rest.Support } throw new RestClientException(String.Format( "Could not extract response: no suitable HttpMessageConverter found for response type [{0}] and content type [{1}]", - typeof(T).FullName, contentType)); + typeof(T).FullName, mediaType)); } } } diff --git a/src/Spring/Spring.Http/Http/Rest/Support/MethodRequestCallback.cs b/src/Spring/Spring.Http/Http/Rest/Support/MethodRequestCallback.cs index bb140af0..7dbdcde1 100644 --- a/src/Spring/Spring.Http/Http/Rest/Support/MethodRequestCallback.cs +++ b/src/Spring/Spring.Http/Http/Rest/Support/MethodRequestCallback.cs @@ -25,9 +25,10 @@ using Spring.Http; namespace Spring.Http.Rest.Support { - /** - * Request callback implementation that sets the Http method. - */ + /// + /// Request callback implementation that sets the HTTP method. + /// + /// Bruno Baia public class MethodRequestCallback : IRequestCallback { #region Logging @@ -36,13 +37,26 @@ namespace Spring.Http.Rest.Support #endregion + /// + /// The HTTP method. + /// protected HttpMethod method; + /// + /// Creates a new instance of . + /// + /// The HTTP method. public MethodRequestCallback(HttpMethod method) { this.method = method; } + /// + /// Gets called by with an opened 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(HttpWebRequest request) { #region Instrumentation diff --git a/src/Spring/Spring.Http/Spring.Http.2008.csproj b/src/Spring/Spring.Http/Spring.Http.2008.csproj index 00af2400..feaa0a0a 100644 --- a/src/Spring/Spring.Http/Spring.Http.2008.csproj +++ b/src/Spring/Spring.Http/Spring.Http.2008.csproj @@ -100,7 +100,7 @@ - + @@ -109,9 +109,11 @@ + + @@ -128,14 +130,10 @@ + + - - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2008 - - diff --git a/src/Spring/Spring.Http/Spring.Http.2010.csproj b/src/Spring/Spring.Http/Spring.Http.2010.csproj index c6da53bd..81f4c29b 100644 --- a/src/Spring/Spring.Http/Spring.Http.2010.csproj +++ b/src/Spring/Spring.Http/Spring.Http.2010.csproj @@ -111,7 +111,9 @@ Code + + Code @@ -121,7 +123,6 @@ - Code @@ -144,19 +145,18 @@ + + + + Code + - - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2010 - - False diff --git a/src/Spring/Spring.Http/Util/AssertUtils.cs b/src/Spring/Spring.Http/Util/AssertUtils.cs new file mode 100644 index 00000000..49a65ca3 --- /dev/null +++ b/src/Spring/Spring.Http/Util/AssertUtils.cs @@ -0,0 +1,121 @@ +#region License + +/* + * Copyright 2002-2010 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 +{ + /// + /// Assertion utility methods that simplify things such as argument checks. + /// + /// + ///

+ /// Not intended to be used directly by applications. + ///

+ ///
+ /// Aleksandar Seovic + /// Erich Eichinger + internal sealed class AssertUtils + { + /// + /// Checks the value of the supplied and throws an + /// if it is . + /// + /// The object to check. + /// The argument name. + /// + /// If the supplied is . + /// + internal static void ArgumentNotNull(object argument, string name) + { + if (argument == null) + { + throw new ArgumentNullException (name, + String.Format(CultureInfo.InvariantCulture, "Argument '{0}' cannot be null.", name)); + } + } + + /// + /// Checks the value of the supplied and throws an + /// if it is . + /// + /// The object to check. + /// The argument name. + /// + /// An arbitrary message that will be passed to any thrown + /// . + /// + /// + /// If the supplied is . + /// + internal static void ArgumentNotNull(object argument, string name, string message) + { + if (argument == null) + { + throw new ArgumentNullException(name, message); + } + } + + /// + /// 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). + /// + internal static void ArgumentHasText(string argument, string name) + { + if (!StringUtils.HasText(argument)) + { + throw new ArgumentNullException(name, + String.Format (CultureInfo.InvariantCulture, + "Argument '{0}' cannot be null or resolve to an empty string : '{1}'.", name, argument)); + } + } + + /// + /// 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. + /// + /// An arbitrary message that will be passed to any thrown + /// . + /// + /// + /// If the supplied is or + /// contains only whitespace character(s). + /// + internal static void ArgumentHasText(string argument, string name, string message) + { + if (!StringUtils.HasText(argument)) + { + throw new ArgumentNullException(name, message); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Http/Util/StringUtils.cs b/src/Spring/Spring.Http/Util/StringUtils.cs new file mode 100644 index 00000000..bbf49fb9 --- /dev/null +++ b/src/Spring/Spring.Http/Util/StringUtils.cs @@ -0,0 +1,103 @@ +#region License + +/* + * Copyright 2002-2010 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 +{ + /// + /// 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) + internal 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 + /// + /// + internal 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 + /// + /// + internal static bool HasText(string target) + { + if (target == null) + { + return false; + } + else + { + return HasLength(target.Trim()); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Http/Util/UriTemplate.cs b/src/Spring/Spring.Http/Util/UriTemplate.cs index b9d51f12..1c048667 100644 --- a/src/Spring/Spring.Http/Util/UriTemplate.cs +++ b/src/Spring/Spring.Http/Util/UriTemplate.cs @@ -28,16 +28,13 @@ namespace Spring.Util // TODO : Check .NET 3.5 class // TODO : Back to original Java behavior Expand(params string[]) method ? - /** - * 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.

See {@link #expand(Map)}, - * {@link #expand(Object[])}, and {@link #match(String)} for example usages. - * - * @author Arjen Poutsma - * @author Juergen Hoeller - * @since 3.0 - * @see URI Templates - */ + ///

+ /// 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. */ @@ -54,11 +51,18 @@ namespace Spring.Util 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; @@ -67,6 +71,23 @@ namespace Spring.Util 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, string> uriVariables = new Dictionary<String, String>(); + /// 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) @@ -105,6 +126,19 @@ namespace Spring.Util //return Expand(uriVariableValues); } + /// + /// 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 string[] uriVariableValues) { if (uriVariableValues.Length != this.variableNames.Length) @@ -123,11 +157,11 @@ namespace Spring.Util return new Uri(uri, UriKind.RelativeOrAbsolute); } - /** - * Indicate whether the given URI matches this template. - * @param uri the URI to match to - * @return true if it matches; false otherwise - */ + /// + /// 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) @@ -137,14 +171,19 @@ namespace Spring.Util return this.matchRegex.IsMatch(uri); } - /** - * Match the given URI to a map of variable values. Keys in the returned map are variable names, values are variable - * values, as occurred in the given URI.

Example:

 UriTemplate template = new
-         * UriTemplate("http://example.com/hotels/{hotel}/bookings/{booking}"); System.out.println(template.match("http://example.com/hotels/1/bookings/42"));
-         * 
will print:
{hotel=1, booking=42}
- * @param uri the URI to match to - * @return a map of variable values - */ + /// + /// 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) { AssertUtils.ArgumentNotNull(uri, "uri"); @@ -158,6 +197,12 @@ namespace Spring.Util return result; } + /// + /// Returns a that represents the current + /// + /// + /// A that represents the current . + /// public override string ToString() { return this.uriTemplate; diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs index 08db23ce..2878a36a 100644 --- a/test/Spring/Spring.Http.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Converters/ByteArrayHttpMessageConverterTests.cs @@ -49,13 +49,18 @@ namespace Spring.Http.Converters 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.CanWrite(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.CanRead(typeof(byte[]), new MediaType("application", "octet-stream"))); + Assert.IsTrue(converter.CanRead(typeof(byte[]), new MediaType("application", "xml"))); Assert.IsTrue(converter.CanWrite(typeof(byte[]), MediaType.ALL)); + Assert.IsFalse(converter.CanRead(typeof(string), new MediaType("application", "octet-stream"))); } [Test] @@ -64,8 +69,8 @@ namespace Spring.Http.Converters byte[] body = new byte[] { 0x1, 0x2 }; HttpWebResponse webResponse = mocks.CreateMock(); - Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(body)).Repeat.Once(); - Expect.Call(webResponse.ContentLength).Return(2).Repeat.Once(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(body)); + Expect.Call(webResponse.ContentLength).Return(2); mocks.ReplayAll(); @@ -80,20 +85,23 @@ namespace Spring.Http.Converters [Test] public void Write() { + MemoryStream requestStream = new MemoryStream(); + byte[] body = new byte[] { 0x1, 0x2 }; - - HttpWebRequest webRequest = WebRequest.Create("http://localhost") as HttpWebRequest; - webRequest.Method = "POST"; + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/octet-stream").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 2).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); converter.Write(body, null, webRequest); - using (Stream postStream = webRequest.GetRequestStream()) - { - //Assert.AreEqual(body.Length, postStream.Length, "Invalid result"); - } - - Assert.AreEqual(new MediaType("application", "octet-stream"), MediaType.ParseMediaType(webRequest.ContentType), "Invalid content-type"); - Assert.AreEqual(2, webRequest.ContentLength, "Invalid content-length"); + byte[] result = requestStream.ToArray(); + Assert.AreEqual(body, result, "Invalid result"); + + mocks.VerifyAll(); } } } diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs new file mode 100644 index 00000000..f4ef15c2 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Atom10FeedHttpMessageConverterTests.cs @@ -0,0 +1,132 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2010 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.Globalization; +using System.ServiceModel.Syndication; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Unit tests for the Atom10FeedHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class Atom10FeedHttpMessageConverterTests + { + private Atom10FeedHttpMessageConverter converter; + private MockRepository mocks; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + 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.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "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.CanRead(typeof(SyndicationFeed), new MediaType("application", "atom+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationItem), new MediaType("application", "atom+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "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 Read() + { + DateTime now = DateTime.Now; + + string body = String.Format("Test FeedThis is a test feedAtom10FeedHttpMessageConverterTests.WriteCopyright 2010{0}Bruno Baïahttp://www.springframework.net/bbaiabruno.baia@springframework.net", + now.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture)); + + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); + + mocks.ReplayAll(); + + SyndicationFeed result = converter.Read(webResponse); + 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"); + + mocks.VerifyAll(); + } + + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); + + DateTime now = DateTime.Now; + + string expectedBody = String.Format("Test FeedThis is a test feedAtom10FeedHttpMessageConverterTests.WriteCopyright 2010{0}Bruno Baïahttp://www.springframework.net/bbaiabruno.baia@springframework.net", + now.ToString("yyyy-MM-ddTHH:mm:sszzz", CultureInfo.InvariantCulture)); + + SyndicationFeed body = new SyndicationFeed("Test Feed", "This is a test feed", new Uri("http://www.springframework.net/Feed"), "Atom10FeedHttpMessageConverterTests.Write", now); + 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"); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/atom+xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); + + converter.Write(body, null, webRequest); + + byte[] result = requestStream.ToArray(); + Assert.AreEqual(expectedBody, Encoding.UTF8.GetString(result), "Invalid result"); + + mocks.VerifyAll(); + } + } +} +#endif diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs new file mode 100644 index 00000000..a2ecf5c9 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/FeedHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,171 @@ +#if NET_4_0 +#region License + +/* + * Copyright 2002-2010 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.Http.Rest; + +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(); + //template.MessageConverters.Add(new StringHttpMessageConverter()); // for debugging purpose + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDownClass() + { + 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.IsNull(result.Body, "Invalid content"); + 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.IsNull(result.Body, "Invalid content"); + 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 + { + [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); + } + else + { + formatter = new Rss20FeedFormatter(feed); + } + + return formatter; + } + + [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/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs new file mode 100644 index 00000000..83776525 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Feed/Rss20FeedHttpMessageConverterTests.cs @@ -0,0 +1,131 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2010 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.Globalization; +using System.ServiceModel.Syndication; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Http.Converters.Feed +{ + /// + /// Unit tests for the Rss20FeedHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class Rss20FeedHttpMessageConverterTests + { + private Rss20FeedHttpMessageConverter converter; + private MockRepository mocks; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + 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.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "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.CanRead(typeof(SyndicationFeed), new MediaType("application", "rss+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationItem), new MediaType("application", "rss+xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(SyndicationFeed), new MediaType("text", "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 Read() + { + DateTime now = DateTime.Now; + + string body = String.Format("Test Feedhttp://www.springframework.net/FeedThis is a test feedCopyright 2010bruno.baia@springframework.net{0}Atom10FeedHttpMessageConverterTests.Write", + now.ToString("ddd, dd MMM yyyy HH:mm:ss zzz", CultureInfo.InvariantCulture).Remove(29, 1)); + + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); + + mocks.ReplayAll(); + + SyndicationFeed result = converter.Read(webResponse); + 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"); + + mocks.VerifyAll(); + } + + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); + + DateTime now = DateTime.Now; + + string expectedBody = String.Format("Test Feedhttp://www.springframework.net/FeedThis is a test feedCopyright 2010bruno.baia@springframework.net{0}Atom10FeedHttpMessageConverterTests.Write", + now.ToString("ddd, dd MMM yyyy HH:mm:ss zzz", CultureInfo.InvariantCulture).Remove(29, 1)); + + SyndicationFeed body = new SyndicationFeed("Test Feed", "This is a test feed", new Uri("http://www.springframework.net/Feed"), "Atom10FeedHttpMessageConverterTests.Write", now); + 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"); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/rss+xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); + + converter.Write(body, null, webRequest); + + byte[] result = requestStream.ToArray(); + Assert.AreEqual(expectedBody, Encoding.UTF8.GetString(result), "Invalid result"); + + mocks.VerifyAll(); + } + } +} +#endif diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs new file mode 100644 index 00000000..eb621124 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,171 @@ +#if NET_4_0 +#region License + +/* + * Copyright 2002-2010 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.Runtime.Serialization; + +using Spring.Http.Rest; + +using NUnit.Framework; + +namespace Spring.Http.Converters.Json +{ + /// + /// Integration tests for the JsonHttpMessageConverter 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(); + //template.MessageConverters.Add(new StringHttpMessageConverter()); // for debugging purpose + + contentType = new MediaType("application", "json"); + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDownClass() + { + webServiceHost.Close(); + } + + [Test] + public void GetForObject() + { + template.MessageConverters.Add(new JsonHttpMessageConverter()); + + 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 PostForMessage() + { + template.MessageConverters.Add(new JsonHttpMessageConverter()); + + User user = new User() { Name = "Lisa Baia" }; + + HttpResponseMessage result = template.PostForMessage("user", user); + Assert.IsNull(result.Body, "Invalid content"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/3"), new Uri(result.Headers[HttpResponseHeader.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" }); + } + + [WebGet(UriTemplate = "user/{id}")] + public User GetUser(string id) + { + WebOperationContext context = WebOperationContext.Current; + context.OutgoingResponse.Format = WebMessageFormat.Json; + + 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; + } + + [WebInvoke(UriTemplate = "user", Method = "POST")] + public void Create(User user) + { + WebOperationContext context = WebOperationContext.Current; + context.OutgoingResponse.Format = WebMessageFormat.Json; + + UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; + UriTemplate template = new UriTemplate("/user/{id}"); + + MediaType mediaType = MediaType.ParseMediaType(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/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterTests.cs new file mode 100644 index 00000000..51d9f948 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Json/JsonHttpMessageConverterTests.cs @@ -0,0 +1,125 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2010 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.Net; +using System.Text; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Http.Converters.Json +{ + /// + /// Unit tests for the JsonHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class JsonHttpMessageConverterTests + { + private JsonHttpMessageConverter converter; + private MockRepository mocks; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + converter = new JsonHttpMessageConverter(); + } + + [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.CanRead(typeof(CustomClass), new MediaType("application", "json"))); + Assert.IsFalse(converter.CanRead(typeof(CustomClass), new MediaType("text", "xml"))); + } + + [Test] + public void Read() + { + string body = "{\"ID\":\"1\",\"Name\":\"Bruno Baïa\"}"; + + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); + + mocks.ReplayAll(); + + CustomClass result = converter.Read(webResponse); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + + mocks.VerifyAll(); + } + + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); + + string expectedBody = "{\"ID\":\"1\",\"Name\":\"Bruno Baïa\"}"; + CustomClass body = new CustomClass("1", "Bruno Baïa"); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/json").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); + + converter.Write(body, null, webRequest); + + byte[] result = requestStream.ToArray(); + Assert.AreEqual(expectedBody, Encoding.UTF8.GetString(result), "Invalid result"); + + mocks.VerifyAll(); + } + + #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/test/Spring/Spring.Http.Tests/Http/Converters/StringHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/StringHttpMessageConverterTests.cs index eeb2a099..7762a1c7 100644 --- a/test/Spring/Spring.Http.Tests/Http/Converters/StringHttpMessageConverterTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Converters/StringHttpMessageConverterTests.cs @@ -50,13 +50,18 @@ namespace Spring.Http.Converters public void CanRead() { Assert.IsTrue(converter.CanRead(typeof(string), new MediaType("text", "plain"))); + Assert.IsTrue(converter.CanWrite(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.CanRead(typeof(string), new MediaType("text", "plain"))); Assert.IsTrue(converter.CanWrite(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] @@ -65,7 +70,7 @@ namespace Spring.Http.Converters string body = "Hello Bruno Baïa"; HttpWebResponse webResponse = mocks.CreateMock(); - Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))).Repeat.Once(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); Expect.Call(webResponse.CharacterSet).Return("utf-8").Repeat.Twice(); mocks.ReplayAll(); @@ -79,48 +84,50 @@ namespace Spring.Http.Converters [Test] public void WriteDefaultCharset() { - string body = "H\u00e9llo W\u00f6rld"; + MemoryStream requestStream = new MemoryStream(); + string body = "H\u00e9llo W\u00f6rld"; string charSet = "ISO-8859-1"; Encoding charSetEncoding = Encoding.GetEncoding(charSet); - HttpWebRequest webRequest = WebRequest.Create("http://localhost") as HttpWebRequest; - webRequest.Method = "POST"; + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "text/plain;charset=ISO-8859-1").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); converter.Write(body, null, webRequest); - using (Stream postStream = webRequest.GetRequestStream()) - { - //Assert.AreEqual(body.Length, postStream.Length, "Invalid result"); - } + byte[] result = requestStream.ToArray(); + Assert.AreEqual(body, charSetEncoding.GetString(result), "Invalid result"); - Assert.AreEqual(new MediaType("text", "plain", charSet), MediaType.ParseMediaType(webRequest.ContentType), "Invalid content-type"); - Assert.AreEqual(charSetEncoding.GetBytes(body).Length, webRequest.ContentLength, "Invalid content-length"); - //Assert.IsFalse(String.IsNullOrEmpty(webRequest.Headers[HttpRequestHeader.AcceptCharset]), "Invalid accept-charset"); + mocks.VerifyAll(); } [Test] public void WriteUTF8() { - string body = "H\u00e9llo W\u00f6rld"; + MemoryStream requestStream = new MemoryStream(); + string body = "H\u00e9llo W\u00f6rld"; string charSet = "UTF-8"; Encoding charSetEncoding = Encoding.GetEncoding(charSet); MediaType mediaType = new MediaType("text", "plain", charSet); - HttpWebRequest webRequest = WebRequest.Create("http://localhost") as HttpWebRequest; - webRequest.Method = "POST"; + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = mediaType.ToString()).PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); converter.Write(body, mediaType, webRequest); - using (Stream postStream = webRequest.GetRequestStream()) - { - //Assert.AreEqual(body.Length, postStream.Length, "Invalid result"); - } + byte[] result = requestStream.ToArray(); + Assert.AreEqual(body, charSetEncoding.GetString(result), "Invalid result"); - Assert.AreEqual(mediaType, MediaType.ParseMediaType(webRequest.ContentType), "Invalid content-type"); - Assert.AreEqual(charSetEncoding.GetBytes(body).Length, webRequest.ContentLength, "Invalid content-length"); - //Assert.IsFalse(String.IsNullOrEmpty(webRequest.Headers[HttpRequestHeader.AcceptCharset]), "Invalid accept-charset"); + mocks.VerifyAll(); } } } diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterIntegrationTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterIntegrationTests.cs deleted file mode 100644 index eaac66de..00000000 --- a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterIntegrationTests.cs +++ /dev/null @@ -1,328 +0,0 @@ -#if NET_4_0 -#region License - -/* - * Copyright 2002-2010 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.Http.Converters; -using Spring.Http.Converters.Xml; - -using NUnit.Framework; - -namespace Spring.Http.Rest.Xml -{ - /// - /// Integration tests for the DataContractHttpMessageConverter class. - /// - /// Bruno Baia - [TestFixture] - public class DataContractHttpMessageConverterIntegrationTests - { - #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); - template.MessageConverters = new List(); - template.MessageConverters.Add(new StringHttpMessageConverter()); // for debugging purpose - - contentType = new MediaType("application", "xml"); - - webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); - webServiceHost.Open(); - } - - [TearDown] - public void TearDownClass() - { - webServiceHost.Close(); - } - - //[Test] - //public void GetString() - //{ - // string result = template.GetForObject("users"); - // Assert.AreEqual("2", result, "Invalid content"); - //} - - [Test] - public void GetUser() - { - template.MessageConverters.Add(new DataContractHttpMessageConverter()); - - 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 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(RestClientException), - // ExpectedMessage = "The server returned 'User with id '5' not found' with the status code 404 - NotFound.")] - //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(contentType, MediaType.ParseMediaType(result.Headers[HttpResponseHeader.ContentType]), "Invalid content-type"); - // Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); - // Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); - //} - - //[Test] - //[ExpectedException(typeof(RestClientException), - // ExpectedMessage = "Could not extract response: no Content-Type found")] - //public void GetStringNoResponse() - //{ - // string result = template.GetForObject("/nothing"); - //} - - //[Test] - //public void HeadForHeaders() - //{ - // WebHeaderCollection result = template.HeadForHeaders("head"); - // Assert.AreEqual(new MediaType("text", "plain"), MediaType.ParseMediaType(result[HttpResponseHeader.ContentType]), "Invalid content-type"); - //} - - //[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"), new Uri(result.Headers[HttpResponseHeader.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() - { - template.MessageConverters.Add(new DataContractHttpMessageConverter()); - - User user = new User() { Name = "Lisa Baia" }; - - User result = template.PostForObject("user", user); - Assert.AreEqual("3", result.ID, "Invalid content"); - Assert.AreEqual(user.Name, result.Name, "Invalid content"); - } - - //[Test] - //[ExpectedException(typeof(RestClientException), - // ExpectedMessage = "The server returned 'Content cannot be null or empty' with the status code 400 - BadRequest.")] - //public void PostStringForObjectWithError() - //{ - // string result = template.PostForObject("user", ""); - //} - - //[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(RestClientException), - // ExpectedMessage = "The server returned 'User id '4' does not exist' with the status code 400 - BadRequest.")] - //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(RestClientException), - // ExpectedMessage = "The server returned 'User id '10' does not exist' with the status code 400 - BadRequest.")] - //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 ExchangePost() - //{ - // HttpResponseMessage result = template.Exchange( - // "user", new HttpRequestMessage("Maryse Baia", HttpMethod.POST)); - - // 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 ExchangePut() - //{ - // HttpResponseMessage result = template.Exchange( - // "user/1", new HttpRequestMessage("Bruno Baia", HttpMethod.PUT)); - - // Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); - // Assert.AreEqual("User id '1' updated with 'Bruno Baia'", result.StatusDescription, "Invalid status description"); - //} - - //[Test] - //[ExpectedException(ExpectedMessage = "The server returned 'Not Found' with the status code 404 - NotFound.")] - //public void ClientError() - //{ - // template.Execute("clienterror", null, null); - //} - - //[Test] - //[ExpectedException(ExpectedMessage = "The server returned 'Internal Server Error' with the status code 500 - InternalServerError.")] - //public void ServerError() - //{ - // template.Execute("servererror", null, null); - //} - - #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" }); - } - - [WebGet(UriTemplate = "user/{id}")] - 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; - } - - [WebInvoke(UriTemplate = "user", Method = "POST")] - public User Post(User user) - { - WebOperationContext context = WebOperationContext.Current; - - UriTemplateMatch match = context.IncomingRequest.UriTemplateMatch; - UriTemplate template = new UriTemplate("/user/{id}"); - - MediaType mediaType = MediaType.ParseMediaType(context.IncomingRequest.ContentType); - - user.ID = (users.Count + 1).ToString(); // generate new ID - - if (!String.IsNullOrEmpty(user.ID)) - { - context.OutgoingResponse.StatusCode = HttpStatusCode.BadRequest; - context.OutgoingResponse.StatusDescription = "Content cannot be null or empty"; - return user; - } - - users.Add(user); - - Uri uri = template.BindByPosition(match.BaseUri, user.ID); - context.OutgoingResponse.SetStatusAsCreated(uri); - context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' created", user.ID); - - return user; - } - } - - #endregion - } -} -#endif \ No newline at end of file diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs index 29f2ee1a..c6a276bf 100644 --- a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/DataContractHttpMessageConverterTests.cs @@ -19,15 +19,14 @@ #endregion -using System; using System.IO; using System.Net; using System.Text; +using System.Runtime.Serialization; +using System.Collections.Generic; using NUnit.Framework; using Rhino.Mocks; -using System.Xml; -using System.Runtime.Serialization; namespace Spring.Http.Converters.Xml { @@ -51,66 +50,102 @@ namespace Spring.Http.Converters.Xml [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.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.IsFalse(converter.CanRead(typeof(NonDataContractClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(CollectionDataContractClass), new MediaType("application", "xml"))); } [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.CanRead(typeof(CustomClass), new MediaType("application", "soap+xml"))); // application/*+xml + Assert.IsTrue(converter.CanWrite(typeof(DataContractClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanWrite(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.IsFalse(converter.CanRead(typeof(NonDataContractClass), new MediaType("application", "xml"))); + Assert.IsTrue(converter.CanRead(typeof(CollectionDataContractClass), new MediaType("application", "xml"))); } - //[Test] - //public void Read() - //{ - // string body = ""; + [Test] + public void Read() + { + string body = @" + + 1Bruno Baïa + "; - // HttpWebResponse webResponse = mocks.CreateMock(); - // Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))).Repeat.Once(); + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); - // mocks.ReplayAll(); + mocks.ReplayAll(); - // XmlDocument result = converter.Read(webResponse); - // Assert.IsNotNull(result, "Invalid result"); + DataContractClass result = converter.Read(webResponse); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); - // mocks.VerifyAll(); - //} + mocks.VerifyAll(); + } - //[Test] - //public void Write() - //{ - // XmlDocument body = new XmlDocument(); - // body.CreateElement("TestElement"); + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); - // HttpWebRequest webRequest = WebRequest.Create("http://localhost") as HttpWebRequest; - // webRequest.Method = "POST"; + string expectedBody = "1Bruno Baïa"; + DataContractClass body = new DataContractClass("1", "Bruno Baïa"); - // converter.Write(body, null, webRequest); + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); - // Assert.AreEqual(new MediaType("application", "xml"), MediaType.ParseMediaType(webRequest.ContentType), "Invalid content-type"); + mocks.ReplayAll(); - // using (Stream postStream = webRequest.GetRequestStream()) - // { - // using (StreamReader reader = new StreamReader(postStream)) - // { - // string result = reader.ReadToEnd(); - // Assert.AreEqual(result.Length, webRequest.ContentLength, "Invalid content-length"); - // } - - // } - //} + converter.Write(body, null, webRequest); + + requestStream.Position = 0; + using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) + { + string result = reader.ReadToEnd(); + Assert.AreEqual(expectedBody, result, "Invalid result"); + } + + mocks.VerifyAll(); + } #region Test classes - + [DataContract] - public class CustomClass + public class DataContractClass { [DataMember] public string ID { get; set; } + + [DataMember] + public string Name { get; set; } + + public DataContractClass(string id, string name) + { + this.ID = id; + this.Name = name; + } + } + + [CollectionDataContract] + public class CollectionDataContractClass : List + { + public CollectionDataContractClass() + : base() + { + } + } + + public class NonDataContractClass + { } #endregion diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs new file mode 100644 index 00000000..0d377637 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XElementHttpMessageConverterTests.cs @@ -0,0 +1,124 @@ +#if NET_3_5 +#region License + +/* + * Copyright 2002-2010 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.Linq; +using System.Xml.Linq; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the XElementHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XElementHttpMessageConverterTests + { + private XElementHttpMessageConverter converter; + private MockRepository mocks; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + 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.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 Read() + { + string body = ""; + + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); + + mocks.ReplayAll(); + + XElement result = converter.Read(webResponse); + 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"); + + mocks.VerifyAll(); + } + + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); + + XElement body = new XElement("Root", + new XElement("TestElement", 1), + new XElement("TestElement", 2)); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); + + converter.Write(body, null, webRequest); + + requestStream.Position = 0; + using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) + { + string result = reader.ReadToEnd(); + Assert.AreEqual(body.ToString(SaveOptions.DisableFormatting), result, "Invalid result"); + } + + mocks.VerifyAll(); + } + } +} +#endif diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs index ee3e261b..65f04042 100644 --- a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlDocumentHttpMessageConverterTests.cs @@ -22,10 +22,10 @@ using System; using System.IO; using System.Net; using System.Text; +using System.Xml; using NUnit.Framework; using Rhino.Mocks; -using System.Xml; namespace Spring.Http.Converters.Xml { @@ -52,6 +52,8 @@ namespace Spring.Http.Converters.Xml 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] @@ -60,20 +62,27 @@ namespace Spring.Http.Converters.Xml Assert.IsTrue(converter.CanWrite(typeof(XmlDocument), new MediaType("application", "xml"))); Assert.IsTrue(converter.CanWrite(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 Read() { - string body = ""; + string body = ""; HttpWebResponse webResponse = mocks.CreateMock(); - Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))).Repeat.Once(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); mocks.ReplayAll(); XmlDocument result = converter.Read(webResponse); 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"); mocks.VerifyAll(); } @@ -81,25 +90,28 @@ namespace Spring.Http.Converters.Xml [Test] public void Write() { - XmlDocument body = new XmlDocument(); - body.CreateElement("TestElement"); + MemoryStream requestStream = new MemoryStream(); - HttpWebRequest webRequest = WebRequest.Create("http://localhost") as HttpWebRequest; - webRequest.Method = "POST"; + XmlDocument body = new XmlDocument(); + body.LoadXml(""); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); converter.Write(body, null, webRequest); - Assert.AreEqual(new MediaType("application", "xml"), MediaType.ParseMediaType(webRequest.ContentType), "Invalid content-type"); - - using (Stream postStream = webRequest.GetRequestStream()) + requestStream.Position = 0; + using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) { - using (StreamReader reader = new StreamReader(postStream)) - { - string result = reader.ReadToEnd(); - Assert.AreEqual(result.Length, webRequest.ContentLength, "Invalid content-length"); - } - + string result = reader.ReadToEnd(); + Assert.AreEqual(body.OuterXml, result, "Invalid result"); } + + mocks.VerifyAll(); } } } diff --git a/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs new file mode 100644 index 00000000..1476ff16 --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlHttpMessageConverterIntegrationTests.cs @@ -0,0 +1,240 @@ +#if NET_4_0 +#region License + +/* + * Copyright 2002-2010 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.Http.Rest; + +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(); + //template.MessageConverters.Add(new StringHttpMessageConverter()); // for debugging purpose + + webServiceHost = new WebServiceHost(typeof(TestService), new Uri(uri)); + webServiceHost.Open(); + } + + [TearDown] + public void TearDownClass() + { + 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.IsNull(result.Body, "Invalid content"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/dc/3"), new Uri(result.Headers[HttpResponseHeader.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.IsNull(result.Body, "Invalid content"); + Assert.AreEqual(new Uri(new Uri(uri), "/user/xml/3"), new Uri(result.Headers[HttpResponseHeader.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" }); + } + + [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; + } + + [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.ParseMediaType(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); + } + + [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; + } + + [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.ParseMediaType(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/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs new file mode 100644 index 00000000..4c60787a --- /dev/null +++ b/test/Spring/Spring.Http.Tests/Http/Converters/Xml/XmlSerializableHttpMessageConverterTests.cs @@ -0,0 +1,137 @@ +#if NET_3_0 +#region License + +/* + * Copyright 2002-2010 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.Net; +using System.Text; + +using NUnit.Framework; +using Rhino.Mocks; + +namespace Spring.Http.Converters.Xml +{ + /// + /// Unit tests for the XmlSerializableHttpMessageConverter class. + /// + /// Bruno Baia + [TestFixture] + public class XmlSerializableHttpMessageConverterTests + { + private XmlSerializableHttpMessageConverter converter; + private MockRepository mocks; + + [SetUp] + public void SetUp() + { + mocks = new MockRepository(); + 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.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 Read() + { + string body = @" + + 1 + Bruno Baïa + "; + + HttpWebResponse webResponse = mocks.CreateMock(); + Expect.Call(webResponse.GetResponseStream()).Return(new MemoryStream(Encoding.UTF8.GetBytes(body))); + + mocks.ReplayAll(); + + CustomClass result = converter.Read(webResponse); + Assert.IsNotNull(result, "Invalid result"); + Assert.AreEqual("1", result.ID, "Invalid result"); + Assert.AreEqual("Bruno Baïa", result.Name, "Invalid result"); + + mocks.VerifyAll(); + } + + [Test] + public void Write() + { + MemoryStream requestStream = new MemoryStream(); + + string expectedBody = "1Bruno Baïa"; + CustomClass body = new CustomClass("1", "Bruno Baïa"); + + HttpWebRequest webRequest = mocks.CreateMock(); + Expect.Call(webRequest.ContentType = "application/xml").PropertyBehavior(); + Expect.Call(webRequest.ContentLength = 1337).PropertyBehavior(); + Expect.Call(webRequest.GetRequestStream()).Return(requestStream); + + mocks.ReplayAll(); + + converter.Write(body, null, webRequest); + + requestStream.Position = 0; + using (StreamReader reader = new StreamReader(requestStream, Encoding.UTF8)) + { + string result = reader.ReadToEnd(); + Assert.AreEqual(expectedBody, result, "Invalid result"); + } + + mocks.VerifyAll(); + } + + #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/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateIntegrationTests.cs b/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateIntegrationTests.cs index c578b4ea..11ca3765 100644 --- a/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateIntegrationTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateIntegrationTests.cs @@ -222,7 +222,7 @@ namespace Spring.Http.Rest [Test] public void ExchangePut() { - HttpResponseMessage result = template.Exchange( + HttpResponseMessage result = template.Exchange( "user/1", new HttpRequestMessage("Bruno Baia", HttpMethod.PUT)); Assert.AreEqual(HttpStatusCode.OK, result.StatusCode, "Invalid status code"); @@ -458,7 +458,6 @@ namespace Spring.Http.Rest context.OutgoingResponse.StatusCode = HttpStatusCode.OK; context.OutgoingResponse.StatusDescription = String.Format("User id '{0}' updated with '{1}'", id, name); - //context.OutgoingResponse.ContentType = "text/plain"; } [WebInvoke(UriTemplate = "user/{id}", Method = "DELETE")] diff --git a/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateTests.cs b/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateTests.cs index 46e1144e..40666ea8 100644 --- a/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateTests.cs +++ b/test/Spring/Spring.Http.Tests/Http/Rest/RestTemplateTests.cs @@ -158,6 +158,7 @@ namespace Spring.Http.Rest Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); ExpectGetResponse(); //Expect.Call(errorHandler.hasError(response)).andReturn(false); + //Expect.Call(response.ContentType).Return(textPlain.ToString()).Repeat.AtLeastOnce(); WebHeaderCollection responseHeaders = new WebHeaderCollection(); responseHeaders[HttpResponseHeader.ContentType] = textPlain.ToString(); Expect.Call(response.Headers).Return(responseHeaders).Repeat.Any(); @@ -181,7 +182,7 @@ namespace Spring.Http.Rest IList mediaTypes = new List(1); mediaTypes.Add(textPlain); Expect.Call>(converter.SupportedMediaTypes).Return(mediaTypes); - Expect.Call(requestFactory.CreateRequest(new Uri("http://example.com/resource"))).Return(request); + Expect.Call(requestFactory.CreateRequest(new Uri("http://example.com/resource"))).Return(request); Expect.Call(request.Method = "GET"); Expect.Call(request.Accept = "foo/bar"); WebHeaderCollection requestHeaders = new WebHeaderCollection(); @@ -279,6 +280,7 @@ namespace Spring.Http.Rest Expect.Call(requestFactory.CreateRequest(new Uri("http://example.com"))).Return(request); Expect.Call(request.Method = "POST"); MediaType contentType = new MediaType("text", "plain"); + Expect.Call(request.ContentType = contentType.ToString()); Expect.Call(converter.CanWrite(typeof(string), contentType)).Return(true); WebHeaderCollection requestHeaders = new WebHeaderCollection(); Expect.Call(request.Headers).Return(requestHeaders).Repeat.Any(); @@ -301,7 +303,7 @@ namespace Spring.Http.Rest } [Test] - public void PostForLocationEntityCustomHeader() + public void PostForLocationMessageCustomHeader() { string helloWorld = "Hello World"; Expect.Call(requestFactory.CreateRequest(new Uri("http://example.com"))).Return(request); @@ -399,7 +401,7 @@ namespace Spring.Http.Rest } [Test] - public void PostForEntity() + public void PostForMessage() { Expect.Call(converter.CanRead(typeof(Version), null)).Return(true); MediaType textPlain = new MediaType("text", "plain"); @@ -434,6 +436,31 @@ namespace Spring.Http.Rest Assert.AreEqual("OK", result.StatusDescription, "Invalid status description"); } + [Test] + public void PostForMessageNoBody() + { + Expect.Call(requestFactory.CreateRequest(new Uri("http://example.com"))).Return(request); + Expect.Call(request.Method = "POST"); + WebHeaderCollection requestHeaders = new WebHeaderCollection(); + 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(response)).andReturn(false); + WebHeaderCollection responseHeaders = new WebHeaderCollection(); + 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.IsNull(result.Body, "Invalid POST result"); + Assert.AreEqual(HttpStatusCode.Created, result.StatusCode, "Invalid status code"); + Assert.AreEqual("CREATED", result.StatusDescription, "Invalid status description"); + } + [Test] public void PostForObjectNull() { diff --git a/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2008.csproj b/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2008.csproj index 7b9892f7..0d3f1137 100644 --- a/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2008.csproj +++ b/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2008.csproj @@ -87,28 +87,43 @@ System + + 3.5 + 3.0 + + 3.0 + + + 3.5 + + + 3.5 + + + + + + - + + + - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2008 - {FAC04F79-4B1F-1D13-B30F-00EE04EC0FBC} Spring.Http.2008 diff --git a/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2010.csproj b/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2010.csproj index 06cc735e..4786b6fd 100644 --- a/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2010.csproj +++ b/test/Spring/Spring.Http.Tests/Spring.Http.Tests.2010.csproj @@ -108,19 +108,30 @@ System + + Code + + + Code + + + + + + + - @@ -128,10 +139,6 @@ - - {710961A3-0DF4-49E4-A26E-F5B9C044AC84} - Spring.Core.2010 - {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} Spring.Http.2010