65.9K
CodeProject 正在变化。 阅读更多。
Home

REST:一个简单的 REST 框架

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (73投票s)

2014 年 10 月 6 日

CPOL

16分钟阅读

viewsIcon

177423

一个使用 .NET 基础类库从头开始编写的简单 REST 框架

引言

在我工作的地方,我很幸运地接触到各种技术,包括 Web 开发、桌面开发、消息传递、线程等等。但我一直对人们如何创建完整的 REST 堆栈很感兴趣,并想知道只用自己的智慧和 .NET BCL 来做到这一点需要什么。

所以我想了想,并提出了以下要求/问题

  1. 如何监听 HTTP 请求?
  2. 如何让用户代码尽可能简单?
  3. 如何让用户代码通过 IOC/依赖注入来使用其他依赖项?
  4. 能否支持 Json/Xml 和其他可能的序列化偏好?

因此,带着这些要点,我开始着手从头开始创建一个 REST 框架。

对于那些不知道 REST 是什么/代表什么的人,维基百科是这样说的:

表示状态转移 (REST) 是万维网体系结构的抽象;更具体地说,REST 是一种架构风格,它由一组协调的架构约束组成,这些约束应用于分布式超媒体系统中的组件、连接器和数据元素。REST 忽略组件实现和协议语法的细节,以便专注于组件的角色、它们与其他组件交互的约束以及它们对重要数据元素的解释。

表示状态转移一词于 2000 年由 Roy Fielding 在其加州大学欧文分校的博士论文中首次提出并定义。REST 已被应用于描述期望的 Web 体系结构、识别现有问题、比较替代解决方案以及确保协议扩展不会违反使 Web 成功的核心约束。Fielding 在开发 HTTP 1.1 和统一资源标识符 (URI) 的同时,与同事合作开发了 REST。

REST 架构风格也适用于 Web 服务的开发,作为 SOAP 等其他分布式计算规范的替代方案。如果 Web 服务符合架构约束部分中描述的约束,则可以将它们称为“RESTful”。如果您只对 REST 在 Web API 中的应用感兴趣,请参阅应用于 Web 服务的章节。

http://en.wikipedia.org/wiki/Representational_state_transfer 最后更新日期 03/10/14

注意:此代码绝非生产就绪代码,它主要是为了好玩而编写的,只是为了尝试从头开始创建一个 REST 框架。如果您想要一个生产就绪的 REST 框架,您可能应该看看以下之一:

 

代码在哪里?

代码可在我的 GitHub 页面找到:https://github.com/sachabarber/rest

 

如何运行演示代码?

代码包含在一个解决方案中,这是一个 Visual Studio 2013 解决方案,打开后应如下所示。

为了正确运行,您应该确保以下 2 个项目被启动

  1. RESTServerConsoleHost
  2. RESTClientConsoleApp

您可以手动执行此操作,或者通过在 Visual Studio 中运行多项目来完成。如下图所示:

当您正确运行时,您应该会看到一些来自 RESTClientConsoleApp 的输出,大致如下:

 

它是如何工作的

本节概述了我编写的简单 REST 框架的工作原理

 

总体思路

我编写的 REST 框架实际上可以分解为几个简单的步骤:

  1. 监听传入的 HTTP 请求
  2. 对于传入的 HTTP 请求,尝试识别一个 IHandler(稍后会详细介绍,别担心)来处理该请求
  3. 根据请求的 HTTP 方法以及请求 URL 提供的的内容/URL,识别要调用的处理程序中的正确方法
  4. 将正确的输入参数从请求流传递给处理程序方法,并获取结果
  5. 获取结果并使用响应流将其返回给调用代码

 

托管/IOC 支持 (最终用户代码)

我想创建一个可以非常轻松地托管在几乎任何应用程序中的东西,而且我不想依赖任何其他框架。

托管(最终用户代码)

Task.Run(() =>
    {

        IWindsorContainer container = new WindsorContainer();
        container.Install(new DomainInstaller(), new HandlerInstaller());

        //run the server
        IDependencyResolver dependencyResolver = new WindsorDependencyResolver(container);
        HttpServer httpServer = new HttpServer(dependencyResolver);
        httpServer.Run(new string[] { @"https://:8001/" });
    });

这里有几点需要注意

  1. 我们使用一个 IOC 容器(我选择了 Castle Windsor,但您可以使用任何您喜欢的)
  2. 您的代码需要实现一个 IDependencyResolver,然后将其交给 REST 框架,以便它能够解析自身的依赖项以及用户代码(即您的代码)的依赖项

 

IOC 代码 (最终用户代码)

您的代码应该提供以下内容,可能值得查看演示的 RESTServerConsoleHost 项目。正如我所说,我使用的是 Castle Windsor,所以根据您选择的 IOC 容器,以下内容可能会有所不同

 

依赖项注册 (最终用户代码)

注册由 IHandler 基于的代码使用的任何依赖项(我们稍后将介绍)

public class DomainInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component
            .For(typeof(IRepository<Person,int>))
            .ImplementedBy(typeof(InMemoryPersonRepository))
            .LifestyleSingleton());

        container.Register(Component
            .For(typeof(IRepository<Account,int>))
            .ImplementedBy(typeof(InMemoryAccountRepository))
            .LifestyleSingleton());
            
        container.Register(Component
            .For(typeof(IRepository<User,int>))
            .ImplementedBy(typeof(InMemoryUserRepository))
            .LifestyleSingleton());
    }
}

 

处理程序注册 (最终用户代码)

注册任何 IHandler

public class HandlerInstaller : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        foreach (var item in this.GetType().Assembly.GetTypes())
        {
            if (item.GetInterfaces().Any(x => x.FullName.Contains("IHandler")))
            {
                AddHandler(container, item);
            }
        }
    }


    private void AddHandler(IWindsorContainer container, Type item)
    {
        container.Register(Component
        .For(typeof(IHandler))
        .ImplementedBy(item)
        .LifestyleTransient());
    }
}

 

自定义 IDependencyResolver (最终用户代码)

