Skip to content

Instantly share code, notes, and snippets.

@theuntitled
Created October 10, 2019 10:20
Show Gist options
  • Select an option

  • Save theuntitled/cd6268e1b72a171b5a05e06eeace3bff to your computer and use it in GitHub Desktop.

Select an option

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
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;
}
}
}
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);
}
}
}
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)
}
};
}
}
}
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