I am working on a WPF application in which a user may initiate a process by pushing a button on the UI. The user may then be prompted with a sequence of actions that they must carry out to complete the process. The view is responsible for passing the initial request to initiate the process down to the domain. The view is also responsible for DISPLAYING the steps that the user must perform to complete the process.
The domain, on the other hand, is squarely response for working out WHAT steps must be carried out by the user. The domain is also capable of detecting when a user has completed the requested step.
If a user initiates a process, and that process requires them to perform some physical action, then I would like a box to pop-up with a message describing what they must do. When the action has been completed, it is detected by the domain, and the window should automatically close.
Passing requests from the View down to the Domain is simple. I do this using the wpf ICommand pattern. It is passing information back the other way that I am finding challenging. I am aware of bindings and the INotifyProperyChanged interface, but I do not feel that this is a good fit for what I am trying to do.
So, here is my initial attempt...
This interface is implemented by the View and consumed by the Domain. It allows the domain to communicate with the user;
public interface IUserRequestMedium
{
/// <summary>
/// Ask the user to perform an action. User does
/// not need to provide any feedback via the user
/// interface, since it is possible for the
/// application to detect when the action has been
/// carried out by the user. The dialog will be closed
/// when either the requested action has been detected,
/// or the user aborts.
/// </summary>
/// <param name="message">
/// Request to be displayed to the user.
/// </param>
/// <param name="userAbortCallback">
/// Callback invoked by the view when the user cancels
/// the request.
/// </param>
/// <param name="actionDetectedCallback">
/// Callback invoked by the domain to confirm the
/// that the requested action has been completed.
/// </param>
void AskUserToPerformDetectableAction(
string message, Action userAbortCallback,
out Action actionDetectedCallback);
}
Here is the View code-behind. Some of this code was taken from tutorials (and subsequently mangled) on the web. It's not working, but I hopefully it communicates my intent.
public partial class MainWindow : Window, IUserRequestMedium
{
// Constructor and other stuff...
public void AskUserToPerformDetectableAction(
string message, Action userAbortCallback,
out Action actionDetectedCallback)
{
Action closeWindow;
NewWindowThread(
() => new ActionRequestBox(message, userAbortCallback),
out closeWindow);
actionDetectedCallback = closeWindow;
}
private Window newWindow;
private void NewWindowThread(
Func<Window> construction,
out Action closeWindow)
{
var thread = new Thread(() =>
{
newWindow = construction();
newWindow.Show();
newWindow.Closed += (sender, e) => newWindow.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Window rememberedWindow = newWindow;
closeWindow = () =>
{
if (rememberedWindow != null)
rememberedWindow.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
new Action(Close));
};
}
}
And here's a usage example from the domain;
public class SomeDomainClass
{
IUserRequestMedium userRequestMedium; // assume this has been assigned in constructor
private Action notifyUserOfActionDetected;
public void PerformSomeProcess()
{
bool processCannotBeCompletedWithoutPowerCycle = ...; // some logic
if (processCannotBeCompletedWithoutPowerCycle)
{
userRequestMedium.AskUserToPerformDetectableAction(
"Please cycle the power on the external device",
CancelProcess,
out notifyUserOfActionDetected);
}
}
public void CancelProcess()
{
// User doesn't want to perform the required action
// so process must be aborted...
}
private void OnPowerCycleDetected()
{
notifyUserOfActionDetected();
}
}
How can I make this work? It is the cross-threading aspect that I am getting caught on. I have not been successful in making the window automatically close when the action is detected by the domain.
Or, taking a step backward, is there a better approach to solve this problem?
After learning a little about Dispatcher.Invoke, this is what I ended up with. So far it seems to be working pretty well.
private Window activeRequestBox;
// invoked on domain thread
public void AskUserToPerformDetectableAction(
string message, Action userAbortCallback,
out Action actionDetectedCallback)
{
OpenDetectableActionRequestBox(message, userAbortCallback);
actionDetectedCallback = CloseRequestBox;
}
private void OpenDetectableActionRequestBox(
string message, Action userAbortCallback)
{
Action openWindow =
() =>
{
activeRequestBox = new DetectableActionRequestBox(
message, userAbortCallback);
activeRequestBox.Closed += RequestBoxClosedHandler;
activeRequestBox.Show();
};
this.Dispatcher.Invoke(openWindow);
}
// invoked on request box thread
private void RequestBoxClosedHandler(object sender, EventArgs e)
{
activeRequestBox = null;
}
// invoked on domain thread
private void CloseRequestBox()
{
if (activeRequestBox != null)
{
Action closeWindow =
() => activeRequestBox.Close();
this.Dispatcher.Invoke(closeWindow);
}
}
Related
I have a Xamarin Forms app that I want to read NFC tags on. I've made an interface called INFC for reading the tags.
/// <summary>
/// This interface defines NFC relating functions that are cross platform.
/// </summary>
public interface INFC
{
/// <summary>
/// Inits the object.
/// </summary>
void Init();
/// <summary>
/// Starts the process for scanning for the included platform.
/// </summary>
/// <param name="tagInformation">Optional related tag information that you may need for the scan.</param>
void StartNFCScan(object tagInformation = null);
/// <summary>
/// Called when the tag is finished scanning and we have the content.
/// </summary>
event EventHandler<String> TagScanned;
}
I created the following Android specific implementation.
[assembly: Dependency(typeof(INFCImplementation))]
namespace Test.Droid.Models
{
/// <summary>
/// The android implementation of the NFC platform.
/// </summary>
public class INFCImplementation : INFC
{
public event EventHandler<String> TagScanned;
public static NfcAdapter adapter { get; set; }
/// <summary>
/// Called to init the object.
/// </summary>
public void Init()
{
//Set the adapter.
adapter = NfcAdapter.GetDefaultAdapter(Forms.Context);
}
/// <summary>
/// Starts the process for scanning for the included platform.
/// </summary>
/// <param name="tagInformation">Optional related tag information that you may need for the scan.</param>
public void StartNFCScan(object tagInformation = null)
{
//Create a variable to hold the tag content.
String tagContent = null;
try
{
//Process the NDEF tag and get the content as a String.
tagContent = "http://stackoverflow.com";
}
catch (Exception e)
{
}
//Raise the tag content with the scanned event.
TagScanned?.Invoke(this, tagContent);
}
}
}
My Main Activity is as follows.
/// <summary>
/// The main activity for the app.
/// </summary>
[Activity(Label = "Test", Icon = "#drawable/icon", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
INFCImplementation nfcImplementation;
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
//Enable experimental fast renderers.
Forms.SetFlags("FastRenderers_Experimental");
Forms.Init(this, bundle);
//Load up the zxing framework.
ZXing.Net.Mobile.Forms.Android.Platform.Init();
//Load up the user dialogs plugin.
UserDialogs.Init(() => (Activity)Forms.Context);
//Init the tinted image renderer.
TintedImageRenderer.Init();
//Store our NFC interface class.
nfcImplementation = DependencyService.Get<INFCImplementation>() as INFCImplementation;
//Init our NFC interface.
nfcImplementation.Init();
LoadApplication(new App());
}
protected override void OnResume()
{
//Call the base method.
base.OnResume();
//Create the intent for NFC reading.
Intent intent = new Intent(this, GetType()).AddFlags(ActivityFlags.SingleTop);
//Start a dispatch on our NFC adapter.
INFCImplementation.adapter?.EnableForegroundDispatch
(
this,
PendingIntent.GetActivity(this, 0, intent, 0),
new[] { new IntentFilter(NfcAdapter.ActionTechDiscovered) },
new String[][]
{
new string[]
{
"android.nfc.tech.Ndef"
},
new string[] {
"android.nfc.tech.MifareClassic"
},
}
);
}
protected override void OnPause()
{
//Call the base method.
base.OnPause();
//Stop the dispatch on our NFC adapter.
INFCImplementation.adapter?.DisableForegroundDispatch(this);
}
protected override void OnNewIntent(Intent intent)
{
//Call the base method.
base.OnNewIntent(intent);
//Check if this is the NFC intent.
if (intent != null && (NfcAdapter.ActionNdefDiscovered.Equals(intent.Action) || NfcAdapter.ActionTechDiscovered.Equals(intent.Action) || NfcAdapter.ActionTagDiscovered.Equals(intent.Action)))
{
var test = intent.GetParcelableExtra(NfcAdapter.ExtraTag) as Tag;
nfcImplementation.StartNFCScan(test);
}
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
{
//Call the base method.
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
//Check with the permissions plugin.
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
//Check with the zxing plugin.
ZXing.Net.Mobile.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
In my view model for the mainpage that is the binding context for the main page I add the following in the constructor.
/// <summary>
/// Constructs the scanner view model with the scanner view we want to use.
/// </summary>
public ScannerPageViewModel()
{
//Subscribe to the tag scanned event.
CrossNFC.Current.TagScanned += ProcessNFCScanResult;
}
private void ProcessNFCScanResult(object sender, string e)
{
SetLabel(e);
}
Ok so for the issue. I believe that this should make it so the OnNewIntent function will call the start NFC scan on the interface and then that will call the event which will fly all the way up to the view model and allow me to handle the content. I do this as I only want to scan NFC tags on one page in the app ONLY while the app is in the foreground. Everytime I get to the invoke call, the TagScanned event is null.
Placing breakpoints around I found that the following occurs when I scan a tag:
MainActivity OnPause Gets Called -> Scanner Page OnDisappearing Gets Called -> OnNewIntent Gets Called and calls the null event -> MainActivity OnResume gets called -> Scanner Page OnAppearing Gets Called
I believe that the OnDisappearing call is making the event unable to process. I based a lot of my code on the NFCForms Github project (https://github.com/poz1/NFCForms) which when downloading the sample project and running it DOES NOT trigger OnDisappearing and OnAppearing. It just calls OnPause, OnNewIntent, and OnResume and the event gets to his page.
Why is my page getting unloaded and the event doesn't get called? If I'm doing something wrong, how can I notify my ViewModel for the specific page when a tag was scanned? I'm thinking this is either an issue with the way I'm making the intent request for NFC or something not related to NFC in which I'm processing view events wrong due to the sample NFCForms application working properly on the same phone.
Edit
I made a completely new project with the same basic code and it worked the way I believe it should have. Now I'm trying to figure out why OnAppearing and OnDisappearing is called on the pages.
Edit 2
I found out that OnAppearing and OnDisappearing gets called if the pages are wrapped in a navigation page. That is why the new project which is a single view did not call it and when I added the navigation page it did call it.
HOWEVER even when changing my project to a single page, the old project I'm working on still had the event as null, while the new test project had the event as valid.
So I'm thinking that somehow I'm not doing events right?
Would this be of some use to your case ?
// In ScannerPage
protected override void OnAppearing ()
{
base.OnAppearing ();
MessagingCenter.Subscribe<string>(this, "eventName", (label) => {
// do something whenever the message is sent
Device.BeginInvokeOnMainThread (() => {
MyScannerPageViewModel.SetLabel(label);
});
});
}
protected override void OnDisappearing ()
{
base.OnDisappearing ();
MessagingCenter.Unsubscribe<string> (this, "eventName");
}
And in the MainActivity chose where you would like to put this line
Xamarin.Forms.MessagingCenter.Send("LabelName","eventName");
EDIT: Changed the code a bit
Last successful post was Jan 2018.
Anyone have this working in 2019? Every test I try the anonymous MessagingCenter message is never heard by the subscriber.
Same use-case as the original poster:
Send the message from MainActivty of Android, listen for it in a ViewModel of the shared/agnostic layer.
In my tests, the anonymous message isn't even heard within the same class, or same layer.
UPDATE:
Further input from the community found a solution: You have to specify the type Application and Application.Current.
https://forums.xamarin.com/discussion/comment/370364#Comment_370364
So the previously working syntax seems broken in 2019 - there is a way around it.
Android layer (MainActivity or Broadcast listener):
MessagingCenter.Send(Xamarin.Forms.Application.Current,"ISENGINEON", result.ToString());
Shared layer view model
MessagingCenter.Subscribe<Application,string>(this,"ISENGINEON",OnEngineOnChanged);
While an answer like this makes me very sad, it did end up working.
Since I found making a new project worked, I made a new forms project with the same name as mine and then removed the Android project from my solution and replaced it with the new one. I then reinstalled all the nuget packages and copied and pasted all my code in the new project.
And it works now.....
So I'm guessing something along the way broke something in the core of the VS project or something. I hate kinda hand wavy answers but this is what worked for me. All the code I posted above was unchanged and it started working.
We have a Silverlight application. It has a couple of pages that sit inside tabs in our UI. In the past, we've called them SavePage, and PanelPage. Save page just has basic functionality for editing the detail of a record, creating new records, and deleting the existing record on screen. PanelPage inherits from SavePage. PanelPage is a little more sophisticated in that panels become visible/invisible based on selections you make on screen.
The code was a huge mess in the Silverlight application. But, recently, I took the step of porting this code to work in Xamarin Forms. I made two failed attempts to do this, and on my third attempt, I got the code to work on all targeted platforms: Silverlight, iOS, Android, and Windows UWP. I'm fairly happy with the class design for now. It could be simpler, but this will do the trick for a while.
The point of this design is that the UI logic is abstracted away from the physical UI controls themselves. I removed the System.Windows usings away from the shared code that sits across both platforms (SavePage, and PanelPage). These pages are more like "Controllers" in the MVC, or MVVC patterns. But, I don't think that what I have created is exactly either of those two patterns. However, I had to split these classes up in to two parts: one for abstract UI calls like SaveAsync(), and one for platform specific UI calls like ReportError. I'm struggling to get the naming of the classes right since I don't even know what design pattern I am using.
Here is a class diagram:
Here is the code for some of the interfaces concerned:
public interface IPage : IRecordSelector
{
/// <summary>
/// This event should be raised when the busy state of a tab changes
/// </summary>
event EventHandler<BusyStateChangedEventArgs> BusyStateChanged;
object PageElement { get; }
}
public interface IButtonDrivenPage : IPage
{
Task SaveClickAsync();
Task DuplicateClickAsync();
Task DeleteClickAsync();
Task NewClickAsync();
event EventHandler<ButtonVisibilityChangedEventArgs> ButtonVisibilityChanged;
IRecord GetRecord();
}
public interface ISavePage : IButtonDrivenPage, IRequestClose
{
string DataContextXmlSnapshot { get; }
bool PromptForChangeCancel { get; }
IRecord SelectedItem { get; }
Task SetSelectedItemAsync(IRecord selectedItem);
event EventHandler SelectedItemChanged;
void Close();
void SetAutomationObject(object automationObject);
ISavePageUIController SavePageUIController { get; }
}
public interface ISavePageUIController: IDisposable
{
/// <summary>
/// The UI controller is notifying the page that the UI content has been loaded
/// </summary>
event EventHandler ContentLoaded;
/// <summary>
/// Prompt the user for a yet or a no
/// </summary>
Task<bool> GetYesNoFromPrompt(string message, string title);
/// <summary>
/// Report an error to the user
/// </summary>
void ReportError(string title, string message, Exception exception);
/// <summary>
/// Notifies the UI that the DataContext/Binding context has changed
/// </summary>
void SetSelectedItem(IRecord selectedItem);
/// <summary>
/// The actual UI object that is displayed on screen as the content of the page
/// </summary>
object PageElement { get; }
/// <summary>
/// Clears residual errors from the screen if they exist
/// </summary>
void ClearErrors();
/// <summary>
/// The record was saved. The selectedItem parameter will be the saved record from the server.
/// </summary>
void CurrentRecordSaved(IRecord selectedItem);
/// <summary>
/// This event occurs when the UI wants to notify the controller that a Save button has been clicked in the UI somewhere
/// </summary>
event EventHandler SaveClicked;
}
public interface IPanelUIController : ISavePageUIController
{
void CreateAndAddPanelFromContent(PagePanel pagePanel, double? panelHeight);
IEnumerable<IPagePanel> GetIPagePanelControls();
void SetHeader(IPanelHeader pageHeader);
void SetVisiblePanels(IList<bool> visiblePanels);
void HideAllPanels();
event EventHandler<RecordsSelectedRoutedEventArgs> PanelPageRecordsSelected;
}
These interfaces have been implemented successfully in Silverlight and Xamarin Forms. So, is this similar to another UI design pattern? Can anyone recommend improvements? Or, tell me what I'd need to do to convert this to a more standard UI design pattern? How about naming? What should I name my classes and interfaces here?
To be honest, I wouldn't be too obsessed with being one or the other (MVC, MVVM or MVP), they're pretty much the same and the point is to get keep that "big split". That said, right now, IMHO you seem to be closest to MVP (Model View Presenter)
The problem is that you have a lot of intermingled logic there: IPage should really just be a View but you have it doing things that a controller typically would. Same with it's children: ISavePagehas a method called SetAutomation object that I would normally expect to see in a Controller (at least if I'm guessing correctly on it's function).
Adam Freeman did a great job talking about how to break this stuff up in ASP.NET MVC 5: http://enos.itcollege.ee/~ijogi/Nooks/Pro%20ASP.NET%20MVC%205/Pro%20ASP.NET%20MVC%205.9781430265290.pdf Check out page 51 where he breaks down what each item should be conceptually, which may help?
I would try doing it this way, given that we've established that your IPage is really a Controller.
public class PageElement
{
IPageController _controller;
// these are your models - your controller will simply allow them to be shown with your other methods
private PageData _data;
private PageData _otherData;
public PageElement(IPageController ctrl)
{
_controller = ctrl;
}
}
public class PageController : IPageController
{
IPageService _service;
public PageController(IPageService service)
{
_service = service;
}
// this is what your button calls when clicked
public void SaveAsync(object sender, SomeEventArgs args)
{
// the service does the actual work
_service.SaveAsync()
}
}
public class PageService : IPageService
{
public void SaveAsync(){ // what it does}
}
Look like a MVC, or previous controller-view pattern (prior to MVC)
Trying to access the HttpContext.Current in a method call back so can I modify a Session variable, however I receive the exception that HttpContext.Current is null. The callback method is fired asynchronously, when the _anAgent object triggers it.
I'm still unsure of the solution to this after viewing similar questions on SO.
A simplified version of my code looks like so:
public partial class Index : System.Web.UI.Page
protected void Page_Load()
{
// aCallback is an Action<string>, triggered when a callback is received
_anAgent = new WorkAgent(...,
aCallback: Callback);
...
HttpContext.Current.Session["str_var"] = _someStrVariable;
}
protected void SendData() // Called on button click
{
...
var some_str_variable = HttpContext.Current.Session["str_var"];
// The agent sends a message to another server and waits for a call back
// which triggers a method, asynchronously.
_anAgent.DispatchMessage(some_str_variable, some_string_event)
}
// This method is triggered by the _webAgent
protected void Callback(string aStr)
{
// ** This culprit throws the null exception **
HttpContext.Current.Session["str_var"] = aStr;
}
[WebMethod(EnableSession = true)]
public static string GetSessionVar()
{
return HttpContext.Current.Session["str_var"]
}
}
Not sure if necessary but my WorkAgent class looks like so:
public class WorkAgent
{
public Action<string> OnCallbackReceived { get; private set; }
public WorkAgent(...,
Action<string> aCallback = null)
{
...
OnCallbackReceived = aCallback;
}
...
// This method is triggered when a response is received from another server
public BackendReceived(...)
{
...
OnCallbackReceived(some_string);
}
}
What happens in the code:
Clicking a button calls the SendData() method, inside this the _webAgent dispatches a message to another server and waits for reply (in the mean time the user can still interact with this page and refer to the same SessionID). Once received it calls the BackendReceived() method which, back in the .aspx.cs page calls the Callback() method.
Question:
When the WorkAgent triggers the Callback() method it tries to access HttpContext.Current which is null. Why is that the case when if I continue on, ignoring the exception, I can still obtain the same SessionID and the Session variable using the ajax returned GetSessionVar() method.
Should I be enabling the aspNetCompatibilityEnabled setting?Should I be creating some sort of asynchronous module handler? Is this related to Integrated/Classic mode?
Here's a class-based solution that is working for simple cases so far in MVC5 (MVC6 supports a DI-based context).
using System.Threading;
using System.Web;
namespace SomeNamespace.Server.ServerCommon.Utility
{
/// <summary>
/// Preserve HttpContext.Current across async/await calls.
/// Usage: Set it at beginning of request and clear at end of request.
/// </summary>
static public class HttpContextProvider
{
/// <summary>
/// Property to help ensure a non-null HttpContext.Current.
/// Accessing the property will also set the original HttpContext.Current if it was null.
/// </summary>
static public HttpContext Current => HttpContext.Current ?? (HttpContext.Current = __httpContextAsyncLocal?.Value);
/// <summary>
/// MVC5 does not preserve HttpContext across async/await calls. This can be used as a fallback when it is null.
/// It is initialzed/cleared within BeginRequest()/EndRequest()
/// MVC6 may have resolved this issue since constructor DI can pass in an HttpContextAccessor.
/// </summary>
static private AsyncLocal<HttpContext> __httpContextAsyncLocal = new AsyncLocal<HttpContext>();
/// <summary>
/// Make the current HttpContext.Current available across async/await boundaries.
/// </summary>
static public void OnBeginRequest()
{
__httpContextAsyncLocal.Value = HttpContext.Current;
}
/// <summary>
/// Stops referencing the current httpcontext
/// </summary>
static public void OnEndRequest()
{
__httpContextAsyncLocal.Value = null;
}
}
}
To use it can hook in from Global.asax.cs:
public MvcApplication() // constructor
{
PreRequestHandlerExecute += new EventHandler(OnPreRequestHandlerExecute);
EndRequest += new EventHandler(OnEndRequest);
}
protected void OnPreRequestHandlerExecute(object sender, EventArgs e)
{
HttpContextProvider.OnBeginRequest(); // preserves HttpContext.Current for use across async/await boundaries.
}
protected void OnEndRequest(object sender, EventArgs e)
{
HttpContextProvider.OnEndRequest();
}
Then can use this in place of HttpContext.Current:
HttpContextProvider.Current
There may be issues as I currently do not understand this related answer. Please comment.
Reference: AsyncLocal (requires .NET 4.6)
When using threads or an async function, HttpContext.Current is not available.
Try using:
HttpContext current;
if(HttpContext != null && HttpContext.Current != null)
{
current = HttpContext.Current;
}
else
{
current = this.CurrentContext;
//**OR** current = threadInstance.CurrentContext;
}
Once you set current with a proper instance, the rest of your code is independent, whether called from a thread or directly from a WebRequest.
Please see the following article for an explanation on why the Session variable is null, and possible work arounds
http://adventuresdotnet.blogspot.com/2010/10/httpcontextcurrent-and-threads-with.html
quoted from the from the article;
the current HttpContext is actually in thread-local storage, which explains why child threads don’t have access to it
And as a proposed work around the author says
pass a reference to it in your child thread. Include a reference to HttpContext in the “state” object of your callback method, and then you can store it to HttpContext.Current on that thread
I develop download manager application that consists of two parts: 1) Duplex WCF service that performs downloading and sends downloading status data to client in real-time. 2) WPF Client that receives downloading status data from the service and displays in DataGrid. In my duplex WCF service there is a callback interface
[ServiceContract(CallbackContract = typeof(IDownloadManagerServiceCalback))]
public interface IDownloadManagerServiceCalback
{
/// <summary>
/// Returns changed downloading status to client.
/// </summary>
/// <returns>Downloading which has changed status</returns>
[OperationContract(IsOneWay = true)]
void UpdateSelectedDownload(DownloadStatus p_SelectedDownload);
}
On the client side I implement this interface:
class CallbackHandler : IDownloadManagerServiceCallback
{
/// <summary>
/// "Download status changed" event.
/// </summary>
public event EventHandler<DownloadStatusChangedEventArgs> DownloadStatusChanged;
public async Task UpdateSelectedDownload(DownloadStatus p_UpdatedDownload)
{
await Task.Run(() =>
{
// If handler was subscribed to event:
if (DownloadStatusChanged != null)
{
DownloadStatus updatedDownload = p_UpdatedDownload;
DownloadStatusChangedEventArgs updatedDownloadArgs = new DownloadStatusChangedEventArgs();
updatedDownloadArgs.Download = updatedDownload;
DownloadStatusChanged(this, updatedDownloadArgs);
}
});
}
}
When I build the solution I have the following error (text of error message i translate from Russian to English because my Visual Studio 2013 is Russianize):
DownloadManager_Client.CallbackHandler doesn’t implement member "DownloadManager_Client.DownloadManager_ServiceReference.IDownloadManagerServiceCallback.UpdateSelectedDownload(DownloadManager_Client.DownloadManager_ServiceReference.DownloadStatus)". "DownloadManager_Client.CallbackHandler.UpdateSelectedDownload(DownloadManager_Client.DownloadManager_ServiceReference.DownloadStatus)" can’t be implemented "DownloadManager_Client.DownloadManager_ServiceReference.IDownloadManagerServiceCallback.UpdateSelectedDownload(DownloadManager_Client.DownloadManager_ServiceReference.DownloadStatus)", because it doesn’t contain appropriate returned “void” type.
Here DownloadManager_Client is the name of WPF client project, DownloadManager_ServiceReference is the name of service reference to WCF service in the client project. How can I correct this error?
The interface should be defined as returning Task not void since your implementation is an async method returning a Task.
EDIT: You are in a pickle because you want to use async which require a Task to be returned however your method is marked as IsOneWay = true - you can't have both. Either IsOneWay = false and keep the async nature or keep one-way but remove the async.
Example 1 - Async method
[ServiceContract(CallbackContract = typeof(IDownloadManagerServiceCalback))]
public interface IDownloadManagerServiceCalback
{
/// <summary>
/// Returns changed downloading status to client.
/// </summary>
/// <returns>Downloading which has changed status</returns>
[OperationContract(IsOneWay = false)]
Task UpdateSelectedDownload(DownloadStatus p_SelectedDownload);
}
Then keep your original implementation returning Task
Example 2 - One-way method
[ServiceContract(CallbackContract = typeof(IDownloadManagerServiceCalback))]
public interface IDownloadManagerServiceCalback
{
/// <summary>
/// Returns changed downloading status to client.
/// </summary>
/// <returns>Downloading which has changed status</returns>
[OperationContract(IsOneWay = true)]
void UpdateSelectedDownload(DownloadStatus p_SelectedDownload);
}
In your implementation remove any await; async; tasks.
General
Async WCF methods should return Task or Task < T >. The only time you have an async void is during an event handler which is not applicable here.
As a general rule with async methods - avoid async void like the plague because an exception thrown in a try catch inside such a method can not be caught by a regular try-catch. The only exception (no pun intended is during event handlers).
Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. More...
Would you like to know more?
Best Practices in Asynchronous Programming
So I'm working on a client that consumes a web service. I used the WSDL and XSD files from the service to generate the proxy class, and all of the synchronous functions work fine. However, given their synchronous nature, making any of the calls causes the UI to stop responding until the call is finished. Classic reason for using async methods, right?
Problem is, I'm still in school for my degree and know little about asynchronous programming. I've tried to read up on it online (my employer even has a Books 24x7 subscription) but I'm having a hard time grasping how I should make the calls and how to handle the response. Here's what I have:
/// <remarks/>
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://localhost:8080/getRecords", RequestNamespace="http://www.<redacted>.com/ws/schemas", ResponseNamespace="http://www.<redacted>.com/ws/schemas", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
[return: System.Xml.Serialization.XmlArrayAttribute("records", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)]
[return: System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
public record[] getRecords([System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] string username, [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] [System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer", IsNullable=false)] string[] ids) {
object[] results = this.Invoke("getRecords", new object[] {
username,
ids});
return ((record[])(results[0]));
}
/// <remarks/>
public void getRecordsAsync(string username, string[] ids) {
this.getRecordsAsync(username, ids, null);
}
/// <remarks/>
public void getRecordsAsync(string username, string[] ids, object userState) {
if ((this.getRecordsOperationCompleted == null)) {
this.getRecordsOperationCompleted = new System.Threading.SendOrPostCallback(this.OngetRecordsOperationCompleted);
}
this.InvokeAsync("getRecords", new object[] {
username,
ids}, this.getRecordsOperationCompleted, userState);
}
private void OngetRecordsOperationCompleted(object arg) {
if ((this.getRecordsCompleted != null)) {
System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
this.getRecordsCompleted(this, new getRecordsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
}
}
There's also this:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.1")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class getRecordsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {
private object[] results;
internal getRecordsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) :
base(exception, cancelled, userState) {
this.results = results;
}
/// <remarks/>
public record[] Result {
get {
this.RaiseExceptionIfNecessary();
return ((record[])(this.results[0]));
}
}
}
and this:
/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.1")]
public delegate void getRecordsCompletedEventHandler(object sender, getRecordsCompletedEventArgs e);
I chose this example because the synchronous call has a return type and the async does not--at least not in the function call itself. I understand that the getRecordsCompletedEventArgs class has the proper return type, and that that is how I will get the data back from the call. What I can't seem to figure out is how to actually do that.
Let's say that I replace my current call to getRecords with getRecordsAsync:
How do I set up the client to respond when the async call completes? I need to drop the XML into a file using a LINQ procedure I've already written, I need to log the operation's success or failure, and I need to notify the user that the operation completed.
How can I ensure that making the call actually happens asynchronously? I remember reading at one point that simply invoking an asynchronous SOAP method doesn't actually happen asynchronously with regard to the current thread unless you do something else first. Any tips?
Are there any other major considerations that I'm missing? (Such as: "If you forget to do this, it'll blow up your program!")
These are all questions that I haven't been able to find convincingly firm answers to so far. Thank you in advance for any help you all can offer.
You need to handle the getRecordsCompleted event on the proxy which was auto-generated for you, like so:
private void Button_Click(object sender, EventArgs e)
{
var proxy = new WebServiceProxy();
// Tell the proxy object that when the web service
// call completes we want it to invoke our custom
// handler which will process the result for us.
proxy.getRecordsCompleted += this.HandleGetRecordsCompleted;
// Make the async call. The UI thread will not wait for
// the web service call to complete. This method will
// return almost immediately while the web service
// call is happening in the background.
// Think of it as "scheduling" a web service
// call as opposed to actually waiting for it
// to finish before this method can progress.
proxy.getRecordsAsync("USERNAME", new[] { 1, 2, 3, 4 });
this.Button.Enabled = false;
}
/// <summary>
/// Handler for when the web service call returns.
/// </summary>
private void HandleGetRecordsCompleted(object sender, getRecordsCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show(e.Error.ToString());
}
else
{
record[] result = e.Result;
// Run your LINQ code on the result here.
}
this.Button.Enabled = true;
}
If you use an auto-generated method on the proxy which ends with Async, the call will be made asynchronously - and that's it. What it sounds to me that you need to prove is that the call is non-blocking (that is, the UI thread does not have to wait for it to complete), and that's a bit tricky as you can't really inject custom logic into the auto-generated code.
A synchronous call made from a UI thread will block the UI and your application will become unresponsive. If that's not happening and your UI still responds to button clicks, keyboard events etc while the web service is running, you can be sure that the call is non-blocking. Obviously this will be tricky to prove if your web service call returns quickly.
You're not showing any client code so it's hard to say if you're missing anything.
For point 1
I think you are missing something on the code you are showing. Maybe the definition of getRecordsCompleted? It may be of type event I suppose, so you can attach a handler of type getRecordsCompletedEventHandler to your event so you can do something with the result of your asynchronous call.
Let's say your client proxy class name is RecordCleint
RecordClient client = new RecordClient();
//attaching an event handler
client.getRecordsCompleted += onGetRecordsCompleted;
//calling the web service asynchronously
client.getRecordsAsync("username", [ids]);
//definition of onGetRecordsCompleted of type getRecordsCompletedEventHandler
private void onGetRecordsCompleted(object sender, getRecordsCompletedEventArgs e)
{
if(e.Error != null)
{
record[] data = e.Result;
//do something with your data
}
else
{
//handle error
}
}
[Edit]
For point 2
If you are generating your client proxy with svcutil (Visual Studio > add Service reference) you can trust in it :) or you can watch the involved Threads with the Visual Studio Thread window.
For point 3
You might have some Thread synchronization problems, for example if you update some UI components in another Thread than the UI thread where they belong to. So you may need to do some extra work (dispatch).
Windows Forms (BeginInvoke or Dispatcher)
WPF (Dispatcher)