Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Osman Coskun 164 posts 398 karma points
    Feb 12, 2024 @ 07:10
    Osman Coskun
    0

    Zipping media files and serving on the fly

    Hello, Let me state up front that I am not a hardcore .net developer.

    On a v8 site i've successfully used ICSharpCode.SharpZipLib.Zip library to zip media files and serve on the fly via an alttemplate with the code below.

    @inherits Umbraco.Web.Mvc.UmbracoViewPage
    @using ICSharpCode.SharpZipLib.Zip
    @using System.IO
    @{
    
        var fileName = Model.Name;
    
        Response.ContentType = "application/zip";
    
        Response.AppendHeader("content-disposition", "attachment;filename=" + fileName + ".zip");
    
        using (ZipOutputStream s = new ZipOutputStream(Response.OutputStream))
        {
            s.SetLevel(9); 
    
            byte[] buffer = new byte[4096];
    
    
                var ImageList = new List<string>();
                if (Model.HasValue("file1") && Model.Value<IPublishedContent>("file1") != null)
                {
                    ImageList.Add(Server.MapPath(Model.Value<IPublishedContent>("file1").Url()));
                }
                if (Model.HasValue("file2") && Model.Value<IPublishedContent>("file2") != null)
                {
                    ImageList.Add(Server.MapPath(Model.Value<IPublishedContent>("file2").Url()));
                }
    
                for (int i = 0; i < ImageList.Count; i++)
                {
                    ZipEntry entry = new ZipEntry(Path.GetFileName(ImageList[i]));
                    entry.DateTime = DateTime.Now;
                    entry.IsUnicodeText = true;
                    s.PutNextEntry(entry);
    
                    using (FileStream fs = System.IO.File.OpenRead(ImageList[i]))
                    {
                        int sourceBytes;
                        do
                        {
                            sourceBytes = fs.Read(buffer, 0, buffer.Length);
                            s.Write(buffer, 0, sourceBytes);
                        } while (sourceBytes > 0);
                    }
                }
    
    
            s.Finish();
            s.Flush();
            s.Close();
    
        }
    }
    

    Now i need to develop a similar functionality on an Umbraco 13 site. I tried to adapt the code as below but it does not work.

    @inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
    @using ICSharpCode.SharpZipLib.Zip
    @using System.IO
    @using System.Diagnostics
    @{
    
        var fileName = Model.Name;
    
        Context.Response.ContentType = "application/zip";
    
        Context.Response.Headers.Add("content-disposition", "attachment;filename=" + fileName + ".zip");
    
        var ms = new MemoryStream();
        using (ZipOutputStream s = new ZipOutputStream(ms))
        {
            s.SetLevel(9);
    
            byte[] buffer = new byte[4096];
            var fileList = new List<string>();
    
    
    
            if (Model.HasValue("file1") && Model.Value<IPublishedContent>("file1") != null)
            {
                IPublishedContent file1 = Model.Value<IPublishedContent>("file1");
                fileList.Add(Website.Controllers.MapPathExtension.MapPath(Context, file1.Url().ToString()));
            }
            if (Model.HasValue("file2") && Model.Value<IPublishedContent>("file2") != null)
            {
                IPublishedContent file1 = Model.Value<IPublishedContent>("file2");
                fileList.Add(Website.Controllers.MapPathExtension.MapPath(Context, file2.Url().ToString()));
            }
    
            for (int i = 0; i < fileList.Count; i++)
            {
                FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(fileList[i]);
                FileInfo fi = new FileInfo(fileList[i]);
    
                ZipEntry entry = new ZipEntry(Model.Name + "/" + fi.Name);
                entry.DateTime = DateTime.Now;
                entry.IsUnicodeText = true;
                s.PutNextEntry(entry);
    
                using (FileStream fs = System.IO.File.OpenRead(fileList[i]))
                {
                    int sourceBytes;
                    do
                    {
                        sourceBytes = fs.Read(buffer, 0, buffer.Length);
                        s.Write(buffer, 0, sourceBytes);
                    } while (sourceBytes > 0);
                }
            }
            s.Finish();
            s.Flush();
            s.Close();
        }
    }
    

    I use the following code the get the physical path for the files as a replacement for Server.MapPath.

    namespace Website.Controllers
    {
        public static class MapPathExtension
        {
            static string WebRootPath { get; set; }
            static string ContentRootPath { get; set; }
    
            /// <summary>
            /// Maps a virtual or relative path to a physical path in a Web site,
            /// using the WebRootPath as the base path (ie. the `wwwroot` folder)
            /// </summary>
            /// <param name="context">HttpContext instance</param>
            /// <param name="relativePath">Site relative path using either `~` or `/` as root indicator</param>
            /// <param name="host">Optional - IHostingEnvironment instance. If not passed retrieved from RequestServices DI</param>
            /// <param name="basePath">Optional - Optional physical base path. By default host.WebRootPath</param>
            /// <param name="useAppBasePath">Optional - if true returns the launch folder rather than the wwwroot folder</param>
            /// <returns>physical path of the relative path</returns>
            public static string MapPath(this HttpContext context,
                string relativePath = null,
                IWebHostEnvironment host = null,
                string basePath = null,
                bool useAppBasePath = false)
            {
                if (string.IsNullOrEmpty(relativePath))
                    relativePath = "/";
    
                // Ignore absolute paths
                if (relativePath.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
                    relativePath.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
                    return relativePath;
    
                if (string.IsNullOrEmpty(basePath))
                {
                    if (string.IsNullOrEmpty(WebRootPath) || string.IsNullOrEmpty(ContentRootPath))
                    {
                        host ??= context.RequestServices.GetService(typeof(IWebHostEnvironment)) as IWebHostEnvironment;
                        WebRootPath = host.WebRootPath;
                        ContentRootPath = host.ContentRootPath;
                    }
                    basePath = useAppBasePath ? ContentRootPath.TrimEnd('/', '\\') : WebRootPath;
                }
    
                relativePath = relativePath.TrimStart('~', '/', '\\');
    
                string path = Path.Combine(basePath, relativePath);
    
                string slash = Path.DirectorySeparatorChar.ToString();
                return path
                    .Replace("/", slash)
                    .Replace("\\", slash)
                    .Replace(slash + slash, slash);
            }
        }
    }
    

    I need help from experienced developers. Thanks in advance.

  • Justin Neville 22 posts 196 karma points c-trib
    Feb 12, 2024 @ 07:42
    Justin Neville
    100

    I've done something similar before but in a SurfaceController.

    Here's some code that may be able to help:

        [HttpGet]
        public IActionResult DownloadZip()
        {
            byte[] bytes = new byte[0];
    
            using (var memoryStream = new MemoryStream())
            {
                using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
                {
                    foreach (var item in _service.Documents)
                    {
                        string path = Path.Join(_webHostEnvironment.WebRootPath, item.Url ?? String.Empty);
                        archive.CreateEntryFromFile(path, Path.GetFileName(path));
                    }
                }
    
                memoryStream.Seek(0, SeekOrigin.Begin);
                bytes = memoryStream.ToArray();
            }
    
            return File(bytes, "application/zip", "Downloads.zip");
        }
    

    Where _service.Documents is a list of documents with relative URLs (that code is not shown for brevity).

    This is for v10 but no reason why it should not work on v13.

    Trying to do what you are doing in a view is probably not good practice as the view could output some blank whitespace that could interfere with the file download.

  • Osman Coskun 164 posts 398 karma points
    Feb 13, 2024 @ 08:53
    Osman Coskun
    0

    I'll try to implement. Thanks Justin.

    Update: I applied Justin's solution using a controller.

    Thanks again Justin.

Please Sign in or register to post replies

Write your reply to:

Draft