Browse Source

断点续传增强

懒得勤快 6 years ago
parent
commit
dd5092fe4c

+ 0 - 1
Masuit.Tools.UnitTest/Masuit.Tools.UnitTest.csproj

@@ -111,7 +111,6 @@
     <Compile Include="HtmlToolsTest.cs" />
     <Compile Include="LinqExtensionTest.cs" />
     <Compile Include="Mvc\BaseTests.cs" />
-    <Compile Include="Mvc\MimeMapperTests.cs" />
     <Compile Include="Mvc\Mocks\MockHttpFilesCollection.cs" />
     <Compile Include="Mvc\Mocks\MockHttpPostedFileBase.cs" />
     <Compile Include="Mvc\Mocks\MockHttpRequest.cs" />

+ 0 - 2
Masuit.Tools.UnitTest/Mvc/BaseTests.cs

@@ -6,8 +6,6 @@ using System.IO;
 using System.Web;
 using System.Web.Mvc;
 using System.Web.Routing;
-using ModelBindingContext = System.Web.ModelBinding.ModelBindingContext;
-using ModelMetadataProviders = System.Web.ModelBinding.ModelMetadataProviders;
 
 namespace Masuit.Tools.UnitTest.Mvc
 {

+ 2 - 2
Masuit.Tools.UnitTest/Mvc/Mocks/MockResumeFileResult.cs

@@ -5,11 +5,11 @@ namespace Masuit.Tools.UnitTest.Mvc.Mocks
 {
     public class MockResumeFileResult : ResumeFileResult
     {
-        public MockResumeFileResult(string fileName, string contentType, HttpRequestBase request) : base(fileName, contentType, request)
+        public MockResumeFileResult(string fileName, HttpRequestBase request) : base(fileName, request)
         {
         }
 
-        public MockResumeFileResult(string fileName, string contentType, HttpRequestBase request, string downloadFileName) : base(fileName, contentType, request, downloadFileName)
+        public MockResumeFileResult(string fileName, HttpRequestBase request, string downloadFileName) : base(fileName,request, downloadFileName)
         {
         }
 

+ 44 - 46
Masuit.Tools.UnitTest/Mvc/ResumeFileResultTests.cs

@@ -1,5 +1,4 @@
 using Masuit.Tools.Mvc;
-using Masuit.Tools.UnitTest.Mvc;
 using Masuit.Tools.UnitTest.Mvc.Mocks;
 using NUnit.Framework;
 using System;
@@ -7,7 +6,7 @@ using System.IO;
 using System.Net;
 using System.Threading;
 
-namespace Mvc.Stream.Tests
+namespace Masuit.Tools.UnitTest.Mvc
 {
     namespace Adstream.Web.Common.test
     {
@@ -16,7 +15,6 @@ namespace Mvc.Stream.Tests
         {
             private FileInfo _file;
             private FileInfo _file2;
-            private string ContentType = "video/mp4";
 
             [SetUp]
             public void Setup()
@@ -63,7 +61,7 @@ namespace Mvc.Stream.Tests
             {
                 Request.Headers[HttpHeaders.IfNoneMatch] = null;
                 Request.Headers[HttpHeaders.IfModifiedSince] = null;
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
@@ -72,7 +70,7 @@ namespace Mvc.Stream.Tests
                 var etag = "invalid etag";
                 Request.Headers[HttpHeaders.IfNoneMatch] = etag;
                 Request.Headers[HttpHeaders.IfModifiedSince] = null;
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
@@ -80,7 +78,7 @@ namespace Mvc.Stream.Tests
             {
                 var etag = ResumeFileResult.Util.Etag(_file);
                 Request.Headers[HttpHeaders.IfNoneMatch] = etag;
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
@@ -88,21 +86,21 @@ namespace Mvc.Stream.Tests
             {
                 var etag = "*";
                 Request.Headers[HttpHeaders.IfNoneMatch] = etag;
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
             public void IsNotModified_Is_False_If_Etag_Is_Empty_And_IfModifiedSince_Is_Invalid()
             {
                 Request.Headers[HttpHeaders.IfModifiedSince] = ResumeFileResult.Util.FormatDate(DateTime.Now);
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
             public void IsNotModified_Is_False_If_Etag_Is_Empty_And_IfModifiedSince_Is_LastFileWriteTime()
             {
                 Request.Headers[HttpHeaders.IfModifiedSince] = ResumeFileResult.Util.FormatDate(_file.LastWriteTime);
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsNotModified());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsNotModified());
             }
 
             [Test]
@@ -110,118 +108,118 @@ namespace Mvc.Stream.Tests
             {
                 Request.Headers[HttpHeaders.IfMatch] = null;
                 Request.Headers[HttpHeaders.IfUnmodifiedSince] = null;
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsPreconditionFailedTest_Is_IsTrue_If_ifMatch_Doesnot_Match_Etag_Of_The_File()
             {
                 Request.Headers[HttpHeaders.IfMatch] = "incorrect";
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsPreconditionFailedTest_Is_IsFalse_If_ifMatch_Matches_Etag_Of_The_File()
             {
                 Request.Headers[HttpHeaders.IfMatch] = ResumeFileResult.Util.Etag(_file);
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsPreconditionFailedTest_Is_IsFalse_If_ifMatch_Equals_Star()
             {
                 Request.Headers[HttpHeaders.IfMatch] = "*";
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsPreconditionFailedTest_Is_IsTrue_If_ifUnmodifiedSince_Doesnot_Equal_FileLastWriteTime()
             {
                 Request.Headers[HttpHeaders.IfUnmodifiedSince] = ResumeFileResult.Util.FormatDate(DateTime.Now);
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsPreconditionFailedTest_Is_IsFalse_If_ifUnmodifiedSince_Equals_FileLastWriteTime()
             {
                 Request.Headers[HttpHeaders.IfUnmodifiedSince] = ResumeFileResult.Util.FormatDate(_file.LastWriteTime);
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsPreconditionFailed());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsPreconditionFailed());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_True_If_Range_Header_Has_Invalid_Format()
             {
                 Request.Headers[HttpHeaders.Range] = "blah";
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_True_If_Start_Greater_Than_End()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=100-0";
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_True_If_End_Equals_Total_File_Size()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-" + _file.Length;
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_True_If_End_Greater_Than_Total_File_Size()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-" + _file.Length + 10;
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_False_If_Range_Header_Is_Null()
             {
                 Request.Headers[HttpHeaders.Range] = null;
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_False_If_Range_Has_StartsWith_Format()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-";
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_False_If_Range_Has_LastXbytes_Format()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=-100";
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void IsRangeNotSatisfiable_Is_False_If_Range_Ends_With_Last_Byte_Position()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=100-" + (_file.Length - 1);
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).IsRangeNotSatisfiable());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).IsRangeNotSatisfiable());
             }
 
             [Test]
             public void SendRange_Is_False_If_Range_And_ifRange_Headers_Are_Null()
             {
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).SendRange());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).SendRange());
             }
 
             [Test]
             public void SendRange_Is_False_If_Range_Is_Null_And_ifRange_Is_Correct()
             {
                 Request.Headers[HttpHeaders.IfRange] = ResumeFileResult.Util.Etag(_file);
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).SendRange());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).SendRange());
             }
 
             [Test]
             public void SendRange_Is_True_If_Range_Is_Correct_And_ifRange_Is_Null()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-100";
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).SendRange());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).SendRange());
             }
 
             [Test]
