
There are various reasons why it is useful to channel certain requests through your own application to an external endpoint; the most obvious is, for example, as a workaround for client statistics from browser adblockers like Microsoft Clarity or Cloudflare Web Analytics.
And you don’t always need an entire proxy tool or proxy framework like YARP with huge effort and overhead. It can be much easier to do it directly in the application, for example with ASP.NET Core MVC .
A custom proxy
A proxy is nothing more than an mediator that forwards a request - either completely or slightly adapted, depending on the situation and requirements.
As a rule for forwarding:
- The headers (except for security-relevant headers such as cookies)
- The HTTP method
- The content and the content type
- Sometimes the IP address of the source, if necessary or desired
ASP.NET Core does not have a built-in mechanism for this, but you can easily implement this yourself, both with MVC and with the Minimal API.
As a demonstration, I have an ASP.NET Core MVC action that receives a request and can forward it to any destination address. The result is the respective status code and content of the external response.
1// Controller to handle proxy requests
2public class SampleProxyController : Controller
3{
4 // Dependency injection for IHttpClientFactory to create HttpClient instances
5 private readonly IHttpClientFactory _httpClientFactory;
6
7 // Constructor to initialize the IHttpClientFactory instance
8 public SampleProxyController(IHttpClientFactory httpClientFactory)
9 {
10 _httpClientFactory = httpClientFactory;
11 }
12
13 // Action method that proxies the request
14 public async Task<IActionResult> Proxy()
15 {
16 // The target proxy URI. This can be dynamically set or statically defined.
17 Uri proxyUri = new("https://myproxyurl/endpoint"); // your config
18
19 // Enforce the HTTP version for the request to HTTP/2
20 Version proxyHttpVersion = new("2.0"); // your config
21
22 // Get the incoming request from the client
23 HttpRequest sourceRequest = this.Request;
24
25 // Extract the query string from the source request, if any
26 string? queryString = sourceRequest.QueryString.Value ?? null;
27
28 // Build the target URI, appending the query string if it exists
29 UriBuilder uriBuilder = new(proxyUri);
30 if (queryString is not null)
31 {
32 uriBuilder.Query = queryString;
33 }
34 Uri targetUri = uriBuilder.Uri;
35
36 // Create a new HTTP request message for the proxy target
37 HttpRequestMessage proxyRequestMessage = new()
38 {
39 RequestUri = targetUri,
40 Method = new HttpMethod(sourceRequest.Method), // Use the HTTP method from the source request
41 Version = proxyHttpVersion
42 };
43
44 // Forward headers from the source request, except the "Cookie" header
45 foreach (KeyValuePair<string, StringValues> header in sourceRequest.Headers)
46 {
47 if (!string.Equals(header.Key, "Cookie", StringComparison.OrdinalIgnoreCase))
48 {
49 proxyRequestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToList());
50 }
51 }
52
53 // Set the Host header to match the target URI's authority
54 proxyRequestMessage.Headers.Host = targetUri.Authority;
55
56 // Add the X-Forwarded-For header to indicate the client's IP address
57 IPAddress? requestIPAddress = sourceRequest.HttpContext.Connection.RemoteIpAddress;
58 proxyRequestMessage.Headers.TryAddWithoutValidation("X-Forwarded-For", requestIPAddress?.ToString());
59
60 // Forward the body of the source request to the proxy target
61 proxyRequestMessage.Content = new StreamContent(sourceRequest.Body);
62 if (sourceRequest.Headers.TryGetValue("Content-Type", out StringValues value))
63 {
64 string? contentType = value;
65 if (contentType is not null)
66 {
67 proxyRequestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
68 }
69 }
70
71 // Create an HttpClient instance using the factory and send the proxy request
72 HttpClient httpClient = _httpClientFactory.CreateClient("YourClientProxyNameHere"); // replace this with your config
73 HttpResponseMessage proxyResponse = await httpClient.SendAsync(proxyRequestMessage).ConfigureAwait(false);
74
75 // Add CORS headers to allow cross-origin requests if required
76 sourceRequest.HttpContext.Response.Headers.AddIfNotExists("Access-Control-Allow-Origin", sourceRequest.Headers.Origin.FirstOrDefault() ?? "*");
77 sourceRequest.HttpContext.Response.Headers.AddIfNotExists("Access-Control-Allow-Headers", "content-type");
78 sourceRequest.HttpContext.Response.Headers.AddIfNotExists("Access-Control-Allow-Credentials", "true");
79
80 // Read the response content and its type from the proxy response
81 string proxyResponseContent = await proxyResponse.Content.ReadAsStringAsync();
82 string? proxyResponseContentType = proxyResponse.Content.Headers.ContentType?.ToString();
83
84 // Return the response from the proxy target as the result of this action
85 return new ContentResult
86 {
87 StatusCode = (int)proxyResponse.StatusCode, // Use the same status code as the proxy response
88 Content = proxyResponseContent, // Forward the response content
89 ContentType = proxyResponseContentType // Set the content type
90 };
91 }
92}

Comments