【发布时间】:2011-02-11 15:05:09
【问题描述】:
我正在开发一个 .NET 4 MVC 3 应用程序。我正在尝试遵循领域驱动的设计范例。现在,我的应用程序分为两部分,一个域和我的网络 MVC 代码。我需要一些帮助来确定我应该在这个结构中的哪个位置使用 RESTful Web 服务。
这个特定项目使用 RESTful Web 服务来检索和保存数据。在我的域中,我有两个实体“客户”和“用户”,它们与同名的 Web 服务配对。例如URL/客户和 URL/用户。每个 Web 服务都采用一些参数,然后以 XML 格式返回适当的数据列表。我需要以(POST、GET、PUT 和 DELETE)的形式实现基本的 CRUD 功能。鉴于此,我有两个主要问题。
1.) 我应该创建什么类型的对象来使用这些 Web 服务?我的直觉是创建一个定义我的 CRUD 操作的 ICustomerService 接口,然后以使用 HTTPWebConnection(或扩展它?)的类的形式创建该接口的实现。有没有更好的方式来使用 RESTful Web 服务?这种类型的类应该是静态的吗?
2.) 这个服务代码应该放在哪里?同样,我的直觉告诉我,除了代码的 Domain 和 WebUI 部分之外,我还需要第三个 Services 部分,其中包含这些 Web 服务客户端的接口和实现,但是由于 Web 服务正在返回客户的 XML 表示和我域中的用户实体,服务不会真正从域中解耦。
提前致谢, 格雷格
编辑
在从事各种项目一段时间后,我发现了一种在 MVC 中处理 REST Web 服务的好方法。
首先,我创建代表我将使用的各种 Web 服务的实体。每个实体都使用 XML 属性将属性与 XML 元素进行匹配。这是一个假设的 Web 服务的简单示例,该服务返回有关人员及其衬衫的信息(这很愚蠢,但我能在飞行中想出最好的方法)。
假设我正在从 Web 服务获取一个 Person 对象。这是 XML。
<result>
<resultCount>1</resultCount>
<person>
<personName>Tom</personName>
<shirt>
<shirtColor>red</shirtColor>
<shirtType>sweater</shirtType>
</shirt>
</person>
</result>
然后我将有两个实体:Person 和 Shirt。我喜欢将整个班级都包括在内,以便新手可以看到所有内容,所以如果这对您的口味来说过于冗长,我很抱歉。
人物
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() 方法,该方法可以从 Web 服务查询中返回人员。
存储库
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 的 Web 服务提供一个良好的开端。
【问题讨论】:
标签: asp.net-mvc rest .net-4.0 dns domain-driven-design