@@ -229,7 +227,7 @@ namespace Mvc.Stream.Tests
             {
                 Request.Headers[HttpHeaders.IfRange] = ResumeFileResult.Util.Etag(_file);
                 Request.Headers[HttpHeaders.Range] = "bytes=0-100";
-                Assert.IsTrue(new MockResumeFileResult(_file.FullName, ContentType, Request).SendRange());
+                Assert.IsTrue(new MockResumeFileResult(_file.FullName, Request).SendRange());
             }
 
             [Test]
@@ -237,14 +235,14 @@ namespace Mvc.Stream.Tests
             {
                 Request.Headers[HttpHeaders.IfRange] = "incorrect etag";
                 Request.Headers[HttpHeaders.Range] = "bytes=0-100";
-                Assert.IsFalse(new MockResumeFileResult(_file.FullName, ContentType, Request).SendRange());
+                Assert.IsFalse(new MockResumeFileResult(_file.FullName, Request).SendRange());
             }
 
             [Test]
             public void HeadersTest_Should_Not_Send_File_If_File_Has_Not_Been_Changed()
             {
                 Request.Headers[HttpHeaders.IfNoneMatch] = ResumeFileResult.Util.Etag(_file);
-                var result = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var result = new MockResumeFileResult(_file.FullName, Request);
                 Assert.IsTrue(result.IsNotModified());
                 result.WriteFileTest(Response);
                 Assert.AreEqual((int)HttpStatusCode.NotModified, Response.StatusCode);
@@ -259,7 +257,7 @@ namespace Mvc.Stream.Tests
             public void HeadersTest_Should_Not_Send_File_IfPreconditionFailed()
             {
                 Request.Headers[HttpHeaders.IfMatch] = "invalid";
-                var result = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var result = new MockResumeFileResult(_file.FullName, Request);
                 Assert.IsTrue(result.IsPreconditionFailed());
 
                 result.WriteFileTest(Response);
@@ -275,7 +273,7 @@ namespace Mvc.Stream.Tests
             public void HeadersTest_Should_Not_Send_File_Is_RangeNotSatisfiable()
             {
                 Request.Headers[HttpHeaders.Range] = "invalid";
-                var result = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var result = new MockResumeFileResult(_file.FullName, Request);
                 Assert.IsTrue(result.IsRangeNotSatisfiable());
                 result.WriteFileTest(Response);
                 Assert.AreEqual((int)HttpStatusCode.RequestedRangeNotSatisfiable, Response.StatusCode);
@@ -289,7 +287,7 @@ namespace Mvc.Stream.Tests
             [Test]
             public void HeadersTest_Should_Send_File_If_All_Headers_Are_Null()
             {
-                var result = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var result = new MockResumeFileResult(_file.FullName, Request);
                 result.WriteFileTest(Response);
                 Assert.AreEqual((int)HttpStatusCode.OK, Response.StatusCode);
                 Assert.IsNotNull(Response.Headers[HttpHeaders.Etag]);
@@ -383,7 +381,7 @@ namespace Mvc.Stream.Tests
             public void TransmissionRange_From_0_To_0()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-0";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 Assert.AreEqual(1, Response.OutputStream.Length);
                 AssertBytes(_file, Response.OutputStream, 0, 1);
@@ -393,7 +391,7 @@ namespace Mvc.Stream.Tests
             public void TransmissionRange_From_1_To_100()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=1-100";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 Assert.AreEqual(100, Response.OutputStream.Length);
                 AssertBytes(_file, Response.OutputStream, 1, 100);
@@ -403,7 +401,7 @@ namespace Mvc.Stream.Tests
             public void TransmissionRange_From_101_To_theEnd()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=101-";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 Assert.AreEqual(_file.Length - 101, Response.OutputStream.Length);
                 AssertBytes(_file, Response.OutputStream, 101, (int)_file.Length);
@@ -413,7 +411,7 @@ namespace Mvc.Stream.Tests
             public void TransmissionRange_WholeFile_WithRangeHeader()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 Assert.AreEqual(_file.Length, Response.OutputStream.Length);
                 AssertBytes(_file, Response.OutputStream, 0, (int)_file.Length);
@@ -423,7 +421,7 @@ namespace Mvc.Stream.Tests
             public void TransmissionRange_WholeFile_WithoutRangeHeader()
             {
                 Request.Headers[HttpHeaders.Range] = null;
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 Assert.AreEqual(_file.Length, Response.OutputStream.Length);
                 AssertBytes(_file, Response.OutputStream, 0, (int)_file.Length);
@@ -433,7 +431,7 @@ namespace Mvc.Stream.Tests
             public void ShouldSend206If_Range_HeaderExists()
             {
                 Request.Headers[HttpHeaders.Range] = "bytes=0-";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
                 Assert.AreEqual(206, Response.StatusCode);
             }
 
@@ -441,7 +439,7 @@ namespace Mvc.Stream.Tests
             public void ShouldSend200If_Range_HeaderDoesNotExist()
             {
                 Request.Headers[HttpHeaders.Range] = null;
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
                 Assert.AreEqual(200, Response.StatusCode);
             }
 
@@ -449,7 +447,7 @@ namespace Mvc.Stream.Tests
             public void IfRangeHeader_Should_Be_Ignored_If_ItNotEquals_Etag()
             {
                 Request.Headers[HttpHeaders.IfRange] = "ifRange fake header";
-                var mock = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var mock = new MockResumeFileResult(_file.FullName, Request);
                 mock.WriteFileTest(Response);
 
                 Assert.AreNotEqual(ResumeFileResult.Util.Etag(_file), Request.Headers[HttpHeaders.IfRange]);
@@ -461,7 +459,7 @@ namespace Mvc.Stream.Tests
             {
                 var etag = ResumeFileResult.Util.Etag(_file);
                 Request.Headers[HttpHeaders.IfRange] = etag;
-                var mock = new MockResumeFileResult(_file.FullName, ContentType, Request);
+                var mock = new MockResumeFileResult(_file.FullName, Request);
                 mock.WriteFileTest(Response);
                 Assert.AreEqual(Response.Headers[HttpHeaders.Etag], etag);
                 Assert.AreEqual(200, Response.StatusCode);
@@ -473,7 +471,7 @@ namespace Mvc.Stream.Tests
                 var etag = ResumeFileResult.Util.Etag(_file);
                 Request.Headers[HttpHeaders.IfRange] = etag;
                 Request.Headers[HttpHeaders.Range] = "bytes=0-";
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
                 Assert.AreEqual(Response.Headers[HttpHeaders.Etag], etag);
                 Assert.IsNotNull(Response.Headers[HttpHeaders.ContentRange]);
                 Assert.AreEqual(206, Response.StatusCode);
@@ -482,22 +480,22 @@ namespace Mvc.Stream.Tests
             [Test]
             public void It_Should_Attach_Content_Disposition_If_There_Is_Download_File_Name()
             {
-                new MockResumeFileResult(_file.FullName, ContentType, Request, "test.name").WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request, "test.name").WriteFileTest(Response);
                 Assert.IsNotNull(Response.Headers[HttpHeaders.ContentDisposition]);
             }
 
-            private System.IO.Stream GetResponseStream(string range)
+            private Stream GetResponseStream(string range)
             {
                 Response.ClearTestResponse();
                 Response.StatusCode = 500;
 
                 Request.Headers[HttpHeaders.Range] = range;
-                new MockResumeFileResult(_file.FullName, ContentType, Request).WriteFileTest(Response);
+                new MockResumeFileResult(_file.FullName, Request).WriteFileTest(Response);
 
                 return Response.OutputStream;
             }
 
-            private void AssertBytes(FileInfo file, System.IO.Stream responseStream, int from, int to)
+            private void AssertBytes(FileInfo file, Stream responseStream, int from, int to)
             {
                 using (var fileStream = file.OpenRead())
                 {

+ 8 - 2
Masuit.Tools/Masuit.Tools.csproj

@@ -130,14 +130,20 @@
     <Compile Include="Models\IspInfo.cs" />
     <Compile Include="Models\PhysicsAddress.cs" />
     <Compile Include="Models\TaobaoIP.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeActionResultBase.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeFileContentResult.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeFilePathResult.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeFileStreamResult.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeRequest.cs" />
     <Compile Include="Mvc\ControllerExtension.cs" />
     <Compile Include="Mvc\HttpHeaders.cs" />
-    <Compile Include="Mvc\Internal\Range.cs" />
+    <Compile Include="Mvc\Internal\ByteRange.cs" />
+    <Compile Include="Mvc\Mime\ContentType.cs" />
+    <Compile Include="Mvc\ActionResults\ResumeFileResult.cs" />
     <Compile Include="Mvc\Mime\DefaultMimeItems.cs" />
     <Compile Include="Mvc\Mime\IMimeMapper.cs" />
     <Compile Include="Mvc\Mime\MimeMapper.cs" />
     <Compile Include="Mvc\Mime\MimeMappingItem.cs" />
-    <Compile Include="Mvc\ResumeFileResult.cs" />
     <Compile Include="Net\CacheHelper.cs" />
     <Compile Include="Net\CookieHelper.cs" />
     <Compile Include="Net\FtpClient.cs" />

+ 261 - 0
Masuit.Tools/Mvc/ActionResults/ResumeActionResultBase.cs

@@ -0,0 +1,261 @@
+using Masuit.Tools.Mvc.Mime;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Web;
+using System.Web.Mvc;
+
+namespace Masuit.Tools.Mvc.ActionResults
+{
+    public abstract class ResumeActionResultBase : ActionResult
+    {
+        [DefaultValue("<q1w2e3r4t5y6u7i8o9p0>")]
+        public string MultipartBoundary { get; set; }
+
+        public string ContentType { get; private set; }
+        private readonly string _fileName;
+        public DateTimeOffset? LastModified { get; set; }
+        public string EntityTag { get; set; }
+        public Stream FileContents { get; set; }
+
+        protected ResumeActionResultBase(string fileName)
+        {
+            MimeMapper mimeMapper = new MimeMapper();
+            string contentType = mimeMapper.GetMimeFromPath(fileName);
+            if (string.IsNullOrEmpty(contentType))
+            {
+                contentType = MimeMapper.DefaultMime;
+            }
+            _fileName = fileName;
+            ContentType = contentType;
+        }
+
+        public override void ExecuteResult(ControllerContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException("context");
+            }
+
+            ResumeRequest resumingRequest = new ResumeRequest(context.HttpContext, FileContents.Length)
+            {
+                FileName = _fileName
+            };
+            context.HttpContext.Response.Headers[HttpHeaders.AccessControlExposeHeaders] = HttpHeaders.ContentDisposition;
+            ExecuteResultBody(context, resumingRequest);
+        }
+
+        public virtual void ExecuteResultBody(ControllerContext context, ResumeRequest resumingRequest)
+        {
+            WriteCommonHeaders(context, resumingRequest);
+
+            if (ShouldProceedAfterEvaluatingPreconditions(context.HttpContext, resumingRequest))
+            {
+                using (FileContents)
+                {
+                    if (resumingRequest.IsRangeRequest)
+                    {
+                        WritePartialContent(context, FileContents, resumingRequest);
+                    }
+                    else
+                    {
+                        WriteFullContent(context, FileContents);
+                    }
+                }
+            }
+        }
+
+
+        protected virtual bool ShouldProceedAfterEvaluatingPreconditions(HttpContextBase context, ResumeRequest resumingRequest)
+        {
+            var request = context.Request;
+            string check;
+            DateTimeOffset preconditionDateTime;
+
+            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfRange)])))
+            {
+                if (DateTimeOffset.TryParse(check, out preconditionDateTime))
+                {
+                    if ((LastModified.Value - preconditionDateTime).TotalSeconds > 1)
+                    {
+                        resumingRequest.Ranges = null;
+                    }
+                }
+                else
+                {
+                    if (!check.Equals(EntityTag))
+                    {
+                        resumingRequest.Ranges = null;
+                    }
+                }
+            }
+
+
+            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfMatch)])))
+            {
+                IEnumerable<string> entitiesTags = check.Split(',');
+
+                if ((string.IsNullOrEmpty(EntityTag) && entitiesTags.Any()) || (!entitiesTags.Any(entity => entitiesTags.Equals(EntityTag))))
+                {
+                    context.Response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
+                    return false;
+                }
+            }
+
+            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfNoneMatch)])))
+            {
+                IEnumerable<string> entitiesTag = check.Split(',');
+                if ((!string.IsNullOrEmpty(EntityTag) && entitiesTag.Contains("*")) || (entitiesTag.Any(entity => entity.Equals(EntityTag))))
+                {
+                    if (context.Request.RequestType == "GET" || context.Request.RequestType == "HEAD")
+                    {
+                        context.Response.StatusCode = (int)HttpStatusCode.NotModified;
+                        return false;
+                    }
+                    else
+                    {
+                        context.Response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
+                        return false;
+                    }
+                }
+            }
+
+            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfUnmodifiedSince)])))
+            {
+                if (DateTimeOffset.TryParse(check, out preconditionDateTime))
+                {
+                    if (!LastModified.HasValue || ((LastModified.Value - preconditionDateTime).TotalSeconds > 0))
+                    {
+                        context.Response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
+                        return false;
+                    }
+                }
+            }
+
+
+            if (!string.IsNullOrEmpty(check = (request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderIfModifiedSince)])))
+            {
+                if (DateTimeOffset.TryParse(check, out preconditionDateTime))
+                {
+                    if (LastModified.HasValue)
+                    {
+                        if ((LastModified.Value - preconditionDateTime).TotalSeconds < 1)
+                        {
+                            context.Response.StatusCode = (int)HttpStatusCode.NotModified;
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        protected virtual void WriteCommonHeaders(ControllerContext context, ResumeRequest resumingRequest)
+        {
+            context.HttpContext.Response.ContentType = resumingRequest.IsMultipartRequest ? $"multipart/byteranges; boundary={MultipartBoundary}" : ContentType;
+
+            context.HttpContext.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderAcceptRanges), "bytes");
+
+            if (!string.IsNullOrEmpty(resumingRequest.FileName))
+            {
+                context.HttpContext.Response.AddHeader("Content-Disposition", $"inline; filename=\"{resumingRequest.FileName}\"");
+            }
+
+            if (!string.IsNullOrEmpty(EntityTag))
+            {
+                context.HttpContext.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderEtag), EntityTag);
+            }
+
+            if (LastModified.HasValue)
+            {
+                context.HttpContext.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderLastModified), LastModified.Value.ToString("R"));
+            }
+        }
+
+        public virtual void WriteFullContent(ControllerContext context, Stream fileContent)
+        {
+            context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK;
+            WriteBinaryData(context, fileContent, 0, fileContent.Length - 1);
+        }
+
+        public virtual void WritePartialContent(ControllerContext context, Stream fileContent, ResumeRequest resumingRequest)
+        {
+            var response = context.HttpContext.Response;
+            response.StatusCode = (int)HttpStatusCode.PartialContent;
+
+            if (!resumingRequest.IsMultipartRequest)
+            {
+                context.HttpContext.Response.AddHeader(HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderContentRange), $"bytes {resumingRequest.Ranges.First().Start}-{resumingRequest.Ranges.First().End}/{fileContent.Length}");
+            }
+
+            foreach (var range in resumingRequest.Ranges)
+            {
+                if (!response.IsClientConnected)
+                {
+                    return;
+                }
+
+                if (resumingRequest.IsMultipartRequest)
+                {
+                    response.Output.WriteLine($"--{MultipartBoundary}");
+                    response.Output.WriteLine($"{HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderContentType)}: {ContentType}");
+                    response.Output.WriteLine($"{HttpWorkerRequest.GetKnownResponseHeaderName(HttpWorkerRequest.HeaderContentRange)}: bytes {resumingRequest.Ranges.First().Start}-{resumingRequest.Ranges.First().End}/{fileContent.Length}");
+                    response.Output.WriteLine();
+                }
+
+                WriteBinaryData(context, fileContent, range.Start, range.End);
+
+                if (resumingRequest.IsMultipartRequest)
+                {
+                    response.Output.WriteLine();
+                }
+            }
+
+            if (resumingRequest.IsMultipartRequest)
+            {
+                response.Output.WriteLine($"--{MultipartBoundary}--");
+                response.Output.WriteLine();
+            }
+        }
+
+        private void WriteBinaryData(ControllerContext context, Stream fileContent, long startIndex, long endIndex)
+        {
+            var response = context.HttpContext.Response;
+            response.BufferOutput = false;
+            byte[] buffer = new byte[0x1000];
+            long totalToSend = endIndex - startIndex;
+            long bytesRemaining = totalToSend + 1;
+            response.AppendHeader("Content-Length", bytesRemaining.ToString());
+            fileContent.Seek(startIndex, SeekOrigin.Begin);
+
+            while (response.IsClientConnected && bytesRemaining > 0)
+            {
+                try
+                {
+                    var count = bytesRemaining <= buffer.Length ? fileContent.Read(buffer, 0, (int)bytesRemaining) : fileContent.Read(buffer, 0, buffer.Length);
+
+                    if (count == 0)
+                    {
+                        return;
+                    }
+
+                    response.OutputStream.Write(buffer, 0, count);
+                    bytesRemaining -= count;
+                }
+                catch (IndexOutOfRangeException)
+                {
+                    response.Output.Flush();
+                    return;
+                }
+                finally
+                {
+                    response.Output.Flush();
+                }
+            }
+        }
+    }
+}