创建一个自定义 IDependencyResolver

public class WindsorDependencyResolver : IDependencyResolver
{
    private readonly IWindsorContainer container;

    public WindsorDependencyResolver(IWindsorContainer container)
    {
        this.container = container;
    }

    public object GetService(Type serviceType)
    {
        return container.Kernel.HasComponent(serviceType) ? container.Resolve(serviceType) : null;
    }

    public IEnumerable<object> GetServices(Type serviceType)
    {
        return container.Kernel.HasComponent(serviceType) ? 
            container.ResolveAll(serviceType).Cast<object>() : new object[] { };
    }
}

 

服务器 (框架的一部分)

基本要求是如何监听 HTTP 请求。我进行了一些谷歌搜索,并找到了 HttpListener 类,该类允许用户代码监听 HTTP 请求。

使用 HttpListener 类的一个缺点是您需要创建一个 URL ACL,但出于学习目的,这对我来说没问题。

如果您不喜欢这样做,您可能想看看这个项目,虽然它不是生产就绪的,但看起来很有趣,并且利用了 OWIN 堆栈,并且没有任何特定的 ACL 要求。

https://github.com/Bobris/Nowin

 

我在本文中介绍的简单 REST 框架的服务器部分出奇地简单,我所做的只是监听传入的 HTTP 请求,并将实际工作委托给所谓的路由动作器。路由动作器使用责任链设计模式逐一进行检查。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RESTServer.Handlers;
using RESTServer.IOC;
using RESTServer.Routing;

namespace RESTServer
{
    public class HttpServer
    {

        private readonly HttpListener listener = new HttpListener();
        private readonly int accepts = 4;
        private readonly IDependencyResolver dependencyResolver;
        private Semaphore sem;

        public HttpServer(IDependencyResolver dependencyResolver, int accepts = 4)
        {
            listener.IgnoreWriteExceptions = true;

            // Multiply by number of cores:
            this.accepts = accepts * Environment.ProcessorCount;
            this.dependencyResolver = dependencyResolver;
        }


        public async void Run(params string[] uriPrefixes)
        {
            // Add the server bindings:
            foreach (var prefix in uriPrefixes)
                listener.Prefixes.Add(prefix);
            listener.Start();

            //Accept connections:
            //1. Higher values mean more connections can be maintained yet at a 
            //   much slower average response time; fewer connections will be rejected.
            //2. Lower values mean less connections can be maintained yet at a 
            //   much faster average response time; more connections will be rejected.
            sem = new Semaphore(accepts, accepts);
            await RunServer();

        }


        private async Task RunServer()
        {
            while (true)
            {
                // Fall through until we've initialized all our connection listeners.
                // When the semaphore blocks (its count reaches 0) we wait until a connection occurs, 
                // upon which the semaphore is released and we create another connection "awaiter."
                sem.WaitOne();
                await StartConnectionListener();
            }
        }
 
        private async Task StartConnectionListener()
        {
            // Wait for a connection
            HttpListenerContext context = await listener.GetContextAsync();
 
            // Allow a new connection listener to be set up.
            sem.Release();

            //process the current request
            await ProcessRequest(context);
        }


        private async Task<bool> ProcessRequest(HttpListenerContext context)
        {
            // Setup Chain of Responsibility for route processing
            VerbRouteActioner verbRouteActioner = new VerbRouteActioner();
            DynamicRouteActioner dynamicRouteActioner = new DynamicRouteActioner();
            BadRouteActioner badRouteActioner = new BadRouteActioner();
            verbRouteActioner.Successor = dynamicRouteActioner;
            dynamicRouteActioner.Successor = badRouteActioner;

            var handlers = dependencyResolver.GetServices(typeof(IHandler)).Cast<IHandler>().ToList();
            await verbRouteActioner.ActionRequest(context, handlers);

            return true;
        }
    }
}

正如您所见,我没有说谎,基本循环非常简单:等待请求,当看到新请求时,调用路由动作器类。正如我所说的,路由动作器是使用责任链设计模式排列的。

使用的顺序如下:

  1. VerbRouteActioner:此项尝试将路由与用户基于的 IVerbHandler<T,TKey> 进行匹配。如果找到,则使用它,否则会将请求交给后继者 (DynamicRouteHandler) 来处理。
  2. DynamicRouteHandler:此项尝试将路由与用户基于的 IDynamicRouteHandler<T,TKey> 进行匹配。如果找到,则使用它,否则会将请求交给后继者 (BadRouteHandler) 来处理。
  3. BadRouteHandler : 这是链中的最后一个环节,它仅返回 HTTP 状态码 404,未找到。

所以我们刚刚提到了一些新东西,xxxRouteActioner 到底是什么?简单来说,它是这个简单 REST 框架内部的一个类,我在这里介绍它,它知道如何处理 HTTP 请求并调用相关的派生 IHandler 对象,其中调用的确切方法不仅取决于请求 URL,还取决于 HTTP 动词 GET/PUT/POST/DELETE。

我遵循标准的 REST 实践,即每个处理 REST 请求的方法都有特定的输入参数和返回值。下表显示了此 REST 框架遵循的规则。

如果您还不完全理解 IHandler 代码的作用,请不要担心,我们稍后将对此进行更详细的介绍。

 

序列化

我允许了两种不同的序列化方式,即 Json/Xml,支持此的序列化器如下所示。

Json

public class JsonPipelineSerializer : ISerializer
{
    public async Task<T> Deserialize<T>(string rawBodyData)
    {
        return await Task.Run(() => JsonConvert.DeserializeObject<T>(rawBodyData));
    }

    public async Task<Byte[]> SerializeAsBytes<T>(T item)
    {
        return await Task.Run(() => Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(item)));
    }
    public async Task<string> Serialize<T>(T item)
    {
        return await Task.Run(() => JsonConvert.SerializeObject(item));
    }
}

XML

public class XmlPipelineSerializer : ISerializer
{
    private XmlSerializer xmlSerializer;

