in my opinion, removing the generic version of HttpResponseMessage -- HttpResponseMessage<T> -- in RC wasn't a good idea, cause it would make the type of the returned object within the payload explicit.
Of course, HTTP isn't statically typed -- but C# is.
Of course, there are cases, where an operation can return different types -- but in this cases, one can still use HttpResponseMessage instead of HttpResponseMessage<T>.
As a way to mitigate the matter and get back to a generic-style typing approach, we developed a dirt-simple RestHelper.
Note that we are using a Rest service built in the Web API approach, and we are using Self Tracking Entities as our DTOs/BusinessEntities, and we are calling the Rest service from a typical ASP.NET ASPX page.
I will post our RestHelper here below for consideration and critique and etc.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Formatting;
namespace Team.Framework.CommonV4.Core
{
/// <summary>
/// This is a generic helper for calling Rest-based services that use Web API.
/// </summary>
/// <typeparam name="T">This is the type of object used for a particular instance of this class.</typeparam>
public class RestHelper<T> where T : class
{
#region StaticFields
/// <summary>
/// This is the default media-type used.
/// </summary>
public const string DefaultMediaTypeHeaderValue = "application/xml";
#endregion StaticFields
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RestHelper<T>"/> class.
/// </summary>
public RestHelper()
{
//Continue.
}
#endregion Constructors
#region Methods
/// <summary>
/// Deletes the data via the specified target URL prefix and ID.
/// </summary>
/// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param>
/// <param name="targetId">The target ID.</param>
/// <returns>This is the change count from the underlying operation.</returns>
public long Delete(string targetUrlPrefix, string targetId)
{
long myChangeCount = long.MinValue;
//Get the Uri data.
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
targetId = (targetId + string.Empty).Trim();
string myDatabaseObjectName = typeof(T).Name.ToString();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Make the call.
System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient();
myHttpClient.Timeout = new TimeSpan(0, 0, 30);
myHttpClient.BaseAddress = new Uri(targetUrlPrefix);
string myChangeCountText = myHttpClient.DeleteAsync(myUrlSuffix).Result.Content.ReadAsStringAsync().Result;
try
{
myChangeCount = Convert.ToInt64(myChangeCountText);
}
catch
{
myChangeCount = 0L;
}
return myChangeCount;
}
/// <summary>
/// Gets the data with the specified target URL prefix.
/// </summary>
/// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param>
/// <returns>This is the data.</returns>
public IEnumerable<T> Get(string targetUrlPrefix)
{
IEnumerable<T> myObjects = null;
//Get the Uri data.
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
string myDatabaseObjectName = typeof(T).Name.ToString();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/";
//Call the service.
System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient();
myHttpClient.Timeout = new TimeSpan(0, 0, 30);
myHttpClient.BaseAddress = new Uri(targetUrlPrefix);
myObjects = myHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<IEnumerable<T>>().Result;
return myObjects;
}
/// <summary>
/// Gets the data with the specified target URL prefix and ID.
/// </summary>
/// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param>
/// <param name="targetId">The target id.</param>
/// <returns>This is the object.</returns>
public T Get(string targetUrlPrefix, string targetId)
{
T myObject = null;
//Get the Uri data.
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
targetId = (targetId + string.Empty).Trim();
string myDatabaseObjectName = typeof(T).Name.ToString();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Make the call.
System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient();
myHttpClient.Timeout = new TimeSpan(0, 0, 30);
myHttpClient.BaseAddress = new Uri(targetUrlPrefix);
myObject = myHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<T>().Result;
return myObject;
}
/// <summary>
/// Posts the data using the specified target URL prefix./
/// </summary>
/// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param>
/// <param name="targetObject">The target object.</param>
/// <returns>This is the newly created object, after the save to the data store.</returns>
public T Post(string targetUrlPrefix, T targetObject)
{
T myNewObject = null;
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
if (targetObject == null)
{
throw new System.ArgumentNullException("targetObject");
}
//Get the url-suffix to use.
string myDatabaseObjectName = typeof(T).Name.ToString();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/";
//Get formatting details.
System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter();
//Make the call.
System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient();
myHttpClient.Timeout = new TimeSpan(0, 0, 30);
myHttpClient.BaseAddress = new Uri(targetUrlPrefix);
myNewObject = myHttpClient.PostAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result;
return myNewObject;
}
/// <summary>
/// Puts the data, using they specified target URL prefix and ID.
/// </summary>
/// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param>
/// <param name="targetId">The target id.</param>
/// <param name="targetObject">The target object.</param>
/// <returns>This is the object.</returns>
public T Put(string targetUrlPrefix, string targetId, T targetObject)
{
T myTargetObjectUpdated = null;
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
targetId = (targetId + string.Empty).Trim();
if (targetObject == null)
{
throw new System.ArgumentNullException("targetObject");
}
//Get the Uri data.
string myDatabaseObjectName = typeof(T).Name.ToString();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Get formatting details.
System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter();
//Make the call.
System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient();
myHttpClient.Timeout = new TimeSpan(0, 0, 30);
myHttpClient.BaseAddress = new Uri(targetUrlPrefix);
myTargetObjectUpdated = myHttpClient.PutAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result;
return myTargetObjectUpdated;
}
#endregion Methods
}
}
It is pretty rudimentary but it does work (as far as we can tell so far).
If anyone has questions or comments or suggestions, then please post them here.
I think it was a great idea. It greatly simplified the underlying processing. Also, they weren't really generic to begin with. Consider that an HttpResponseMessage wraps an HttpContent. The T should have related to that, if anything, but it isn't relevant anyway
as the generic parameter is only used in reading from the content via ReadAsAsync<>. You won't normally read immediately after constructing messages, so the generic parameter is next to useless. I think it existed before to support lazy formatting of ObjectContent,
which is now formatted immediately (thank goodness). Therefore, I don't see any benefit to generically-typed messages. Disclaimer: I'm an F# MVP, and I almost always want generics. It's just not useful here.
@Mark: Initialize HttpClient in your constructor and dispose it in a Dispose method. Darrel Miller has an answer on these forums describing the danger of using HttpClient-per-method: it will close your connection. It's designed to be used over a longer
period of time.
in my opinion, this leads to a lack of information within the method-signature. It feels like just using object as return-type in every method. When I want to find out, what the method returns, I have to look into the method-body, which isn't what one generally
wants.
One more problem: It will be quite difficult to create a human-readable help-page, cause there is no metadata that describes, which types the operation expects or returns. For instance, if you use the current implementation of ApiExplorer, it can just tell
you, that a request-message is expected and that an response-message will be returned :-(
in my opinion, this leads to a lack of information within the method-signature. It feels like just using object as return-type in every method. When I want to find out, what the method returns, I have to look into the method-body, which isn't what one generally
wants.
Except that you are actually returning a media type, not a C# type. This platform is meant to make it easy to create and consume web APIs, not necessarily to replace WCF as a remote procedure call mechanism. In order to determine what is returned, you need
to look at the media type definitions, not the C# version of that type.
manfred.steyer
One more problem: It will be quite difficult to create a human-readable help-page, cause there is no metadata that describes, which types the operation expects or returns. For instance, if you use the current implementation of ApiExplorer, it can just tell
you, that a request-message is expected and that an response-message will be returned :-(
Also, there's nothing stopping you from defining a subclass that displays the type as a generic parameter. Just know that it's only documentation and can have no valuable effect on the Web API pipeline, thus it's not useful in the released bits.
Yes and no. When you look at web-apis out there (twitter, facebook etc.), most of them provide you some kind of json and/or xml. And in this cases, it is not enough to know, that this is just text/json or text/xml. You need to know, how this data is structured.
Most documentations provide a sample for the json and xml-represenation; some provide an xml-schema.
This information is currently not available and so the API-Controller is not able to provide it. And the mentioned blog-entry, which I was aware of, doesn't change this.
Initialize HttpClient in your constructor and dispose it in a Dispose method
...OK I have done this and below is what I have now so, if you can and will, please do comment, etc...
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Formatting;
namespace Team.Framework.CommonV4.Core
{
/// <summary>
/// This is a generic helper for calling Rest-based services that use Web API.
/// </summary>
/// <typeparam name="T">This is the type of object used for a particular instance of this class.</typeparam>
public class RestHelper<T> where T : class, IDisposable
{
#region StaticFields
/// <summary>
/// This is the default media-type used, which some consumers may need to know.
/// </summary>
public const string DefaultMediaTypeHeaderValue = "application/xml";
/// <summary>
/// This is the default
/// </summary>
public static readonly TimeSpan DefaultTimeout = new TimeSpan(0, 0, 30);
#endregion StaticFields
#region Fields
/// <summary>
/// This is local property storage.
/// </summary>
private System.Net.Http.HttpClient mainHttpClientField = null;
/// <summary>
/// This is local property storage.
/// </summary>
private string mainUrlPrefixField = string.Empty;
#endregion Fields
#region Constructors
[Obsolete("Parameterless construction of this object is not supported.")]
/// <summary>
/// This helps prevent a default instance of the <see cref="RestHelper<T>"/> class from being created.
/// </summary>
private RestHelper()
{
throw new System.ApplicationException("Parameterless construction of this object is not supported.");
}
/// <summary>
/// Initializes a new instance of the <see cref="RestHelper<T>"/> class.
/// </summary>
/// <param name="targetUrlPrefix">
/// This is the URL prefix to be used for calls, for example "http://localhost:51121/api/CityCode/".
/// </param>
public RestHelper(string targetUrlPrefix)
{
targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim();
const int MyUrlLengthMin = 1;
if (targetUrlPrefix.Length <= MyUrlLengthMin)
{
throw new System.ApplicationException("The targetUrlPrefix.Length='" +
targetUrlPrefix.Length.ToString() + "' must be no less than Min='" + MyUrlLengthMin.ToString() + "'.");
}
this.mainUrlPrefixField = targetUrlPrefix;
}
#endregion Constructors
#region Properties
/// <summary>
/// Gets the main HTTP client to be used for requests.
/// </summary>
private System.Net.Http.HttpClient MainHttpClient
{
get
{
if (this.mainHttpClientField == null)
{
this.mainHttpClientField = new System.Net.Http.HttpClient();
this.mainHttpClientField.Timeout = RestHelper<T>.DefaultTimeout;
this.mainHttpClientField.BaseAddress = new Uri(this.MainUrlPrefix);
}
return this.mainHttpClientField;
}
}
/// <summary>
/// Gets the main URL prefix to be used for requests.
/// </summary>
public string MainUrlPrefix
{
get
{
return (this.mainUrlPrefixField + string.Empty).Trim();
}
}
#endregion Properties
#region Methods
/// <summary>
/// Deletes the data via the specified target URL prefix and ID.
/// </summary>
/// <param name="targetId">The target ID.</param>
/// <returns>This is the change count from the underlying operation.</returns>
public long Delete(string targetId)
{
long myChangeCount = long.MinValue;
//Get the Uri data.
targetId = (targetId + string.Empty).Trim();
string myDatabaseObjectName = typeof(T).Name.ToString().Trim();
myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Make the call.
string myChangeCountText = this.MainHttpClient.DeleteAsync(myUrlSuffix).Result.Content.ReadAsStringAsync().Result;
try
{
myChangeCount = Convert.ToInt64(myChangeCountText);
}
catch
{
myChangeCount = 0L;
}
return myChangeCount;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <remarks>
/// Dispose() calls Dispose(true).
/// Reference:
/// http://msdn.microsoft.com/en-us/library/ms244737.aspx
/// </remarks>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing">
/// This is <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.
/// </param>
/// <remarks>
/// The bulk of the clean-up code is implemented in Dispose(bool).
/// Reference:
/// http://msdn.microsoft.com/en-us/library/ms244737.aspx
/// </remarks>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
//Free managed resources.
if (this.mainHttpClientField != null)
{
this.mainHttpClientField.Dispose();
this.mainHttpClientField = null;
}
}
//Free native resources if there are any.
}
/// <summary>
/// Gets the data with the specified target URL prefix.
/// </summary>
/// <returns>This is the data.</returns>
public IEnumerable<T> Get()
{
IEnumerable<T> myObjects = null;
//Get the Uri data.
string myDatabaseObjectName = typeof(T).Name.ToString().Trim();
string myUrlSuffix = myDatabaseObjectName + "/";
//Call the service.
myObjects = this.MainHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<IEnumerable<T>>().Result;
return myObjects;
}
/// <summary>
/// Gets the data with the specified target URL prefix and ID.
/// </summary>
/// <param name="targetId">The target id.</param>
/// <returns>This is the object.</returns>
public T Get(string targetId)
{
T myObject = null;
//Get the Uri data.
targetId = (targetId + string.Empty).Trim();
string myDatabaseObjectName = typeof(T).Name.ToString().Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Make the call.
myObject = this.MainHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<T>().Result;
return myObject;
}
/// <summary>
/// Posts (creates) the data using the specified target URL prefix.
/// </summary>
/// <param name="targetObject">The target object.</param>
/// <returns>This is the newly created object, after the save to the data store.</returns>
public T Post(T targetObject)
{
T myNewObject = null;
if (targetObject == null)
{
throw new System.ArgumentNullException("targetObject");
}
//Get the url-suffix to use.
string myDatabaseObjectName = typeof(T).Name.ToString().Trim();
string myUrlSuffix = myDatabaseObjectName + "/";
//Get formatting details.
System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter();
//Make the call.
myNewObject = this.MainHttpClient.PostAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result;
return myNewObject;
}
/// <summary>
/// Puts (updates) the data, using they specified target URL prefix and ID.
/// </summary>
/// <param name="targetId">The target id.</param>
/// <param name="targetObject">The target object.</param>
/// <returns>This is the object.</returns>
public T Put(string targetId, T targetObject)
{
T myTargetObjectUpdated = null;
targetId = (targetId + string.Empty).Trim();
if (targetObject == null)
{
throw new System.ArgumentNullException("targetObject");
}
//Get the Uri data.
string myDatabaseObjectName = typeof(T).Name.ToString().Trim();
string myUrlSuffix = myDatabaseObjectName + "/" + targetId;
//Get formatting details.
System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter();
//Make the call.
myTargetObjectUpdated = this.MainHttpClient.PutAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result;
return myTargetObjectUpdated;
}
/// <summary>
/// Releases unmanaged resources and performs other cleanup operations
/// before the <see cref="RestHelper<T>"/> is reclaimed by garbage collection.
/// </summary>
/// <remarks>
/// NOTE: Leave out the finalizer altogether if this class doesn't
/// own unmanaged resources itself, but leave the other methods exactly as they are.
/// Reference:
/// http://msdn.microsoft.com/en-us/library/ms244737.aspx
/// </remarks>
~RestHelper()
{
//Finalizer calls Dispose(false).
this.Dispose(false);
}
#endregion Methods
}
}
manfred.stey...
Member
35 Points
57 Posts
Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 10, 2012 10:37 PM|LINK
Hi,
in my opinion, removing the generic version of HttpResponseMessage -- HttpResponseMessage<T> -- in RC wasn't a good idea, cause it would make the type of the returned object within the payload explicit.
Of course, HTTP isn't statically typed -- but C# is.
Of course, there are cases, where an operation can return different types -- but in this cases, one can still use HttpResponseMessage instead of HttpResponseMessage<T>.
What do you think?
Wishes,
Manfred
mkamoski2
Member
31 Points
54 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 12, 2012 03:00 PM|LINK
Dear Manfred --
We too were a bit wondering at the change.
As a way to mitigate the matter and get back to a generic-style typing approach, we developed a dirt-simple RestHelper.
Note that we are using a Rest service built in the Web API approach, and we are using Self Tracking Entities as our DTOs/BusinessEntities, and we are calling the Rest service from a typical ASP.NET ASPX page.
I will post our RestHelper here below for consideration and critique and etc.
using System; using System.Collections; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Formatting; namespace Team.Framework.CommonV4.Core { /// <summary> /// This is a generic helper for calling Rest-based services that use Web API. /// </summary> /// <typeparam name="T">This is the type of object used for a particular instance of this class.</typeparam> public class RestHelper<T> where T : class { #region StaticFields /// <summary> /// This is the default media-type used. /// </summary> public const string DefaultMediaTypeHeaderValue = "application/xml"; #endregion StaticFields #region Constructors /// <summary> /// Initializes a new instance of the <see cref="RestHelper<T>"/> class. /// </summary> public RestHelper() { //Continue. } #endregion Constructors #region Methods /// <summary> /// Deletes the data via the specified target URL prefix and ID. /// </summary> /// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param> /// <param name="targetId">The target ID.</param> /// <returns>This is the change count from the underlying operation.</returns> public long Delete(string targetUrlPrefix, string targetId) { long myChangeCount = long.MinValue; //Get the Uri data. targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); targetId = (targetId + string.Empty).Trim(); string myDatabaseObjectName = typeof(T).Name.ToString(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Make the call. System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient(); myHttpClient.Timeout = new TimeSpan(0, 0, 30); myHttpClient.BaseAddress = new Uri(targetUrlPrefix); string myChangeCountText = myHttpClient.DeleteAsync(myUrlSuffix).Result.Content.ReadAsStringAsync().Result; try { myChangeCount = Convert.ToInt64(myChangeCountText); } catch { myChangeCount = 0L; } return myChangeCount; } /// <summary> /// Gets the data with the specified target URL prefix. /// </summary> /// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param> /// <returns>This is the data.</returns> public IEnumerable<T> Get(string targetUrlPrefix) { IEnumerable<T> myObjects = null; //Get the Uri data. targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); string myDatabaseObjectName = typeof(T).Name.ToString(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/"; //Call the service. System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient(); myHttpClient.Timeout = new TimeSpan(0, 0, 30); myHttpClient.BaseAddress = new Uri(targetUrlPrefix); myObjects = myHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<IEnumerable<T>>().Result; return myObjects; } /// <summary> /// Gets the data with the specified target URL prefix and ID. /// </summary> /// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param> /// <param name="targetId">The target id.</param> /// <returns>This is the object.</returns> public T Get(string targetUrlPrefix, string targetId) { T myObject = null; //Get the Uri data. targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); targetId = (targetId + string.Empty).Trim(); string myDatabaseObjectName = typeof(T).Name.ToString(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Make the call. System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient(); myHttpClient.Timeout = new TimeSpan(0, 0, 30); myHttpClient.BaseAddress = new Uri(targetUrlPrefix); myObject = myHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<T>().Result; return myObject; } /// <summary> /// Posts the data using the specified target URL prefix./ /// </summary> /// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param> /// <param name="targetObject">The target object.</param> /// <returns>This is the newly created object, after the save to the data store.</returns> public T Post(string targetUrlPrefix, T targetObject) { T myNewObject = null; targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); if (targetObject == null) { throw new System.ArgumentNullException("targetObject"); } //Get the url-suffix to use. string myDatabaseObjectName = typeof(T).Name.ToString(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/"; //Get formatting details. System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter(); //Make the call. System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient(); myHttpClient.Timeout = new TimeSpan(0, 0, 30); myHttpClient.BaseAddress = new Uri(targetUrlPrefix); myNewObject = myHttpClient.PostAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result; return myNewObject; } /// <summary> /// Puts the data, using they specified target URL prefix and ID. /// </summary> /// <param name="targetUrlPrefix">The target URL prefix, for example "http://localhost:51121/api/".</param> /// <param name="targetId">The target id.</param> /// <param name="targetObject">The target object.</param> /// <returns>This is the object.</returns> public T Put(string targetUrlPrefix, string targetId, T targetObject) { T myTargetObjectUpdated = null; targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); targetId = (targetId + string.Empty).Trim(); if (targetObject == null) { throw new System.ArgumentNullException("targetObject"); } //Get the Uri data. string myDatabaseObjectName = typeof(T).Name.ToString(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Get formatting details. System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter(); //Make the call. System.Net.Http.HttpClient myHttpClient = new System.Net.Http.HttpClient(); myHttpClient.Timeout = new TimeSpan(0, 0, 30); myHttpClient.BaseAddress = new Uri(targetUrlPrefix); myTargetObjectUpdated = myHttpClient.PutAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result; return myTargetObjectUpdated; } #endregion Methods } }It is pretty rudimentary but it does work (as far as we can tell so far).
If anyone has questions or comments or suggestions, then please post them here.
HTH.
Thanks.
-- Mark Kamoski
panesofglass
Member
730 Points
237 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 04:50 AM|LINK
panesofglass
Member
730 Points
237 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 04:54 AM|LINK
manfred.stey...
Member
35 Points
57 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 05:04 PM|LINK
Hi,
in my opinion, this leads to a lack of information within the method-signature. It feels like just using object as return-type in every method. When I want to find out, what the method returns, I have to look into the method-body, which isn't what one generally wants.
One more problem: It will be quite difficult to create a human-readable help-page, cause there is no metadata that describes, which types the operation expects or returns. For instance, if you use the current implementation of ApiExplorer, it can just tell you, that a request-message is expected and that an response-message will be returned :-(
Wishes,
Manfred
panesofglass
Member
730 Points
237 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 05:14 PM|LINK
Except that you are actually returning a media type, not a C# type. This platform is meant to make it easy to create and consume web APIs, not necessarily to replace WCF as a remote procedure call mechanism. In order to determine what is returned, you need to look at the media type definitions, not the C# version of that type.
I haven't seen this problem. The IApiExplorer does a really good job of detecting what's going on and can produce good docs based on media types: http://blogs.msdn.com/b/yaohuang1/archive/2012/05/21/asp-net-web-api-generating-a-web-api-help-page-using-apiexplorer.aspx
Also, there's nothing stopping you from defining a subclass that displays the type as a generic parameter. Just know that it's only documentation and can have no valuable effect on the Web API pipeline, thus it's not useful in the released bits.
manfred.stey...
Member
35 Points
57 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 05:27 PM|LINK
Yes and no. When you look at web-apis out there (twitter, facebook etc.), most of them provide you some kind of json and/or xml. And in this cases, it is not enough to know, that this is just text/json or text/xml. You need to know, how this data is structured. Most documentations provide a sample for the json and xml-represenation; some provide an xml-schema.
This information is currently not available and so the API-Controller is not able to provide it. And the mentioned blog-entry, which I was aware of, doesn't change this.
Wishes,
Manfred
panesofglass
Member
730 Points
237 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 06:44 PM|LINK
Here you go:
public class HttpRequestMessage<T> : HttpRequestMessage { public HttpRequestMessage(HttpMethod httpMethod, Uri requestUri, T value, MediaTypeFormatter formatter) { Method = httpMethod; RequestUri = requestUri; Content = new ObjectContent<T>(value, formatter); } } public class HttpResponseMessage<T> : HttpResponseMessage { public HttpResponseMessage(T value, HttpStatusCode statusCode, MediaTypeFormatter formatter) { StatusCode = statusCode; Content = new ObjectContent<T>(value, formatter); } }mkamoski2
Member
31 Points
54 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 13, 2012 08:39 PM|LINK
Dear PanesOfGlass --
I appreciate your help.
Regarding this...
...OK I have done this and below is what I have now so, if you can and will, please do comment, etc...
using System; using System.Collections; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Formatting; namespace Team.Framework.CommonV4.Core { /// <summary> /// This is a generic helper for calling Rest-based services that use Web API. /// </summary> /// <typeparam name="T">This is the type of object used for a particular instance of this class.</typeparam> public class RestHelper<T> where T : class, IDisposable { #region StaticFields /// <summary> /// This is the default media-type used, which some consumers may need to know. /// </summary> public const string DefaultMediaTypeHeaderValue = "application/xml"; /// <summary> /// This is the default /// </summary> public static readonly TimeSpan DefaultTimeout = new TimeSpan(0, 0, 30); #endregion StaticFields #region Fields /// <summary> /// This is local property storage. /// </summary> private System.Net.Http.HttpClient mainHttpClientField = null; /// <summary> /// This is local property storage. /// </summary> private string mainUrlPrefixField = string.Empty; #endregion Fields #region Constructors [Obsolete("Parameterless construction of this object is not supported.")] /// <summary> /// This helps prevent a default instance of the <see cref="RestHelper<T>"/> class from being created. /// </summary> private RestHelper() { throw new System.ApplicationException("Parameterless construction of this object is not supported."); } /// <summary> /// Initializes a new instance of the <see cref="RestHelper<T>"/> class. /// </summary> /// <param name="targetUrlPrefix"> /// This is the URL prefix to be used for calls, for example "http://localhost:51121/api/CityCode/". /// </param> public RestHelper(string targetUrlPrefix) { targetUrlPrefix = (targetUrlPrefix + string.Empty).Trim(); const int MyUrlLengthMin = 1; if (targetUrlPrefix.Length <= MyUrlLengthMin) { throw new System.ApplicationException("The targetUrlPrefix.Length='" + targetUrlPrefix.Length.ToString() + "' must be no less than Min='" + MyUrlLengthMin.ToString() + "'."); } this.mainUrlPrefixField = targetUrlPrefix; } #endregion Constructors #region Properties /// <summary> /// Gets the main HTTP client to be used for requests. /// </summary> private System.Net.Http.HttpClient MainHttpClient { get { if (this.mainHttpClientField == null) { this.mainHttpClientField = new System.Net.Http.HttpClient(); this.mainHttpClientField.Timeout = RestHelper<T>.DefaultTimeout; this.mainHttpClientField.BaseAddress = new Uri(this.MainUrlPrefix); } return this.mainHttpClientField; } } /// <summary> /// Gets the main URL prefix to be used for requests. /// </summary> public string MainUrlPrefix { get { return (this.mainUrlPrefixField + string.Empty).Trim(); } } #endregion Properties #region Methods /// <summary> /// Deletes the data via the specified target URL prefix and ID. /// </summary> /// <param name="targetId">The target ID.</param> /// <returns>This is the change count from the underlying operation.</returns> public long Delete(string targetId) { long myChangeCount = long.MinValue; //Get the Uri data. targetId = (targetId + string.Empty).Trim(); string myDatabaseObjectName = typeof(T).Name.ToString().Trim(); myDatabaseObjectName = (myDatabaseObjectName + string.Empty).Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Make the call. string myChangeCountText = this.MainHttpClient.DeleteAsync(myUrlSuffix).Result.Content.ReadAsStringAsync().Result; try { myChangeCount = Convert.ToInt64(myChangeCountText); } catch { myChangeCount = 0L; } return myChangeCount; } /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> /// <remarks> /// Dispose() calls Dispose(true). /// Reference: /// http://msdn.microsoft.com/en-us/library/ms244737.aspx /// </remarks> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases unmanaged and - optionally - managed resources /// </summary> /// <param name="disposing"> /// This is <c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources. /// </param> /// <remarks> /// The bulk of the clean-up code is implemented in Dispose(bool). /// Reference: /// http://msdn.microsoft.com/en-us/library/ms244737.aspx /// </remarks> protected virtual void Dispose(bool disposing) { if (disposing) { //Free managed resources. if (this.mainHttpClientField != null) { this.mainHttpClientField.Dispose(); this.mainHttpClientField = null; } } //Free native resources if there are any. } /// <summary> /// Gets the data with the specified target URL prefix. /// </summary> /// <returns>This is the data.</returns> public IEnumerable<T> Get() { IEnumerable<T> myObjects = null; //Get the Uri data. string myDatabaseObjectName = typeof(T).Name.ToString().Trim(); string myUrlSuffix = myDatabaseObjectName + "/"; //Call the service. myObjects = this.MainHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<IEnumerable<T>>().Result; return myObjects; } /// <summary> /// Gets the data with the specified target URL prefix and ID. /// </summary> /// <param name="targetId">The target id.</param> /// <returns>This is the object.</returns> public T Get(string targetId) { T myObject = null; //Get the Uri data. targetId = (targetId + string.Empty).Trim(); string myDatabaseObjectName = typeof(T).Name.ToString().Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Make the call. myObject = this.MainHttpClient.GetAsync(myUrlSuffix).Result.Content.ReadAsAsync<T>().Result; return myObject; } /// <summary> /// Posts (creates) the data using the specified target URL prefix. /// </summary> /// <param name="targetObject">The target object.</param> /// <returns>This is the newly created object, after the save to the data store.</returns> public T Post(T targetObject) { T myNewObject = null; if (targetObject == null) { throw new System.ArgumentNullException("targetObject"); } //Get the url-suffix to use. string myDatabaseObjectName = typeof(T).Name.ToString().Trim(); string myUrlSuffix = myDatabaseObjectName + "/"; //Get formatting details. System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter(); //Make the call. myNewObject = this.MainHttpClient.PostAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result; return myNewObject; } /// <summary> /// Puts (updates) the data, using they specified target URL prefix and ID. /// </summary> /// <param name="targetId">The target id.</param> /// <param name="targetObject">The target object.</param> /// <returns>This is the object.</returns> public T Put(string targetId, T targetObject) { T myTargetObjectUpdated = null; targetId = (targetId + string.Empty).Trim(); if (targetObject == null) { throw new System.ArgumentNullException("targetObject"); } //Get the Uri data. string myDatabaseObjectName = typeof(T).Name.ToString().Trim(); string myUrlSuffix = myDatabaseObjectName + "/" + targetId; //Get formatting details. System.Net.Http.Formatting.MediaTypeFormatter myFormatter = new XmlMediaTypeFormatter(); //Make the call. myTargetObjectUpdated = this.MainHttpClient.PutAsync<T>(myUrlSuffix, targetObject, myFormatter).Result.Content.ReadAsAsync<T>().Result; return myTargetObjectUpdated; } /// <summary> /// Releases unmanaged resources and performs other cleanup operations /// before the <see cref="RestHelper<T>"/> is reclaimed by garbage collection. /// </summary> /// <remarks> /// NOTE: Leave out the finalizer altogether if this class doesn't /// own unmanaged resources itself, but leave the other methods exactly as they are. /// Reference: /// http://msdn.microsoft.com/en-us/library/ms244737.aspx /// </remarks> ~RestHelper() { //Finalizer calls Dispose(false). this.Dispose(false); } #endregion Methods } }Thanks.
-- Mark Kamoski
idmitriev
Member
10 Points
13 Posts
Re: Was removing HttpResponseMessage<T> in RC really a good idea?
Jun 14, 2012 08:40 AM|LINK
public class HttpResponseMessage<T> : HttpResponseMessage { public HttpResponseMessage(T value, HttpStatusCode statusCode, MediaTypeFormatter formatter) { StatusCode = statusCode; Content = new ObjectContent<T>(value, formatter); } }Is there a way not to specify the formatter implicitly and let it be negotiated at a later time (right before actually writing the response)?