+ 23 - 0
Masuit.Tools/Mvc/ActionResults/ResumeFileContentResult.cs

@@ -0,0 +1,23 @@
+using System;
+using System.IO;
+
+namespace Masuit.Tools.Mvc.ActionResults
+{
+    public class ResumeFileContentResult : ResumeActionResultBase
+    {
+        public ResumeFileContentResult(byte[] fileContents, string fileName) : base(fileName)
+        {
+            if (fileContents == null)
+            {
+                throw new ArgumentNullException("fileContents");
+            }
+
+            FileContents = new MemoryStream(fileContents);
+        }
+
+        public ResumeFileContentResult(byte[] fileContents, string fileName, string etag) : this(fileContents, fileName)
+        {
+            EntityTag = etag;
+        }
+    }
+}

+ 27 - 0
Masuit.Tools/Mvc/ActionResults/ResumeFilePathResult.cs

@@ -0,0 +1,27 @@
+using System;
+using System.IO;
+
+namespace Masuit.Tools.Mvc.ActionResults
+{
+    public class ResumeFilePathResult : ResumeActionResultBase
+    {
+        private FileInfo MediaFile { get; set; }
+
+        public ResumeFilePathResult(string fileName) : base(fileName)
+        {
+            if (string.IsNullOrEmpty(fileName))
+            {
+                throw new ArgumentException();
+            }
+
+            MediaFile = new FileInfo(fileName);
+            LastModified = MediaFile.LastWriteTime;
+            FileContents = MediaFile.OpenRead();
+        }
+
+        public ResumeFilePathResult(string fileName, string etag) : this(fileName)
+        {
+            EntityTag = etag;
+        }
+    }
+}

+ 14 - 13
Masuit.Tools/Mvc/ResumeFileResult.cs → Masuit.Tools/Mvc/ActionResults/ResumeFileResult.cs

@@ -1,4 +1,5 @@
 using Masuit.Tools.Mvc.Internal;
+using Masuit.Tools.Mvc.Mime;
 using System;
 using System.Globalization;
 using System.IO;
@@ -27,27 +28,27 @@ namespace Masuit.Tools.Mvc
         private readonly string _ifUnmodifiedSince;
         private readonly string _ifRange;
         private readonly string _etag;
-        private readonly Range _range;
+        private readonly ByteRange _byteRange;
         private readonly FileInfo _file;
         private readonly string _lastModified;
         private readonly bool _rangeRequest;
         private readonly string _downloadFileName;
 
-        public ResumeFileResult(string fileName, string contentType, HttpRequestBase request) : this(fileName, contentType, request, null)
+        public ResumeFileResult(string fileName, HttpRequestBase request) : this(fileName, request, null)
         {
         }
 
-        public ResumeFileResult(string fileName, string contentType, HttpRequestBase request, string downloadFileName) : this(fileName, contentType, request.Headers[HttpHeaders.IfNoneMatch], request.Headers[HttpHeaders.IfModifiedSince], request.Headers[HttpHeaders.IfMatch], request.Headers[HttpHeaders.IfUnmodifiedSince], request.Headers[HttpHeaders.IfRange], request.Headers[HttpHeaders.Range], downloadFileName)
+        public ResumeFileResult(string fileName, HttpRequestBase request, string downloadFileName) : this(fileName, request.Headers[HttpHeaders.IfNoneMatch], request.Headers[HttpHeaders.IfModifiedSince], request.Headers[HttpHeaders.IfMatch], request.Headers[HttpHeaders.IfUnmodifiedSince], request.Headers[HttpHeaders.IfRange], request.Headers[HttpHeaders.Range], downloadFileName)
         {
         }
 
 
-        public ResumeFileResult(string fileName, string contentType, string ifNoneMatch, string ifModifiedSince, string ifMatch, string ifUnmodifiedSince, string ifRange, string range, string downloadFileName) : base(fileName, contentType)
+        public ResumeFileResult(string fileName, string ifNoneMatch, string ifModifiedSince, string ifMatch, string ifUnmodifiedSince, string ifRange, string range, string downloadFileName) : base(fileName, new MimeMapper().GetMimeFromPath(fileName))
         {
             _file = new FileInfo(fileName);
             _lastModified = Util.FormatDate(_file.LastWriteTime);
             _rangeRequest = range != null;
-            _range = Range(range);
+            _byteRange = Range(range);
             _etag = Etag();
             _ifNoneMatch = ifNoneMatch;
             _ifModifiedSince = ifModifiedSince;
@@ -93,7 +94,7 @@ namespace Masuit.Tools.Mvc
         /// <returns></returns>
         protected long ContentLength()
         {
-            return _range.End - _range.Start + 1;
+            return _byteRange.End - _byteRange.Start + 1;
         }
 
         /// <summary>
@@ -119,7 +120,7 @@ namespace Masuit.Tools.Mvc
 
             response.AppendHeader(HttpHeaders.ContentLength, contentLength.ToString(CultureInfo.InvariantCulture));
             response.AppendHeader(HttpHeaders.AcceptRanges, "bytes");
-            response.AppendHeader(HttpHeaders.ContentRange, $"bytes {_range.Start}-{_range.End}/{_file.Length}");
+            response.AppendHeader(HttpHeaders.ContentRange, $"bytes {_byteRange.Start}-{_byteRange.End}/{_file.Length}");
             response.AppendHeader(HttpHeaders.AccessControlExposeHeaders, HttpHeaders.ContentDisposition);
 
             if (!string.IsNullOrWhiteSpace(_downloadFileName))
@@ -129,7 +130,7 @@ namespace Masuit.Tools.Mvc
 
             try
             {
-                response.TransmitFile(FileName, _range.Start, contentLength);
+                response.TransmitFile(FileName, _byteRange.Start, contentLength);
             }
             catch (Exception ex)
             {
@@ -148,7 +149,7 @@ namespace Masuit.Tools.Mvc
         /// <returns></returns>
         protected bool IsRangeNotSatisfiable()
         {
-            return _range.Start >= _file.Length || _range.Start < 0 || _range.End >= _file.Length || _range.Start > _range.End;
+            return _byteRange.Start >= _file.Length || _byteRange.Start < 0 || _byteRange.End >= _file.Length || _byteRange.Start > _byteRange.End;
         }
 
         /// <summary>
@@ -211,7 +212,7 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="range"></param>
         /// <returns></returns>
-        private Range Range(string range)
+        private ByteRange Range(string range)
         {
             var lastByte = _file.Length - 1;
 
@@ -235,20 +236,20 @@ namespace Masuit.Tools.Mvc
                             end = lastByte;
                         }
 
-                        return new Range
+                        return new ByteRange
                         {
                             Start = start,
                             End = end
                         };
                     }
                 }
