好的,让我们假设一下,我为一个名为Foo的实体提供了一个API,它看起来如下所示:
.../api/foo (GET, PUT, POST) or (SELECT, INSERT, UPDATE internally)这对于很多消费者来说都很好,尤其是移动设备,因为它非常简洁和轻量级。现在让我们假设,如果我们坚持使用REST,那么存在一个名为ComeForth的操作,它看起来如下所示:
.../api/foo/1/comeforth (POST) or (perform the come forth operation)好的,我们已经知道了,但是现在假设我需要消费者提供更多关于该操作的信息,所以为了保持简洁,我将构建一个新的资源来保存Foo ID和其他一些信息,名为ComeForth,而API现在看起来如下所示:
.../api/comeforth (POST) or (perform the come forth operation)现在,对我来说,以前的API .../api/foo/1/comeforth似乎没问题,但是第二个API觉得我想把一个正方形的钉放进一个圆孔里,仅仅因为我可以随意创建资源,但这并不意味着我应该这么做。所以,我的问题是:
ComeForth操作发布一个基于SOAP的服务吗?JavaScript或移动设备这样的消费者来说,这是更多的工作吗?).../api/foo/1/comeforth的API也会违反这条规则,不是吗?无论如何,我只是想确保我正在使用正确的技术来满足需求。
发布于 2012-09-21 19:31:14
在您所描述的情况下,所操作的资源不是Foo,而是一个事务(基于您的注释)。您可以针对特定操作类型(ComeForth)的T (Foo)类型实体建模一个长时间运行的事务。
控制器接受用于处理的事务后请求,并返回事务的表示形式,该表示包括分配给事务的唯一标识符,该标识符可用于跟踪事务的进度。
客户端执行GET操作,使用事务接受处理时收到的唯一标识符检索长期运行的事务的状态。
为了演示目的,我选择了使用XML序列化,但您可以将参与事务的实体序列化为字节数组或任何有意义的东西。
示例Web:
/事务/{id}
Web服务模型:
[DataContract()]
public class Transaction
{
public Transaction()
{
this.Id = Guid.Empty;
}
/// <summary>
/// Gets or sets the unique identifier for this transaction.
/// </summary>
/// <value>
/// A <see cref="Guid"/> that represents the unique identifier for this transaction.
/// </value>
[DataMember()]
public Guid Id
{
get;
set;
}
/// <summary>
/// Gets or sets a value indicating if this transaction has been completed.
/// </summary>
/// <value>
/// <see langword="true"/> if this transaction has been completed; otherwise, <see langword="false"/>.
/// </value>
[DataMember()]
public bool IsComplete
{
get;
set;
}
/// <summary>
/// Gets or sets the action being performed.
/// </summary>
/// <value>The action being performed.</value>
[DataMember()]
public string Action
{
get;
set;
}
/// <summary>
/// Gets or sets the serialized representation of the entity participating in the transaction.
/// </summary>
/// <value>The serialized representation of the entity participating in the transaction.</value>
[DataMember()]
public string Entity
{
get;
set;
}
/// <summary>
/// Gets or sets the assembly qualified name of the entity participating in the transaction.
/// </summary>
/// <value>
/// The <see cref="Type.AssemblyQualifiedName"/> of the <see cref="Entity"/>.
/// </value>
[DataMember()]
public string EntityType
{
get;
set;
}
/// <summary>
/// Returns the <see cref="Entity"/> as a type of <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type to project the <see cref="Entity"/> as.</typeparam>
/// <returns>
/// An object of type <typeparamref name="T"/> that represents the <see cref="Entity"/>.
/// </returns>
public T As<T>() where T : class
{
T result = default(T);
var serializer = new XmlSerializer(typeof(T));
using (var reader = XmlReader.Create(new MemoryStream(Encoding.UTF8.GetBytes(this.Entity))))
{
result = serializer.Deserialize(reader) as T;
}
return result;
}
/// <summary>
/// Serializes the specified <paramref name="entity"/>.
/// </summary>
/// <typeparam name="T">The type of entity being serialized.</typeparam>
/// <param name="entity">The entity to serialize.</param>
public static Transaction From<T>(T entity, string action = null) where T : class
{
var transaction = new Transaction();
transaction.EntityType = typeof(T).AssemblyQualifiedName;
transaction.Action = action;
var serializer = new XmlSerializer(typeof(T));
byte[] data = null;
using (var stream = new MemoryStream())
{
serializer.Serialize(stream, entity);
stream.Flush();
data = stream.ToArray();
}
transaction.Entity = Encoding.UTF8.GetString(data);
return transaction;
}
}
[DataContract()]
public class Foo
{
public Foo()
{
}
[DataMember()]
public string PropertyA
{
get;
set;
}
[DataMember()]
public int PropertyB
{
get;
set;
}
[DataMember()]
public Foo PropertyC
{
get;
set;
}
}TransactionsController:
public class TransactionsController : ApiController
{
public TransactionsController() : base()
{
}
private static ConcurrentDictionary<Guid, Transaction> _transactions = new ConcurrentDictionary<Guid, Transaction>();
/// <summary>
/// Using to initiate the processing of a transaction
/// </summary>
/// <param name="transaction"></param>
/// <returns></returns>
[HttpPost()]
public HttpResponseMessage Post(Transaction transaction)
{
if(transaction == null)
{
return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, new HttpError("Unable to model bind request."));
}
transaction.Id = Guid.NewGuid();
// Execute asynchronous long running transaction here using the model.
_transactions.TryAdd(transaction.Id, transaction);
// Return response indicating request has been accepted fro processing
return this.Request.CreateResponse<Transaction>(HttpStatusCode.Accepted, transaction);
}
/// <summary>
/// Used to retrieve status of a pending transaction.
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet()]
public HttpResponseMessage Get(Guid id)
{
Transaction transaction = null;
if(!_transactions.TryGetValue(id, out transaction))
{
return this.Request.CreateErrorResponse(HttpStatusCode.NotFound, new HttpError("Transaction does not exist"));
}
return this.Request.CreateResponse<Transaction>(HttpStatusCode.OK, transaction);
}
}事务控制器的示例客户端调用:
var foo = new Foo()
{
PropertyA = "ABC",
PropertyB = 123,
PropertyC = new Foo()
{
PropertyA = "DEF",
PropertyB = 456
}
};
var transaction = Transaction.From<Foo>(foo, "ComeForth");
Guid pendingTransactionId = Guid.Empty;
// Initiate a transaction
using(var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:12775/api/", UriKind.Absolute);
using (var response = client.PostAsJsonAsync<Transaction>("transactions", transaction).Result)
{
response.EnsureSuccessStatusCode();
pendingTransactionId = response.Content.ReadAsAsync<Transaction>().Result.Id;
}
}
// Retrieve status of transaction
Transaction pendingTransaction = null;
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:12775/api/", UriKind.Absolute);
var requestUri = String.Format(null, "transactions\\{0}", pendingTransactionId.ToString());
using (var response = client.GetAsync(requestUri).Result)
{
response.EnsureSuccessStatusCode();
pendingTransaction = response.Content.ReadAsAsync<Transaction>().Result;
}
}
// Check if transaction has completed
if(pendingTransaction.IsComplete)
{
}因此,您仍然可以使用REST和ASP.NET Web来建模一个长期运行的进程的启动,您只需要将操作表示为它自己的单独资源即可。希望这对您的开发工作有所帮助。
发布于 2012-09-21 15:15:38
对我来说,这听起来是一个非常开放的问题,需要考虑很多因素。
当调用匹配CRUD (创建、检索、更新、删除)时,REST是很好的,以Twitter为例,您可以创建、检索、更新和删除twitter帖子。
现在,考虑到处理事务的支付处理器,您可以创建一个事务(即传递一个cc#),它将有条件地做一些事情,然后可能返回一个事务结果(成功或失败)。您不能真正“更新”事务,而“撤回”事务并不是真正撤回您发送的数据。你当然不能“删除”一个交易,你可以取消一个,或执行退款(部分或全部)。对于这个例子,REST是没有意义的。
这并不是说你不能有一个休息和操作的混合体。在某些实体符合REST的情况下,但是在REST不合适的地方还有其他方法(例如处理支付)。
选择REST或SOAP的决定应该由目标受众来决定,WCF服务(它使用SOAP)在.NET中实现要比在REST中容易得多,如果消费技术是ruby的话,则反之亦然。
https://stackoverflow.com/questions/12533166
复制相似问题