2012-10-22 25 views
22

Mục tiêu của tôi là xác thực yêu cầu API Web bằng cách sử dụng AuthorizationFilter hoặc DelegatingHandler. Tôi muốn tìm mã nhận dạng khách hàng và mã xác thực ở một vài nơi, bao gồm cả phần thân yêu cầu. Lúc đầu, có vẻ như điều này sẽ dễ dàng, tôi có thể làm điều gì đó như thế nàyTại sao nội dung yêu cầu API Web đọc một lần?

var task = _message.Content.ReadAsAsync<Credentials>(); 

task.Wait(); 

if (task.Result != null) 
{ 
    // check if credentials are valid 
} 

Vấn đề là chỉ có thể đọc HttpContent một lần. Nếu tôi làm điều này trong một Trình xử lý hoặc Bộ lọc thì nội dung đó không có sẵn cho tôi trong phương thức hành động của tôi. Tôi tìm thấy một vài câu trả lời ở đây trên StackOverflow, như thế này: Read HttpContent in WebApi controller giải thích rằng đó là cố ý theo cách này, nhưng họ không nói TẠI SAO. Điều này có vẻ giống như một giới hạn khá nghiêm trọng ngăn tôi sử dụng bất kỳ mã phân tích nội dung API Web mát nào trong Bộ lọc hoặc Trình xử lý.

Đây có phải là hạn chế kỹ thuật không? Có phải nó đang cố gắng giữ cho tôi không thực hiện một điều RẤT NHIỀU (tm) mà tôi không thấy?

POSTMORTEM:

Tôi đã xem xét nguồn như Filip được đề xuất. ReadAsStreamAsync trả về luồng nội bộ và không có gì ngăn bạn gọi Seek nếu luồng hỗ trợ nó. Trong các thử nghiệm của tôi nếu tôi gọi là ReadAsAsync thì thực hiện điều này:

message.Content.ReadAsStreamAsync().ContinueWith(t => t.Result.Seek(0, SeekOrigin.Begin)).Wait(); 

Quy trình ràng buộc mô hình tự động sẽ hoạt động tốt khi nó nhấn phương pháp hành động của tôi. Tôi không sử dụng này, mặc dù tôi đã lựa chọn một cái gì đó trực tiếp hơn:

var buffer = new MemoryStream(_message.Content.ReadAsByteArrayAsync().WaitFor()); 
var formatters = _message.GetConfiguration().Formatters; 
var reader = formatters.FindReader(typeof(Credentials), _message.Content.Headers.ContentType); 
var credentials = reader.ReadFromStreamAsync(typeof(Credentials), buffer, _message.Content, null).WaitFor() as Credentials; 

Với một phương pháp mở rộng (tôi đang ở trong .NET 4.0 với không có từ khóa đang chờ đợi)

public static class TaskExtensions 
{ 
    public static T WaitFor<T>(this Task<T> task) 
    { 
     task.Wait(); 
     if (task.IsCanceled) { throw new ApplicationException(); } 
     if (task.IsFaulted) { throw task.Exception; } 
     return task.Result; 
    } 
} 

Một bắt ngoái, HttpContent có mã hóa cứng tối đa kích thước bộ đệm:

internal const int DefaultMaxBufferSize = 65536; 

vì vậy, nếu nội dung của bạn sẽ là lớn hơn mà bạn sẽ cần phải tự gọi LoadIntoBufferAsync với kích thước lớn hơn trước khi bạn cố gắng gọi ReadAsByteArrayAsync.

+3

Mặc dù bạn đã tìm được cách để đọc nội dung nhiều lần, các bạn có biết tại sao bạn không thể đọc nó hai lần không? Bởi vì tôi thực sự kéo tóc của tôi để tìm hiểu lý do tại sao hạn chế này là có ở nơi đầu tiên. –

Trả lời

21

Câu trả lời bạn đã chỉ ra không hoàn toàn chính xác.

