Response Caching in .Net 6

This post explains how to configure Response Caching Middleware in an ASP.NET Core app using .Net 6. The middleware determines when responses are cacheable, stores responses, and serves responses from cache.

Caching response helps improve the performance and response time of Web APIs where it is possible. But it takes some effort to build out a caching infrastructure and manage it. The Response caching middleware which comes out of the box simplifies the implementation significantly. And also it does not require building custom implementation for caching, plus provides clients the option to choose whether to use a cached value or not.

Create a .Net 6 API, and modify the Program.cs file as below:

using Polly;
using Polly.Extensions.Http;

var builder = WebApplication.CreateBuilder(args);
var httpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    //.CircuitBreakerAsync(2, TimeSpan.FromSeconds(30));
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddResponseCaching(x => x.MaximumBodySize = 1024);
builder.Services.AddHttpClient("errorApi", c => { c.BaseAddress = new Uri("https://localhost:7250"); });
builder.Services.AddSingleton<IAsyncPolicy<HttpResponseMessage>>(httpRetryPolicy);


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

// UseCors must be called before UseResponseCaching
//app.UseCors();

app.UseResponseCaching();

app.Use(async (context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl =
        new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
        {
            Public = true,
            MaxAge = TimeSpan.FromSeconds(10)
        };

    context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = new string[] { "Accept-Encoding" };
    await next();
});

app.Run();

This API adds headers to control caching on subsequent requests:

  • Cache-Control: Caches cacheable responses for up to 10 seconds.
  • Vary: Configures the middleware to serve a cached response only if the Accept-Encoding header of subsequent requests matches that of the original request.

Add a ValuesController.cs and a simple Get endpoint:

[HttpGet]
public int Get()
{
    return DateTime.Now.Second;
}

Run the API and hit the Get endpoint through Postman and add Cache-Control Header with value “public” and you’ll observe the cached response for 10 seconds.

Retry and Circuit Breaker Policy example .Net 6 and Polly

In this example, we’ll implement the Wait and Retry and Circuit Breaker policy using .Net 6 Web API and Polly. For more details on what Circuit Breaker is, refer to the MSDN documentation.

Create a WebAPI with ValuesController in .Net 6 which will always return an Exception in the Get call.

using Microsoft.AspNetCore.Mvc;

namespace ExternalAPI.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ValuesController : ControllerBase
    {

        private readonly ILogger<ValuesController> _logger;

        public ValuesController(ILogger<ValuesController> logger)
        {
            _logger = logger;
        }

        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        {
            throw new Exception("Error");
        }

    }
}

Let’s call this ExternalAPI and running on https://localhost:7250/.

Create another .Net 6 WebAPI called CircuitBreaker.Demo and install Polly and Newtonsoft.json Nuget packages.

Configure Polly and HttpClient in Program.cs file as shown below:

using Polly;
using Polly.Extensions.Http;

var builder = WebApplication.CreateBuilder(args);
var httpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    
    .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(retryAttempt));

// Add services to the container.

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddHttpClient("errorApi", c => { c.BaseAddress = new Uri("https://localhost:7250"); });
builder.Services.AddSingleton<IAsyncPolicy<HttpResponseMessage>>(httpRetryPolicy);


var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Now add the PollyController to call the ExternalAPI using the HttpClient:

using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Polly;

namespace CircuitBreaker.Demo.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class PollyController : ControllerBase
    {

        private readonly ILogger<PollyController> _logger;
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly IAsyncPolicy<HttpResponseMessage> _policy;

        public PollyController(ILogger<PollyController> logger, IHttpClientFactory httpClientFactory, IAsyncPolicy<HttpResponseMessage> policy)
        {
            _logger = logger;
            _httpClientFactory = httpClientFactory;
            _policy = policy;
        }

        [HttpGet(Name = "GetApiResult")]
        public async Task<ActionResult<IEnumerable<string>>> Get()
        {
            var client = _httpClientFactory.CreateClient("errorApi");
            var response = await _policy.ExecuteAsync(() => client.GetAsync("values"));
            response.EnsureSuccessStatusCode();
            return JsonConvert.DeserializeObject<string[]>(await response.Content.ReadAsStringAsync());
        }

    }
}

