Created
October 10, 2019 10:20
-
-
Save theuntitled/cd6268e1b72a171b5a05e06eeace3bff to your computer and use it in GitHub Desktop.
ASP.NET Core 3 Partial Content Response (Status Code 206) for electron-updater
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Microsoft.AspNetCore.Http; | |
| using Microsoft.AspNetCore.Routing; | |
| namespace Example | |
| { | |
| public class ByteRangesRouteConstraint : IRouteConstraint | |
| { | |
| public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) | |
| { | |
| if (!values.TryGetValue("url", out var url) || url == null) | |
| { | |
| return false; | |
| } | |
| var fileName = url.ToString(); | |
| if (!fileName.EndsWith(".7z") && !fileName.EndsWith(".exe")) | |
| { | |
| return false; | |
| } | |
| if (!httpContext.Request.Headers.TryGetValue("User-Agent", out var userAgent) || userAgent != "electron-builder") | |
| { | |
| return false; | |
| } | |
| var typedHeaders = httpContext.Request.GetTypedHeaders(); | |
| return typedHeaders.Range != null; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System.IO; | |
| using Microsoft.AspNetCore.Hosting; | |
| using Microsoft.AspNetCore.Mvc; | |
| namespace Example.Controllers | |
| { | |
| public class PartialContentController : Controller | |
| { | |
| private readonly IWebHostEnvironment _environment; | |
| public PartialContentController(IWebHostEnvironment environment) | |
| { | |
| _environment = environment; | |
| } | |
| public IActionResult Download([FromRoute] string url) | |
| { | |
| var path = Path.Combine(_environment.WebRootPath, url); | |
| if (!System.IO.File.Exists(path)) | |
| { | |
| return NotFound(); | |
| } | |
| return new PartialContentResult(path); | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.IO; | |
| using System.Linq; | |
| using System.Net; | |
| using System.Net.Http; | |
| using System.Threading.Tasks; | |
| using Microsoft.AspNetCore.Http; | |
| using Microsoft.AspNetCore.Mvc; | |
| using Microsoft.Net.Http.Headers; | |
| using ContentRangeHeaderValue = System.Net.Http.Headers.ContentRangeHeaderValue; | |
| namespace Example | |
| { | |
| public class PartialContentResult : IActionResult | |
| { | |
| private readonly string _path; | |
| public PartialContentResult(string path) | |
| { | |
| _path = path; | |
| } | |
| public async Task ExecuteResultAsync(ActionContext context) | |
| { | |
| var header = context.HttpContext.Request.GetTypedHeaders(); | |
| context.HttpContext.Response.StatusCode = (int) HttpStatusCode.PartialContent; | |
| await using var fileStream = File.Open(_path, FileMode.Open, FileAccess.Read); | |
| if (header.Range.Ranges.Count == 1) | |
| { | |
| var partialContent = await GetPartialContent(header.Range.Ranges.First(), fileStream); | |
| await partialContent.CopyToAsync(context.HttpContext.Response.Body); | |
| return; | |
| } | |
| var content = new MultipartContent("byteranges"); | |
| foreach (var range in header.Range.Ranges) | |
| { | |
| var partialContent = await GetPartialContent(range, fileStream); | |
| content.Add(partialContent); | |
| } | |
| context.HttpContext.Response.ContentType = content.Headers.ContentType.ToString(); | |
| await content.CopyToAsync(context.HttpContext.Response.Body); | |
| } | |
| private async Task<ByteArrayContent> GetPartialContent(RangeItemHeaderValue range, FileStream fileStream) | |
| { | |
| if (!range.From.HasValue || !range.To.HasValue) | |
| { | |
| throw new Exception($"Invalid range \"{range}\""); | |
| } | |
| var contentLength = Convert.ToInt32(range.To.Value - range.From.Value) + 1; | |
| var bytes = new byte[contentLength]; | |
| fileStream.Seek(range.From.Value, SeekOrigin.Begin); | |
| fileStream.Read( | |
| bytes, | |
| 0, | |
| contentLength | |
| ); | |
| return new ByteArrayContent(bytes) | |
| { | |
| Headers = | |
| { | |
| ContentLength = contentLength, | |
| ContentRange = new ContentRangeHeaderValue(range.From.Value, range.To.Value, fileStream.Length) | |
| } | |
| }; | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Microsoft.AspNetCore.Builder; | |
| using Microsoft.AspNetCore.Hosting; | |
| using Microsoft.AspNetCore.StaticFiles; | |
| using Microsoft.Extensions.DependencyInjection; | |
| using Microsoft.Extensions.Hosting; | |
| namespace Example | |
| { | |
| public class Startup | |
| { | |
| public void ConfigureServices(IServiceCollection services) | |
| { | |
| services.AddControllers(); | |
| services.AddRouting(options => { options.ConstraintMap.Add("byteranges", typeof(ByteRangesRouteConstraint)); }); | |
| } | |
| public void Configure(IApplicationBuilder app, IWebHostEnvironment env) | |
| { | |
| if (env.IsDevelopment()) | |
| { | |
| app.UseDeveloperExceptionPage(); | |
| } | |
| app.UseRouting(); | |
| var provider = new FileExtensionContentTypeProvider(); | |
| provider.Mappings[".yml"] = "application/x-yaml"; | |
| provider.Mappings[".7z"] = "application/octet-stream"; | |
| provider.Mappings[".exe"] = "application/octet-stream"; | |
| app.UseStaticFiles(new StaticFileOptions | |
| { | |
| ContentTypeProvider = provider | |
| }); | |
| // app.UseFileServer(true); | |
| app.UseEndpoints(endpoints => | |
| { | |
| endpoints.MapControllers(); | |
| endpoints.MapControllerRoute( | |
| "PartialContent", | |
| "{*url:byteranges}", | |
| new | |
| { | |
| Controller = "PartialContent", | |
| Action = "Download" | |
| }); | |
| }); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment