Consommer des Services Web REST .NET MVC 3

Je suis en train de travailler sur un .NET MVC 4 3 application. Je suis en train de suivre un domain driven design paradigme. Pour l'instant, mon application est divisée en deux parties, un nom de domaine et mon MVC code pour le web. J'aimerais un peu d'aide pour déterminer où, dans cette structure, je dois consommer un service web RESTful.

Ce projet utilise un service web RESTful pour récupérer et conserver les données. Dans mon domaine, j'ai deux entités "Client" et "Utilisateur" qui se jumeler avec les services web du même nom. par exemple, l'URL/Client et l'URL/Utilisateur. Chacun des services web prend que quelques paramètres, puis renvoie une liste appropriée de données en XML. J'ai besoin de mettre en œuvre CRUD de base de la fonctionnalité sous la forme de (POST, GET, PUT et DELETE). Compte tenu de cela, j'ai deux questions principales.

1.) Quel type d'objet que je dois créer pour consommer ces services web? Mon instinct est de créer un ICustomerService interface qui définit ma opérations CRUD, et puis créer une implémentation de cette interface sous la forme d'une classe qui utilise HTTPWebConnection (ou s'étend-il?). Est-il une meilleure façon de consommer des services web RESTful? Ce type de classe statique?

2.) Où ce service devrait-il code? Encore une fois, mon petit doigt me dit qu'en plus du Domaine et à l'interface utilisateur web des sections de mon code, j'ai besoin d'un troisième, les Services de la section qui contient les interfaces et implémentations de ces clients de services web, mais depuis que les services web sont de retour représentations XML du Client et de l'Utilisateur et les entités qui sont dans mon domaine, le service ne sera pas vraiment être découplé du domaine.

Merci d'avance,
Greg

MODIFIER

Après avoir travaillé sur différents projets pour un certain temps, j'ai trouvé une bonne manière de gérer les services web REST dans MVC.

Tout d'abord, je créer des Entités qui représentent les différents services web que j'utilise. Chaque entité utilise des attributs XML de faire correspondre les propriétés avec les éléments XML. Voici un exemple simple pour un hypothétique service web qui renvoie des informations sur des personnes et de leurs t-shirts (c'est stupide, mais le mieux que je pouvais venir à la volée).

Disons que je suis une Personne objet du service web. Voici le XML.

<result>
    <resultCount>1</resultCount>
    <person>
        <personName>Tom</personName>
        <shirt>
            <shirtColor>red</shirtColor>
            <shirtType>sweater</shirtType>
        </shirt>
    </person>
</result>

J'aurais alors deux entités: la Personne et la Chemise. J'aime bien inclure l'ensemble de la classe, de sorte que les débutants peuvent tout voir, donc, je suis désolé si c'est trop verbeux à vos goûts.

Personne

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Xml.Serialization;

namespace Test.Entities
{
    [XmlRoot("person")]
    public class Person
    {
        /*
        Notice that the class name doesn't match the XML Element. This is okay because we
        are using XmlElement to tell the deserializer that 
        Name and <personName> are the same thing
        */
        [XmlElement("personName")]
        public string Name { get; set; }

        [XmlElement("shirt")]
        public Shirt Shirt { get; set; }
    }
}

Shirt

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Xml.Serialization;

    namespace Test.Entities
    {
        public class Shirt
        {
            [XmlElement("shirtColor")]
            public string Color { get; set; }

            [XmlElement("shirtType")]
            public string Type { get; set; }

            /*
            This is specific to our Entity and doesn't exist in the web service so we can use
            XmlIgnore to make the deserializer ignore it
            */      
            [XmlIgnore]
            public string SpecialDbId { get; set; }
        }

    }

Nous pouvons alors utiliser un XmlSerializer pour convertir en objet XML et XML pour les objets. Ici c'est une classe que j'ai modifié pour ce faire. Je m'excuse car je ne me souviens pas de la source d'origine. (Il y a probablement beaucoup de place à l'amélioration à l'intérieur de cette classe)

ObjectSerializer

using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
using System;
using System.Xml.Linq;
public static class ObjectSerializer
{
///<summary>
///To convert a Byte Array of Unicode values (UTF-8 encoded) to a complete String.
///</summary>
///<param name="characters">Unicode Byte Array to be converted to String</param>
///<returns>String converted from Unicode Byte Array</returns>
private static string UTF8ByteArrayToString(byte[] characters)
{
UTF8Encoding encoding = new UTF8Encoding();
string constructedString = encoding.GetString(characters);
return (constructedString);
}
///<summary>
///Converts the String to UTF8 Byte array and is used in De serialization
///</summary>
///<param name="pXmlString"></param>
///<returns></returns>
private static Byte[] StringToUTF8ByteArray(string pXmlString)
{
UTF8Encoding encoding = new UTF8Encoding();
byte[] byteArray = encoding.GetBytes(pXmlString);
return byteArray;
}
///<summary>
///Serialize an object into an XML string
///</summary>
///<typeparam name="T"></typeparam>
///<param name="obj"></param>
///<returns></returns>
public static string SerializeObject<T>(T obj)
{
try
{
XDocument xml;
using (MemoryStream stream = new MemoryStream())
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
XmlSerializer serializer = new XmlSerializer(typeof(T));
serializer.Serialize(stream, obj, ns);
stream.Close();
byte[] buffer = stream.ToArray();
UTF8Encoding encoding = new UTF8Encoding();
string stringXml = encoding.GetString(buffer);
xml = XDocument.Parse(stringXml);
xml.Declaration = null;
return xml.ToString();
}
}
catch
{
return string.Empty;
}
}
///<summary>
///Reconstruct an object from an XML string
///</summary>
///<param name="xml"></param>
///<returns></returns>
public static T DeserializeObject<T>(string xml)
{
XmlSerializer xs = new XmlSerializer(typeof(T));
MemoryStream memoryStream = new MemoryStream(StringToUTF8ByteArray(xml));
XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8);
return (T)xs.Deserialize(memoryStream);
}
}

