WWF Workflow persistence store exception - workflow-foundation-4

We have a system that is using "WWF" as its workflow engine and the requests to proceed with the workflow faild frequently and the log is filled with this exception.
System.Runtime.DurableInstancing.InstancePersistenceException: The SqlWorkflowInstanceStore lock does not exist in the database. This could have occurred because the SQL Server is busy or because the connection was temporarily lost.
and it fires in this event (application.Aborted = (e) =>{}), Any ideas about how to solve this issue ?
Here is how I load the workflow and relase the lock
//Create an instance of the workflow and its application and associate with workflow application.
Activity workflow = Activator.CreateInstance(workflowType) as Activity;
WorkflowApplication application = new WorkflowApplication(workflow);
application.SynchronizationContext = SyncSynchronizationContext.SingletonInstance;
//Hold the workflow store
application.InstanceStore = CreateInstanceStore(WorkflowDatabaseConnectionString);
var instanceHandle = application.InstanceStore.CreateInstanceHandle(guid);
var ownerCommand = new CreateWorkflowOwnerCommand();
var view = application.InstanceStore.Execute(instanceHandle, ownerCommand, TimeSpan.FromDays(30));
application.InstanceStore.DefaultInstanceOwner = view.InstanceOwner;
// Do whatever needs to be dome with multiple WorkflowApplications
if (pParticipant != null)
application.Extensions.Add(pParticipant);
//Register workflow application services from the external world
ExternalRegisteredServices.ForEach(service => application.Extensions.Add(service));
ReadOnlyCollection<BookmarkInfo> currentBookmarks = null;
Dictionary<string, object> wfContextBag = null;
application.PersistableIdle = (workflowApplicationIdleEventArgs) =>
{
currentBookmarks = workflowApplicationIdleEventArgs.Bookmarks;
wfContextBag = workflowApplicationIdleEventArgs
.GetInstanceExtensions<WorkflowContext>()
.First()
.GetBag();
return PersistableIdleAction.Unload;
};
application.OnUnhandledException = (e) =>
{
if (wfUnhandledExceptionEventHandler != null)
wfUnhandledExceptionEventHandler(e);
return UnhandledExceptionAction.Abort;
};
application.Aborted = (e) =>
{
if (wfAbortedEventHandler != null)
wfAbortedEventHandler(e);
};
application.Completed = (e) =>
{
if (wfCompletedEventHandler != null)
wfCompletedEventHandler(e);
};
application.Load(guid);
BookmarkResumptionResult resumptionResult = BookmarkResumptionResult.NotFound;
if (!string.IsNullOrEmpty(bookmarkName))
resumptionResult = application.ResumeBookmark(bookmarkName, null);
if (resumptionResult != BookmarkResumptionResult.Success)
currentBookmarks = application.GetBookmarks();
var deleteOwnerCommand = new DeleteWorkflowOwnerCommand();
application.InstanceStore.Execute(instanceHandle, deleteOwnerCommand, TimeSpan.FromSeconds(30));
instanceHandle.Free();

You can try using application.Unload(), this will help you to solve your Abort exception. By default, the unload operation must complete in 30 seconds. However, if it doesn't happens then Abort event gets triggered.

Related

Azure AD SSO in ASP.NET - How to update token silently?

