我用来开发微软技术的Web应用程序.NET开发。 我想教育自己,以了解Web服务REST方法。 到目前为止,我很热爱ServiceStack框架。
但有时我发现自己在我习惯与WCF的方式来写服务。 所以我有一个问题,它的错误我。
我有2个要求DTO是如此喜欢这两个服务:
[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<GetBookingLimitResponse>
{
public int Id { get; set; }
}
public class GetBookingLimitResponse
{
public int Id { get; set; }
public int ShiftId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Limit { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<GetBookingLimitsResponse>
{
public DateTime Date { get; set; }
}
public class GetBookingLimitsResponse
{
public List<GetBookingLimitResponse> BookingLimits { get; set; }
public ResponseStatus ResponseStatus { get; set; }
}
作为对这些请求DTO的看到我有类似的要求DTO几乎是每一个服务,这似乎是不干燥。
我试图用GetBookingLimitResponse
类在列表里面GetBookingLimitsResponse
出于这个原因ResponseStatus
内GetBookingLimitResponse
的情况下,我有一个错误类dublicated GetBookingLimits
服务。
我也有这些要求,是服务实现:
public class BookingLimitService : AppServiceBase
{
public IValidator<AddBookingLimit> AddBookingLimitValidator { get; set; }
public GetBookingLimitResponse Get(GetBookingLimit request)
{
BookingLimit bookingLimit = new BookingLimitRepository().Get(request.Id);
return new GetBookingLimitResponse
{
Id = bookingLimit.Id,
ShiftId = bookingLimit.ShiftId,
Limit = bookingLimit.Limit,
StartDate = bookingLimit.StartDate,
EndDate = bookingLimit.EndDate,
};
}
public GetBookingLimitsResponse Get(GetBookingLimits request)
{
List<BookingLimit> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
List<GetBookingLimitResponse> listResponse = new List<GetBookingLimitResponse>();
foreach (BookingLimit bookingLimit in bookingLimits)
{
listResponse.Add(new GetBookingLimitResponse
{
Id = bookingLimit.Id,
ShiftId = bookingLimit.ShiftId,
Limit = bookingLimit.Limit,
StartDate = bookingLimit.StartDate,
EndDate = bookingLimit.EndDate
});
}
return new GetBookingLimitsResponse
{
BookingLimits = listResponse.Where(l => l.EndDate.ToShortDateString() == request.Date.ToShortDateString() && l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList()
};
}
}
正如你看到的我也想在这里使用验证功能,所以我必须写验证类为每个请求DTO我有。 所以,我有我应该让我的服务号码由低分组类似服务为一体的服务的感觉。
但在这里,在我的脑海里弹出的疑问,我应该比客户端需要这样发送请求的更多信息?
我觉得我的思维方式应该改变,因为我不开心的与我写的思维就像一个WCF家伙当前的代码。
可有人告诉我正确的方向可循。
为了让你的差异的味道,你应该想想,当在设计基于消息的服务ServiceStack我将提供一些例子比较WCF / VS的WebAPI ServiceStack的做法:
WCF VS ServiceStack API设计
WCF鼓励你去思考的Web服务作为普通的C#方法调用,如:
public interface IWcfCustomerService
{
Customer GetCustomerById(int id);
List<Customer> GetCustomerByIds(int[] id);
Customer GetCustomerByUserName(string userName);
List<Customer> GetCustomerByUserNames(string[] userNames);
Customer GetCustomerByEmail(string email);
List<Customer> GetCustomerByEmails(string[] emails);
}
这是相同的服务合同是什么样子与ServiceStack 新的API :
public class Customers : IReturn<List<Customer>>
{
public int[] Ids { get; set; }
public string[] UserNames { get; set; }
public string[] Emails { get; set; }
}
要记住的重要概念是整个查询(又名请求)的请求消息来捕获(即请求DTO),而不是在服务器方法签名。 采用基于消息的设计的明显直接的好处是,上面的RPC调用的任何组合可以在1条远程消息得到满足,由单个服务实现。
的WebAPI VS ServiceStack API设计
同样的WebAPI促进了WCF做了类似的类似C#的RPC API:
public class ProductsController : ApiController
{
public IEnumerable<Product> GetAllProducts() {
return products;
}
public Product GetProductById(int id) {
var product = products.FirstOrDefault((p) => p.Id == id);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product;
}
public Product GetProductByName(string categoryName) {
var product = products.FirstOrDefault((p) => p.Name == categoryName);
if (product == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return product;
}
public IEnumerable<Product> GetProductsByCategory(string category) {
return products.Where(p => string.Equals(p.Category, category,
StringComparison.OrdinalIgnoreCase));
}
public IEnumerable<Product> GetProductsByPriceGreaterThan(decimal price) {
return products.Where((p) => p.Price > price);
}
}
ServiceStack基于消息的API设计
虽然ServiceStack鼓励你保留一个基于消息的设计:
public class FindProducts : IReturn<List<Product>> {
public string Category { get; set; }
public decimal? PriceGreaterThan { get; set; }
}
public class GetProduct : IReturn<Product> {
public int? Id { get; set; }
public string Name { get; set; }
}
public class ProductsService : Service
{
public object Get(FindProducts request) {
var ret = products.AsQueryable();
if (request.Category != null)
ret = ret.Where(x => x.Category == request.Category);
if (request.PriceGreaterThan.HasValue)
ret = ret.Where(x => x.Price > request.PriceGreaterThan.Value);
return ret;
}
public Product Get(GetProduct request) {
var product = request.Id.HasValue
? products.FirstOrDefault(x => x.Id == request.Id.Value)
: products.FirstOrDefault(x => x.Name == request.Name);
if (product == null)
throw new HttpError(HttpStatusCode.NotFound, "Product does not exist");
return product;
}
}
再次捕获请求DTO请求的实质。 基于消息的设计也能够凝结5个独立的RPC的WebAPI服务到2基于消息的ServiceStack的。
集团通过调用语义和响应类型
它分为2个不同的服务,基于呼叫语义和响应类型的例子:
在每个请求DTO每个属性都有相同的语义是FindProducts
每个属性的作用就像一个过滤器(例如AND)而在GetProduct
它就像一个组合子(例如OR)。 该服务也返回IEnumerable<Product>
和Product
的返回类型,这将需要在类型化的API的调用点不同的处理。
在WCF /的WebAPI(和其他RPC服务框架),只要你有一个特定的客户要求,你会认为该请求匹配的控制器上添加新的服务器签名。 在ServiceStack的基于消息的方法,但是你应该总是想着这个功能属于哪里,以及是否你能够增强现有的服务。 你也应该考虑如何支持在一个通用的方法客户特定的要求,使同样的服务可有益于其它潜在的未来使用情况。
重新分解GetBooking限制服务
随着信息上面我们可以开始重新分解你的服务。 既然你有一个返回不同的结果2种不同的服务,例如GetBookingLimit
返回1项和GetBookingLimits
返回不少,他们需要被保存在不同的服务。
区分服务运营VS类型
然而,你应该有你的服务操作(例如,请求DTO),这是每个服务唯一的,用于捕获服务的要求,以及它们返回的DTO类型之间有一个清晰分裂。 请求DTO的通常操作,以便他们是动词,而DTO类型的实体/数据容器所以他们的名词。
返回通用的反应
在新的API,ServiceStack响应不再需要ResponseStatus性质,因为如果它不存在一般是ErrorResponse
DTO会抛出和系列化的客户端来代替。 这使你不必你的回应包含ResponseStatus
性能。 随着中说我会重新因素你的新服务合同:
[Route("/bookinglimits/{Id}")]
public class GetBookingLimit : IReturn<BookingLimit>
{
public int Id { get; set; }
}
public class BookingLimit
{
public int Id { get; set; }
public int ShiftId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Limit { get; set; }
}
[Route("/bookinglimits/search")]
public class FindBookingLimits : IReturn<List<BookingLimit>>
{
public DateTime BookedAfter { get; set; }
}
对于GET请求我倾向于给他们留下了路线定义的时候他们不是模糊的,因为它是更少的代码。
保持一致的命名
你应该保留搭话上唯一或主键字段,即,查询服务时所提供的值的字段匹配(例如:内径)只获取 1分的结果。 对于搜索服务,就像一个过滤器,并返回多个匹配的结果我请使用查找或搜索动词的信号,这是情况下所需的范围内,其下降。
瞄准自我描述的服务合同
也尽量描述与每个字段名,这些属性是你的公共API的一部分,应该是自描述为它做什么。 例如,只要看一眼的服务合同(例如,请求DTO)我们不知道呢,我认为BookedAfter什么日期 ,但它也可能是BookedBefore或BookedOn如果只返回那一天进行预订。
这样做的好处是,现在您的调用点类型的.NET客户端变得更容易阅读:
Product product = client.Get(new GetProduct { Id = 1 });
List<Product> results = client.Get(
new FindBookingLimits { BookedAfter = DateTime.Today });
服务实现
我已经删除了[Authenticate]
从您的要求DTO的属性,因为你可以而不是只指定了一次服务实现,它现在的样子:
[Authenticate]
public class BookingLimitService : AppServiceBase
{
public BookingLimit Get(GetBookingLimit request) { ... }
public List<BookingLimit> Get(FindBookingLimits request) { ... }
}
错误处理和验证
有关如何添加验证你要么只选择信息抛出C#的异常并应用自己的自定义它们,否则你必须使用该选项的内置流利的验证 ,但你并不需要将其注入到你的服务如你就可以将它们全部用在你的APPHOST,例如一行:
container.RegisterValidators(typeof(CreateBookingValidator).Assembly);
验证器是无接触和无侵入性的意义,你可以使用一个分层的方法添加它们,并保持它们而不需要修改服务实现或DTO类。 因为它们需要一个额外的类我只会用他们与副作用(如POST / PUT)为GET的操作往往有最小的验证和抛出C#异常需要较少的锅炉板。 所以,你可以有一个验证的一个例子是首先建立在预订时:
public class CreateBookingValidator : AbstractValidator<CreateBooking>
{
public CreateBookingValidator()
{
RuleFor(r => r.StartDate).NotEmpty();
RuleFor(r => r.ShiftId).GreaterThan(0);
RuleFor(r => r.Limit).GreaterThan(0);
}
}
根据不同的使用情况,而不必单独CreateBooking
和UpdateBooking
DTO的我将重新使用相同的请求DTO两个在这种情况下,我会说出StoreBooking
。
该“效应初探DTO的”似乎是不必要的,因为ResponseStatus属性不再需要。 。 虽然,我认为你可能还需要一个匹配响应类,如果你使用SOAP。 如果删除响应的DTO你不再需要推BookLimit到响应的对象。 此外,ServiceStack的TranslateTo()可以很好的帮助。
下面是我怎么会尽量简化你贴什么...因人而异。
作出BookingLimit一个DTO - 这将是BookingLimit的所有其他系的代表性。
public class BookingLimitDto
{
public int Id { get; set; }
public int ShiftId { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public int Limit { get; set; }
}
请求和DTO的是很重要的
[Route("/bookinglimit", "GET")]
[Authenticate]
public class GetBookingLimit : IReturn<BookingLimitDto>
{
public int Id { get; set; }
}
[Route("/bookinglimits", "GET")]
[Authenticate]
public class GetBookingLimits : IReturn<List<BookingLimitDto>>
{
public DateTime Date { get; set; }
}
不再返回效应初探对象......只是BookingLimitDto
public class BookingLimitService : AppServiceBase
{
public IValidator AddBookingLimitValidator { get; set; }
public BookingLimitDto Get(GetBookingLimit request)
{
BookingLimitDto bookingLimit = new BookingLimitRepository().Get(request.Id);
//May need to bookingLimit.TranslateTo<BookingLimitDto>() if BookingLimitRepository can't return BookingLimitDto
return bookingLimit;
}
public List<BookingLimitDto> Get(GetBookingLimits request)
{
List<BookingLimitDto> bookingLimits = new BookingLimitRepository().GetByRestaurantId(base.UserSession.RestaurantId);
return
bookingLimits.Where(
l =>
l.EndDate.ToShortDateString() == request.Date.ToShortDateString() &&
l.StartDate.ToShortDateString() == request.Date.ToShortDateString()).ToList();
}
}