    public async Task<T> Deserialize<T>(string rawBodyData)
    {
        xmlSerializer = new XmlSerializer(typeof(T));

        return await Task.Run(async () =>
        {
            T temp = default(T);
            using (Stream ms = await GenerateStreamFromString(rawBodyData))
            {
                temp = (T)xmlSerializer.Deserialize(ms);
            }
            return temp;
        });
    }

    public async Task<Byte[]> SerializeAsBytes<T>(T item)
    {
        xmlSerializer = new XmlSerializer(typeof(T));
        return await Task.Run(() =>
        {
            using (MemoryStream ms = new MemoryStream())
            {
                xmlSerializer.Serialize(ms, item);
                ms.Position = 0;
                return ms.ToArray();
            }
        });
    }

    public async Task<string> Serialize<T>(T item)
    {
        xmlSerializer = new XmlSerializer(typeof(T));
        return await Task.Run(() =>
        {
            using (MemoryStream ms = new MemoryStream())
            {
                xmlSerializer.Serialize(ms, item);
                ms.Position = 0;
                using (StreamReader sr = new StreamReader(ms))
                {
                    return sr.ReadToEnd();
                }
            }
        });
    }

    private async Task<Stream> GenerateStreamFromString(string s)
    {
        MemoryStream stream = new MemoryStream();
        await Task.Run(() =>
        {
            StreamWriter writer = new StreamWriter(stream, Encoding.UTF8);
            writer.Write(s);
            writer.Flush();
            stream.Position = 0;
        });
        return stream;
    }
}

 

路由/处理程序

我们已经提到,简单 REST 框架有一个概念,即使用一些代码来处理特定请求,如果无法处理,则使用 HttpServer 代码中设置的责任链设计模式调用其后继者。

那么,xxxRouteActioner 类到底是什么样子的?以下是任何 xxxRouteActioner 继承的抽象类。

public abstract class RouteActioner
{
    public abstract Task<bool> ActionRequest(
        HttpListenerContext context, 
        IList<IHandler> handlers);

    public RouteActioner Successor { set; protected get; }
}

我实际上在 REST 框架中提供了三个基于 RouteActioner 的类,本文档介绍的 REST 框架。下表描述了它们。

xxxRouteHandler 代码处理以下类型的事务:

  • HTTP 请求是否属于此类型的 xxxRouteActioner/IHandler
  • 是否存在与所请求路由匹配的正确类型的 IHandler
  • 请求是否包含与 xxxRouteActioner/IHandler 配合使用的正确参数?

我们将在下面查看主要的两个 xxxRouteHandlers 及其最终用户代码对应的部分。

 

VerbRouteActioner / IVerbHandler<T,TKey>

我们现在知道 xxxRouteActioners 和它们对应的用户代码之间存在一对一的映射。本节讨论 VerbRouteActioner / IVerbHandler<T,TKey>

 

IVerbHandler<T,TKey> (框架的一部分)

由于存在 IVerbHandler<T,TKey> 接口,如下所示,因此还有一个 VerbRouteActioner 与用户代码提供的 IVerbHandler<T,TKey> 实现一起使用。

这是 IVerbHandler<T,TKey> 接口,可以看到我们已经有了一组用户代码必须实现的方法。这些方法在我们上面看到的表中进行了概述,其中我们期望用户代码实现这些方法。

/// <summary>
/// A standard REST interface
/// </summary>
/// <typeparam name="T">An intention of the type of 
/// REST resource</typeparam>
/// <typeparam name="TKey">An intention of the type of 
/// the Id field of the REST resource</typeparam>
public interface IVerbHandler<T, TKey> : IHandler
{
    /// <summary>
    /// Gets a REST resource by its Id
    /// </summary>
    Task<T> Get(TKey id);

    /// <summary>
    /// Gets all instances of REST resource
    /// </summary>
    Task<IEnumerable<T>> Get();

    /// <summary>
    /// Add a new REST resource. Where the newly added resource is returned
    /// </summary>
    Task<T> Post(T item);

    /// <summary>
    /// Updates the REST resource identified by its Id, with the new REST resource
    /// </summary>
    Task<bool> Put(TKey id, T item);

    /// <summary>
    /// Deletes a new REST resource by its Id
    /// </summary>
    Task<bool> Delete(TKey id);
}

可以看到,由于所有需要实现的方法都是接口的一部分,我决定将返回类型设置为 Task<T>。这允许 REST 框架使用 async/await 来 await 用户提供的 IVerbHandler<T,TKey> 实现的返回值。

 

IVerbHandler<T,TKey> 实现 (最终用户代码)

所以我们已经看到了接口,那么用户提供的 IVerbHandler<T,TKey> 实现到底是什么样子的?

它看起来像这样:

[RouteBase("/people", SerializationToUse.Xml)]
public class PersonHandler : IVerbHandler<Person, int>
{
    private readonly IRepository<Person,int> personRepository;

    public PersonHandler(IRepository<Person,int> personRepository)
    {
        this.personRepository = personRepository;
    }

    #region IVerbHandler<Person,int> Members

    public async Task<Person> Get(int id)
    {
        return await Task.Run(() => personRepository.Get(id));
    }

    public async Task<IEnumerable<Person>> Get()
    {
        return await Task.Run(() => personRepository.GetAll());
    }

    public async Task<Person> Post(Person item)
    {
        return await Task.Run(() => personRepository.Add(item));
    }

    public async Task<bool> Put(int id, Person item)
    {
            
        return await Task.Run(() =>
        {
            item.Id = id;
            return personRepository.Update(item);
        });
    }

    public async Task<bool> Delete(int id)
    {
        return await Task.Run(() => personRepository.Delete(id));
    }

    #endregion
}

