WF 4.5 bookmark and persistence in hosted web app - c#

I developed workflow for an approval system. I used Bookmarks and
Persistence features to persist app state before unloading.
Workflow
Send an email to P1 for approval.
If P1 approved then next send next approval request to P2.
I am using WorkflowApplication class to invoke workflow from my asp.net application.
I am running my asp.net website on localhost (IIS Express).
I am not sure how do i handle Application shutdown, restart, scenarios
for my current workflow.
My code always hit app.run as first thing.
So, suppose, email to sent P1 for approval and meanwhile i stopped IIS Express/VS.
Now i launch web app again - app.run will again so i will lose previous persistence. right?
How i can make sure that workflow is running all the time on hosted application? on IIS server in actual environment? whenever workflow is invoked it should first check whether any previous state persisted? if yes then load that and run from that point?
Any link, help will be appreciated
My idea:
On web app restart when it comes to app.run, just check in persistence database and if any previous state persisted, read it out and put some logic to restart from that point.
But i really want to know, WF 4.5 does not handle self nicely? how workflowhostservice will help me in this case?
In my following code, i am calling app.run before resuming bookmark 1 and bookmark 2.
Code:
AutoResetEvent syncEvent = new AutoResetEvent(false);
Guid wfID;
ApprovalWorkflow appFlow = new ApprovalWorkflow
{
approvalStatus = "approved"
};
WorkflowApplication app = new WorkflowApplication(appFlow);
InstanceStore store = new SqlWorkflowInstanceStore(#"Data Source=.\SQLEXPRESS;Initial Catalog=WF45GettingStartedTutorial;Integrated Security=True");
InstanceHandle handle = store.CreateInstanceHandle();
InstanceView view = store.Execute(handle, new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30));
handle.Free();
store.DefaultInstanceOwner = view.InstanceOwner;
app.InstanceStore = store;
app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Unload;
};
app.Unloaded = delegate(WorkflowApplicationEventArgs e)
{
syncEvent.Set();
};
wfID = app.Id;
app.Run();
syncEvent.WaitOne();
// resume bookmark 1
appFlow = new ApprovalWorkflow { approvalStatus = "approved" };
app = new WorkflowApplication(appFlow);
app.InstanceStore = store;
app.PersistableIdle = delegate(WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Unload;
};
app.Unloaded = delegate(WorkflowApplicationEventArgs e)
{
syncEvent.Set();
};
app.Load(wfID);
app.ResumeBookmark("B1", "test1");
syncEvent.WaitOne();
// resume bookmark 2
appFlow = new ApprovalWorkflow { approvalStatus = "approved" };
app = new WorkflowApplication(appFlow);
app.InstanceStore = store;
app.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
syncEvent.Set();
};
app.Unloaded = (workflowApplicationEventArgs) =>
{
syncEvent.Set();
};
app.Load(wfID);
app.ResumeBookmark("B2", "test");
syncEvent.WaitOne();
}

Related

Android Notification Sound Causes Media Volume to Duck (Lower) & It Never Comes Back

I just converted one of my apps to target Android API 9 (was targeting API 8); now when notifications are sent out, the volume of media is lowered and never comes back to full volume.
The app uses WebView to play media files. This was not happening prior to targeting API 9. I had to convert the app into level 9 so that I could upload to the Google Play Store. I am running a Samsung S7 which was originally designed for API level 6 (with the OS upgraded to 8.0), not sure if that has something to do with the issue. Another detail is that I use Xamarin.Android for development, not sure if that matters either.
Additionally, I forced the notifications to play a blank sound (a very short[couple ms] blank mp3) in the same build that I converted the app to target API 9:
var channelSilent = new Android.App.NotificationChannel(CHANNEL_ID, name + " Silent", Android.App.NotificationImportance.High)
{
Description = description
};
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build()
//blank is blank mp3 file with nothing in it, a few ms in duration
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3")
channelSilent.SetSound(uri, alarmAttributes);
...so it could also be the blank sound that is causing the ducking to malfunction, not the API change. Is there something to do with notification sound ducking that could be causing the issue? Is there any other way to mute a notification with Xamarin.Android other than playing a blank sound? That is one route I think would be worth trying to fix this issue.
Here is the code I am using to generate notifications:
private static List<CustomNotification> _sentNotificationList = new List<CustomNotification>();
private static NotificationManagerCompat _notificationManager;
public async void SendNotifications(List<CustomNotification> notificationList)
{
await Task.Run(() =>
{
try
{
var _ctx = Android.App.Application.Context;
if (_notificationManager == null)
{
_notificationManager = Android.Support.V4.App.NotificationManagerCompat.From(_ctx);
}
if (notificationList.Count == 0)
{
return;
}
int notePos = 0;
foreach (var note in notificationList)
{
var resultIntent = new Intent(_ctx, typeof(MainActivity));
var valuesForActivity = new Bundle();
valuesForActivity.PutInt(MainActivity.COUNT_KEY, _count);
valuesForActivity.PutString("URL", note._noteLink);
resultIntent.PutExtras(valuesForActivity);
var resultPendingIntent = PendingIntent.GetActivity(_ctx, MainActivity.NOTIFICATION_ID, resultIntent, PendingIntentFlags.UpdateCurrent);
resultIntent.AddFlags(ActivityFlags.SingleTop);
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build();
//I am playing this blank sound to prevent android from spamming sounds as the notifications get sent out
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3");
//if the notification is the first in our batch then use this
//code block to send the notifications with sound
if (!_sentNotificationList.Contains(note) && notePos == 0)
{
var builder = new Android.Support.V4.App.NotificationCompat.Builder(_ctx, MainActivity.CHANNEL_ID + 1)
.SetAutoCancel(true)
.SetContentIntent(resultPendingIntent) // Start up this activity when the user clicks the intent.
.SetContentTitle(note._noteText) // Set the title
.SetNumber(1) // Display the count in the Content Info
.SetSmallIcon(Resource.Drawable.bitchute_notification2)
.SetContentText(note._noteType)
.SetPriority(NotificationCompat.PriorityMin);
MainActivity.NOTIFICATION_ID++;
_notificationManager.Notify(MainActivity.NOTIFICATION_ID, builder.Build());
_sentNotificationList.Add(note);
notePos++;
}
//if the notification isn't the first in our batch, then use this
//code block to send the notifications without sound
else if (!_sentNotificationList.Contains(note))
{
var builder = new Android.Support.V4.App.NotificationCompat.Builder(_ctx, MainActivity.CHANNEL_ID)
.SetAutoCancel(true) // Dismiss the notification from the notification area when the user clicks on it
.SetContentIntent(resultPendingIntent) // Start up this activity when the user clicks the intent.
.SetContentTitle(note._noteText) // Set the title
.SetNumber(1) // Display the count in the Content Info
.SetSmallIcon(Resource.Drawable.bitchute_notification2)
.SetContentText(note._noteType)
.SetPriority(NotificationCompat.PriorityHigh);
MainActivity.NOTIFICATION_ID++;
_notificationManager.Notify(MainActivity.NOTIFICATION_ID, builder.Build());
_sentNotificationList.Add(note);
notePos++;
}
ExtStickyService._notificationsHaveBeenSent = true;
}
}
catch
{
}
});
}
In my MainActivity I've created two different notification channels: one is silent; the other uses default notification setting for the device:
void CreateNotificationChannel()
{
var alarmAttributes = new Android.Media.AudioAttributes.Builder()
.SetContentType(Android.Media.AudioContentType.Sonification)
.SetUsage(Android.Media.AudioUsageKind.Notification).Build();
var uri = Android.Net.Uri.Parse("file:///Assets/blank.mp3");
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
// Notification channels are new in API 26 (and not a part of the
// support library). There is no need to create a notification
// channel on older versions of Android.
return;
}
var name = "BitChute";
var description = "BitChute for Android";
var channelSilent = new Android.App.NotificationChannel(CHANNEL_ID, name + " Silent", Android.App.NotificationImportance.High)
{
Description = description
};
var channel = new Android.App.NotificationChannel(CHANNEL_ID + 1, name, Android.App.NotificationImportance.High)
{
Description = description
};
channel.LockscreenVisibility = NotificationVisibility.Private;
//here is where I set the sound for the silent channel... this could be the issue?
var notificationManager = (Android.App.NotificationManager)GetSystemService(NotificationService);
channelSilent.SetSound(uri, alarmAttributes);
notificationManager.CreateNotificationChannel(channel);
notificationManager.CreateNotificationChannel(channelSilent);
}
Full source: https://github.com/hexag0d/BitChute_Mobile_Android_BottomNav/tree/APILevel9
EDIT: something really interesting is that if I pulldown the system ui bar, the volume goes back to normal. Very strange workaround but it might help diagnose the cause.
DOUBLE EDIT: I used .SetSound(null, null) instead of using the blank .mp3 and the ducking works fine now. See comments

