how to manipulate with WF4 and ASP.NET threading - asp.net

Currently i have an ASP.NET webform application which collaborate with my custom workflow.
I am facing the timing problem of the thread between the httpcontext from ASP.NET and activity context(NativeActivityContext).
My workflow having persistence ability which implement the SqlWorkflowInstanceStore.
Code below is one of the activities inside my workflow, it's doing something like page navigation and return the desired page's url by the extension(PageNavigationExtension).
protected override void Execute(NativeActivityContext context)
{
string bookmarkName = this.BookmarkName.Get(context);
string urlPage = this.UrlPage.Get(context);
bool createBookmark = this.CreateBookmark.Get(context);
if (urlPage == null)
{
throw new ArgumentException(
string.Format("UrlPage {0}: UrlPage can't be null",
this.DisplayName), "UrlPage");
}
if (bookmarkName == null)
{
throw new ArgumentException(
string.Format("Bookmark {0}: BookmarkName can't be null",
this.DisplayName), "Bookmark");
}
innerExecute(context);
if (createBookmark)
context.CreateBookmark(bookmarkName, new BookmarkCallback(ResumerBookmark)); ;
}
private void innerExecute(NativeActivityContext context) {
PageNavigationExtension extension =
context.GetExtension<PageNavigationExtension>();
if (extension != null)
{
extension.Url = UrlPage.Get(context);
extension.ObjToReturn = ObjToReturn.Get(context);
}
}
void ResumeBookmark(NativeActivityContext context, Bookmark bookmark, object state)
{
bool action = (bool)state;
if (action == true) {
PageNavigationExtension extension =
context.GetExtension<PageNavigationExtension>();
if (extension != null)
{
extension.Url = UrlPage.Get(context);
extension.ObjToReturn = ObjToReturn.Get(context);
}
}
else
{
PageNavigationExtension extension = context.GetExtension<PageNavigationExtension>();
context.SetValue(base.Result, action);
context.SetValue(ObjToReturn, extension.ObjToReturn);
}
}
This part shown below is part of my ASPX page, which resume to the activity(code shown above).
public string directNavigate(string bMarkName) {
WorkflowApplication workflow = GetWorkflow(null);
workflow.Load(this.WorkflowID);
workflow.ResumeBookmark(bMarkName, true);
return pageNaviExtension.Url;
}
The problem occur during i resume the bookmark in my "directNavigate" function, the activity context is not calling the "ResumeBookmark" BookmarkCallback delegate, which until the line of the "return pageNaviExtension.Url;", the acititycontext thread just go to the line "bool action = (bool)state;" of my "ResumeBookmark" BookmarkCallback delegate.
In short which mean i can't get the page url return by my activity during the line of "return pageNaviExtension.Url;" and at the end the thread for the HttpContext for my ASPX page exit the "directNavigate" function without the page url.

