Caching problem while using HttpActionExecutedContext in web API c# MVC - asp.net-mvc

I am facing worst & awkward issue of my life.
I am using HttpActionExecutedContext for caching my WEB API end points.
My web API is working properly in case of caching, but when I have updated the data & at that time I wanted to reset the caching at that time problem is arises.
Problem 1 :-
When I have deleted the bin folder from the server, then also API was sending the data to me.
(I have consumed API in ANDROID phone, I have tested in 2 phones after deleting the BIN bolder, In 1st phone API was giving data even after BIN DELETION & in 2nd phone API was giving partial data such as 1 end point was working but another was not).
How can this be possible ?
Problem 2 :-
Where data is saved when we use HttpActionExecutedContext. Wheather data is saved application pool or something ?
Problem 3 :-
How to clear the cache of WEB API.
Here is the code of WEB API.
public class CacheFilter : ActionFilterAttribute
{
public int TimeDuration { get; set; }
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
actionExecutedContext.Response.Headers.CacheControl = new System.Net.Http.Headers.CacheControlHeaderValue
{
MaxAge = TimeSpan.FromMinutes(1440),
MustRevalidate = true,
Public = true
};
}
}
Controller Code
[HttpGet]
[Route("SubCategory")]
[CacheFilter()]
public string SubCategory()
{
BAL_CAT_ALL obj = new BAL_CAT_ALL();
var data = obj.GetAllSubCategory();
return data;
}

While issuing a request to server when the data has been updated , can you please add the below 2 headers in your request:-
'Cache-Control', 'no-cache' 'Pragma', 'no-cache'
Give it a try and let us know if the issue is resolved or ?

Related

How to handle user OIDC tokens in Blazor Server when the browser is refreshed and the cookie’s tokens are invalid?

Microsoft recommend against using HttpContext in Blazor Server (here). To work around the issue of how to pass user tokens to a Blazor Server app, Microsoft recommend storing the tokens in a Scoped service (here). Jon McGuire’s blog suggests a similar approach that stores the tokens in Cache (here).
Microsoft’s approach above works just fine as long as the user stays within the same Blazor Server connection. However if the access_token is refreshed and the user then reloads the page either by pressing F5 or by pasting a URL into the address bar, then an attempt is made to retrieve the tokens from the cookie. By this time, the access_token and refresh_token in the cookie are no longer valid. Jon McGuire mentions this problem at the end of his blog post and refers to it as Stale Cookies (here). He gives hints about a possible solution, but is very light on implementation instructions. There are many comments at the bottom of that post from people unable to implement a solution, with no apparent working solution suggested. I spent a lot of time searching for a solution and all I found were people asking for one and not receiving any answers that worked.
Having found a solution that seems to work well, and also seems fairly principled, I thought it might be worth sharing my solution here. I would welcome any constructive criticism or suggestions for any significant improvements.
Edit 20220715: After some feedback on our approach from Dominic Baier we removed our Scoped UserSubProvider service in favour of using AuthenticationStateProvider instead. This has simplified our approach. I have edited the following answer to reflect this change.
This approach combines advice from Microsoft on how to pass tokens to a Blazor Server app (here), with server side storage of tokens in a Singleton service for all users (inspired by Dominick Baier’s Blazor Server sample project on GitHub here).
Instead of capturing the tokens in the _Host.cshtml file and storing them in a Scoped service (like Microsoft do in their example), we use the OnTokenValidated event in a similar way to Dominick Baier’s sample, storing the tokens in a Singleton service that holds tokens for all Users, we call this service ServerSideTokenStore.
When we use our HttpClient to call an API and it needs an access_token (or refresh_token), then it retrieves the User’s sub from an injected AuthenticationStateProvider, uses it to call ServerSideTokenStore.GetTokensAsync(), which returns a UserTokenProvider (similar to Microsoft’s TokenProvider) containing the tokens. If the HttpClient needs to refresh the tokens then it populates a UserTokenProvider and saves it by calling ServerSideTokenStore.SetTokensAsync().
Another issue we had was if a separate instance of the web browser is open while the app restarts (and therefore loses the data held in ServerSideTokenStore) the user would still be authenticated using the cookie, but we’ve lost the access_token and refresh_token. This could happen in production if the application is restarted, but happens a lot more frequently in a dev environment. We work around this by handling OnValidatePrincipal and calling RejectPrincipal() if we cannot get a suitable access_token. This forces a round trip to IdentityServer which provides a new access_token and refresh_token. This approach came from this stack overflow thread.
(For clarity/focus, some of the code that follows excludes some standard error handling, logging, etc.)
Getting the User sub claim from AuthenticationStateProvider
Our HttpClient gets the user's sub claim from an injected AuthenticationStateProvider. It uses the userSub string when calling ServerSideTokenStore.GetTokensAsync() and ServerSideTokenStore.SetTokensAsync().
var state = await AuthenticationStateProvider.GetAuthenticationStateAsync();
string userSub = state.User.FindFirst("sub")?.Value;
UserTokenProvider
public class UserTokenProvider
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
public DateTimeOffset Expiration { get; set; }
}
ServerSideTokenStore
public class ServerSideTokenStore
{
private readonly ConcurrentDictionary<string, UserTokenProvider> UserTokenProviders = new();
public Task ClearTokensAsync(string userSub)
{
UserTokenProviders.TryRemove(userSub, out _);
return Task.CompletedTask;
}
public Task<UserTokenProvider> GetTokensAsync(string userSub)
{
UserTokenProviders.TryGetValue(userSub, out var value);
return Task.FromResult(value);
}
public Task StoreTokensAsync(string userSub, UserTokenProvider userTokenProvider)
{
UserTokenProviders[userSub] = userTokenProvider;
Return Task.CompletedTask;
}
}
Startup.cs ConfigureServices (or equivalent location if using .NET 6 / whatever)
public void ConfigureServices(IServiceCollection services)
{
// …
services.AddAuthentication(…)
.AddCookie(“Cookies”, options =>
{
// …
options.Events.OnValidatePrincipal = async context =>
{
if (context.Principal.Identity.IsAuthenticated)
{
// get user sub
var userSub = context.Principal.FindFirst(“sub”).Value;
// get user's tokens from server side token store
var tokenStore =
context.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var tokens = await tokenStore.GetTokenAsync(userSub);
if (tokens?.AccessToken == null
|| tokens?.Expiration == null
|| tokens?.RefreshToken == null)
{
// if we lack either an access or refresh token,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
// if the access token has expired, attempt to refresh it
if (tokens.Expiration < DateTimeOffset.UtcNow)
{
// we have a custom API client that takes care of refreshing our tokens
// and storing them in ServerSideTokenStore, we call that here
// …
// check the tokens have been updated
var newTokens = await tokenStore.GetTokenAsync(userSubProvider.UserSub);
if (newTokens?.AccessToken == null
|| newTokens?.Expiration == null
|| newTokens.Expiration < DateTimeOffset.UtcNow)
{
// if we lack an access token or it was not successfully renewed,
// then reject the Principal (forcing a round trip to the id server)
context.RejectPrincipal();
return;
}
}
}
}
}
.AddOpenIdConnect(“oidc”, options =>
{
// …
options.Events.OnTokenValidated = async n =>
{
var svc = n.HttpContext.RequestServices.GetRequiredService<IServerSideTokenStore>();
var culture = new CultureInfo(“EN”) ;
var exp = DateTimeOffset
.UtcNow
.AddSeconds(double.Parse(n.TokenEndpointResponse !.ExpiresIn, culture));
var userTokenProvider = new UserTokenProvider()
{
AcessToken = n.TokenEndpointResponse.AccessToken,
Expiration = exp,
RefreshToken = n.TokenEndpointResponse.RefreshToken
}
await svc.StoreTokensAsync(n.Principal.FindFirst(“sub”).Value, userTokenProvider);
};
// …
});
// …
}

Web API - Keeping the connection opened on a long import process

We have an MVC5 SPA application where one of our Web API controllers needs to process the import of a file. This process is made once per month, and can take up to 5 hours (our client wants this import to be made via Web API).
Our problem is that the connection is lost (ERR_CONNECTION_RESET) after one hour, and we need to keep it opened during the entire process.
We think one option is to send partial content from the server, to let the browser know the connection is still active and processing. In MVC Controller, something like this:
public class PartialActionResult : ActionResult
{
private Counter _counter;
private Action<Counter> _counterIncrease;
public PartialActionResult(Action<Counter> counterIncrease)
{
_counter = new Counter();
_counterIncrease = counterIncrease;
}
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.ContentType = "text/html";
context.HttpContext.Response.BufferOutput = true;
if (context.HttpContext.Response.IsClientConnected)
{
while (_counter.Value < 100)
{
_counterIncrease(_counter);
context.HttpContext.Response.Write("processing...");
if (_counter.Value == 100)
context.HttpContext.Response.Write("OK");
context.HttpContext.Response.Flush();
}
}
}
}
With this, we can pass a delegate as reference, and from there we can update the information of the import status.
Is there any option for Web API, as this ActionResult only works for MVC Controllers? Other than that, any option to maintain the connection opened? BTW we cannot use signalR, just in case that might work.
Thanks in advance

Time out 500 error on Edmx

I developed a website using Asp.Net MVC and Edmx database and I published this website on azure and my database is also on azure and I've a functionality on website that uploads excel record into database and that excel sheet contain almost 18000 records every time I upload that sheet it throw Timeout error after some time so what should I do.
Initially I was not using any command Timeout but after doing some research I'm using this in constructor
public ProfessionalServicesEntities()
: base("name=ProfessionalServicesEntities")
{
this.Database.CommandTimeout = 10000;
//this.Database.CommandTimeout = 0; //I tried this too.
//((IObjectContextAdapter)this).ObjectContext.CommandTimeout = 3600;
}
Here is the code of
function :-
public void SaveEquipments(IEnumerable<EquipSampleEntity> collection)
{
using (ProfessionalServicesEntities db = new ProfessionalServicesEntities())
{
string modelXml = XmlSerialization.ListToXml(collection.Where(x=>x.Type == Model).ToList());
string accessoryXml = XmlSerialization.ListToXml(collection.Where(x => x.Type == Accessory).ToList());
db.ImportEquipmentFile(modelXml, accessoryXml);
}
}
here is context file code for SP:-
public virtual int ImportEquipmentFile(string modelXml, string accessoryXml)
{
var modelXmlParameter = modelXml != null ?
new ObjectParameter("ModelXml", modelXml) :
new ObjectParameter("ModelXml", typeof(string));
var accessoryXmlParameter = accessoryXml != null ?
new ObjectParameter("AccessoryXml", accessoryXml) :
new ObjectParameter("AccessoryXml", typeof(string));
return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction("ImportEquipmentFile", modelXmlParameter, accessoryXmlParameter);
}
You may be processing the excel on upload itself and processing it row by row. You have two options, one is to schedule a background job to pickup the upload file and insert it to DB and complete the request.
Next option is to read the whole file in one go and do a single bulk insert into the DB.
There are too many things that can cause this. In Azure App Service there is a Front-end which has a timeout of 240 seconds. If your application takes more time, then you might run into this. This could be one of the probable causes.
In order to understand what is happening. Enabled Web Server Logging and Failed Request Tracing.
See this for how to proceed further: https://learn.microsoft.com/en-us/azure/app-service-web/web-sites-enable-diagnostic-log

MachineKeyDataProtector - Invalid link when confirmation email sent through background job

I've been pulling my hair out over this. Anytime a user registration email is sent out via my windows service (background task), I get an "Invalid link".
My setup
I'm using Hangfire as a windows service on our development server. This is where the problematic GenerateEmailConfirmationToken call is happening. It's in a completely different context, outside of the ASP.NET pipeline. So I have setup machineKey values to correspond with that in the web.config of the MVC application:
In the app.config of the Windows Service Console project, which transforms to MyApp.exe.config, I have a machineKey element
In the MVC 5 project - I have a machineKey element that matches the MyApp.exe.config machineKey element.
I've verified that BOTH of these have the same machine key element data.
The Problem
When I generate a user using the ASP.NET MVC context and pipeline (IE without going through the Hangfire Background job processing), the link works fine.
When I use the background job processor, I always get invalid link. I'm all out of ideas here.
Why is this happening? Is it because the token is being generated in a different thread? How do I get around this?
Relevant code for the various projects
IoC Bootstrapping
Gets called by both applications (Windows Service and MVC Web App)
container.Register<IUserTokenProvider<AppUser, int>>(() => DataProtector.TokenProvider, defaultAppLifeStyle);
DataProtector.cs
public class DataProtector
{
public static IDataProtectionProvider DataProtectionProvider { get; set; }
public static DataProtectorTokenProvider<AppUser, int> TokenProvider { get; set; }
static DataProtector()
{
DataProtectionProvider = new MachineKeyProtectionProvider();
TokenProvider = new DataProtectorTokenProvider<AppUser, int>(DataProtectionProvider.Create("Confirmation", "ResetPassword"));
}
}
Things I've Tried
Using a DpapiDataProtectionProvider
Custom MachineKeyProtectionProvider from Generating reset password token does not work in Azure Website
The MachineKeyProtectionProvider.cs code is exactly as the linked post above.
I've also tried other purposes like "YourMom" and "AllYourTokensAreBelongToMe" to no avail. Single purposes, multiple purposes - it doesn't matter - none work.
I'm also calling HttpUtility.UrlEncode(code) on the code that gets generated in both places (Controller and Background Job).
Solution
igor got it right, except it was not a code issue. It was because of a rogue service picking up the job, which had a different machine key. I had been staring at the problem so long that I did not see a second service running.
As I understand your problem there are 2 possible places where failure could occur.
1. MachineKey
It could be that the MachineKey itself is not producing a consistent value between your 2 applications. This can happen if your machineKey in the .config file is not the same in both applications (I did read that you checked it but a simple type-o, added space, added to the wrong parent element, etc. could lead to this behavior.). This can be easily tested to rule it out as a point of failure. Also the behavior might be different depending on the referenced .net framework, MachineKey.Protect
The configuration settings that are required for the MachineKeyCompatibilityMode.Framework45 option are required for this method even if the MachineKeySection.CompatibilityMode property is not set to the Framework45 option.
I created a random key pair for testing and using this key I generated a test value I assigned to variable validValue below in the code. If you copy/paste the following section into your web.config and app.config the Unprotect of that keyvalue will work.
web.config / app.config
<system.web>
<httpRuntime targetFramework="4.6.1"/>
<machineKey decryption="AES" decryptionKey="9ADCFD68D2089D79A941F9B8D06170E4F6C96E9CE996449C931F7976EF3DD209" validation="HMACSHA256" validationKey="98D92CC1E5688DB544A1A5EF98474F3758C6819A93CC97E8684FFC7ED163C445852628E36465DB4E93BB1F8E12D69D0A99ED55639938B259D0216BD2DF4F9E73" />
</system.web>
Service Application Test
class Program
{
static void Main(string[] args)
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
}
}
Mvc Controller (or Web Api controller) Test
public class WebTestController : Controller
{
// GET: WebTest
public ActionResult Index()
{
// should evaluate to SomeTestString
const string validValue = "03AD03E75A76CF13FDDA57425E9D362BA0FF852C4A052FD94F641B73CEBD3AC8B2F253BB45550379E44A4938371264BFA590F9E68E59DB57A9A4EB5B8B1CCC59";
var unprotected2 = MachineWrapper.Unprotect(validValue);
return View(unprotected2);
}
}
Common Code
using System;
using System.Linq;
using System.Text;
using System.Web.Security;
namespace Common
{
public class MachineWrapper
{
public static string Protect()
{
var testData = "SomeTestString";
return BytesToString(MachineKey.Protect(System.Text.Encoding.UTF8.GetBytes(testData), "PasswordSafe"));
}
public static string Unprotect(string data)
{
var bytes = StringToBytes(data);
var result = MachineKey.Unprotect(bytes, "PasswordSafe");
return System.Text.Encoding.UTF8.GetString(result);
}
public static byte[] StringToBytes(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string BytesToString(byte[] bytes)
{
var hex = new StringBuilder(bytes.Length * 2);
foreach (byte b in bytes)
hex.AppendFormat("{0:x2}", b);
return hex.ToString().ToUpper();
}
}
}
If this passes both Console and the Web Application will get the same value and not throw a CryptographicException message Error occurred during a cryptographic operation. If you want to test with your own keys just run Protect from the common MachineWrapper class and record the value and re-execute for both apps.
2. UserManager uses Wrong Type
I would start with the previous section BUT the other failure point is that your custom machine key provider is not being used by the Microsoft.AspNet.Identity.UserManager. So here are some questions/action items that can help you figure out why this is happening:
Is container.Register the Unity IoC framework or are you using another framework?
Are you sure that your Di framework is also injecting that instance in the Microsoft.AspNet.Identity.UserManager in both the Service application as well as the Web application?
Have put a break point in public byte[] Protect of your MachineKeyDataProtector class to see if this is called in both the Service application as well as the Web application?
From examples I have seen so far (including the one you posted with the custom MachineKey solution) you need to manually bootstrap the type during application startup but then again I have not ever tried to hook into the Identity framework to replace this component using DI.
If you look at the default Visual Studio template code that is provided when you create a new MVC application the code file App_Start\IdentityConfig.cs would be the place to add this new provider.
Method:
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
Replace
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"));
}
With this
var provider = new MachineKeyProtectionProvider();
manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(provider.Create("ResetPasswordPurpose"));
And this has to be configured for both applications if you are not using a common library where this is configured.