Bạn luôn có thể đọc dưới dạng chuỗi (ReadAsStringAsync) hoặc dưới dạng byte [] (ReadAsByteArrayAsync) khi chúng đệm yêu cầu trong nội bộ.

Ví dụ xử lý giả dưới đây:

public class MyHandler : DelegatingHandler 
{ 
    protected override async System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) 
    { 
     var body = await request.Content.ReadAsStringAsync(); 
     //deserialize from string i.e. using JSON.NET 

     return base.SendAsync(request, cancellationToken); 
    } 
} 

Cùng áp dụng cho byte []:

public class MessageHandler : DelegatingHandler 
{ 
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     var requestMessage = await request.Content.ReadAsByteArrayAsync(); 
     //do something with requestMessage - but you will have to deserialize from byte[] 

     return base.SendAsync(request, cancellationToken); 
    } 
} 

Mỗi sẽ không làm cho nội dung được đưa lên được null khi nó đạt đến bộ điều khiển.

+0

Cảm ơn bạn đã phản hồi! Tôi đã nhận thấy điều này trong khi tôi đang làm thử nghiệm của mình. Tôi có hai lo ngại về điều này. 1. Tôi cũng có thể nhận được tại các dòng trực tiếp bằng cách sử dụng HttpContext. Tôi đã thực sự hy vọng tận dụng khả năng trong Web API để phù hợp với trình định dạng cho loại phương tiện truyền thông, để nếu cơ thể ở dạng XML, JSON hoặc chỉ là một biểu mẫu chuẩn, tôi sẽ không phải hành xử khác. 2. Thực tế là các phương pháp "ReadAsAsync" này có các tác dụng phụ khác nhau khiến tôi lo lắng. Sự khác biệt không được ghi lại mà tôi có thể tìm thấy. Tôi có thể dựa vào nó? – MichaC

+0

Các hành vi khác nhau của HttpContent không phải là một tính năng của Web API nhưng của System.Net.Http được vận chuyển sớm hơn nhiều - tôi nghĩ rằng đó là lý do tại sao không ai nghĩ rằng một cách rõ ràng tài liệu nó cho Web API. Những gì đọc như chuỗi/byte [] hiện dưới mui xe là gọi LoadIntoBufferAsync http://msdn.microsoft.com/en-us/library/hh138085(v=vs.110), tôi cũng khuyên bạn nên kiểm tra mã nguồn cho HttpContent. Trong các trường hợp khác, như sử dụng MediaTypeFormatters, bạn sử dụng luồng một cách rõ ràng và vì bạn di chuyển vị trí của luồng về phía trước (và nó không thể tua lại) trình kết nối mô hình sẽ luôn nhận được null từ nó. –

+0

Và có bạn có thể dựa vào nó, nó không phải là một hack, hành vi này là như thiết kế :-) –

2

Tôi sẽ đặt clientId và khóa xác thực trong tiêu đề thay vì nội dung.

Bằng cách này, bạn có thể đọc chúng bao nhiêu lần tùy thích!

+0

Cảm ơn bạn đã đề xuất! Tôi đã xem xét các tiêu đề, nhưng có vẻ như tôi sẽ phải tạo một số tham số tiêu đề HTTP không chuẩn cho mục đích đó và tôi không chắc chắn nó sẽ dễ dàng như thế nào đối với các API khách hàng http khác nhau trên các nền tảng khác nhau để hỗ trợ điều đó. Chắc chắn tôi có thể loại bỏ lựa chọn đó quá nhanh. – MichaC

+0

Tôi đồng ý. Nếu một cái gì đó nhỏ của nó, một tiêu đề tùy chỉnh là tốt. Nếu đó là chuỗi "whacko" .. bạn cũng có thể tạo chuỗi base64. Một lần nữa ... nếu nó nhỏ. – granadaCoder

Các vấn đề liên quan