-                return new Range
+                return new ByteRange
                 {
                     Start = -1,
                     End = -1
                 };
             }
-            return new Range
+            return new ByteRange
             {
                 Start = 0,
                 End = lastByte

+ 18 - 0
Masuit.Tools/Mvc/ActionResults/ResumeFileStreamResult.cs

@@ -0,0 +1,18 @@
+using System;
+using System.IO;
+
+namespace Masuit.Tools.Mvc.ActionResults
+{
+    public class ResumeFileStreamResult : ResumeActionResultBase
+    {
+        public ResumeFileStreamResult(Stream fileStream, string fileName) : base(fileName)
+        {
+            FileContents = fileStream ?? throw new ArgumentNullException("fileStream");
+        }
+
+        public ResumeFileStreamResult(Stream fileStream, string fileName, string etag) : this(fileStream, fileName)
+        {
+            EntityTag = etag;
+        }
+    }
+}

+ 69 - 0
Masuit.Tools/Mvc/ActionResults/ResumeRequest.cs

@@ -0,0 +1,69 @@
+using Masuit.Tools.Mvc.Internal;
+using System.Web;
+
+namespace Masuit.Tools.Mvc.ActionResults
+{
+    public class ResumeRequest
+    {
+        public string FileName { get; set; }
+        public string ContentType { get; set; }
+        public ByteRange[] Ranges { get; set; }
+
+        public bool IsRangeRequest => (Ranges != null && Ranges.Length > 0);
+
+        public bool IsMultipartRequest => (Ranges != null && Ranges.Length > 1);
+
+
+        public ResumeRequest(HttpContextBase context, long contentLength)
+        {
+            HttpRequestBase request = context.Request;
+            ContentType = request.ContentType;
+
+            if (!string.IsNullOrEmpty(request.FilePath))
+            {
+                FileName = VirtualPathUtility.GetFileName(request.FilePath);
+            }
+
+            ParseRequestHeaderRanges(context, contentLength);
+        }
+
+
+        protected virtual void ParseRequestHeaderRanges(HttpContextBase context, long contentSize)
+        {
+            var request = context.Request;
+            string rangeHeader = request.Headers[HttpWorkerRequest.GetKnownRequestHeaderName(HttpWorkerRequest.HeaderRange)];
+
+            if (!string.IsNullOrEmpty(rangeHeader))
+            {
+                string[] ranges = rangeHeader.Replace("bytes=", string.Empty).Split(",".ToCharArray());
+                Ranges = new ByteRange[ranges.Length];
+
+                for (int i = 0; i < ranges.Length; i++)
+                {
+                    const int start = 0, end = 1;
+
+                    string[] currentRange = ranges[i].Split("-".ToCharArray());
+
+                    if (long.TryParse(currentRange[end], out var parsedValue))
+                    {
+                        Ranges[i].End = parsedValue;
+                    }
+                    else
+                    {
+                        Ranges[i].End = contentSize - 1;
+                    }
+
+                    if (long.TryParse(currentRange[start], out parsedValue))
+                    {
+                        Ranges[i].Start = parsedValue;
+                    }
+                    else
+                    {
+                        Ranges[i].Start = contentSize - Ranges[i].End;
+                        Ranges[i].End = contentSize - 1;
+                    }
+                }
+            }
+        }
+    }
+}

+ 65 - 19
Masuit.Tools/Mvc/ControllerExtension.cs

@@ -1,4 +1,6 @@
-using System.Web.Mvc;
+using Masuit.Tools.Mvc.ActionResults;
+using System.IO;
+using System.Web.Mvc;
 
 namespace Masuit.Tools.Mvc
 {
@@ -9,11 +11,10 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="virtualPath">服务端本地文件的虚拟路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath, string contentType)
+        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath)
         {
-            return ResumeFile(controller, virtualPath, contentType, fileDownloadName: null);
+            return ResumeFile(controller, virtualPath, null);
         }
 
         /// <summary>
@@ -21,12 +22,11 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="virtualPath">服务端本地文件的虚拟路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <param name="fileDownloadName">下载的文件名</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath, string contentType, string fileDownloadName)
+        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath, string fileDownloadName)
         {
-            return ResumeFile(controller, virtualPath, contentType, fileDownloadName, etag: null);
+            return ResumeFile(controller, virtualPath, fileDownloadName, etag: null);
         }
 
         /// <summary>
@@ -34,29 +34,77 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="virtualPath">服务端本地文件的虚拟路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <param name="fileDownloadName">下载的文件名</param>
         /// <param name="etag">ETag</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath, string contentType, string fileDownloadName, string etag)
+        public static ResumeFileResult ResumeFile(this ControllerBase controller, string virtualPath, string fileDownloadName, string etag)
         {
             string physicalPath = controller.ControllerContext.HttpContext.Request.MapPath(virtualPath);
-            return new ResumeFileResult(physicalPath, contentType, controller.ControllerContext.HttpContext.Request)
+            return new ResumeFileResult(physicalPath, controller.ControllerContext.HttpContext.Request)
             {
                 FileDownloadName = fileDownloadName
             };
         }
 
