Skip to main content

GDPR "compliance" in .NET Core web application

Incorporating GDPR compliance is something that takes time, effort and thorough understanding of the law. In this post, we are going to show you how to create a stop-gap in your .NET Core web applications until you are able to invest time and resources into understanding your application's structure, usage of personal data, and restructuring it to conform to the GDPR.

We will make a sample ASP.NET Core web application to demonstrate how to put a GDPR compliance stop-gap in place.

Why should you care about GDPR?

Before we get into the implementation, you should first understand roughly what the GDPR is and why you need to care about it. Put simply, the GDPR are a set of regulations that give consumers rights and protections to their personal information. In order to do business with consumers in the EU, or offer any service that uses consumer data of EU residents, you must be GDPR compliant. Failure to compliant will result in fines.

GDPR compliance (credit Fox Studios via Vox)
Reading the GDPR primer

Blocking EU users

To become GDPR "compliant," we are going to block any user coming from the EU. Yes - that is our stop-gap. We do not have to worry about GDPR compliance if no user from the EU can visit our website*.

*Not legal advice, I am no attorney.

We will be using MaxMind's country database to check the IP address of the user and redirect the user to an empty page (that has no third party tracking or analytics) and display to them that GDPR compliance work is still underway.

Installing the DB

Head on over to the official page and download the GeoLite2 Country MaxMind DB.

Downloading the GeoLite2 Country database file
What to download on the page

If the link is broken, you can also find the DB file in a github repo of mine. This free version of the full service has just enough what we need, detecting if the user is coming from the EU.

Building the application

Create a new project in Visual Studio and create a new .NET Core web application. Be sure to choose the MVC (Model-View-Controller) application as we do not want Razor pages.

Creating our .NET Core web application
Creating our .NET Core web application

Add a MaxMind folder to your solution. Within that folder, add the GeoLite2-Country.mmdb file that is contained in the tar.gz file you downloaded from the MaxMind page mentioned above. To add the .mmdb file, right-click the MaxMind folder and choose Add > Existing Item...

I'd recommend 7-Zip if you don't already have it installed to unarchive the .tar.gz file.

Our Solution Explorer window after adding the DB file
Creating the MaxMind folder

Right click the GeoLite2-Country.mmdb file and select Properties and change the Copy to Output Directory value to Copy Always. Doing this will always copy up the file which will save our behind in case we are using the Publish features of Visual Studio (Azure App Service, IIS, FTP, folder, etc.) to deploy our application. If we don't do this, our deployed application won't have the the DB file in the solution and we'll get an error when trying to access the DB file.

Setting Copy to Output Directory
Copy always the file


Next, go and right-click your project (GDPR in the screenshot above) and choose Manage NuGet Packages... Search for "maxmind" and install the MaxMind.Db package. If you aren't seeing any packages, be sure your Package source is set to All.

Installing MaxMind.Db Nuget package
Installing MaxMind.Db Nuget package

Create two files within the MaxMind folder; IMaxMindService and MaxMindService. Within IMaxMindService, add the following code:

using System.Collections.Generic;
namespace GDPR.MaxMind // YOURS WILL BE DIFFERENT!
{
    public interface IMaxMindService
    {
        Dictionary<string, object> GetData(string IP);
    }
}

Within the MaxMindService, add the following code:

using MaxMind.Db;
using System;
using System.Collections.Generic;
using System.Net;
namespace GDPR.MaxMind // YOURS WILL BE DIFFERENT!
{
    public class MaxMindService : IMaxMindService, IDisposable
    {
        private Reader Reader { get; set; }
        
        /// <summary>  
        /// Returns the Reader that use our DB file  
        /// </summary>  
        /// <returns></returns>  
        private Reader GetReference()
        {
            if (Reader == null)
            {
                Reader = new Reader("MaxMind/GeoLite2-Country.mmdb");
            }
            return Reader;
        }
        
        /// <summary>  
        /// Returns data of a given IP address  
        /// </summary>  
        /// <param name="IP"></param>  
        /// <returns></returns>  
        public Dictionary<string, object> GetData(string IP)
        {
            return GetReference().Find<Dictionary<string, object>>(IPAddress.Parse(IP));
        }
        
        /// <summary>  
        /// Disposes the object  
        /// </summary>  
        public void Dispose()
        {
            if (GetReference() != null)
            {
                Reader.Dispose();
            }
        }
    }
}