mvc4 acting as a gateway

what i want to achieve is:
a central server connected to the database, using entity framework
a server who for some reason can't reach the database but forward the requests to the central server (not all of them only the one who require the database)
some httpclients who can't reach the central server nor the database but only the middle server
I've already tried with success modifying the controller method to create an http client who redo the reuest to the cenral server, but that seems the worst way to me, especially because i've lots of controllers and methods
public User GetUser(int id)
{
if (Properties.Settings.Default.SyncEnabled)
{
System.Net.Http.HttpClient httpClient = new System.Net.Http.HttpClient();
httpClient.BaseAddress = Properties.Settings.Default.SyncAddress;
var result = httpClient.GetAsync(this.Request.Url.PathAndQuery).Result;
return this.Content(result.Content.ReadAsStringAsync().Result, result.Content.Headers.ContentType.MediaType);
}
else
{
User user = DbContext.Users.Find(id);
user.LastOnline = DateTime.Now;
DbContext.SaveChanges();
return user;
}
}
i was thinking about using register route, but i'd like to know if it's a good idea, before reading how routes works...
i'm also intrested in how would you implement that.
Since nobody gave me an answer i'm gonna suggest myself to try with this:
http://www.iis.net/learn/extensions/url-rewrite-module/reverse-proxy-with-url-rewrite-v2-and-application-request-routing
seems that reverse proxy can do the trick, but i need to do it with two separate iis

Resources