这里有几件事要讨论。

  1. 我们实现了 IVerbHandler<T,TKey>,其中 T 是资源类型,TKeyT 类型使用的 ID 字段的类型。然后,我们必须编写 IVerbHandler<T,TKey> 中的所有方法。
  2. 我们可以接受其他服务的依赖项。这里显示的是,我们接受了 IRepository<Person> 的依赖项,这是一个内存中的存储库。
  3. 我们使用一个特殊属性来提供一些路由元数据。此属性称为 RouteBaseAttribute,它允许指定两项内容:
    1. 请求路由的基础部分。这类似于您在使用约定式的东西(如 ASP MVC)时得到的内容,我们将把 HTTP 请求解析到一个特定的控制器来处理请求。我为了简单起见选择了属性。我们将继续查看 VerbRouteActioner 时了解其用法。
    2. 我们可以指定要使用的序列化,即 Xml/Json。

 

VerbRouteActioner (框架的一部分)

VerbRouteActioner 是此简单 REST 框架中使用的责任链设计模式的一部分。VerbRouteActioner 内部的操作大致如下列要点所示:

  • 我们尝试从我们拥有的 IHandler 列表中找到一个 IVerbHandler<T,TKey>,该列表与给定的请求路由匹配。这是通过使用 RouteBaseAttribute 来完成的,该属性是用户实现的 IVerbHandler<T,TKey> 的一部分。
  • 然后,我们检查 HTTP 请求以查看使用的方法,这使我们能够识别正在请求的 REST 调用。
  • 然后,我们提取 IVerbHandler<T,TKey> 的 ID(TKey 类型)和内容(T 类型),然后调用用户实现的 IVerbHandler<T,TKey> 中的相关代码。

这是 VerbRouteActioner 的完整代码。它确实使用了另一个辅助类,我没有在此包含它,因为我认为即使不深入了解该辅助类的内部工作原理,代码也足够易读。如果您感兴趣,该类名为 RestMethodActioner

public class VerbRouteActioner : RouteActioner
{
    RestMethodActioner restMethodActioner = new RestMethodActioner();

    public override async Task<bool> ActionRequest(
        HttpListenerContext context, 
        IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {
            object matchingHandler = null;

            //1. try and find the handler who has same base address as the request url
            //   if we find a handler, go to step 2, otherwise try successor
            //2. find out what verb is being used

            var httpMethod = context.Request.HttpMethod;
            var url = context.Request.RawUrl;
            bool result = false;

            var routeResult = await restMethodActioner.FindHandler(
                typeof (IVerbHandler<,>), context, handlers, false);

            if (routeResult.Handler != null)
            {
                //handler is using RouteBase, so fair chance it is a VerbHandler
                var genericArgs = GetVerbHandlerGenericArgs(routeResult.Handler.GetType());

                MethodInfo method = typeof (VerbRouteActioner).GetMethod("DispatchToHandler",
                    BindingFlags.NonPublic | BindingFlags.Instance);

                MethodInfo generic = method.MakeGenericMethod(genericArgs[0], genericArgs[1]);
                result = await (Task<bool>) generic.Invoke(this, new object[]
                {
                    context, routeResult.Handler, httpMethod, routeResult.SerializationToUse
                });

                return result;

            }

            result = await this.Successor.ActionRequest(context, handlers);
            return result;
        });
    }

    private async Task<bool> DispatchToHandler<T, TKey>(
        HttpListenerContext context, object handler,
        string httpMethod, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            MethodInfo method = typeof (VerbRouteActioner).GetMethod("CreateVerbHandler",
                BindingFlags.NonPublic | BindingFlags.Instance);
            MethodInfo generic = method.MakeGenericMethod(new[] {typeof (T), typeof (TKey)});
            IVerbHandler<T, TKey> actualHandler =
                (IVerbHandler<T, TKey>) generic.Invoke(this, new[] {handler});
            var result = false;


            switch (httpMethod)
            {
                case "GET":
                    result = await HandleGet<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "PUT":
                    result = await HandlePut<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "POST":
                    result = await HandlePost<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
                case "DELETE":
                    result = await HandleDelete<T, TKey>(
                        actualHandler, context, serializationToUse);
                    break;
            }
            return result;
        });
    }

    private async Task<bool> HandleGet<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {

        return await Task.Run(async () =>
        {
            var result = false;
            if (restMethodActioner.IsGetAll(context.Request.RawUrl))
            {
                var items = await actualHandler.Get();
                result = await restMethodActioner.SetResponse<List<T>>(
                    context, items.ToList(),
                    serializationToUse);
            }
            else
            {
                TKey id = await restMethodActioner.ExtractId<TKey>(
                    context.Request);
                var item = await actualHandler.Get(id);
                result = await restMethodActioner.SetResponse<T>(
                    context, item, serializationToUse);
            }

            return result;
        });
    }

    private async Task<bool> HandlePost<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);
            T itemAdded = await actualHandler.Post(item);
            bool result = await restMethodActioner.SetResponse<T>(
                context, itemAdded, serializationToUse);
            return result;
        });
    }
  
    private async Task<bool> HandleDelete<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);
            bool updatedOk = await actualHandler.Delete(id);
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }

    private async Task<bool> HandlePut<T, TKey>(
        IVerbHandler<T, TKey> actualHandler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);
            T item = await restMethodActioner.ExtractContent<T>(context.Request, 
            	serializationToUse);
            bool updatedOk = await actualHandler.Put(id, item);
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }


    /// <summary>
    /// Called via Reflection
    /// </summary>
    private IVerbHandler<T, TKey> CreateVerbHandler<T, TKey>(object item)
    {
        Expression convertExpr = Expression.Convert(
                                    Expression.Constant(item),
                                    typeof(IVerbHandler<T, TKey>)
                                    );

        var x = Expression.Lambda<
            Func<IVerbHandler<T, TKey>>>(convertExpr).Compile()();
        return x;
    }

    private Type[] GetVerbHandlerGenericArgs(Type item)
    {

        var ints = item.GetInterfaces();
        var verbInterface = item.GetInterfaces().Single(
            x => x.FullName.Contains("IVerbHandler"));
        return verbInterface.GenericTypeArguments;
    }
}

 

DynamicRouteActioner / IDynamicRouteHandler<T,TKey>

我们现在知道 xxxRouteActioners 和它们对应的用户代码之间存在一对一的映射。本节讨论 DynamicRouteActioner / IDynamicRouteHandler<T,TKey>

 

