Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to Generate a Zip Containing the User Personal Data to be GDPR Compliant in .NET Core 2.1

0.00/5 (No votes)
23 Sep 2018 1  
How to generate a zip containing the user personal data to be GDPR compliant in .NET Core 2.1

Introduction

I have a web site built on .NET Core 2.1 and I want it to be GDPR compliant, so I need to provide a way to download the user data. In this site, each user can have files in a subdirectory inside the root, in addition to the information saved in the database. I wanted to be able to provide a zip containing all his files and a dictionary with all his personal data.

Background

This link provides an example of dynamically generating a zip file with minimal threading and memory usage.
.NET Core 2.1 provides you with templates generated code to comply with GDPR, you can read more here.

Using the Code

First, I created the required fileCallbackResult and WriteOnlyStreamWrapper classes following the first link of the background.

In the DownloadPersonalData Razor Page generated by .NET Core, the personal information from the ApplicationUser is added to a dictionary and download as a JSON. I wanted to be able to download a zip file containing any information and existing file belonging to the user.

In order to do that, I added the following methods:

private async Task<Dictionary<string, string>> ReadClient(ApplicationUser user, string UserId)
{
   var personalData = new Dictionary<string, string>();
  
   //get the data from the identity application user
   var personalDataProps = typeof(ApplicationUser).GetProperties().Where(
   prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));

   foreach (var p in personalDataProps)
   {
      personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
   }

   //get any other data the client may have in the database
   User client = await _context.User.AsNoTracking().FirstOrDefaultAsync(b => b.UserID == UserId);
   personalData.Add("ClientName", client.Name);
   personalData.Add("ClientEmail", client.Email);

   ......

   return personalData;
}

This method builds the dictionary containing all data from the database.

private Dictionary<string, string> ListClientFiles(string UserId)
{
   //Calculate the client directory in the file system
   var clientpath = GetClientPath(UserId);
   var filenamesAndUrls = new Dictionary<string, string>();
   if (Directory.Exists(Path.GetDirectoryName(clientpath)))
   {
      DirectoryInfo d = new DirectoryInfo(clientpath);
      //Get recursively all the files in the directory
      FileInfo[] Files = d.GetFiles("*.*", SearchOption.AllDirectories);
      foreach (FileInfo file in Files)
      {
        string fileName = Path.GetFileNameWithoutExtension(file.Name);
        string fileExtension = file.Extension;

        //in the case of having the same fileName in different directories 
        //inside the client directory, suffix the number i.e. profile.html and profile(1).html
        if (filenamesAndUrls.ContainsKey(fileName + fileExtension)) {
            int copy = 0;
            do {
                copy++;
            } while (filenamesAndUrls.ContainsKey(fileNname + "(" + copy + ")" + fileExtension));

         fileName = fileName +"("+copy+")";
        }

        fileName = fileName + fileExtension;

        filenamesAndUrls.Add(fileName, file.FullName);
        }
    }
   return filenamesAndUrls;

}

This method creates a dictionary with the keys and full path of the files to be zip.

Finally, I modify the OnPostAsync method in DownloadPersonalData Razor Page to do the following:

Dictionary<string, string> personalData = await ReadClient(user, UserId);
var filenamesAndUrls = ListClientFiles(UserId);

return new FileCallbackResult(new MediaTypeHeaderValue("application/octet-stream"), 
                              async (outputStream, _) =>
   {
      using (var zipArchive = new ZipArchive(new WriteOnlyStreamWrapper(outputStream), 
             ZipArchiveMode.Create))
      {
          //the personal data dictionary is saved in a Json file
          var userZipEntry = zipArchive.CreateEntry("personaldata.json");
          using (var userZipStream = userZipEntry.Open())
          using (var stream = new MemoryStream(Encoding.UTF8.GetBytes
                (JsonConvert.SerializeObject(personalData))) )
              await stream.CopyToAsync(userZipStream);

          //all other personal files
          foreach (var kvp in filenamesAndUrls)
           {
               var zipEntry = zipArchive.CreateEntry(kvp.Key);
               using (var zipStream = zipEntry.Open())
               using (var stream = System.IO.File.OpenRead(kvp.Value))
                   await stream.CopyToAsync(zipStream);
           }
        }
   })
{
   FileDownloadName = "DownloadPersonalData.zip"
};

So, the dictionary containing the personal data - or any other pertinent data coming from the database - is inserted to the zip as a zip entry through a memory stream without needing to save it in a temporary file. If you rather, you could create many entries in the zip for the different information in different tables in the database.

The other personal files are read and inserted to the zip as other zip entries.

History

  • 24th September, 2018: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here