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
Vous devez vous connecter pour publier un commentaire.
Vous êtes sur la bonne voie. Je place le ICustomerService dans le domaine de l'emballage, et un HttpWebConnection mise en œuvre de ce service dans un emballage séparé qui fait référence au domaine de l'emballage.
Cette classe peut être statique, mais ne doit pas l'être, si vous êtes dans le doute, alors ne le faites pas statique.
Vous avez raison que les services ne sont pas complètement découplé de la le domaine, mais c'est parce qu'ils mettent en œuvre un contrat de service définis dans le domaine de la couche, en termes du domaine.
Ce qui est découplé du domaine est le fait qu'ils sont de savon/webservice clients ou http/rest clients et ceux qui sont les détails techniques que vous ne voulez pas dans votre domaine de code.
De sorte que votre service de la mise en œuvre se traduit par XML dans le domaine des entités et les rend disponibles pour les autres objets dans le domaine.
Disons que j'ai Vue/Contrôleur qui a un peu d'actions-à-dire, ListCustomers, ListCustomer(string id), UpdateCustomer, et DeleteCustomer. Quand je crée des modèles pour ces actions/points de vue, les modèles de simplement être une Liste<Client> ou un seul Client? J'ai lu sur des Contextes et des Agrégats, mais je n'ai pas trouvé quelque chose de concret sur la mise en œuvre. Encore une fois, ma réaction instinctive est que j'aurais un modèle qui contient mon entité et que le contrôleur appeler le service approprié pour mettre à jour les modèles et ensuite passer le modèle de la vue, mais je suis un débutant, donc cela pourrait être mis en place.
Certaines personnes utilisent leur domaine d'objets comme des modèles dans une application mvc, c'est ok dans les cas simples. Votre approche est la meilleure de l'OMI: envelopper les objets du domaine et des services dans asp.net mvc modèles. Vous voudrez peut-être arrêter de penser à des contextes et des agrégats pour un moment et jetez un oeil à des modèles de vue en asp.net mvc.
OriginalL'auteur Marijn