IDynamicRouteHandler<T,TKey> (框架的一部分)

存在一个 IDynamicRouteHandler<T,TKey> 接口,如下所示,还有一个 DynamicRouteActioner 与用户代码提供的 IDynamicRouteHandler<T,TKey> 实现一起使用。

这是 IDynamicRouteHandler<T,TKey> 接口,可以看到这次没有任何方法。这意味着用户可以自由指定任何方法名称/签名。这显然意味着最终用户代码可能是错误的,并且无法解析为标准的 REST 方法,因此我们需要对这种类型的最终用户处理程序进行更多检查,我们将在稍后看到。

/// <summary>
/// Used as a marker interface when you want to supply your own custom routes
/// by using the 
/// </summary>
/// <typeparam name="T">An intention of the type of 
/// REST resource</typeparam>
/// <typeparam name="TKey">An intention of the type of the Id 
/// field of the REST resource</typeparam>
public interface IDynamicRouteHandler<T,TKey> : IHandler
{
}

 

IDynamicRouteHandler<T,TKey> 实现 (最终用户代码)

所以我们已经看到了接口,那么用户提供的 IDynamicRouteHandler<T,TKey> 实现到底是什么样子的?它看起来像这样:

[RouteBase("/users", SerializationToUse.Json)]
public class UserHandler : IDynamicRouteHandler<User, int>
{
    private readonly IRepository<User,int> userRepository;

    public UserHandler(IRepository<User,int> userRepository)
    {
        this.userRepository = userRepository;
    }

    #region IDynamicRouteHandler<User,int> Members


    [Route("/GetUserByTheirId/{0}", HttpMethod.Get)]
    public async Task<User> GetUserByTheirId(int id)
    {
        return await Task.Run(() => userRepository.Get(id));
    }

    [Route("/GetAllUsers", HttpMethod.Get)]
    public async Task<IEnumerable<User>> GetAllUsers()
    {
        return await Task.Run(() => userRepository.GetAll());
    }

    [Route("/AddASingleUser", HttpMethod.Post)]
    public async Task<User> AddASingleUser(User item)
    {
        return await Task.Run(() => userRepository.Add(item));
    }

    [Route("/UpdateAUserUsingId/{0}", HttpMethod.Put)]
    public async Task<bool> UpdateTheUserWithId(int id, User item)
    {
        return await Task.Run(() =>
        {
            item.Id = id;
            return userRepository.Update(item);
        });
    }

    [Route("/DeleteUserByTheirId/{0}", HttpMethod.Delete)]
    public async Task<bool> DeleteAUser(int id)
    {
        return await Task.Run(() => userRepository.Delete(id));
    }
   
    #endregion
}

这里有几件事要讨论。

  1. 我们实现了 IDynamicRouteHandler<T,TKey>,其中 T 是资源类型,TKeyT 类型使用的 ID 字段的类型。然后,我们必须编写 IDynamicRouteHandler<T,TKey> 中的所有方法。
  2. 我们可以接受其他服务的依赖项。这里显示的是,我们接受了 IRepository<User> 的依赖项,这是一个内存中的存储库。
  3. 我们使用一个特殊属性来提供一些路由元数据。此属性称为 RouteBaseAttribute,它允许指定两项内容:
    1. 请求路由的基础部分。这类似于您在使用约定式的东西(如 ASP MVC)时得到的内容,我们将把 HTTP 请求解析到一个特定的控制器来处理请求。我为了简单起见选择了属性。我们将继续查看 VerbRouteActioner 时了解其用法。
    2. 我们可以指定要使用的序列化,即 Xml/Json。
  4. 每个方法都使用另一个特殊属性来提供更多的路由元数据。此属性称为 RouteAttribute,它允许指定两项内容:
    1. 请求的路由的操作部分。这类似于您在使用约定式的东西(如 ASP MVC)时得到的内容,我们将把 HTTP 请求解析到一个特定的操作来处理请求。我为了简单起见选择了属性。我们将继续查看 DynamicRouteActioner 时了解其用法。
    2. 我们可以指定要使用的 HttpMethod,即 GET/PUT/POST/DELETE。

 

DynamicRouteActioner (框架的一部分)

DynamicRouteActioner 是此简单 REST 框架中使用的责任链设计模式的一部分。DynamicRouteActioner 内部的操作大致如下列要点所示:

  • 我们尝试从我们拥有的 IHandler 列表中找到一个 IDynamicHandler<T,TKey>,该列表与给定的请求路由匹配。这是通过使用 RouteBaseAttribute 来完成的,该属性是用户实现的 IVerbHandler<T,TKey> 的一部分。
  • 然后,我们检查 HTTP 请求以查看使用的方法,这使我们能够识别正在请求的 REST 调用。因此,我们从请求中获取一个 HTTP 方法,但我们仍然需要识别 IDynamicHandler<T,TKey> 中要调用的正确方法,其中 IDynamicHandler<T,TKey> 实际上只是一个标记接口,最终用户代码可以提供任何方法签名,这些签名可能是有效的,也可能无效。那么我们如何处理这个问题?我们如何知道要调用哪个方法?这是另一个自定义属性(即 RouteAttribute)的工作,我们可以像这样使用它:[Route("/GetUserByTheirId/{0}", HttpMethod.Get)] 放在最终用户 IDynamicHandler<T,TKey> 实现中的任何方法上。通过使用 RouteAttribute,我们可以指定一个 URL 部分用于与传入的 HTTP 请求进行匹配(在约定式系统中,如 ASP MVC,可以认为此属性能够定位一个特定的操作),还可以指定 IDynamicHandler<T,TKey> 打算处理的正确 HTTP 方法。那么,我们如何使用 RouteAttribute 来帮助我们识别正确的 IDynamicHandler<T,TKey> 方法呢?答案在于非常彻底的参数/返回值检查。只有当以下所有条件为真时,我们才假设提供的 IDynamicHandler<T,TKey> 方法之一是正确的,并且可以调用
    • RouteBaseAttribute + RouteAttribute 部分代表整个请求 URL。
    • RouteAttribute HttpMethod 值与传入的 HTTP 请求方法匹配。
    • RouteAttribute 属性化方法的签名参数/返回值(可能是 Task<T> 也可能不是)匹配。
  • 然后,我们提取 IDynamicRouteHandler<T,TKey> 的 ID(TKey 类型)和内容(T 类型),然后调用用户实现的 IDynamicRouteHandler<T,TKey> 中的相关代码。
  • 如果当前找到的 IDynamicRouteHandler<T,TKey> 方法是 Task<T> 类型,则使用 async/await 来 await 方法调用的结果,并将其写入 HTTP 响应流。但是,如果它只是一个标准的返回值 T,则无需等待,返回值仅用于写入 HTTP 响应流。