WWF: SqlWorkflowInstanceStoreBehavior verus SqlWorkflowInstanceStore

I have a Windows Service wrapping a WCF Service, which contains a WorkflowApplication, which runs Activities. I have also configured SQL Server 2008 Express (I know, it's approaching EOL, but the documentation explicitly states that only SQL Server 2005 or SQL Server 2008 are supported) to host the database and the connection works. To be even clearer: The entire process of the Activity completes and receives the return (I'm calling it via the WCF client wrapped in PowerShell).
The issue that I'm having is that I've configured SqlWorkflowInstanceStoreBehavior on the ServiceHost and SqlWorkflowInstanceStore on the WorkflowApplication. Neither of these throws a SQL exception but I think that the ServiceHost is taking precidence, as all that I can see is a singe entry on the LockOwnersTable.
Code from Windows Service:
this.obj = new ServiceHost(typeof(WorkflowService));
SqlWorkflowInstanceStoreBehavior instanceStoreBehavior = new SqlWorkflowInstanceStoreBehavior("Server=.\\SQL2008EXPRESS;Initial Catalog=WorkflowInstanceStore;Integrated Security=SSPI")
{
HostLockRenewalPeriod = TimeSpan.FromSeconds(5),
InstanceCompletionAction = InstanceCompletionAction.DeleteNothing,
InstanceLockedExceptionAction = InstanceLockedExceptionAction.AggressiveRetry,
InstanceEncodingOption = InstanceEncodingOption.GZip,
RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(2)
};
this.obj.Description.Behaviors.Add(instanceStoreBehavior);
this.obj.Open();
Code from WCF Service/WorkflowApplication:
SqlWorkflowInstanceStore newSqlWorkflowInstanceStore = new SqlWorkflowInstanceStore("Server=.\\SQL2008EXPRESS;Initial Catalog=WorkflowInstanceStore;Integrated Security=SSPI")
{
EnqueueRunCommands = true,
HostLockRenewalPeriod = TimeSpan.FromSeconds(5),
InstanceCompletionAction = InstanceCompletionAction.DeleteNothing,
InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry,
RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(5)
};
InstanceHandle workflowInstanceStoreHandle = newSqlWorkflowInstanceStore.CreateInstanceHandle();
CreateWorkflowOwnerCommand createWorkflowOwnerCommand = new CreateWorkflowOwnerCommand();
InstanceView newInstanceView = newSqlWorkflowInstanceStore.Execute(workflowInstanceStoreHandle, createWorkflowOwnerCommand, TimeSpan.FromSeconds(30));
newSqlWorkflowInstanceStore.DefaultInstanceOwner = newInstanceView.InstanceOwner;
// Now stage the WorkflowApplication, using the SQL instance.
AutoResetEvent syncEvent = new AutoResetEvent(false);
WorkflowApplication newWorkflowApplication = new WorkflowApplication(unwrappedActivity)
{
InstanceStore = newSqlWorkflowInstanceStore
};
Questions:
Does the ServiceHost SqlWorkflowInstanceStoreBehavior override the SqlWorkflowInstanceStore on the WorkflowApplication? If so, the obvious answer would be to remove the SqlWorkflowInstanceStoreBehavior on the ServiceHost; however, as inferred before, I fear that will prove fruitless, as the WorkflowApplication currently isn't logging anything (or even attempting to, from what I can tell).
ASAppInstanceService seems specific to WindowsServer. Is is possible to host those (for dev/pre-production) on Windows 10, if the ServiceHost (via Windows Service option) is always going to block/disable the WorkflowApplication from making the SQL calls?
Figued out the answer:
newWorkflowApplication.Persist();

IAP not working on UWP apps for Win10Mobile