We are using the <summary> tag to include comments that will show up when hovering over the members of MaxMindService. This is a great practice to do which exponentially helps understanding and readability whether coding individually or on a team. Don't be afraid to use this in your code!

The <summary> tag's text displayed
The <summary> tag in action

What did we just do?

We added the database file to our solution, which contains IPs and whether or not these IPs are in the EU (among other things). Next, we created a service contract with our IMaxMindService interface and created an implementation of that interface with our MaxMindService class.

Service contracts are frequently used within the realm of WCF. However - we are using them in this context to refer to a service we are going to register within the .NET Core dependency injection.

What is next?

We will register our service within the .NET Core services collection by going into Startup.cs and adding the following code (you'll need to add a using statement too at the top of your Startup.cs file as well):

// This method gets called by the runtime. Use this method to add services to the container.  
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<CookiePolicyOptions>(options =>
    {
        // This lambda determines whether user consent for non-essential cookies is needed for a given request.  
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });
    
    // Added this line below!  
    services.AddSingleton<IMaxMindService, MaxMindService>();
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}

Using the .AddSingleton creates the service as a singleton which reuses the first instance of the service that's created for any subsequent requests (until the application shuts down).

This is all great, but you may be asking - how do we use the service? Well, if we consider the original requirement that we want to redirect users from the EU to a separate page, the best page to put code to redirect users would be before they even see a page, right? In .NET Core, this means we need to write code within a filter.

.NET Core filter pipeline (credit Microsoft)
.NET Core request pipeline

Filters can run code before controller actions are executed, and since it is in the controller action that views get generated, it is the filter we will be creating next. Create a GDPRFilter class in the MaxMind folder and add the following code to it:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net;
namespace GDPR.MaxMind // THIS WILL BE DIFFERENT FOR YOU 
{
    public class GDPRFilter : ActionFilterAttribute
    {
        private readonly IMaxMindService _iMaxMindService;
        
        public GDPRFilter()
        {
        }
        
        public GDPRFilter(IMaxMindService iMaxMindService)
        {
            _iMaxMindService = iMaxMindService;
        }
        
        public override void OnActionExecuting(ActionExecutingContext context)
        {
            // Grab the client's ip address  
            IPAddress clientIP = context.HttpContext.Connection.RemoteIpAddress;
            
            // Localhost woes   
            if (clientIP.ToString() == "::1" ||
             clientIP.ToString() == "127.0.0.1")
            {
                (context.Controller as Controller).ViewBag.isEU = "False";
                // Un-comment me to test redirect on localhost
                //if (!(context.Controller is UnavailableController))
                //{
                //    context.Result = new RedirectToActionResult("Index", "Unavailable", null);
                //} 
            }
            else
            {
                // See if the client is from the EU  
                Dictionary<string, object> raw = _iMaxMindService.GetData(clientIP.ToString());
                var continentData = (raw["continent"]);
                var json = JsonConvert.SerializeObject(continentData);
                var dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                if (dictionary["code"].ToString() == "EU")
                {
                    (context.Controller as Controller).ViewBag.isEU = "True";
                    if (!(context.Controller is UnavailableController))
                    {
                        // Redirect to our not available controller  
                        context.Result = new RedirectToActionResult("Index", "Unavailable", null);
                    }
                }
                else
                {
                    (context.Controller as Controller).ViewBag.isEU = "False";
                }
            }
        }
    }
}

What this action filter is doing is looking at the client IP address and checking it against the MaxMindService (that is injected through constructor injection) into the action filter. If the data about the client's IP address contains "EU", that means the IP is from the EU and we should direct the user elsewhere.

As you also see above, I am also assigning a ViewBag property, isEU, that is set to True or False that can be used within our .cshtml views in case we want to conditionally show or hide certain elements on the page.

We still need to create the UnavailableController, but if you've been following in this tutorial - building the solution will result in an error because we actually haven't made the UnavailableController yet. Simply comment out the if statement that contains the UnavailableController and add an UnavailableController (uncomment the if statment after you've added this controller):

using Microsoft.AspNetCore.Mvc;
namespace GDPR.Controllers // YOURS WILL BE DIFFERENT
{
    public class UnavailableController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
    }
}

Adding a new controller
The UnavailableController

While this code is good, our action filter isn't going to actually be able to use the MaxMindService because we can't pass a dependency directly into an action filter. The way to do it is to use a ServiceFilter. What this means is we need to add our scoped filter to the Startup.cs class and on our controllers:

Adding .AddScoped<GDPRFilter>()
Adding .AddScoped<GDPRFilter>()