这是 DynamicRouteHandler 的完整代码。它确实使用了另一个辅助类,我没有在此包含它,因为我认为即使不深入了解该辅助类的内部工作原理,代码也足够易读。如果您感兴趣,该类名为 RestMethodActioner

 

public class DynamicRouteActioner : RouteActioner
{
    RestMethodActioner restMethodActioner = new RestMethodActioner();

    public override async Task<bool> ActionRequest(
        HttpListenerContext context, IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {


            object matchingHandler = null;

            //1. try and find the handler who has same base address as the request url
            //   if we find a handler, go to step 2, otherwise try successor
            //2. find out what verb is being used

            var httpMethod = context.Request.HttpMethod;
            var url = context.Request.RawUrl;
            bool result = false;

            var routeResult = await restMethodActioner.FindHandler(
                typeof (IDynamicRouteHandler<,>), context, handlers, true);

            if (routeResult.Handler != null)
            {
                //handler is using RouteBase, so fair chance it is a VerbHandler
                var genericArgs = GetDynamicRouteHandlerGenericArgs(
                    routeResult.Handler.GetType());

                MethodInfo method = typeof (DynamicRouteActioner).GetMethod("DispatchToHandler",
                    BindingFlags.NonPublic | BindingFlags.Instance);

                MethodInfo generic = method.MakeGenericMethod(genericArgs[0], genericArgs[1]);
                result = await (Task<bool>) generic.Invoke(this, new object[]
                {
                    context, routeResult.Handler, httpMethod, url, 
                    routeResult.SerializationToUse
                });

                return result;

            }

            result = await this.Successor.ActionRequest(context, handlers);
            return result;
        });
    }

    private async Task<bool> DispatchToHandler<T, TKey>(
        HttpListenerContext context, object handler,
        string httpMethod, string url, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var result = false;

            DynamicMethodInfo method = null;
            switch (httpMethod)
            {
                case "GET":
                    method = await ObtainGetMethod<T, TKey>(handler, url);
                    result = await HandleGet<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "PUT":
                    method = await ObtainPutMethod<T, TKey>(handler, url);
                    result = await HandlePut<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "POST":
                    method = await ObtainPostMethod<T, TKey>(handler, url);
                    result = await HandlePost<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
                case "DELETE":
                    method = await ObtainDeleteMethod<T, TKey>(handler, url);
                    result = await HandleDelete<T, TKey>(method, handler, 
                        context, serializationToUse);
                    break;
            }
            return result;
        });
    }


    private async Task<DynamicMethodInfo> ObtainGetMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possibleGetMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Get);

            if (restMethodActioner.IsGetAll(url))
            {
                string attributeUrl = url.Substring(url.LastIndexOf("/"));

                var method = possibleGetMethods.Where(x => x.Route.Route == attributeUrl)
                    .Select(x => x.Method).FirstOrDefault();

                if (MethodResultIsCorrectType<Task<IEnumerable<T>>>(method))
                {
                    return new DynamicMethodInfo(method, true);
                }

                if (MethodResultIsCorrectType<IEnumerable<T>>(method))
                {
                    return new DynamicMethodInfo(method, false);
                }
                throw new HttpResponseException(string.Format(
                    "Incorrect return type/parameters for route '{0}'", url));
            }
            else
            {
                var method = GetIdMethodMatch(possibleGetMethods, url);

                if (MethodResultIsCorrectType<Task<T>>(method)
                    && MethodHasSingleCorrectParameterOfType<TKey>(method))
                {
                    return new DynamicMethodInfo(method, true);
                }

                if (MethodResultIsCorrectType<T>(method)
                    && MethodHasSingleCorrectParameterOfType<TKey>(method))
                {
                    return new DynamicMethodInfo(method, false);
                }
                throw new HttpResponseException(string.Format(
                    "Incorrect return type/parameters for route '{0}'", url));
            }
        });
    }

    private async Task<DynamicMethodInfo> ObtainPutMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possiblePutMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Put);

            var method = GetIdMethodMatch(possiblePutMethods, url);

            if (MethodResultIsCorrectType<Task<bool>>(method)
                && MethodHasCorrectPutParameters<T, TKey>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<bool>(method)
                && MethodHasCorrectPutParameters<T, TKey>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });

    }

    private async Task<DynamicMethodInfo> ObtainPostMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possiblePostMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Post);

            string attributeUrl = url.Substring(url.LastIndexOf("/"));

            var method = possiblePostMethods.Where(x => x.Route.Route == attributeUrl)
                .Select(x => x.Method).FirstOrDefault();

            if (MethodResultIsCorrectType<Task<T>>(method)
                && MethodHasSingleCorrectParameterOfType<T>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<T>(method)
                && MethodHasSingleCorrectParameterOfType<T>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });

    }

    private async Task<DynamicMethodInfo> ObtainDeleteMethod<T, TKey>(
        object handler, string url)
    {
        return await Task.Run(async () =>
        {
            var possibleDeleteMethods = ObtainPossibleMethodMatches(handler, HttpMethod.Delete);

            var method = GetIdMethodMatch(possibleDeleteMethods, url);

            if (MethodResultIsCorrectType<Task<bool>>(method)
                && MethodHasSingleCorrectParameterOfType<TKey>(method))
            {
                return new DynamicMethodInfo(method, true);
            }

            if (MethodResultIsCorrectType<bool>(method)
                && MethodHasSingleCorrectParameterOfType<TKey>(method))
            {
                return new DynamicMethodInfo(method, false);
            }
            throw new HttpResponseException(string.Format(
                "Incorrect return type/parameters for route '{0}'", url));
        });
    }


    private async Task<bool> HandleGet<T, TKey>(
        DynamicMethodInfo methodInfo, object handler, 
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var result = false;
            if (restMethodActioner.IsGetAll(context.Request.RawUrl))
            {
                if (methodInfo.IsTask)
                {
                    var items = await (Task<IEnumerable<T>>) methodInfo.Method.Invoke(handler, null);
                    result = await restMethodActioner.SetResponse<List<T>>(context,
                        items.ToList(), serializationToUse);
                }
                else
                {
                    var items = (IEnumerable<T>) methodInfo.Method.Invoke(handler, null);
                    result = await restMethodActioner.SetResponse<List<T>>(context,
                        items.ToList(), serializationToUse);
                }
            }
            else
            {
                TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);

                if (methodInfo.IsTask)
                {
                    var item = await (Task<T>) methodInfo.Method.Invoke(
                        handler, new object[] {id});
                    result = await restMethodActioner.SetResponse<T>(context,
                        item, serializationToUse);
                }
                else
                {
                    var item = (T) methodInfo.Method.Invoke(handler, new object[] {id});
                    result = await restMethodActioner.SetResponse<T>(context,
                        item, serializationToUse);
                }
            }
            return result;
        });
    }


    private async Task<bool> HandlePut<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);
            TKey id = await restMethodActioner.ExtractId<TKey>(
                context.Request);
            var updatedOk = false;

            if (methodInfo.IsTask)
            {
                updatedOk = await (Task<bool>) methodInfo.Method.Invoke(handler, 
                    new object[] {id, item});
            }
            else
            {
                updatedOk = (bool) methodInfo.Method.Invoke(handler, null);
            }
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }

    private async Task<bool> HandlePost<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            T itemAdded = default(T);
            T item = await restMethodActioner.ExtractContent<T>(
                context.Request, serializationToUse);

            if (methodInfo.IsTask)
            {
                itemAdded = await (Task<T>) methodInfo.Method.Invoke(
                    handler, new object[] {item});
            }
            else
            {
                itemAdded = (T) methodInfo.Method.Invoke(handler, 
                    new object[] {item});
            }

            bool result = await restMethodActioner.SetResponse<T>(
                context, itemAdded, serializationToUse);
            return result;
        });
    }


    private async Task<bool> HandleDelete<T, TKey>(
        DynamicMethodInfo methodInfo, object handler,
        HttpListenerContext context, 
        SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            var updatedOk = false;
            TKey id = await restMethodActioner.ExtractId<TKey>(context.Request);

            if (methodInfo.IsTask)
            {
                updatedOk = await (Task<bool>) methodInfo.Method.Invoke(
                    handler, new object[] {id});
            }
            else
            {
                updatedOk = (bool) methodInfo.Method.Invoke(handler, new object[] {id});
            }
            updatedOk &= await restMethodActioner.SetOkResponse(context);
            return updatedOk;
        });
    }


    private bool MethodHasSingleCorrectParameterOfType<TKey>(MethodInfo method)
    {
        var parameters = method.GetParameters();
        return parameters.Count() == 1 && parameters[0].ParameterType == typeof (TKey);
    }

    private bool MethodHasCorrectPutParameters<T,TKey>(MethodInfo method)
    {
        var parameters = method.GetParameters();
        return parameters.Count() == 2 &&
                parameters[0].ParameterType == typeof (TKey) &&
                parameters[1].ParameterType == typeof (T);

    }


    private bool MethodResultIsCorrectType<T>(MethodInfo method)
    {
        return method.ReturnType.IsAssignableFrom(typeof (T));
    }

    private IEnumerable<MethodMatch> ObtainPossibleMethodMatches(
        object handler, HttpMethod httpMethod)
    {
        return from x in handler.GetType().GetMethods()
                let attrib = x.GetCustomAttributes(typeof(RouteAttribute), false)
                where attrib.Length > 0 && ((RouteAttribute)attrib[0]).HttpVerb == httpMethod
                select new MethodMatch(x, (RouteAttribute)attrib[0]);
    }

    private MethodInfo GetIdMethodMatch(IEnumerable<MethodMatch> possibleMatches, string url)
    {
        string attributeUrl = url.Substring(0, url.LastIndexOf("/"));
        attributeUrl = attributeUrl.Substring(attributeUrl.LastIndexOf("/"));
        attributeUrl = attributeUrl + "/{0}";
        return possibleMatches.Where(x => x.Route.Route == attributeUrl)
            .Select(x => x.Method).FirstOrDefault();
    }

    private Type[] GetDynamicRouteHandlerGenericArgs(Type item)
    {

        var ints = item.GetInterfaces();
        var verbInterface = item.GetInterfaces().Single(
            x => x.FullName.Contains("IDynamicRouteHandler"));
        return verbInterface.GenericTypeArguments;
    }

}

 

 