Found the solution which is the AutoResetEvent come into.
During the workflowapplication initialization ,i add the WorkflowApplication.Unloaded delegate to signal the
AutoResetEvent (wfApp.Unloaded = (a) => { waitHandler.Set(); };)
and while resuming the bookmark, explicitly call the AutoResetEvent.WaitOne();,
where it block the HttpContext to continue running until my workflow is unloaded or completed and return out the result.
workflow.ResumeBookmark(bMarkName, true);
AutoResetEvent.WaitOne();`

Related

Avoid dynamic invocation when processing a bus event that implements a generic interface

I have recently completed a course about Microservices and RabbitMQ using ASP.NET Core and noticed that event processing uses dynamic invocation. The code is the following (only relevant parts retained ):
public sealed class RabbitMqBus : IEventBus
{
private readonly IMediator _mediator;
private readonly Dictionary<string, List<Type>> _handlers;
private readonly List<Type> _eventTypes;
}
public void Subscribe<TEvent, THandler>() where TEvent : Event where THandler : IEventHandler<TEvent>
{
string eventName = typeof(TEvent).Name;
var handlerType = typeof(THandler);
if (!_eventTypes.Contains(typeof(TEvent)))
_eventTypes.Add(typeof(TEvent));
if (!_handlers.ContainsKey(eventName))
_handlers.Add(eventName, new List<Type>());
if (_handlers[eventName].Any(s => s == handlerType))
throw new ArgumentException($"Handler type {handlerType.Name} is already registered for {eventName}");
_handlers[eventName].Add(handlerType);
StartBasicConsume<TEvent>();
}
private async Task ConsumerReceived(object sender, BasicDeliverEventArgs e)
{
string eventName = e.RoutingKey;
string message = Encoding.UTF8.GetString(e.Body);
try
{
await ProcessEvent(eventName, message);
}
catch (Exception exception)
{
Console.WriteLine(exception);
throw;
}
}
private async Task ProcessEvent(string eventName, string message)
{
if (_handlers.ContainsKey(eventName))
{
using var scope = _serviceScopeFactory.CreateScope();
var subscriptions = _handlers[eventName];
foreach (var subscription in subscriptions)
{
var handler = scope.ServiceProvider.GetService(subscription);
if (handler == null)
continue;
//TODO: check if this can be made typed and avoid messy dynamic invocation at the end
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
object #event = JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task) concreteType.GetMethod("Handle").Invoke(handler, new[] {#event});
}
}
}
public interface IEventHandler
{
}
public interface IEventHandler<in TEvent>: IEventHandler
where TEvent: Event
{
Task Handle(TEvent #event);
}
My issue is with the code following the TODO because it relies on reflection and dynamic invocation of a method with a clear name ("Handle").
My first improvement is eliminating the magic string through nameof:
Type eventType = _eventTypes.SingleOrDefault(t => t.Name == eventName);
Event #event = (Event) JsonConvert.DeserializeObject(message, eventType);
Type concreteType = typeof(IEventHandler<>).MakeGenericType(eventType);
await (Task)concreteType.GetMethod(nameof(IEventHandler<Event>.Handle)).Invoke(handler, new object[] { #event });
However, reflection is still used. I understand that this is required because Handle is defined as a generic method (part of a generic interface) and the received event is dynamically constructed.
Is there a way to refactor this code to avoid reflection?

WF4: Bookmark with NonBlocking option

Has anyone used CreateBookmark() with BookmarkOptions.NonBlocking?
I'm trying to use it with MultipleResume option but seems I cannot even resume.
Bookmark activity:
public InArgument<string> BookmarkName { get; set; }
public InArgument<BookmarkOptions> BookmarkOptions { get; set; }
protected override void Execute(NativeActivityContext context)
{
var options = BookmarkOptions.Get(context);
context.CreateBookmark(BookmarkName.Get(context),
ReadCompleteCallback,options);
}
Test Code:
[TestMethod]
public void TestMethod1()
{
InitWorkflow();
wfat = WorkflowApplicationTest.Create(sm);
wfat.TestActivity();
Assert.IsTrue(wfat.WaitForIdleEvent());
var res = wfat.TestWorkflowApplication.ResumeBookmark("First", "data");
Assert.IsTrue(res == BookmarkResumptionResult.Success, "Resumption fail with result:" + res);
Assert.IsTrue(wfat.Bookmarks.Contains("First"), "No first bkmk");
}
private void InitWorkflow()
{
sm = new StateMachine()
{
States =
{ //First state with non blocking bookmark
new State(){
DisplayName = "First",Entry = new BookmarkActivity(){BookmarkName = "First",BookmarkOptions =
BookmarkOptions.NonBlocking | BookmarkOptions.MultipleResume},
Transitions =
{
new Transition(){ }
}
}, //Second state with blocking bookmark
new State(){
DisplayName = "Second",Entry = new BookmarkActivity(){BookmarkName = "Second",BookmarkOptions =
BookmarkOptions.None},
Transitions =
{
new Transition(){ }
}
},
new State(){
DisplayName = "End",
IsFinal = true
}
}
};
sm.InitialState = sm.States[0];
sm.InitialState.Transitions[0].To = sm.States[1];
sm.States[1].Transitions[0].To = sm.States[2];
}
Result of ResumeBookmark in above test code is 'NotFound'
I would appreciate any working code that demonstrates NonBlocking option.
Even NonBlocking bookmarks are removed when the activity that created it is completed. They allow the activity to continue execution but that's it.
Bottom line you've to maintain an activity in a not completed state (usually the outside activity) and everything inside it will execute even when a NonBlocking bookmark is found.
That's why you're getting a NotFound error. The activity that created the bookmark has ended and the bookmark no longer exists.
P.S.: A somehow usual use case for NonBlocing bookmarks is, for example, when you've a long running activity, that might throw exceptions while executing, and that way you've the possibility to resume the workflow at a previous state.

send data from silverlight to asp.net (or php?) when the user click on a button

If I click on a button of my Silverlight app, another third-party web app must gets some data.
My experience, until now, has been to create web service functions that you can call when you need, but in this case I have to give the possibility to the customer to "handle the click event on the button". In the actual case the third-party app is ASP.Net, but, if it were possible, I would like to do something portable.
Before to start with some crazy idea that will comes in my mind, I would ask: How would you do that?
Pileggi
I Use This Class To Create And Post a Form Dynamically
public class PassData
{
public static PassData Default = new PassData();
public void Send(string strUrl, Dictionary<string, object> Parameters, string ContainerClientID = "divContainer")
{
var obj = HtmlPage.Document.GetElementById(ContainerClientID);
if (obj != null)
{
HtmlElement divContainer = obj as HtmlElement;
ClearContent((HtmlElement)divContainer);
HtmlElement form = HtmlPage.Document.CreateElement("form");
form.SetAttribute("id", "frmPostData");
form.SetAttribute("name", "frmPostData");
form.SetAttribute("target", "_blank");
form.SetAttribute("method", "POST");
form.SetAttribute("action", strUrl);
if (Parameters != null)
foreach (KeyValuePair<string, object> item in Parameters)
{
HtmlElement hidElement = HtmlPage.Document.CreateElement("input");
hidElement.SetAttribute("name", item.Key);
hidElement.SetAttribute("value", item.Value.ToString());
form.AppendChild(hidElement);
}
divContainer.AppendChild(form);
form.Invoke("submit");
ClearContent((HtmlElement)divContainer);
}
}
private void ClearContent(System.Windows.Browser.HtmlElement obj)
{
foreach (HtmlElement item in obj.Children)
{
obj.RemoveChild(item);
}
}
}
divContainer is id of a div in html

interesting service behaviour in silverlight

I have a Silverlight project which takes some encrypted string thru its Service Reference: DataService (service which is done in an ASP.NET project).
The method from TransactionServices.cs to get the encrypted string is:
public void GetEncryptedString(string original)
{
DataService.DataServiceClient dataSvc = WebServiceHelper.Create();
dataSvc.GetEncryptedStringCompleted += new EventHandler<SpendAnalyzer.DataService.GetEncryptedStringCompletedEventArgs>(dataSvc_GetEncryptedStringCompleted);
dataSvc.GetEncryptedStringAsync(original);
}
On completing, put the result in encodedString var (which is initialized with an empty value):
void dataSvc_GetEncryptedStringCompleted(object sender, SpendAnalyzer.DataService.GetEncryptedStringCompletedEventArgs e)
{
if (e.Error == null)
{
try
{
if (e.Result == null) return;
this.encodedString = e.Result;
}
catch (Exception ex)
{
Logger.Error("TransactionService.cs: dataSvc_GetEncryptedStringCompleted: {0} - {1}",
ex.Message, ex.StackTrace);
MessageBox.Show(ex.ToString());
}
}
}
Now I want to get the encoded string from my MainPage.xaml like:
TransactionService ts = new TransactionService();
ts.GetEncryptedString(url);
Console.WriteLine(ts.encodedString);
I do not uderstand why ts.encodedString is empty. When I do the debug I see that it actually prints out empty and AFTER that it goes to the void dataSvc_GetEncryptedStringCompleted to take the result and fill it.
Can you point me what I've done wrong? Is there a way to wait for the encodedString to be fetched and only after that to continue?
Thanks a lot.
When you call the ts.GetEncryptedString(url); you just started async operation. And therefor the value you are accessing is will be set only in the callback method.
But you access it before the value is modified by the callback.
The solution which I am using will looks similar to folowing:
Redefine the GetEncryptedString method signature.
public void GetEncryptedString(string original, Action callback)
{
DataService.DataServiceClient dataSvc = WebServiceHelper.Create();
dataSvc.GetEncryptedStringCompleted += (o,e) =>
{
dataSvc_GetEncryptedStringCompleted(o,e);
callback();
}
dataSvc.GetEncryptedStringAsync(original);
}
Call it like this:
ts.GetEncryptedString(url, OtherLogicDependantOnResult);
where
OtherLogicDependantOnResult is
void OtherLogicDependantOnResult()
{
//... Code
}

Unable to hook into PropertyChanged event using MVVM-Light

Greetings, creating my first MVVM based WPF app and trying to figure out why I'm unable to hook into the PropertyChanged event of a dependency property.
Code in the parent view model:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
foreach (var client in clients)
{
client.PropertyChanged += onClientPropertyChanged;
}
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
}
// Never gets called
void onClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Name")
{
//...
}
}
ViewableCollection is a simple extension of ObservableCollection to encapsulate a View.
In the ClientViewModel the setters are being called but RaisePropertyChanged isn't working as I would expect, because onClientPropertyChanged isn't being invoked. Both view models inherit from ViewModelBase.
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
client.Name = value;
RaisePropertyChanged("Name");
}
}
If I wire up PropertyChanged to a method inside the ClientViewModel then it is being fired, so I'm stumped as to why this isn't working in the parent view model. Where am I going wrong?
This SO question explains the problem; ObservableCollection protects the PropertyChanged event.
One solution is to use MVVM-Light Messenger:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
Messenger.Default.Register<PropertyChangedMessage<string>>(this, (pcm) =>
{
var clientVM = pcm.Sender as ClientViewModel;
if (clientVM != null && pcm.PropertyName == "Name")
{
// ...
}
});
}
createClients() should be refactored, but for consistency with the question code I'll leave it in there. Then a slight change to the property setter:
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
string oldValue = client.Name;
client.Name = value;
RaisePropertyChanged<string>("Name", oldValue, value, true);
}
}

Resources