When you run both the APIs, the call to External APIs is automatically made using Wait and Retry policy after every retryAttempt number of seconds 3 times. After that, the calls are stopped as the ExternalAPI keeps giving 500 error.

CircuitBreaker example:

Now change the Http Retry Policy line in the Program.cs file to:

var httpRetryPolicy = Policy.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
    .CircuitBreakerAsync(2, TimeSpan.FromSeconds(30));

The above line will open the circuit and will start giving Circuit Open Exception after 2 attempts and keep the circuit Open for 30 seconds, which means you cannot make further calls to the API for that duration. The Exception will now be shown as below:

Call .Net core API from Console with App Bearer token

In the following example, we’re using a .Net Core 3.1 Console App that will call API with POST request that requires Authentication with a bearer token in Authrorization Header. The token is generated by passing credentials to another API endpoint.

For more details on how to use appSettings.json file in Console App, check this post.

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;

namespace ConsoleApp1
{

    class Credentials
    {
        public string username { get; set; }
        public string password { get; set; }
    }

    class Token
    {
        public string token { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            string ResponseString = "";
            HttpWebResponse response = null;
            HttpWebResponse response2 = null;
            IConfiguration Config = new ConfigurationBuilder()
                .AddJsonFile("appSettings.json")
                .Build();

            try
            {
                var baseURL = Config.GetSection("baseURL").Value;
                var request = (HttpWebRequest)WebRequest.Create(baseURL + "/token");
                request.Accept = "application/json"; //"application/xml";
                request.Method = "POST";

		//Get credentials from config.
                var dusername = EncryptionService.Decrypt(Config.GetSection("credentials")["username"]);
                var dpassword = EncryptionService.Decrypt(Config.GetSection("credentials")["password"]);

                Credentials cred = new Credentials()
                {
                    username = dusername,
                    password = dpassword
                };

                var myContent = JsonConvert.SerializeObject(cred);

                var data = Encoding.ASCII.GetBytes(myContent);

                request.ContentType = "application/json";
                request.ContentLength = data.Length;

                using (var stream = request.GetRequestStream())
                {
                    stream.Write(data, 0, data.Length);
                }

                using (response = (HttpWebResponse)request.GetResponse())
                {
                    ResponseString = new StreamReader(response.GetResponseStream()).ReadToEnd();
                }

		//Get the token from the /token end-point and call another end-point.
                Token token = JsonConvert.DeserializeObject<Token>(ResponseString);

                var request2 = (HttpWebRequest)WebRequest.Create(baseURL + "/ProcessData");
                request2.Accept = "application/json"; //"application/xml";
                request2.Method = "POST";
				
		//Pass token in Authorization Header.
                request2.Headers["Authorization"] = "Bearer " + token.token;

                using (response2 = (HttpWebResponse)request2.GetResponse())
                {
                    ResponseString = new StreamReader(response2.GetResponseStream()).ReadToEnd();
                }

                Console.WriteLine(ResponseString);
                Environment.Exit(0);
            }
            catch (WebException ex)
            {
                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    response = (HttpWebResponse)ex.Response;
                    ResponseString = "Some error occured: " + response.StatusCode.ToString();
                }
                else
                {
                    ResponseString = "Some error occured: " + ex.Status.ToString();
                }
            }

        }
    }

}

Upload word document to .Net core WebAPI

We’ll be working with a .docm template in Word 2016 which contains a textbox. This control is given a name using the Content Control Properties.

This is available under Developer Menu in MS Word, then click on Properties.

Save this template as .docm

Create .Net Core WebAPI under .net core version 3.1 for this example. The sample code is as below. The library System.IO is used to access the value in the variable.

using System;
using System.IO;
using System.IO.Packaging;
using System.Xml;
using System.Xml.XPath;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;