Ensuite, créer un service générique pour gérer votre HTTP opérations. J'utilise GET et POST. Voici ma classe.

HttpService

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml.Linq;
namespace Test.Infrastructure
{
public class HttpService
{
public HttpService()
{
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(AcceptCertificate);
}
public XDocument Post(Uri host, string path, Dictionary<string, string> headers, string payload, NetworkCredential credential)
{
try
{
Uri url = new Uri(host.Url, path);
MvcHtmlString encodedPayload = MvcHtmlString.Create(payload);
UTF8Encoding encoding = new UTF8Encoding();
byte[] data = encoding.GetBytes(encodedPayload.ToHtmlString());
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "POST";
request.Credentials = credential;
request.ContentLength = data.Length;
request.KeepAlive = false;
request.ContentType = "application/xml";
MvcHtmlString htmlString1;
MvcHtmlString htmlString2;
foreach (KeyValuePair<string, string> header in headers)
{
htmlString1 = MvcHtmlString.Create(header.Key);
htmlString2 = MvcHtmlString.Create(header.Value);
request.Headers.Add(htmlString1.ToHtmlString(), htmlString2.ToHtmlString());
}
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(data, 0, data.Length);
requestStream.Close();
}
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
using (Stream responseStream = response.GetResponseStream())
{
if (response.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Created)
{
throw new HttpException((int)response.StatusCode, response.StatusDescription);
}
XDocument xmlDoc = XDocument.Load(responseStream);
responseStream.Close();
response.Close();
return xmlDoc;
}
}
catch (Exception ex)
{
throw;
}
}
public XDocument Get(Uri host, string path, Dictionary<string, string> parameters, NetworkCredential credential)
{
try
{
Uri url;
StringBuilder parameterString = new StringBuilder();
if (parameters == null || parameters.Count <= 0)
{
parameterString.Clear();
} else {
parameterString.Append("?");
foreach (KeyValuePair<string, string> parameter in parameters)
{
parameterString.Append(parameter.Key + "=" + parameter.Value + "&");
}
}
url = new Uri(host.Url, path + parameterString.ToString().TrimEnd(new char[] { '&' }));
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.Credentials = credential;
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException((int)response.StatusCode, response.StatusDescription);
}
XDocument xmlDoc = XDocument.Load(response.GetResponseStream());
return xmlDoc;
}
}
catch (Exception ex)
{
throw;
}
}
/*
I use this class for internal web services.  For external web services, you'll want
to put some logic in here to determine whether or not you should accept a certificate
or not if the domain name in the cert doesn't match the url you are accessing.
*/
private static bool AcceptCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
}
}

Ensuite, vous créez votre référentiel d'utiliser le HttpService. J'ai mis en place un GetPeople() méthode qui permettrait de gens à retourner à partir d'une requête du service web.

Référentiel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Linq;
using System.Configuration;
using Test.Entities;
namespace Test.Infrastructure
{
public class PersonRepository
{
private HttpService _httpService;
public PersonRepository()
{
_httpService = new HttpService();
}
public IQueryable<Person> GetPeople()
{
try
{
Uri host = new Uri("http://www.yourdomain.com");
string path = "your/rest/path";
Dictionary<string, string> parameters = new Dictionary<string, string>();
//Best not to store this in your class
NetworkCredential credential = new NetworkCredential("username", "password");
XDocument xml = _httpService.Get(host, path, parameters, credential);
return ConvertPersonXmlToList(xml).AsQueryable();
} 
catch
{
throw;
}
}
private List<Person> ConvertPersonXmlToList(XDocument xml)
{
try
{
List<Person> perople = new List<Person>();
var query = xml.Descendants("Person")
.Select(node => node.ToString(SaveOptions.DisableFormatting));
foreach (var personXml in query)
{
people.Add(ObjectSerializer.DeserializeObject<Person>(personXml));
}
return people;
}
catch
{
throw;
}
}
}
}

Enfin, vous devez utiliser votre dépôt de votre contrôleur. Je n'utilise pas de l'injection de dépendances ici (DI), mais vous auriez idéalement voulez dans votre version finale.

Contrôleur

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Test.Entities;
using Test.Infrastructure;
using System.Net;
using System.Text;
namespace Test.Controllers
{
public class PeopleController
{
private PersonRepository _personRepository;
public PeopleController()
{
_personRepository = new PersonRepository();
}
public List<Person> List()
{
return _personRepository.GetPeople().ToList<Person>();
}
}
}

J'ai tapé ce jusqu'à la volée et modifié à partir de ma solution réelle, donc je m'excuse pour les éventuelles fautes de frappe ou des erreurs. Je vais faire de mon mieux pour corriger tout ce que j'ai trouver, mais cela devrait donner un bon départ à la création d'une ré-utilisable solution pour traiter avec le RESTE basée sur des services web.

OriginalL'auteur GregB | 2011-02-11