I have a quite simple ASP.NET project that has the Azure AD Authentication installed.
It uses the CookieAuthentication by default and uses the Azure AD SSO to login.
So what I can't understand is that if I login and left the page opened for 1 hour - which is the Azure AD access token expiration time, it just stops working.
To avoid this, I tried to update the access token silently before it is expired but failed.
Not even sure why the app stops working as it's using Cookie for authorization and it uses the Azure AD login only for Authentication.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = (context) =>
{
var threshold = DateTime.UtcNow.AddMinutes(55);
if (context.Properties.ExpiresUtc < threshold)
{
var authManager = context.OwinContext.Authentication;
string signedInUserID = context.Identity.FindFirst(System.IdentityModel.Claims.ClaimTypes.NameIdentifier).Value;
if (authContext == null)
authContext = new AuthenticationContext(Authority, new ADALTokenCache(signedInUserID));
ClientCredential credential = new ClientCredential(clientId, appKey);
try
{
var result = authContext.AcquireTokenSilentAsync(graphResourceId, clientId).Result;
}
catch (AggregateException ex)
{
if (ex.InnerException.GetType() == typeof(AdalSilentTokenAcquisitionException))
{
var result = authContext.AcquireTokenAsync(graphResourceId, credential).Result;
}
}
}
return Task.FromResult(0);
}
}
});
This is the ADALTokenCache.
public class ADALTokenCache : TokenCache
{
private ApplicationDbContext db = new ApplicationDbContext();
private string userId;
private UserTokenCache Cache;
public ADALTokenCache(string signedInUserId)
{
// Associate the cache to the current user of the web app
userId = signedInUserId;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// Look up the entry in the database
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
// Place the entry in memory
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits,"ADALCache"));
}
// Clean up the database
public override void Clear()
{
base.Clear();
var cacheEntry = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
db.UserTokenCacheList.Remove(cacheEntry);
db.SaveChanges();
}
// Notification raised before ADAL accesses the cache.
// This is your chance to update the in-memory copy from the DB, if the in-memory version is stale
void BeforeAccessNotification(TokenCacheNotificationArgs args)
{
if (Cache == null)
{
// First time access
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
}
else
{
// Retrieve last write from the DB
var status = from e in db.UserTokenCacheList
where (e.webUserUniqueId == userId)
select new
{
LastWrite = e.LastWrite
};
// If the in-memory copy is older than the persistent copy
if (status.First().LastWrite > Cache.LastWrite)
{
// Read from from storage, update in-memory copy
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == userId);
}
}
this.Deserialize((Cache == null) ? null : MachineKey.Unprotect(Cache.cacheBits, "ADALCache"));
}
// Notification raised after ADAL accessed the cache.
// If the HasStateChanged flag is set, ADAL changed the content of the cache
void AfterAccessNotification(TokenCacheNotificationArgs args)
{
// If state changed
if (this.HasStateChanged)
{
Cache = new UserTokenCache
{
webUserUniqueId = userId,
cacheBits = MachineKey.Protect(this.Serialize(), "ADALCache"),
LastWrite = DateTime.Now
};
// Update the DB and the lastwrite
db.Entry(Cache).State = Cache.UserTokenCacheId == 0 ? EntityState.Added : EntityState.Modified;
db.SaveChanges();
this.HasStateChanged = false;
}
}
void BeforeWriteNotification(TokenCacheNotificationArgs args)
{
// If you want to ensure that no concurrent write take place, use this notification to place a lock on the entry
var t = args;
}
public override void DeleteItem(TokenCacheItem item)
{
base.DeleteItem(item);
}
}
This is what I tried, but not working.
Would appreciate any help.
Thanks in advance.

Cosmos DB partitioned access to a database