Adding the ServiceFilter
Adding the ServiceFilter to our controllers

Finishing up

We are almost done. We never actually made the view for the UnavailableController, let's do that now:

Unavailable index page
Adding the unavailable page
Index.cshtml

@{  
   ViewData["Title"] = "EU!";  
 }  

 <p>  
   You are from the EU, we can't serve you now. Check back later.  
 </p>  

Running the site now will show us a normal page:

Web application homepage
The homepage

But - if we happen to un-comment some code within the GDPRFilter that's commented out, we can see what users will see if they happened to visit our site from the EU:
Testing the EU redirect locally
Testing the EU redirect locally

Blocked from visiting the webpage
Blocked!

Thanks for reading

The full github repo can be found right here if you'd like to download the full source.

Comments

  1. Thank you for writing this informative post. High Technologies Solutions providing best coaching classes in south delhi.Otherwise If any one who want to learn dot-net contact us on 9311002620 or visit:- https://htsindia.com/Courses/microsoft-courses/dotnettraininginstituteinsouthdelhi

    ReplyDelete
  2. An excellent article. Quality information is provided in this article. Without a doubt, I'll investigate it. Here are some incredibly helpful tips. Much appreciation. The good work must continue.
    Colleges In Hyderabad For B.com

    ReplyDelete
  3. This is a great website that I recently found. I was, in fact, apprehended using this resource that you provide. Big congratulations for building such an amazing blog website!Best CA Coaching Centres in Hyderabad

    ReplyDelete
  4. It was this wonderful website that I recently came onto. I was actually apprehended using this resource that you have available. A huge congratulations for building such a wonderful blog website! Colleges In Hyderabad For B.com

    ReplyDelete
  5. This site is fascinating and incredibly inventive. After a long search, I finally find this wonderful article with a clear description of the subject, and I find this post to be really helpful. Kindly update them so I can enjoy more original posts from you.B.Com Colleges In Hyderabad


    ReplyDelete

Post a Comment

Popular posts from this blog

[Fix] - ASUS PCE-AC68 adapter (no internet)

There seem to be a lot of problems with this adapter, even with such strong performance . Why so many issues? I'm not quite sure, but I needed to find a fix because I kept on losing wifi. The ASUS PCE-AC68 The fix Keeping it short - this is how I fixed the issue: Downloaded the driver for my OS from ASUS's support page -  https://www.asus.com/us/Networking/PCEAC68/HelpDesk_Download/ (in my case it was Windows 10 64-bit). Open Device Manager by holding the Windows key and pressing R, then typing "devmgmt.msc" and hitting Enter. (Don't worry, this isn't a scam . We are simply opening Window's Device Manager through the Microsoft Management Console snap-in .) Navigate to the yellow warning sign sitting under Network adapters and right click it. Select Update driver . Select Browse my computer for driver software  and choose the following path of the OS that you have installed on your computer. (The path for the driver on my computer was C

UI redesigns are mostly a waste of time

To preface the article, I primarily work on, and prefer, back-end code. I've been involved in both web and software development for over 4 years now and worked with many front-end and back-end frameworks. New Twitter UI Before all of the UI designers that read this go out and riot and champion against me for saying UI redesigns are a waste of time, let me say that I do value design . I think at the bare minimum, a product or website needs to be usable , and if you possess a good eye and steady hand , you should feel compelled to create something that looks pleasing. David Just stop redesigning the UI all the time . UI redesigns, in my opinion, are a waste of time 95% of the time. Let me explain further. No one cares Come see our fresh new look ! What about our new  material design , come see! I'm sorry, but besides fixing the UI where it impacts the usability of your application, no one is raving about how a redesign makes the application any better.

Logging into a website with Powershell

Powershell is great, and it's lately been my go-to shell while I'm working on Windows. Sorry command prompt I really don't do a lot of work in the shell, but I do like to play with low-level interfaces from time to time. The article is about Linux shells, but goes into good explanation about what a shell is if you don't know. Log into a website Today, I wanted to do something that I have never really tried before and that is logging into a website using Powershell . The concepts behind this are quite simple really, as Powershell has support to send HTTP requests  and that's usually all we need, unless the server has CSRF protections in place (which it should). We are going to attempt  to log in to my favorite website for buying socks, Absolute Socks : Is that a turkey?! In order to do that, we need to have a login. So if you don't already have an account on www.absolutesocks.com , go make one now. Viewing the login request On websites,