Я работаю над приложением .NET 4 MVC 3. Я пытаюсь следовать принципам доменного дизайна. В настоящее время мое приложение разбито на две части: домен и мой код MVC для Интернета. Мне нужна помощь в определении того, где в этой структуре мне следует использовать веб-сервис RESTful.
Этот конкретный проект использует веб-сервис RESTful для извлечения и сохранения данных. В моем домене есть две сущности «Клиент» и «Пользователь», которые соединяются с веб-службами с одинаковыми именами. например URL / Клиент и URL / Пользователь. Каждый веб-сервис принимает несколько параметров, а затем возвращает соответствующий список данных в формате XML. Мне нужно реализовать базовую функциональность CRUD в виде (POST, GET, PUT и DELETE). Учитывая это, у меня есть два основных вопроса.
1.) Какой тип объекта я должен создать, чтобы использовать эти веб-службы? Мой инстинкт инстинкта - создать интерфейс ICustomerService, который определяет мои операции CRUD, а затем создать реализацию этого интерфейса в форме класса, который использует HTTPWebConnection (или расширяет его?). Есть ли лучший способ использовать веб-сервисы RESTful? Должен ли этот тип класса быть статическим?
2.) Куда должен идти этот сервисный код? Опять же, моя интуиция говорит мне, что в дополнение к разделам «Код» и «Веб-интерфейс» мне нужен третий раздел «Службы», который содержит интерфейсы и реализации этих клиентов веб-служб, но поскольку веб-службы возвращают представления клиента в XML-формате и сущности пользователей, которые находятся в моем домене, службы не будут отключены от домена.
Заранее спасибо,
Грег
EDIT
Проработав некоторое время над различными проектами, я нашел хороший способ обработки веб-сервисов REST в MVC.
Во-первых, я создаю сущности, которые представляют различные веб-службы, которые я буду использовать. Каждая сущность использует атрибуты XML для сопоставления свойств с элементами XML. Вот простой пример для гипотетического веб-сервиса, который возвращает информацию о людях и их рубашках (это глупо, но лучшее, что я мог придумать на лету).
Допустим, я получаю объект Person из веб-службы. Вот XML.
<result>
<resultCount>1</resultCount>
<person>
<personName>Tom</personName>
<shirt>
<shirtColor>red</shirtColor>
<shirtType>sweater</shirtType>
</shirt>
</person>
</result>
Тогда у меня было бы две сущности: Персона и Рубашка. Мне нравится включать весь класс, чтобы новички могли видеть все, поэтому я извиняюсь, если это слишком многословно для ваших вкусов.
Человек
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; }
}
}
футболка
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; }
}
}
Затем мы можем использовать XmlSerializer для преобразования объекта в XML и XML в объекты. Вот класс, который я изменил, чтобы сделать это. Я прошу прощения, поскольку я не помню первоисточник. (В этом классе, вероятно, есть много возможностей для совершенствования)
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);
}
}
Затем создайте универсальный сервис для обработки ваших операций HTTP. Я использую GET и POST. Вот мой класс.
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;
}
}
}
Затем вы создаете свой репозиторий для использования HttpService. Я реализовал простой метод GetPeople (), который возвращал бы людей из запроса веб-службы.
Репозиторий
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;
}
}
}
}
Наконец, вам нужно использовать свой репозиторий в вашем контроллере. Я не использую здесь никакого внедрения зависимостей (DI), но в идеале вы бы хотели использовать его в окончательной сборке.
Контроллер
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>();
}
}
}
Я набрал это на лету и изменил его из моего фактического решения, поэтому я прошу прощения за любые опечатки или ошибки. Я сделаю все возможное, чтобы исправить все, что найду, но это должно послужить хорошим началом для создания повторно используемого решения для работы с веб-сервисами на основе REST.