namespace SampleApplicationUpload.Controllers
{
    public class UploadController : ControllerBase
    {
        [HttpPost]
        [Route("upload")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status400BadRequest)]
        public IActionResult Upload()
        {
            string filename = string.Empty;
            string extension = string.Empty;
            string result = string.Empty;
            bool uploaded = false;
            string ModifiedBy = string.Empty;
            var request = HttpContext.Request;

            if (request.Form.Files.Count > 0)
            {
                filename = request.Form.Files[0].FileName;
                extension = filename.Substring(filename.LastIndexOf(".")).ToLower();
                if (!string.IsNullOrEmpty(extension) && extension.Equals(".docm"))
                {
                    using (var memoryStream = new MemoryStream())
                    {
                        request.Form.Files[0].CopyTo(memoryStream);
                        if (memoryStream.Length < 2097152)
                        {
                            result = UploadWorStream(memoryStream, extension, out ModifiedBy, out uploaded);
                        }
                        else
                        {
                            return BadRequest("File size too large");
                        }
                    }
                }
                else
                {
                    return BadRequest("File format not recognised. Upload .docm file only");
                }
            }           
            return Ok(result);
        }
        private string UploadWorStream(MemoryStream ms, string ext, out string ModifiedBy, out bool uploaded)
        {
            string contenttype = string.Empty;
            bool erroroccurs = false;
            ModifiedBy = string.Empty;
            var xmlDoc = new XmlDocument();
            uploaded = false;
            switch (ext)
            {
                case ".docm":
                    contenttype = "application/vnd.ms-word";
                    break;
                default:
                    erroroccurs = true;
                    break;
            }

            if (!erroroccurs)
            {
                if (contenttype != string.Empty)
                {
                    Stream fs = ms;
                    var br = new BinaryReader(fs);
                    string xmlDocRelType = "http://schemas.openxmlformats.org/officeDocument/" +
                                           "2006/relationships/customXml";
                    string officeDocRelType = "http://schemas.openxmlformats.org/" +
                                              "officeDocument/2006/relationships/officeDocument";
                    PackagePart xmlPart = default(PackagePart);
                    PackagePart documentPart = default(PackagePart);
                    Uri documentUri = default(Uri);
                    Uri xmlUri = default(Uri);
                    using (Package officePackage = Package.Open(fs, FileMode.Open, FileAccess.Read))
                    {
                        foreach (var relationship1 in officePackage.GetRelationshipsByType(officeDocRelType))
                        {
                            documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative),
                                                                       relationship1.TargetUri);
                            documentPart = officePackage.GetPart(documentUri);
                            break;
                        }

                        if (documentPart != null)
                            foreach (var relationship2 in documentPart.GetRelationshipsByType(xmlDocRelType))
                            {
                                xmlUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative),
                                                                      relationship2.TargetUri);
                            }
                        xmlPart = officePackage.GetPart(xmlUri);
                        Stream inputStream = xmlPart.GetStream(FileMode.Open, FileAccess.Read);
                        var ts = new StreamReader(inputStream);
                        xmlDoc.Load(ts);
                        ts.Close();
                        ts.Dispose();
                        officePackage.Close();
                    }

                    XPathNavigator xpnGet2 = xmlDoc.CreateNavigator();
                   // string txtSample = xpnGet2.Evaluate("string(//ROOT[1]/SampleDoc[1]/@txtSample)").ToString();
                    string txtSample = Convert.ToString(xpnGet2.Evaluate("string(//ROOT/SampleDoc/@txtSample)"));
                    if (txtSample.Length > 0)
                    {
                        return txtSample;
                    }
                    else {
                        return "Unable to read document";
                    }
                }
                else
                {
                    uploaded = false;
                    return "Error";
                }
            }
            else
            {
                uploaded = false;
                return "Error";
            }
        }
    }
}

Finally try to access the WebAPI using a Postman request and check the value while debugging the end-point.

curl --location --request POST 'https://localhost:<port>/upload' \
--form 'attachment
=@/C:/Users/username/Desktop/TestDocument.docm'