I've developed a free UWP app with IAP included.
I developed this app for Windows 10 Mobile. I published an IAP to the store named customBackground, and published my app as well as the IAP to the store.
After both of them has published, I downloaded my published app from the store and try to buy the IAP.
However it returned an system popup saying "This in-app item is no longer availble in MY-APP-NAME". I don't know why this happened.
There are problems:
When I was trying in debug mode in visual studio using CurrentAppSimulator class, it doesn't popup the purchase state selection on my phone or emulator, but it pops up on desktop version. it only reads the stored state in WindowsStoreProxy.xml.
I've also tried using CurrentApp class in debug/release mode after the IAP is published, but no luck, it returns the same error as the store version.
internet permission in Package.appxmanifest is enabled.
Here's the code in my released app in the store:
private async void buy_Click(object sender, RoutedEventArgs e)
{
LicenseInformation licenseInformation = CurrentApp.LicenseInformation;
if (!licenseInformation.ProductLicenses["customBackground"].IsActive)
{
Debug.WriteLine("Buying this feature...");
try
{
await CurrentApp.RequestProductPurchaseAsync("customBackground");
if (licenseInformation.ProductLicenses["customBackground"].IsActive)
{
var purchaseStat = LocalizedStrings.GetString(LocalizedStringEnum.havePurchased);
var b = new MessageDialog(purchaseStat);
b.ShowAsync();
Debug.WriteLine("You bought this feature.");
isIAPValid = true;
}
else
{
var purchaseStat = LocalizedStrings.GetString(LocalizedStringEnum.notPurchased);
var b = new MessageDialog(purchaseStat);
b.ShowAsync();
Debug.WriteLine("this feature was not purchased.");
}
}
catch (Exception)
{
var purchaseStat = LocalizedStrings.GetString(LocalizedStringEnum.purchaseFailed);
var b = new MessageDialog(purchaseStat);
b.ShowAsync();
Debug.WriteLine("Unable to buy this feature.");
}
}
else
{
var purchaseStat = LocalizedStrings.GetString(LocalizedStringEnum.alreadyOwned);
var b = new MessageDialog(purchaseStat);
b.ShowAsync();
Debug.WriteLine("You already own this feature.");
isIAPValid = true;
}
}
Thanks!
Later after one day, I found my iap is working.
Seems like a temporary problem, Microsoft does need to improve their store system.

Sharepoint 2010 SPImport.Run security exception

I want to use SPExport (which is working OK) and SPImport to copy one web to another location. I am using Application Page in Sharepoint Foundation 2010. This code is executed on a Button click event.
using (SPWeb web = site.OpenWeb(sourceWebUrl))
{
SPExportSettings exportSettings = new SPExportSettings();
exportSettings.FileLocation = exportPath;
exportSettings.BaseFileName = exportFileName;
exportSettings.SiteUrl = site.Url;
exportSettings.ExportMethod = SPExportMethodType.ExportAll;
exportSettings.FileCompression = true;
exportSettings.IncludeVersions = SPIncludeVersions.All;
exportSettings.IncludeSecurity = SPIncludeSecurity.All;
exportSettings.ExcludeDependencies = false;
exportSettings.ExportFrontEndFileStreams = true;
exportSettings.OverwriteExistingDataFile = true;
SPExportObject expObj = new SPExportObject();
expObj.IncludeDescendants = SPIncludeDescendants.All;
expObj.Id = web.ID;
expObj.Type = SPDeploymentObjectType.Web;
exportSettings.ExportObjects.Add(expObj);
SPExport export = new SPExport(exportSettings);
export.Run();
}
using (SPWeb web = site.OpenWeb(destinationWebUrl))
{
web.AllowUnsafeUpdates = true;
SPImportSettings importSettings = new SPImportSettings();
web.FileLocation = exportPath;
web.BaseFileName = exportFileName;
web.IncludeSecurity = SPIncludeSecurity.All;
web.UpdateVersions = SPUpdateVersions.Overwrite;
web.RetainObjectIdentity = false;
web.SiteUrl = site.Url;
web.WebUrl = web.Url;
web.Validate();
SPImport import = new SPImport(importSettings);
import.Run();
web.AllowUnsafeUpdates = false;
}
Exception "The security validation for this page is invalid. Click Back in your Web browser, refresh the page, and try your operation again. " is thrown when SPImport.Run() is called.
I haven't been able to find a solution for this problem neither adding FormDigest control on application page nor Allowing Unsafe Updates on the destination web.
Also, running this code from Console Application works OK, but if code runs from Application Page it is not working (even with elevated security).
Any help would be appreciated. Thanks.
Managed to do this by adding
SPUtility.ValidateFormDigest();
at line 1.