IBadRouteActioner

正如我前面所说,这个简单 REST 框架使用的责任链设计模式中的最后一个链是 BadRouteActioner,它仅用于返回 HTTP 状态码 404,未找到。

这是它的代码:

public class BadRouteActioner : RouteActioner
{
    public override async Task<bool> ActionRequest(
	    HttpListenerContext context, 
        IList<IHandler> handlers)
    {
        return await Task.Run(async () =>
        {

            HttpListenerResponse response = context.Response;
            using (System.IO.Stream output = response.OutputStream)
            {
                var buffer = Encoding.UTF8.GetBytes("Not Found");
                output.Write(buffer, 0, buffer.Length);
                response.StatusCode = 404;
                response.StatusDescription = Enum.GetName(typeof (HttpStatusCode),
                    HttpStatusCode.NotFound);
            }
            return true;
        });
    }
}

 

 

.NET 客户端

我想创建一些辅助代码,使使用 .NET 客户端尽可能简单。为此,我开发了以下辅助代码,它使得编写 REST 方法非常容易。

public class RESTWebClient : WebClient
{
    private WebRequest request = null;
    private XmlPipelineSerializer xmlPipelineSerializer = new XmlPipelineSerializer();
    private JsonPipelineSerializer jsonPipelineSerializer = new JsonPipelineSerializer();

    protected override WebRequest GetWebRequest(Uri address)
    {
        this.request = base.GetWebRequest(address);

        if (this.request is HttpWebRequest)
        {
            ((HttpWebRequest)this.request).AllowAutoRedirect = false;
        }

        return this.request;
    }

    public async Task<RESTResponse<T>> Get<T>(
        string url, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            string response = await Task.Run(() => DownloadString(url));
            return await CreateResponse<T>(response, serializationToUse);
        });
    }

    public async Task<RESTResponse<T>> Post<T>(
        string url, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            byte[] responsebytes = await UploadDataForMethod(url, "POST", item, serializationToUse);
            string responsebody = string.Empty;
            if (serializationToUse == SerializationToUse.Xml)
            {
                responsebody = Encoding.UTF8.GetString(responsebytes);
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                responsebody = Encoding.UTF8.GetString(responsebytes);
            }
            return await CreateResponse<T>(responsebody, serializationToUse);
        });
    }

    public async Task<HttpStatusCode> Delete(string url)
    {
        return await Task.Run(async () =>
        {
            var request = WebRequest.Create(url);
            request.Method = "DELETE";
            var response = await request.GetResponseAsync();
            return ((HttpWebResponse) response).StatusCode;
        });
    }


    public async Task<HttpStatusCode> Put<T>(
        string url, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            await UploadDataForMethod(url, "PUT", item, serializationToUse);
            return await StatusCode();
        });
    }




    private async Task<byte[]> UploadDataForMethod<T>(
        string url, string httpMethod, T item, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            if (serializationToUse == SerializationToUse.Xml)
            {
                Headers.Add("Content-Type", "application/xml");
                var serialized = await xmlPipelineSerializer.SerializeAsBytes(item);
                return await Task.Run(() => UploadData(url, httpMethod, serialized));
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                Headers.Add("Content-Type", "application/json");
                var serialized = await jsonPipelineSerializer.SerializeAsBytes(item);
                return await Task.Run(() => UploadData(url, httpMethod, serialized));
            }
            throw new InvalidOperationException("You need to specify either Xml or Json serialization");
        });

    }


    private async Task<HttpStatusCode> StatusCode()
    {
        return await Task.Run(() =>
        {
            if (this.request == null)
            {
                throw (new InvalidOperationException(
                    "Unable to retrieve the status code, maybe you haven't made a request yet."));
            }

            HttpWebResponse response = base.GetWebResponse(this.request) as HttpWebResponse;

            if (response != null)
            {
                return response.StatusCode;
            }
            throw (new InvalidOperationException(
                "Unable to retrieve the status code, maybe you haven't made a request yet."));
        });
    }

    private async Task<RESTResponse<T>> CreateResponse<T>(
        string response, SerializationToUse serializationToUse)
    {
        return await Task.Run(async () =>
        {
            if (serializationToUse == SerializationToUse.Xml)
            {
                return new RESTResponse<T>()
                {
                    Content = await xmlPipelineSerializer.Deserialize<T>(response),
                    StatusCode = await StatusCode()
                };
            }
            if (serializationToUse == SerializationToUse.Json)
            {
                return new RESTResponse<T>()
                {
                    Content = await jsonPipelineSerializer.Deserialize<T>(response),
                    StatusCode = await StatusCode()
                };
            }
            throw new InvalidOperationException(
                "You need to specify either Xml or Json serialization");
        });
    }

}

使用此代码,执行此简单 REST 框架所需的各种 REST 调用归结为如下代码:

GET

一个简单的 GET 请求如下所示:

public async Task GetPerson(int id)
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string getUrl = string.Format("https://:8001/people/{0}", id);
            var response = await client.Get<Person>(getUrl, SerializationToUse.Xml);
        }
    });
}

 

PUT

一个简单的 PUT 请求如下所示:

public async Task PutPerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string getUrl = string.Format("https://:8001/people/{0}", 1);
            var response = await client.Get<Person>(getUrl, SerializationToUse.Xml);
            var person = response.Content;
            string newLastName = string.Format("{0}_Modified_{1}", person.LastName, DateTime.Now.Ticks);
            person.LastName = newLastName;

            string putUrl = string.Format("https://:8001/people/{0}", 1);
            var statusCode = await client.Put(putUrl, person, SerializationToUse.Xml);
        }
    });
}

POST

一个简单的 POST 请求如下所示:

public async Task PostPerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string postUrl = "https://:8001/people";
            Person newPerson = new Person();
            newPerson.FirstName = string.Format("FirstName_{0}", DateTime.Now.Ticks);
            newPerson.LastName = string.Format("LastName_{0}", DateTime.Now.Ticks);
            var response = await client.Post<Person>(postUrl, newPerson, SerializationToUse.Xml);
        }
    });
}

删除

一个简单的 DELETE 请求如下所示:

public async Task DeletePerson()
{
    await Task.Run(async () =>
    {
        using (RESTWebClient client = new RESTWebClient())
        {
            string deleteUrl = string.Format("https://:8001/people/{0}", 1);
            var statusCode = await client.Delete(deleteUrl);
        }
    });
}

 

其他客户端

要使用其他客户端,如 JavaScript 客户端,有许多有用的工具,如 JQuery/Angular。所以,如果您计划使用 JavaScript 客户端进行实验,互联网/StackOverflow 是您的朋友。

 

就这些

总之,这就是我想说的全部。我意识到这对于人们来说可能没有什么实际用处,除了作为一个学习练习,但如果您愿意写评论或投票,我将非常欢迎。

 

 

 

 

© . All rights reserved.