+        /// <summary>
+        /// 可断点续传和多线程下载的FileResult
+        /// </summary>
+        /// <param name="controller"></param>
+        /// <param name="fileStream">文件流</param>
+        /// <param name="fileDownloadName">下载的文件名</param>
+        /// <returns></returns>
+        public static ResumeActionResultBase ResumeFile(this ControllerBase controller, FileStream fileStream, string fileDownloadName)
+        {
+            return new ResumeFileStreamResult(fileStream, fileDownloadName);
+        }
+
+        /// <summary>
+        /// 可断点续传和多线程下载的FileResult
+        /// </summary>
+        /// <param name="controller"></param>
+        /// <param name="fileStream">文件流</param>
+        /// <param name="fileDownloadName">下载的文件名</param>
+        /// <param name="etag">ETag</param>
+        /// <returns></returns>
+        public static ResumeActionResultBase ResumeFile(this ControllerBase controller, FileStream fileStream, string fileDownloadName, string etag)
+        {
+            return new ResumeFileStreamResult(fileStream, fileDownloadName, etag);
+        }
+
+        /// <summary>
+        /// 可断点续传和多线程下载的FileResult
+        /// </summary>
+        /// <param name="controller"></param>
+        /// <param name="bytes">文件流</param>
+        /// <param name="fileDownloadName">下载的文件名</param>
+        /// <returns></returns>
+        public static ResumeActionResultBase ResumeFile(this ControllerBase controller, byte[] bytes, string fileDownloadName)
+        {
+            return new ResumeFileContentResult(bytes, fileDownloadName);
+        }
+
+        /// <summary>
+        /// 可断点续传和多线程下载的FileResult
+        /// </summary>
+        /// <param name="controller"></param>
+        /// <param name="bytes">文件流</param>
+        /// <param name="fileDownloadName">下载的文件名</param>
+        /// <param name="etag">ETag</param>
+        /// <returns></returns>
+        public static ResumeActionResultBase ResumeFile(this ControllerBase controller, byte[] bytes, string fileDownloadName, string etag)
+        {
+            return new ResumeFileContentResult(bytes, fileDownloadName, etag);
+        }
+
         /// <summary>
         /// 可断点续传和多线程下载的FileResult
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="physicalPath">服务端本地文件的物理路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath, string contentType)
+        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath)
         {
-            return ResumePhysicalFile(controller, physicalPath, contentType, fileDownloadName: null, etag: null);
+            return ResumePhysicalFile(controller, physicalPath, null, null);
         }
 
         /// <summary>
@@ -64,12 +112,11 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="physicalPath">服务端本地文件的物理路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <param name="fileDownloadName">下载的文件名</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath, string contentType, string fileDownloadName)
+        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath, string fileDownloadName)
         {
-            return ResumePhysicalFile(controller, physicalPath, contentType, fileDownloadName, etag: null);
+            return ResumePhysicalFile(controller, physicalPath, fileDownloadName, etag: null);
         }
 
         /// <summary>
@@ -77,13 +124,12 @@ namespace Masuit.Tools.Mvc
         /// </summary>
         /// <param name="controller"></param>
         /// <param name="physicalPath">服务端本地文件的物理路径</param>
-        /// <param name="contentType">Content-Type</param>
         /// <param name="fileDownloadName">下载的文件名</param>
         /// <param name="etag">ETag</param>
         /// <returns></returns>