Running WinForms WebBrowser control as NETWORK_SERVICE

I need to use the WinForms WebBrowser control in an ASP.NET application in order to take screenshots of webpages.
I'd much rather run it in a console app, and communicate to the app via the ASP.NET application, but it's not my call, I've been told it has to run as part of the website.
It all pretty much works apart from every call to navigate starts fresh with a new session, it's like the cookies aren't being persisted. As an experiment, I changed my IIS application pool to run as me rather than NETWORK_SERVICE, and it all works fine. So it's something strange about it running as network service.
I'm guessing that the network service account doesn't have the permissions to keep track of the ASP.NET_SessionId cookie and the auth cookie, but both these are non-persistent cookies, so I don't know why that would be.
Here is some simplified code so you can get the gist of what I'm doing. There is a bunch of logging and storing of images that I cut out, so it's not complete code. Basically a developer/tester/admin can run this code once (task kicked off via webpage), it will generate bitmaps of every page in the system, they can release a new version of the website and then run it again, it will tell you the differences (new pages, pages removed, pages changed).
public void Run()
{
var t = new Thread(RunThread);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void RunThread()
{
try
{
using (var browser = new WebBrowser())
{
browser.DocumentCompleted += BrowserDocumentCompleted;
if (!VisitPage(browser, "special page that sets some session flags for testing - makes content predictable")))
throw new TestRunException("Unable to disable randomness");
foreach (var page in pages)
{
VisitPage(browser, page);
}
}
}
// An unhandled exception in a background thread in ASP.NET causes the app to be recycled, so catch everything here
catch (Exception)
{
Status = TestStatus.Aborted;
}
}
private bool VisitPage(WebBrowser browser, string page)
{
finishedEvent.Reset();
var timeout = false;
stopwatch = new Stopwatch();
browser.Navigate(page);
stopwatch.Start();
while (!timeout && !finishedEvent.WaitOne(0))
{
Application.DoEvents();
if (stopwatch.ElapsedMilliseconds > 10000)
timeout = true;
}
Application.DoEvents();
if (timeout)
{
if (resource != null)
ShopData.Shop.LogPageCrawlTestLine(testRunID, resource, sequence++, (int)stopwatch.ElapsedMilliseconds, null);
}
browser.Stop();
return !timeout;
}
private void BrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
try
{
var browser = (WebBrowser)sender;
var width = browser.Document.Body.ScrollRectangle.Width;
var height = browser.Document.Body.ScrollRectangle.Height;
var timeTaken = (int)stopwatch.ElapsedMilliseconds;
if ((width == 0) || (height == 0))
{
return;
}
browser.Width = width;
browser.Height = height;
byte[] buffer;
using (var bitmap = new Bitmap(width, height))
using (var memoryStream = new MemoryStream())
{
browser.DrawToBitmap(bitmap, new Rectangle(0, 0, width, height));
bitmap.Save(memoryStream, ImageFormat.Bmp);
buffer = memoryStream.ToArray();
}
// stores image
}
finally
{
finishedEvent.Set();
}
}
So in summary:
The above code is started by a ASP.NET page and it runs in the backgound
It works fine if I run it in an IIS application pool set to run as a proper user
If I run it in an IIS application pool as NETWORK_SERVICE then every navigate has a new session, rather than persisting a session for the lifetime of the WebBrowser.
I can get it working fine outside of ASP.NET, but that is not an option for me currently, it has to run inside this ASP.NET application.
//Update
On the advice of my colleague I'm using impersonation in the background thread where I'm creating the WebBrowser, running as a local user. This works fine and I'm pretty happy with this solution. He's a stackoverflow user so I'll let him post the answer and get the credit :)
Using impersonation in the background thread would allow the WebBrowser to run as a user with permissions to run IE and thus store cookies, instead of it running as the Application Pool user (NETWORK_SERVICE).
You can setup the Impersonation in the web.config or programatically to restrict it to a specific thread.
not offcially supported due to its usage of WinInet. see http://support.microsoft.com/kb/238425 for limitations of WinInet in a service.
Do you see the session cookie in the requests coming from the WebBrowser control? I cannot find anything on how this control is supposed to handle cookies - if it ignores them you would get the behavior you are describing

Categories

Resources