Click here to Skip to main content
15,881,757 members
Articles / Web Development / ASP.NET / ASP.NET Core

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

Rate me:
Please Sign up or sign in to vote.
4.78/5 (8 votes)
23 Sep 2018CPOL2 min read 8.1K   12  
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:

C#
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.

C#
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:

C#
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)


Written By
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --