Tất cả những câu trả lời rất tốt. Tôi yêu sự đơn giản của lớp học của ông Meacham là GenericHandlerRouteHandler<T>
. Đó là một ý tưởng tuyệt vời để loại bỏ một tham chiếu không cần thiết đến một đường dẫn ảo nếu bạn biết lớp cụ thể HttpHandler
. Tuy nhiên, lớp GenericHandlerRoute<T>
là không cần thiết. Lớp hiện tại Route
có nguồn gốc từ RouteBase
đã xử lý tất cả sự phức tạp của kết hợp tuyến đường, thông số, v.v., vì vậy chúng tôi chỉ có thể sử dụng nó cùng với GenericHandlerRouteHandler<T>
.
Dưới đây là phiên bản được kết hợp với ví dụ sử dụng thực tế bao gồm các thông số tuyến đường.
Đầu tiên là các trình xử lý tuyến đường. Có hai được bao gồm, ở đây - cả hai đều có cùng tên lớp, nhưng một cái chung chung và sử dụng thông tin kiểu để tạo một cá thể của HttpHandler
cụ thể như trong cách sử dụng của Mr. Meacham, và sử dụng đường dẫn ảo và BuildManager
để tạo một phiên bản của HttpHandler
thích hợp như trong cách sử dụng của shellscape. Tin tốt là .NET cho phép cả hai sống cạnh nhau tốt, vì vậy chúng ta chỉ có thể sử dụng bất cứ cái gì chúng ta muốn và có thể chuyển đổi giữa chúng như chúng ta mong muốn.
using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
public class HttpHandlerRouteHandler<T> : IRouteHandler where T : IHttpHandler, new() {
public HttpHandlerRouteHandler() { }
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new T();
}
}
public class HttpHandlerRouteHandler : IRouteHandler {
private string _VirtualPath;
public HttpHandlerRouteHandler(string virtualPath) {
this._VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return (IHttpHandler) BuildManager.CreateInstanceFromVirtualPath(this._VirtualPath, typeof(IHttpHandler));
}
}
Giả sử rằng chúng tôi tạo ra một HttpHandler
mà dòng văn bản cho người sử dụng từ một nguồn bên ngoài thư mục ảo của chúng tôi, thậm chí có thể từ một cơ sở dữ liệu, và rằng chúng ta muốn đánh lừa trình duyệt của người dùng tin rằng chúng tôi đang trực tiếp phục vụ tệp cụ thể thay vì chỉ cung cấp tải xuống (nghĩa là cho phép trình cắm của trình duyệt xử lý tệp thay vì buộc người dùng lưu tệp). HttpHandler
có thể mong đợi một id tài liệu để xác định vị trí tài liệu cần cung cấp và có thể mong đợi tên tệp để cung cấp cho trình duyệt - một tên có thể khác với tên tệp được sử dụng trên máy chủ.
Sau đây cho thấy việc đăng ký các tuyến đường sử dụng để thực hiện điều này với một DocumentHandler
HttpHandler
:
routes.Add("Document", new Route("document/{documentId}/{*fileName}", new HttpHandlerRouteHandler<DocumentHandler>()));
tôi đã sử dụng {*fileName}
thay vì chỉ {fileName}
để cho phép các tham số fileName
để hoạt động như một nhận tất cả tùy chọn tham số.
Để tạo một URL cho một tập tin phục vụ bởi HttpHandler
này, chúng ta có thể thêm các phương pháp tĩnh sau để một lớp học nơi một phương pháp như vậy sẽ phù hợp, ví dụ như trong lớp HttpHandler
, bản thân:
public static string GetFileUrl(int documentId, string fileName) {
string mimeType = null;
try { mimeType = MimeMap.GetMimeType(Path.GetExtension(fileName)); }
catch { }
RouteValueDictionary documentRouteParameters = new RouteValueDictionary { { "documentId", documentId.ToString(CultureInfo.InvariantCulture) }
, { "fileName", DocumentHandler.IsPassThruMimeType(mimeType) ? fileName : string.Empty } };
return RouteTable.Routes.GetVirtualPath(null, "Document", documentRouteParameters).VirtualPath;
}
Tôi bỏ qua các định nghĩa của MimeMap
và và IsPassThruMimeType
để giữ ví dụ này đơn giản. Nhưng chúng nhằm xác định xem các loại tệp cụ thể có nên cung cấp tên tệp của họ trực tiếp trong URL hay không trong tiêu đề HTTP Content-Disposition
. Một số phần mở rộng tập tin có thể bị chặn bởi IIS hoặc Quét URL, hoặc có thể làm cho mã thực thi có thể gây ra sự cố cho người dùng - đặc biệt nếu nguồn của tệp là một người dùng độc hại khác. Bạn có thể thay thế logic này bằng một số logic lọc khác, hoặc bỏ qua logic đó hoàn toàn nếu bạn không bị loại rủi ro này.
Vì trong ví dụ cụ thể này tên tệp có thể bị bỏ qua khỏi URL, sau đó, hiển nhiên, chúng tôi phải truy xuất tên tệp từ đâu đó. Trong ví dụ cụ thể này, tên tệp có thể được truy xuất bằng cách thực hiện tra cứu bằng id tài liệu và bao gồm tên tệp trong URL chỉ nhằm mục đích cải thiện trải nghiệm của người dùng. Vì vậy, DocumentHandler
HttpHandler
có thể xác định xem tên tệp có được cung cấp trong URL hay không và nếu không, thì có thể chỉ cần thêm tiêu đề HTTP Content-Disposition
vào phản hồi.
Ở về chủ đề, một phần quan trọng của khối mã trên là việc sử dụng RouteTable.Routes.GetVirtualPath()
và các thông số định tuyến để tạo ra một URL từ đối tượng Route
mà chúng tôi tạo ra trong quá trình đăng ký lộ trình.
Đây là phiên bản được tưới nước của lớp DocumentHandler
HttpHandler
(bị bỏ qua nhiều vì lợi ích của sự rõ ràng). Bạn có thể thấy rằng lớp này sử dụng các tham số tuyến đường để lấy id tài liệu và tên tệp khi có thể; nếu không, nó sẽ cố gắng truy lục id tài liệu từ tham số chuỗi truy vấn (tức là, giả định rằng định tuyến không được sử dụng).
public void ProcessRequest(HttpContext context) {
try {
context.Response.Clear();
// Get the requested document ID from routing data, if routed. Otherwise, use the query string.
bool isRouted = false;
int? documentId = null;
string fileName = null;
RequestContext requestContext = context.Request.RequestContext;
if (requestContext != null && requestContext.RouteData != null) {
documentId = Utility.ParseInt32(requestContext.RouteData.Values["documentId"] as string);
fileName = Utility.Trim(requestContext.RouteData.Values["fileName"] as string);
isRouted = documentId.HasValue;
}
// Try the query string if no documentId obtained from route parameters.
if (!isRouted) {
documentId = Utility.ParseInt32(context.Request.QueryString["id"]);
fileName = null;
}
if (!documentId.HasValue) { // Bad request
// Response logic for bad request omitted for sake of simplicity
return;
}
DocumentDetails documentInfo = ... // Details of loading this information omitted
if (context.Response.IsClientConnected) {
string fileExtension = string.Empty;
try { fileExtension = Path.GetExtension(fileName ?? documentInfo.FileName); } // Use file name provided in URL, if provided, to get the extension.
catch { }
// Transmit the file to the client.
FileInfo file = new FileInfo(documentInfo.StoragePath);
using (FileStream fileStream = file.OpenRead()) {
// If the file size exceeds the threshold specified in the system settings, then we will send the file to the client in chunks.
bool mustChunk = fileStream.Length > Math.Max(SystemSettings.Default.MaxBufferedDownloadSize * 1024, DocumentHandler.SecondaryBufferSize);
// WARNING! Do not ever set the following property to false!
// Doing so causes each chunk sent by IIS to be of the same size,
// even if a chunk you are writing, such as the final chunk, may
// be shorter than the rest, causing extra bytes to be written to
// the stream.
context.Response.BufferOutput = true;
context.Response.ContentType = MimeMap.GetMimeType(fileExtension);
context.Response.AddHeader("Content-Length", fileStream.Length.ToString(CultureInfo.InvariantCulture));
if ( !isRouted
|| string.IsNullOrWhiteSpace(fileName)
|| string.IsNullOrWhiteSpace(fileExtension)) { // If routed and a file name was provided in the route, then the URL will appear to point directly to a file, and no file name header is needed; otherwise, add the header.
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", HttpUtility.UrlEncode(documentInfo.FileName)));
}
int bufferSize = DocumentHandler.SecondaryBufferSize;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, bufferSize)) > 0 && context.Response.IsClientConnected) {
context.Response.OutputStream.Write(buffer, 0, bytesRead);
if (mustChunk) {
context.Response.Flush();
}
}
}
}
}
catch (Exception e) {
// Error handling omitted from this example.
}
}
Ví dụ này sử dụng một số lớp tùy chỉnh bổ sung, chẳng hạn như lớp Utility
để đơn giản hóa một số nhiệm vụ tầm thường. Nhưng hy vọng bạn có thể vượt qua điều đó. Phần thực sự quan trọng trong lớp này liên quan đến chủ đề hiện tại, tất nhiên, là việc thu hồi các tham số tuyến đường từ context.Request.RequestContext.RouteData
. Nhưng tôi đã thấy một số bài đăng ở nơi khác yêu cầu cách truyền các tệp lớn bằng cách sử dụng HttpHandler
mà không cần nhai bộ nhớ máy chủ, do đó, có vẻ như một ý tưởng hay là kết hợp các ví dụ.
Đây là một câu trả lời thực sự xuất sắc và chi tiết. – tallseth