【问题标题】:Consuming REST Web Service in .NET MVC 3在 .NET MVC 3 中使用 REST Web 服务
【发布时间】: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


    【解决方案1】:

    你在正确的轨道上。我会将 ICustomerService 放在域包中,并将此服务的 HttpWebConnection 实现放在引用域包的单独包中。

    这个类可以是静态的,但不必是静态的——如果你有疑问,那就不要让它成为静态的。

    您说得对,服务并没有完全与域解耦,但那是因为它们实现了域层中定义的服务契约,就域而言。 与域分离的是它们是soap/webservice 客户端或http/rest 客户端,这些是您不希望在域代码中出现的技术细节。

    因此,您的服务实现将 XML 转换为域实体,并使它们可用于域中的其他对象。

    【讨论】:

    • 感谢您的失败 - 非常清楚。拆分接口和实现是有意义的,因为这样我的域并不关心数据来自哪里,允许我稍后通过直接连接到数据库来实现相同的服务,而无需更改任何其他代码。
    • 假设我有一个视图/控制器,它有几个动作要说,ListCustomers、ListCustomer(string id)、UpdateCustomer 和 DeleteCustomer。当我为这些操作/视图创建模型时,这些模型只是一个 List 还是单个 Customer?我已经阅读了有关 Contexts 和 Aggregates 的内容,但没有找到任何具体的实现方式。同样,我的直觉反应是我有一个包含我的实体的模型,并且控制器会调用适当的服务来更新模型,然后将模型传回视图,但我是一个菜鸟,所以这可以远离。
    • 有些人在 mvc 应用程序中使用他们的域对象作为模型,这在简单的情况下是可以的。您的方法更好 IMO:将域对象和服务包装在 asp.net mvc 模型中。您可能想暂时停止考虑上下文和聚合,并查看 asp.net mvc 中的视图模型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-18
    • 2016-02-04
    相关资源
    最近更新 更多