Upload file in ReactJS to WebAPI

Create a new React App using create-react-app as explained in this Post. The sample ReactJS code is available here.

In the index.js file change the code as below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

For the App.js file, change the code as below:

import React from 'react';       
import './App.css';
import Fileupload from './FileUpload'
function App() {    
  return (    
    <div className="App">    
      <Fileupload></Fileupload>
    </div>    
  );    
}
export default App;

Now comes the FileUpload component as FileUpload.js:

import React from 'react';
import axios from 'axios';

class Fileupload extends React.Component {

    constructor(props) {    
            super(props);    
            this.state = {    
                    file: '',    
        };    
    }

    async submit(e) {    
        e.preventDefault();    
        const url = `http://url`;    
        const formData = new FormData();    
        formData.append('body', this.state.file);    
        const config = {    
            headers: {    
                'content-type': 'multipart/form-data',
                'token': 'xxx'                
            },
        };
        const HTTP = axios.create({
            withCredentials: true
        });

        //return post(url, formData, config);
        return HTTP.post(url, formData, config);
    }
    setFile(e) {    
        this.setState({ file: e.target.files[0] });    
    }

    render() {    
        return (    
                <div className="container-fluid">    
                        <form onSubmit={e => this.submit(e)}>    
                                <div className="col-sm-12 btn btn-primary">    
                                        File Upload    
                        </div>    
                                <h1>File Upload</h1>    
                                <input type="file" onChange={e => this.setFile(e)} />    
                                <button className="btn btn-primary" type="submit">Upload</button>    
                        </form>    
                </div>    
        )    
    }
}
export default Fileupload

Here I’m using axios to call the API via Post request. Passing the credentials in the call with create.
The token part is optional in the Headers, you can add/remove headers based on your requirement.

From the WebAPI end, I’m using .Net core. You can receive the file in the Action method as below:

[HttpPost]
[Route("upload")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public IActionResult UploadForm([FromQuery] string username)
{
	var request = HttpContext.Request;
            
	if (request.Form.Files.Count > 0)
	{
		filename = request.Form.Files[0].FileName;
		extension = filename.Substring(filename.LastIndexOf(".")).ToLower();
		if (!string.IsNullOrEmpty(extension) && extension.Equals(".docx"))
		{
			using (var memoryStream = new MemoryStream())
			{
				request.Form.Files[0].CopyTo(memoryStream);
				
				// Upload the file if less than 2 MB
				if (memoryStream.Length < 2097152)
				{
					string check = Service.CheckifValid(username);
					if (check.Equals("ok"))
					{
						//Process the stream and pass the data to the back-end in the Service Layer.
						result = Service.UploadStream(memoryStream, filename, extension, username);
					}
					else
					{
						return Ok(check);
					}
				}
				else
				{
					return BadRequest("File size too large");
				}
			}
		}
		else
		{
			return BadRequest("File format not recognised.");
		}
	}
	//Other ToDos...
}

How to use Server.MapPath in asp.net core

Server.MapPath is not available in asp.net core. Instead, you can use IWebHostEnvironment interface to access any physical contents kept in the specified path.

Below is the Controller code using the IWebHostEnvironment ContentRootPath property which gets the absolute path to the directory that contains the Application content files:

[ApiController]
public class DocumentController : ControllerBase
{
	private IDocumentService offService = null;
	private readonly IWebHostEnvironment _host;

	public DocumentController(IDocumentService offService, IWebHostEnvironment host)
	{
		this.offService = offService;
		this._host = host;
	}
	
	[HttpGet]
	[Route("some/route")]
	public IActionResult ExportToFile([FromQuery] int UserId)
	{
		var username = HttpContext.User.Identity.Name.Split('\\')[1].ToString();
		
		//get the memoryStream by passing the absolute path:
		MemoryStream memoryStream = offService.GetWordStream(_host.ContentRootPath);
		
		memoryStream.Position = 0;
		
		return File(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "File.docx"); // returns a FileStreamResult
	}
}	

Use the path in the File IO methods, example below:

File.Copy(path + "\\DocPath\\File.docm", strFileName);

In the debug mode, this path is the WebAPI Project absolute path to the directory. During runtime, this path is the absolute path where the files are hosted on the Server.

Download word file using axios get from WebAPI

The below code is using axios get method with Headers token to be passed with credentials which in this case would be Windows Authentication to the WebAPI.

The response returned by the Promise contains the header “content-disposition” which has the name of the file being downloaded.

The content-disposition header value is as below:

attachment; filename="SomeFile.docm"; filename*=UTF-8''SomeFile.docm

The content-type required for this file from the API is:

application/vnd.openxmlformats-officedocument.wordprocessingml.document

The returned response has the blob in the data property which then requires to be converted to a downloadble link. The filename is being extracted from the content-disposition header.

const HTTP1 = axios.create({
                  withCredentials: true
                });
const response = HTTP1.get('http://path/worddoc?userid=<someid>', {
		        headers: {
		          'token': 'xxxxx'
		        },
				responseType: 'blob'
		      }).then((response) => {
			
				const headerval = response.headers['content-disposition'];
				var filename = headerval.split(';')[1].split('=')[1].replace('"', '').replace('"', '');
				
				const downloadUrl = window.URL.createObjectURL(new Blob([response.data]));
				const link = document.createElement('a');
				link.href = downloadUrl;
				link.setAttribute('download', filename); //any other extension
				document.body.appendChild(link);
				link.click();
				link.remove();
			  });

For IE11 browser, downloading blob response type directly does not work. A workaround for this is to recognize the browser via User Agent modify the above code in Javscript for IE:

var ua = window.navigator.userAgent;
var msie = ua.indexOf(".NET ");

if(msie>0) {
          window.navigator.msSaveBlob(new Blob([response.data]), filename);
}

How to add folders in .net core webapi to Publish directory

When you add a folder in your project, your .csproj file gets modified as shown below:

<ItemGroup>
	<Folder Include="AppData\" />
	<Folder Include="OfflineFiles\2019\" />
</ItemGroup>

To add a folder to the Publish directory with some pre-existing contents, add the below ItemGroup in your .csproj file:

<ItemGroup>
	<Content Include="OfflineFiles\**">
		<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
	</Content>
</ItemGroup>

If you have a folder without any pre-existing content, add the below Target action in the .csproj file:

<Target Name="CreateAppDataFolder" AfterTargets="AfterPublish">
	<MakeDir Directories="$(PublishUrl)AppData" Condition="!Exists('$(PublishUrl)AppData')" />
</Target>

If you check the WebPublishMethod (.pubxml file under Properties folder) which in this is case is FileSystem, the publishUrl contains the directory where the files are published:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <WebPublishMethod>FileSystem</WebPublishMethod>
    <PublishProvider>FileSystem</PublishProvider>
    <LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
    <LastUsedPlatform>Any CPU</LastUsedPlatform>
    <SiteUrlToLaunchAfterPublish />
    <LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
    <ExcludeApp_Data>False</ExcludeApp_Data>
    <ProjectGuid>xxxx</ProjectGuid>
    <publishUrl>C:\Path</publishUrl>
    <DeleteExistingFiles>False</DeleteExistingFiles>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <SelfContained>false</SelfContained>
  </PropertyGroup>
</Project>

I’m using .net core 3.1 for the above method. This method might vary in other versions of .net core.

Authorize .net core webapi with token using Windows Authentication

Create a TokenFactory class that creates an encrypted token with the Windows authenticated username:

public class TokenFactory
{
	public static Aes aes = Aes.Create();
	public static string GenerateToken(string UserName)
	{
		//Get the required values...
		TokenDetails encrytionData = new TokenDetails
		{
			UserName = uName,
			Role = rName,
			SecretKey = sKey,
			Date = DateTime.Now,
			Interval = duration
		};
		//To Do: Generate with Cryptography encryption logic.
		var jsonSerialize = JsonConvert.SerializeObject(encrytionData);
		byte[] encrypted = Cryptography.Encrypt(jsonSerialize, aes.Key, aes.IV);
		var authToken = Convert.ToBase64String(encrypted);
		return authToken;
	}
	
	public static bool ValidateToken(string authToken)
	{
		bool isValidated;
		if (!String.IsNullOrEmpty(authToken))
		{
			var bytarr = Convert.FromBase64String(authToken);
			// Decrypt the bytes to a string.
			string dcryptToken = Cryptography.Decrypt(bytarr, aes.Key, aes.IV);
			var obj = JsonConvert.DeserializeObject<TokenDetails>(dcryptToken);
			TimeSpan timeSpan = DateTime.Now - obj.Date;
			
			if (timeSpan.TotalMinutes > CommonUtil.Duration)
			{
				isValidated = false;
			}
			else
			{
				isValidated = true;
			}
		}
		else
		{
			isValidated = false;
		}
		return isValidated;
	}
	
	public static string GetUserFromToken(string authToken)
	{
		var bytarr = Convert.FromBase64String(authToken);
		// Decrypt the bytes to a string.
		string dcryptToken = Cryptography.Decrypt(bytarr, aes.Key, aes.IV);
		var obj = JsonConvert.DeserializeObject<TokenDetails>(dcryptToken);
		return Convert.ToString(obj.UserName);
	}
}

Below is TokenDetails class:

public class TokenDetails
{
	public DateTime Date { get; set; }
	public string  SecretKey { get; set; }
	public string Role { get; set; }
	public string UserName { get; set; }
	public int Interval { get; set; }
}

Create a RequestFilter that will inherit ActionFilterAttribute to authorize any request using the [RequestFilter] attribute:

public class RequestFilter : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext context)
	{
		Microsoft.AspNetCore.Http.IHeaderDictionary headers = context.HttpContext.Request.Headers;
		var user = context.HttpContext.User.Identity.Name;
		bool IsValidated = false;
		string token = string.Empty;
		if (CheckValidHeaders(context))
		{
			if (context.HttpContext.Request.Headers.ContainsKey("token"))
			{
				token = context.HttpContext.Request.Headers["token"].ToString();
				IsValidated = TokenFactory.ValidateToken(token, user);
			}
		}

		if (!IsValidated)
		{
			if (user.Equals(TokenFactory.GetUserFromToken(token)))
			{
				token = TokenFactory.GenerateToken(user);
				context.HttpContext.Request.Headers.Remove("token");
				context.HttpContext.Request.Headers.Add("token", token);
			}
			else
			{
				//Return UnAuthorized if current Windows User is not same as the one encrypted in token.
				context.Result = new UnauthorizedResult();
			}
		}
	}

	public override void OnActionExecuted(ActionExecutedContext context)
	{
		var token = Convert.ToString(context.HttpContext.Request.Headers["token"]);
		context.HttpContext.Response.Headers.Add("token", token);
	}

	private bool CheckValidHeaders(ActionExecutingContext context)
	{
		if (String.IsNullOrEmpty(context.HttpContext.Request.Headers["token"]))
		{
			return false;
		}

		return true;
	}
}

Sample Controller implementation to fetch the token:

[ApiController]
public class UserController : ControllerBase
{
	// GET: api/User
	[Route("User")]
	[Authorize]
	[HttpGet]
	public IActionResult GetUser()
	
	{
		var user = HttpContext.User.Identity.Name;
		string token = TokenFactory.GenerateToken(user);
		//bool isValid = TokenFactory.ValidateToken(token, user);
		return Ok(token);
	}
}

Use the [Authorize] attribute if you’ve also enabled Anonymous Authentication to allow OPTIONS request. This is explained here in further detail.

You can Authorize your users with the [RequestFilter] to filter out the Unauthorized candidates.

[RequestFilter]
[ApiController]
[Authorize]
public class AppraisalsHomeController : ControllerBase
{
	//To Do...
}