I am implementing a multi-tenant application using cosmosDB. I am using partition keys to separate multiple users data. Following best practices i am trying to allow each tenant to have its own db access token.
I create a user and permission and use the created token to access the partition. But I get the following error:
Partition key provided either doesn't correspond to definition in the collection or doesn't match partition key field values specified
in the document.
ActivityId: 1659037a-118a-4a2d-8615-bb807b717fa7, Microsoft.Azure.Documents.Common/1.22.0.0, Windows/10.0.17134
documentdb-netcore-sdk/1.9.1
My code goes as follows:
Constructor Initiates the client
public Projects (CosmosDbConfig cosmosConfig)
{
config = cosmosConfig;
client = new DocumentClient(new Uri(config.Endpoint), config.AuthKey);
collectionUri = UriFactory.CreateDocumentCollectionUri(config.Database, config.Collection);
config.AuthKey = GetUserToken().Result;;
client = new DocumentClient(new Uri(config.Endpoint), config.AuthKey);
}
The get user function creates the user and retrieves the token. User Ids are partition keys.
private async Task<string> GetUserToken()
{
User user = null;
try
{
try
{
user = await client.ReadUserAsync(UriFactory.CreateUserUri(config.Database, config.PartitionKey));
var permission = await GetorCreatePermission(user, config.Collection, config.PartitionKey);
return permission.Token;
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
if (user == null)
{
user = new User
{
Id = config.PartitionKey
};
user = await client.CreateUserAsync(UriFactory.CreateDatabaseUri(config.Database), user);
var permission = await GetorCreatePermission(user, config.Collection, config.PartitionKey);
return permission.Token;
}
else
{
throw new Exception("");
}
}
catch (Exception ex)
{
throw ex;
}
}
Permission are done per collections and holds the collection name as ID since Ids are unique per user.
private async Task<Permission> GetorCreatePermission(User user,
string collection,
string paritionKey)
{
var permDefinition = new Permission
{
Id = collection,
PermissionMode = PermissionMode.All,
ResourceLink = collectionUri.OriginalString,
ResourcePartitionKey = new PartitionKey(paritionKey),
};
var perms = client.CreatePermissionQuery(user.PermissionsLink).AsEnumerable().ToList();
var perm = perms.FirstOrDefault(x => x.Id == collection);
if (perm != null)
{
return perm;
}
else
{
var result = await client.CreatePermissionAsync(user.SelfLink, permDefinition);
perm = result.Resource;
return perm;
}
}
The create function utilizes the new client and this where the error occurs.
public async Task<string> Create(Project p)
{
var result = await client.CreateDocumentAsync(collectionUri, p, new RequestOptions()
{ PartitionKey = new PartitionKey(config.PartitionKey),
});
var document = result.Resource;
return document.Id;
}
Since error says that partition key is incorrect i can suggest you try define partition key pathes while creating collection:
var docCollection = new DocumentCollection();
docCollection.Id = config.CollectionName;
docCollection.PartitionKey.Paths.Add(string.Format("/{0}", config.PartitionKey );
collectionUri = UriFactory.CreateDocumentCollectionUri(config.Database, docCollection);

UserManager - Access to Disposed Object

Using ASP.NET Core 2.
I am trying to init some application-wide data. For that I created a new class called DbInitializer, an interface called IDbInitializer, and registered them with AddScoped in Startup.ConfigureServices.
Then from Startup.Configure, I call DbInitializer.Initialize() and inside that method there are a few calls to UserManager:
if (appDbContext.Roles.All(i => i.Name != adminGroupName))
{
await roleManager.CreateAsync(new IdentityRole(adminGroupName));
};
var role = appDbContext.Roles.First(i => i.Name == adminGroupName);
if (appDbContext.Users.All(i => i.UserName != adminSettings.Username))
{
await userManager.CreateAsync(new ApplicationUser { UserName = adminSettings.Username,
Email = adminSettings.Username,
FirstName = adminSettings.FirstName,
LastName = adminSettings.LastName,
EmailConfirmed = true }, adminSettings.Password);
}
var adminUser = appDbContext.Users.First(i => i.UserName == adminSettings.Username);
if (!appDbContext.UserRoles.Any(i => i.RoleId == role.Id && i.UserId == adminUser.Id))
{
await userManager.AddToRoleAsync(adminUser, role.Name);
}
The problem is that I am getting, at random the exception
Cannot access a disposed object
from userManager.CreateAsync or userManager.AddToRoleAsync.
I believe the problem is that your DbInitializer.Initialize() method call (which should be marked async and return a Task) is not properly being awaited, since the Startup.Configure method must return void. It should work if you change your call to be DbInitializer.Initialize().Wait() which will wait for the task to complete.

Set a job to failed using Hangfire with ASP.NET?

I have an ASP.NET app which sends emails whenever the user signs up in the web site. I'm using hangfire in order to manage the jobs and postal in order to send emails.
It all works great, but here's the thing:
I want the superuser to change how many times the APP can send the email before deleting the job.
Here's my code
public static void WelcomeUser(DBContexts.Notifications not)
{
try{
var viewsPath = Path.GetFullPath(HostingEnvironment.MapPath(#"~/Views/Emails"));
var engines = new ViewEngineCollection();
engines.Add(new FileSystemRazorViewEngine(viewsPath));
Postal.EmailService service = new Postal.EmailService(engines);
WelcomeUserMail welcomeUserMail = new WelcomeUserMail();
welcomeUserMail.To = not.ReceiverEmail;
welcomeUserMail.UserEmail = not.ReceiverEmail;
welcomeUserMail.From = BaseNotification.GetEmailFrom();
service.Send(welcomeUserMail);
}
catch(Exception e)
{
DBContexts.DBModel dbModel = new DBModel();
DBContexts.Notifications notificacionBD = dbModel.Notifications.Find(not.NotificationID);
notificacionBD.Status = false;
notificacionBD.Timestamp = DateTime.Now;
notificacionBD.Error = e.Message;
int numberOfRetriesAllowed = ParameterHelper.getNumberOfRetriesAllowed();
if (notificacionBD.Retries > numberOfRetriesAllowed)
{
//In this case Hangfire won't put this job in the failed section but rather in the processed section.
dbModel.SaveChanges();
}
else
{
notificacionBD.Retries++;
dbModel.SaveChanges();
throw new Exception(e.Message);
}
}
}
Why not just add attributes to handle it automatically?
[AutomaticRetry(Attempts = 10, LogEvents = true, OnAttemptsExceeded = AttemptsExceededAction.Delete)]
public void MyTask(){
//doing stuff
}
Or you could just make your own attribute that mimics the AutommaticRetryAttribute class but you handle it how you want?
https://github.com/HangfireIO/Hangfire/blob/a5761072f18ff4caa80910cda4652970cf52e693/src/Hangfire.Core/AutomaticRetryAttribute.cs

Workflow application.PersistableIdle event not firing

Hi I am new to Windows Workflow. This may be very easy, but I am stuck on this from long.
I have a state machine workflow, in which i have a workflow host class.
Persistence is not working in this code. While debugging pointer never goes to application.persistableIdle event.
I use custom input argument, for which I have set as Serializable.
below is my code of the host class:
static InstanceStore instanceStore;
static AutoResetEvent instanceUnloaded = new AutoResetEvent(false);
static Activity activity = new Activity1();
static Guid id = new Guid();
static int intContractHeaderKey;
static Contract contract = new Contract();
public ContractActivityHost(Guid wfid, Int32 contractHeaderID)
{
SetupInstanceStore();
StartAndUnloadInstance(contractHeaderID);
if (intContractHeaderKey > 0)
{
LoadAndCompleteInstance(id, intContractHeaderKey);
}
}
static void StartAndUnloadInstance(Int32 contractHeaderID)
{
contract = new Contract();
//var objContract = new object();
var input = new Dictionary<string, object>
{
{"TheContract", contract}
};
input.Add("ContractHeaderKey", contractHeaderID);
WorkflowApplication application = new WorkflowApplication(activity, input);
application.InstanceStore = instanceStore;
//returning IdleAction.Unload instructs the WorkflowApplication to persists application state and remove it from memory
application.PersistableIdle = (e) =>
{
return PersistableIdleAction.Unload;
};
application.Unloaded = (e) =>
{
instanceUnloaded.Set();
};
//application.Idle = (e) =>
// {
// //application.Unload();
// instanceUnloaded.Set();
// };
//This call is not required
//Calling persist here captures the application durably before it has been started
application.Persist();
id = application.Id;
application.Run();
instanceUnloaded.WaitOne();
//application.Unload();
//contract = (Contract)objContract;
intContractHeaderKey = contract.ContractID;
}
static void LoadAndCompleteInstance(Guid wfid, Int32 contractHeaderID)
{
//string input = Console.ReadLine();
while (!contract.ContractWFPause)
{
contract.FireContract(contract.ContractID);
WorkflowApplication application = new WorkflowApplication(activity);
application.InstanceStore = instanceStore;
application.Completed = (workflowApplicationCompletedEventArgs) =>
{
//Console.WriteLine("\nWorkflowApplication has Completed in the {0} state.", workflowApplicationCompletedEventArgs.CompletionState);
strWFStatus = "Completed";
};
application.Unloaded = (workflowApplicationEventArgs) =>
{
//Console.WriteLine("WorkflowApplication has Unloaded\n");
strWFStatus = "Unloaded";
instanceUnloaded.Set();
};
application.Load(wfid);
instanceUnloaded.WaitOne();
}
}
private static void SetupInstanceStore()
{
instanceStore =
new SqlWorkflowInstanceStore(#"Data Source=.;Initial Catalog=WorkflowInstanceStore;Integrated Security=True;");
InstanceHandle handle = instanceStore.CreateInstanceHandle();
InstanceView view = instanceStore.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(10));
handle.Free();
instanceStore.DefaultInstanceOwner = view.InstanceOwner;
}
I have been trying to resolve this from long time, but not sure where I am missing anything. I have gone through couple of sample applications and changed my code to match the flow and logic, but still it does not work.
After application.persist, record is inserted in [System.Activities.DurableInstancing].[InstancesTable] view.
But debug pointer does not move beyond instanceUnloaded.WaitOne();
it actually goes to idle state. if I uncomment application.idle event, it goes in that event code.
Any help to resolve this would be great.
Thanks.
Please check If you have added the below details
instanceStore = new SqlWorkflowInstanceStore(ConfigurationManager.ConnectionStrings["WFPersistenceDb"].ConnectionString);
StateMachineStateTracker.Promote(this.instanceStore);

Resources