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






4.78/5 (8 votes)
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