在 Limyee 电商平台中创建新内容、应用程序/或其他自定义数据和服务时,您可以通过 REST 公开这些服务来进行外部集成。这提供了极大的灵活性,允许外部集成访问您的定制数据。
何时应使用 REST 端点?
每当您希望使 Limyee 电商平台外部的应用程序能够与您的自定义数据进行交互时,都应通过 REST 公开数据。这也可以在 Limyee 电商平台界面中使用,用于小组件的 AJAX 操作。此外,如果要提供更广泛的响应,或将响应限制为特定字段,则可以将一个或多个进程 API 包装到新的 REST 端点中。
我们的示例为实体提供推荐的标准 REST 端点集:创建、更新、删除、获取和列表。如果你的方案不需要它们,则不需要全部使用它们。
必需引用的 DLL:
- Limyee.Components.dll
- Limyee.Rest.dll
示例内容
该过程的第一步是编写自定义数据或服务以创建 REST 端点。虽然本文重点介绍自定义数据,但您也可以创建一个自定义对象来存储从一个或多个进程 API 检索的平台数据并返回这些数据。在此示例中,我们将重点介绍一个名为“Dog”的自定义类。
using System; namespace Samples { public class Dog { public Guid Id { get; set; } public string Name { get; set; } public string Breed { get; set; } } }
在本演示中,我们假设存在一个支持服务来对此对象执行 CRUD 操作。我们将其称为 DogService,并假定其方法单独采用所有必需参数,并将所有可选参数分组到 Dog<MethodName>Options 类中。这与进程 API 的模式相同。
注册 REST 路由
实现 IRestEndpoints 需要一个 Register 方法,该方法为您提供了一个用于注册 REST 路由的控制器。路由将始终以标准的 Limyee 电商平台 REST URL(<siteurl> + “/api/{version}”)开头,我们将定义 URL 的其余部分,包括每个所需方法的版本。IRestEndpoints 也是从 IPlugin 派生出来的,所以你还需要实现基本的 IPlugin 接口。
端点的声明实际上相当简单。参数为:
- int version:这允许您在同一 URL 上拥有多个版本的端点。这允许您更改API,但如果您愿意,可以保持一些向后兼容性。此值也用于 api 之后的 url 的根目录中。例如,如果将此值设置为 1,则端点的基本 url 为 http://<siteurl>/api/v1,如果设置为 2,则为 /api/v2,依此类推。
- string relativeUrl:这是对基本 REST URL(<siteurl> + “/api/{version}”) 的添加,用于向处理程序标识此方法。与 HTTP 方法和任何参数约束一起,这使得 url 是唯一的。
- object parameterDefaults:指定有关路由的默认设置,例如:REST 资源名称是什么以及正在执行的 REST 操作。它还可用于为任何可选参数设置默认值。
- object parameterConstraints:约束将用于指定路由 URL 中参数的所需格式等内容。
- HttpMethod method:对应于标准的 HTTP 方法(GET、POST、PUT、DELETE)。在这里,我们使用 HttpMethod 枚举。
- Func<IRestRequest,IRestResponse> handler:这是将用于处理请求和发出响应的委托。它可以是已定义的方法,也可以是内联 Func<> 声明。
public void Register(IRestEndpointController restRoutes) { restRoutes.Add(1, "dogs", new { resource = "dog", action = "create" }, null, HttpMethod.Post, CreateDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "update" }, null, HttpMethod.Put, UpdateDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "delete" }, null, HttpMethod.Delete, DeleteDog); restRoutes.Add(1, "dog/{id}", new { resource = "dog", action = "show" }, null, HttpMethod.Get, GetDog); restRoutes.Add(1, "dogs", new { resource = "dog", action = "list" }, null, HttpMethod.Get, ListDogs); }
声明路由 URL 类似于 MVC 路由,因为路由可能会意外重叠,从而完全隐藏某些路由。您需要确保不会发生这种情况,以便所有方法都可用,并且对这些方法的调用不会路由到错误的内部方法。您可以通过 HTTP 方法、参数约束和路由本身的组合来区分路由。关于重叠路由,需要注意和避免的几种情况:
- 匹配路由:只要 HTTP 方法不同,这是安全的。例如,“更新”、“删除”和“获取”都使用相同的路由(“dog/{id}”),但使用 HTTP 方法(分别为 PUT、DELETE 和 GET)进行区分。
- 匹配 Http 方法:只要路由是唯一的,这是安全的。Get 和 List 具有相同的 HTTP 方法 (GET),因此它们需要不同的路由 URL 来区分它们(“dog/{id}”和“dogs”)。
- 匹配路由参数令牌:当路由与包含参数标记的路由相似时,需要小心。例如,如果在路由“dog/create”(映射到 Create 方法)之前声明了路由“dog/{id}”(映射到 Get 方法),如果在 {id} 上没有参数约束指定它必须是字符串以外的内容时,则“create”可以被解释为与 {id} 令牌匹配,从而导致“dog/create”触发 Id 无效的 Get 方法。
每个 REST 方法都接受一个 IRestRequest 对象,并返回一个实现 IRestResponse 的对象。请求存储数据有两个主要组件:PathParameters 和 Form。PathParameters 的内容是从 url 路径解析的内容,包括查询字符串,Form 的内容是从请求正文中解析的。您还可以选择直接从 IRestRequest.Request.QueryString 访问查询字符串。
IRestResponse 接口相当通用,提供位置允许您放置数据对象,以便可以将其序列化到 HTTP 响应中。它还需要数据对象的名称(对于最外层的序列化实体),并提供列表访问响应中的错误字符串。
用于执行每个操作的函数是单独声明的。首先,我们必须创建一个实现 IRestResponse 的 响应对象,以便我们可以以已知格式发送数据。
[XmlRoot(ElementName = "Response")] [RestAction(RestAction.Show, "achievement")] public class DogResponse : IRestResponse { private Dog _dog; public Dog Dog { get { return _dog; } set { _dog = value; } } public string Name { get { return "Dog"; } } public object Data { get { return _dog; } } public string[] Errors { get; set; } }
当我们要添加对象的实例时,将使用 Create。在此示例中,添加狗只需要名称,因此我们检查该名称,如果缺少,则提前返回错误。可以在此处进行其他检查,例如,必需的参数需要是整数。检查可选参数更容易,我们只需要将其分配给选项对象(如果它确实存在)。收集到所需的全部信息后,您可以调用内部服务,执行必要的操作,并在 REST 响应中返回相关信息。重要的是要将内部调用包装在 try...catch 代码块中,因此您可以确保返回的请求是 REST 响应,而不是 ASP.net 错误页。如果缺少必需的参数或未满足要求,或者内部出现其他问题,则可能会返回错误响应(建议为此使用 IUserRenderableException)。如果包含全部必需的参数,则可以分析每个参数,然后将其与基础内部服务一起使用以执行请求的操作。
public IRestResponse CreateDog(IRestRequest request) { var response = new DogResponse(); if (request.Form["Name"] == null) { response.Errors = new[] { "Name is required" }; return response; } var name = request.PathParameters["Name"].ToString(); var options = new DogCreateOptions(); if (request.Form["Breed"] != null) options.Breed = request.Form["Breed"]; try { var createdDog = DogService.Create(name, options); response.Dog = createdDog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Update 使用最初从 Create 返回的 Id 来查找对象,这允许所有其他参数都是可选的,并且仅在指定时更改。
public IRestResponse UpdateDog(IRestRequest request) { var response = new DogResponse(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } var id = Guid.Parse(request.PathParameters["Id"].ToString()); var options = new DogUpdateOptions(); if (request.Form["Name"] != null) options.Name = request.PathParameters["Name"].ToString(); if (request.Form["Breed"] != null) options.Breed = request.PathParameters["Breed"].ToString(); try { var createdDog = DogService.Update(id, options); response.Dog = createdDog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Delete 只需要对象的 ID 即可将其从数据库中删除。
public IRestResponse DeleteDog(IRestRequest request) { var response = new DefaultRestResponse(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } var id = Guid.Parse(request.PathParameters["Id"].ToString()); try { DogService.Delete(id); } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
Get 也只需要 Id 即可返回完整对象。
public IRestResponse GetDog(IRestRequest request) { var response = new DogResponse(); var options = new DogGetOptions(); if (!request.PathParameters.ContainsKey("Id")) { response.Errors = new[] { "Id is required" }; return response; } options.Id = Guid.Parse(request.PathParameters["Id"].ToString()); try { var dog = DogService.Get(options); response.Dog = dog; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
List 在从中返回的内容而言,是最灵活的方法,因为不包含必需的参数,可使用其他可选参数用于返回自定义列表。
public IRestResponse ListDogs(IRestRequest request) { var response = new DefaultRestResponse(); var options = new DogListOptions(); if (request.PathParameters.ContainsKey("Name")) options.Name = request.PathParameters["Name"].ToString(); if (request.PathParameters.ContainsKey("Breed")) options.Breed = request.PathParameters["Breed"].ToString(); if (request.PathParameters.ContainsKey("SortBy")) options.SortBy = request.PathParameters["SortBy"].ToString(); if (request.PathParameters.ContainsKey("SortOrder")) options.SortOrder = request.PathParameters["SortOrder"].ToString(); if (request.PathParameters.ContainsKey("PageSize")) options.PageSize = Int32.Parse(request.PathParameters["PageSize"].ToString()); if (request.PathParameters.ContainsKey("PageIndex")) options.PageIndex = Int32.Parse(request.PathParameters["PageIndex"].ToString()); try { var dogs = DogService.List(options); response.Name = "Dogs"; response.Data = dogs; } catch (Exception ex) { response.Errors = new[] { "Error: '{0}'", ex.ToString() }; } return response; }
使用已注册的路由
有关调用 REST 端点的信息,请参阅 REST API:发起请求。