-        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath, string contentType, string fileDownloadName, string etag)
+        public static ResumeFileResult ResumePhysicalFile(this ControllerBase controller, string physicalPath, string fileDownloadName, string etag)
         {
-            return new ResumeFileResult(physicalPath, contentType, controller.ControllerContext.HttpContext.Request)
+            return new ResumeFileResult(physicalPath, controller.ControllerContext.HttpContext.Request)
             {
                 FileDownloadName = fileDownloadName
             };

+ 1 - 1
Masuit.Tools/Mvc/Internal/Range.cs → Masuit.Tools/Mvc/Internal/ByteRange.cs

@@ -1,6 +1,6 @@
 namespace Masuit.Tools.Mvc.Internal
 {
-    internal class Range
+    public struct ByteRange
     {
         public long Start { get; set; }
         public long End { get; set; }

+ 177 - 0
Masuit.Tools/Mvc/Mime/ContentType.cs

@@ -0,0 +1,177 @@
+namespace Masuit.Tools.Mvc.Mime
+{
+    /// <summary>
+    /// 默认MIME映射器,可以根据文件扩展名获取标准内容类型。
+    /// </summary>
+    public static class ContentType
+    {
+        /// <summary>
+        /// 默认Mime  - 如果没有找到任何其他映射则作为默认的Mime-Type
+        /// </summary>
+        public const string DefaultMime = "application/octet-stream";
+        public const string Abs = "audio/x-mpeg";
+        public const string Ai = "application/postscript";
+        public const string Aif = "audio/x-aiff";
+        public const string Aifc = "audio/x-aiff";
+        public const string Aiff = "audio/x-aiff";
+        public const string Aim = "application/x-aim";
+        public const string Art = "image/x-jg";
+        public const string Asf = "video/x-ms-asf";
+        public const string Asx = "video/x-ms-asf";
+        public const string Au = "audio/basic";
+        public const string Avi = "video/x-msvideo";
+        public const string Avx = "video/x-rad-screenplay";
+        public const string Bcpio = "application/x-bcpio";
+        public const string Bin = "application/octet-stream";
+        public const string Bmp = "image/bmp";
+        public const string Body = "text/html";
+        public const string Cdf = "application/x-cdf";
+        public const string Cer = "application/x-x509-ca-cert";
+        public const string Class = "application/java";
+        public const string Cpio = "application/x-cpio";
+        public const string Csh = "application/x-csh";
+        public const string Css = "text/css";
+        public const string Dib = "image/bmp";
+        public const string Doc = "application/msword";
+        public const string Dtd = "application/xml-dtd";
+        public const string Dv = "video/x-dv";
+        public const string Dvi = "application/x-dvi";
+        public const string Eps = "application/postscript";
+        public const string Etx = "text/x-setext";
+        public const string Exe = "application/octet-stream";
+        public const string Gif = "image/gif";
+        public const string Gtar = "application/x-gtar";
+        public const string Gz = "application/x-gzip";
+        public const string Ogv = "video/ogg";
+        public const string Oga = "audio/ogg";
+        public const string Ogg = "audio/ogg";
+        public const string Hdf = "application/x-hdf";
+        public const string Htc = "text/x-component";
+        public const string Htm = "text/html";
+        public const string Html = "text/html";
+        public const string Hqx = "application/mac-binhex40";
+        public const string Ief = "image/ief";
+        public const string Jad = "text/vnd.sun.j2me.app-descriptor";
+        public const string Jar = "application/java-archive";
+        public const string Java = "text/plain";
+        public const string Jnlp = "application/x-java-jnlp-file";
+        public const string Jpe = "image/jpeg";
+        public const string Jpeg = "image/jpeg";
+        public const string Jpg = "image/jpeg";
+        public const string Js = "text/javascript";
+        public const string Jsf = "text/plain";
+        public const string Jspf = "text/plain";
+        public const string Kar = "audio/x-midi";
+        public const string Latex = "application/x-latex";
+        public const string M3u = "audio/x-mpegurl";
+        public const string Mac = "image/x-macpaint";
+        public const string Man = "application/x-troff-man";
+        public const string Mathml = "application/mathml+xml";
+        public const string Me = "application/x-troff-me";
+        public const string Mid = "audio/x-midi";
+        public const string Midi = "audio/x-midi";
+        public const string Mif = "application/x-mif";
+        public const string Mov = "video/quicktime";
+        public const string Movie = "video/x-sgi-movie";
+        public const string Mp1 = "audio/x-mpeg";
+        public const string Mp2 = "audio/x-mpeg";
+        public const string Mp3 = "audio/x-mpeg";
+        public const string Mp4 = "video/mp4";
+        public const string Mpa = "audio/x-mpeg";
+        public const string Mpe = "video/mpeg";
+        public const string Mpeg = "video/mpeg";
+        public const string Mpega = "audio/x-mpeg";
+        public const string Mpg = "video/mpeg";
+        public const string Mpv2 = "video/mpeg2";
+        public const string Ms = "application/x-wais-source";
+        public const string Nc = "application/x-netcdf";
+        public const string Oda = "application/oda";
+        public const string Odb = "application/vnd.oasis.opendocument.database";
+        public const string Odc = "application/vnd.oasis.opendocument.chart";
+        public const string Odf = "application/vnd.oasis.opendocument.formula";
+        public const string Odg = "application/vnd.oasis.opendocument.graphics";
+        public const string Odi = "application/vnd.oasis.opendocument.image";
+        public const string Odm = "application/vnd.oasis.opendocument.text-master";
+        public const string Odp = "application/vnd.oasis.opendocument.presentation";
+        public const string Ods = "application/vnd.oasis.opendocument.spreadsheet";
+        public const string Odt = "application/vnd.oasis.opendocument.text";
+        public const string Otg = "application/vnd.oasis.opendocument.graphics-template";
+        public const string Oth = "application/vnd.oasis.opendocument.text-web";
+        public const string Otp = "application/vnd.oasis.opendocument.presentation-template";
+        public const string Ots = "application/vnd.oasis.opendocument.spreadsheet-template ";
+        public const string Ott = "application/vnd.oasis.opendocument.text-template";
+        public const string Pbm = "image/x-portable-bitmap";
+        public const string Pct = "image/pict";
+        public const string Pdf = "application/pdf";
+        public const string Pgm = "image/x-portable-graymap";
+        public const string Pic = "image/pict";
+        public const string Pict = "image/pict";
+        public const string Pls = "audio/x-scpls";
+        public const string Png = "image/png";
+        public const string Pnm = "image/x-portable-anymap";
+        public const string Pnt = "image/x-macpaint";
+        public const string Ppm = "image/x-portable-pixmap";
+        public const string Ppt = "application/vnd.ms-powerpoint";
+        public const string Pps = "application/vnd.ms-powerpoint";
+        public const string Ps = "application/postscript";
+        public const string Psd = "image/x-photoshop";
+        public const string Qt = "video/quicktime";
+        public const string Qti = "image/x-quicktime";
+        public const string Qtif = "image/x-quicktime";
+        public const string Ras = "image/x-cmu-raster";
+        public const string Rdf = "application/rdf+xml";
+        public const string Rgb = "image/x-rgb";
+        public const string Rm = "application/vnd.rn-realmedia";
+        public const string Roff = "application/x-troff";
+        public const string Rtf = "application/rtf";
+        public const string Rtx = "text/richtext";
+        public const string Sh = "application/x-sh";
+        public const string Shar = "application/x-shar";
+        public const string Smf = "audio/x-midi";
+        public const string Sit = "application/x-stuffit";
+        public const string Snd = "audio/basic";
+        public const string Src = "application/x-wais-source";
+        public const string Sv4cpio = "application/x-sv4cpio";
+        public const string Sv4crc = "application/x-sv4crc";
+        public const string Svg = "image/svg+xml";
+        public const string Svgz = "image/svg+xml";
+        public const string Swf = "application/x-shockwave-flash";
+        public const string T = "application/x-troff";
+        public const string Tar = "application/x-tar";
+        public const string Tcl = "application/x-tcl";
+        public const string Tex = "application/x-tex";
+        public const string Texi = "application/x-texinfo";
+        public const string Texinfo = "application/x-texinfo";
+        public const string Tif = "image/tiff";
+        public const string Tiff = "image/tiff";
+        public const string Tr = "application/x-troff";
+        public const string Tsv = "text/tab-separated-values";
+        public const string Txt = "text/plain";
+        public const string Ulw = "audio/basic";
+        public const string Ustar = "application/x-ustar";
+        public const string Vxml = "application/voicexml+xml";
+        public const string Xbm = "image/x-xbitmap";
+        public const string Xht = "application/xhtml+xml";
+        public const string Xhtml = "application/xhtml+xml";
+        public const string Xls = "application/vnd.ms-excel";
+        public const string Xml = "application/xml";
+        public const string Xpm = "image/x-xpixmap";
+        public const string Xsl = "application/xml";
+        public const string Xslt = "application/xslt+xml";
+        public const string Xul = "application/vnd.mozilla.xul+xml";
+        public const string Xwd = "image/x-xwindowdump";
+        public const string Vsd = "application/x-visio";
+        public const string Wav = "audio/x-wav";
+        public const string Wbmp = "image/vnd.wap.wbmp";
+        public const string Wml = "text/vnd.wap.wml";
+        public const string Wmlc = "application/vnd.wap.wmlc";
+        public const string Wmls = "text/vnd.wap.wmlscript";
+        public const string Wmlscriptc = "application/vnd.wap.wmlscriptc";
+        public const string Wmv = "video/x-ms-wmv";
+        public const string Wrl = "x-world/x-vrml";
+        public const string Wspolicy = "application/wspolicy+xml";
+        public const string Z = "application/x-compress";
+        public const string z = "application/x-compress";
+        public const string Zip = "application/zip";
+    }
+}

+ 1 - 1
Masuit.Tools/Mvc/Mime/MimeMapper.cs

@@ -11,7 +11,7 @@ namespace Masuit.Tools.Mvc.Mime
         /// <summary>
         /// 默认Mime  - 如果没有找到任何其他映射则作为默认的Mime-Type
         /// </summary>
-        private const string DefaultMime = "application/octet-stream";
+        public const string DefaultMime = "application/octet-stream";
 
         /// <summary>
         /// 在文件路径中搜索文件扩展名的默认正则表达式