Click here to Skip to main content
13,799,009 members
Click here to Skip to main content
Add your own
alternative version

Stats

3.9K views
11 bookmarked
Posted 23 Sep 2018
Licenced CPOL

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

, 23 Sep 2018
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Silvia Campo
Canada Canada
No Biography provided

You may also be interested in...

Comments and Discussions

 
-- There are no messages in this forum --
Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web01 | 2.8.181207.3 | Last Updated 24 Sep 2018
Article Copyright 2018 by Silvia Campo
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid