Invoke operation in wcf ria service - silverlight

I use some invoke operation method in wcf ria service.like following method:
[Invoke]
public int GetCountOfAccountsByRequestId(long requestId)
{
return ObjectContext.Accounts.Count(a => a.RequestId ==requestId);
}
When i want get data from this method , i use the following code to run and get value from invoke method:
int countOfAccounts = 0;
InvokeOperation<int> invokeOperation = context.GetCountOfAccountsByRequestId(selectedRequest.RequestId);
invokeOperation.Completed += (s, args) =>
{
if (invokeOperation.HasError)
{
var messageWindow = new MessageWindow(invokeOperation.Error.Message);
messageWindow.Show();
invokeOperation.MarkErrorAsHandled();
}
else
{
countOfAccounts = invokeOperation.Value;
if (countOfAccounts == 0)
{
// do some thing
}
}
};
But this method requires a lot of coding to Run invoke method and get value from this.Also as part of this code is executed asynchronously.Similarly, some method must be implemented in tandem.And the return value of each method is related to previous methods.
How can i implement this actions?!
How can i write better than this without any problem?

For WCF RIA Services I usually create a service class in my project to handle all the interactions with the service in one place. So I would use this service class to make the calls easier to understand and use.
I put your existing code in a separate class so you can see how it might be called. Note: This is just an example. :)
public class ServiceWrapper
{
...
public void GetCountOfAccountsByRequestId(int requestId, Action<int> callback)
{
context.GetCountOfAccountsByRequestId(requestId, InvokeComplete, callback);
}
private void InvokeComplete<T>(InvokeOperation<T> io)
{
var callback = (Action<T>)io.UserState;
if (io.HasError == false)
{
callback(io.Value);
}
else
{
var messageWindow = new MessageWindow(io.Error.Message);
messageWindow.Show();
io.MarkErrorAsHandled();
}
}
}
public class YourCode
{
public void SomeMethod()
{
serviceWrapper.GetCountOfAccountsByRequestId(selectedRequest.RequestId, GetCountCompleted);
}
public void GetCountCompleted(int countOfAccounts)
{
if (countOfAccounts == 0)
{
// do some thing
}
}
}

Related

ABPFramwork - Remove api from layer application in swagger

I have created a project using abpframwork. When running swagger, swagger receives the function in the application layer is a api. I don't want that. Can you guys tell me how to remove it in swagger
Code in Application Layer
public class UserService : AdminSSOAppService, ITransientDependency, IValidationEnabled, IUserService
{
IUserRepository _userRepository;
private readonly ILogger<UserService> _log;
public UserService(IUserRepository userRepository,
ILogger<UserService> log
)
{
_userRepository = userRepository;
_log = log;
}
public async Task<List<UserDto>> GetList()
{
var list = await _userRepository.GetListAsync();
return ObjectMapper.Map<List<User>, List<UserDto>>(list);
}
public async Task<UserDto> GetUserById(int Id)
{
var user = await _userRepository.GetAsync(c=>c.Id == Id);
return ObjectMapper.Map<User, UserDto>(user);
}
}
Code in HttpApi Layer
[Area(AdminSSORemoteServiceConsts.ModuleName)]
[RemoteService(Name = AdminSSORemoteServiceConsts.RemoteServiceName)]
[Route("api/user/user-profile")]
public class UserController : ControllerBase, IUserService
{
private readonly IUserService _userAppService;
public UserController(IUserService userAppService)
{
_userAppService = userAppService;
}
[HttpGet]
[Route("get-list-httpapi")]
public Task<List<UserDto>> GetList()
{
return _userAppService.GetList();
}
[HttpGet]
[Route("get-by-id-httpapi")]
public Task<UserDto> GetUserById(int Id)
{
return _userAppService.GetUserById(Id);
}
}
I can suggest a workaround as to enable only the APIs you need to appear on swagger (though the ones that don't appear anymore will still be available for consumption).
I would suggest you add a configuration part in your *.Http.Api project module inside your ConfigureSwaggerServices, like so:
context.Services.AddSwaggerGen(options =>
{
options.DocInclusionPredicate(
(_, apiDesc) =>
apiDesc
.CustomAttributes()
.OfType<IncludeInSwaggerDocAttribute>()
.Any());
});
And for the attribute, it would be very simple, like so:
[AttributeUsage(AttributeTargets.Class)]
public class IncludeInSwaggerDocAttribute : Attribute
{
}
This will let you achieve what you want, however I still recommend reading the doc carefully to be able to implement DDD.

Testing this command

I'm quite new to unit tests and I've got some troubles testing this command
internal async Task OnDeleteTreasurerCommandExecute(TesorieraItemResult tesoriera)
{
try
{
if (await MessageService.ShowAsync("Confermare l'operazione?", string.Empty, MessageButton.YesNo, MessageImage.Question) == MessageResult.Yes)
{
await repository.DeleteTesorieraItemAsync(tesoriera.ID_ISTITUTO,tesoriera.ID_DIVISA,tesoriera.PROGRESSIVO);
await MessageService.ShowInformationAsync("Operazione completata");
if (SelectedInstitute != null)
await OnLoadDataCommandExecute();
}
}
catch (Exception ex)
{
ErrorService.HandleError(GetType(), ex);
}
}
I'm using Catel as MVVM framework
how do I simulate the yes/no answers?
Thanks
You need to substitute the MessageService with a class that can return yes or no answer. Here's an example using NSubstitute.
Install-Package NSubstitute
Install-Package NUnit
Let us say you have a class that has a method that
needs Yes, then No:
public class AccountViewModel
{
readonly IMessageService _messageService;
readonly ICustomerRepository _customerRepository;
public AccountViewModel(IMessageService messageService, ICustomerRepository customerRepository)
{
_messageService = messageService;
_customerRepository = customerRepository;
}
public async Task OnDeleteCustomer(Customer customer)
{
if (await MessageService.ShowAsync(
"Confirm?",
string.Empty,
MessageButton.YesNo,
MessageImage.Question) == MessageResult.Yes)
{
_customerRepository.Delete(customer);
await MessageService.ShowInformationAsync("Completed");
}
}
}
Then your test case looks like this:
public class TestAccountViewModel
{
[TestCase]
public class TestDeleteCustomer()
{
// arrange
var messageService = Substitute.For<IMessageService>();
messageService
.ShowAsync(
Arg.Any<string>(),
Arg.Any<string>(),
Arg.Any<MessageButton>(),
Arg.Any<MessageImage>())
.Returns(Task.FromResult(MessageResult.Yes);
messageService
.ShowInformationAsync(Arg.Any<string>())
.Returns(Task.FromResult<object>(null));
var customerRepository = Substitute.For<ICustomerRepository>();
// act
var sut = new AccountViewModel(messageService, customerRepository);
var customer = new Customer();
sut.OnDeleteCustomer(customer);
// assert
Assert.IsTrue(customerRepository.Received().DeleteCustomer(customer));
}
}
In a past version, Catel provided a test implementation of the IMessageService that allowed you to queue expected results so you could test the different paths inside a command.
I just noticed this class is no longer available, but you can easily implement a test stub yourself (using mocking, etc). Or you could contribute to Catel and revive the test implementation.

Easy way to dynamically invoke web services (without JDK or proxy classes)

In Python I can consume a web service so easily:
from suds.client import Client
client = Client('http://www.example.org/MyService/wsdl/myservice.wsdl') #create client
result = client.service.myWSMethod("Bubi", 15) #invoke method
print result #print the result returned by the WS method
I'd like to reach such a simple usage with Java.
With Axis or CXF you have to create a web service client, i.e. a package which reproduces all web service methods so that we can invoke them as if they where normal methods. Let's call it proxy classes; usually they are generated by wsdl2java tool.
Useful and user-friendly. But any time I add/modify a web service method and I want to use it in a client program I need to regenerate proxy classes.
So I found CXF DynamicClientFactory, this technique avoids the use of proxy classes:
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.dynamic.DynamicClientFactory;
//...
//create client
DynamicClientFactory dcf = DynamicClientFactory.newInstance();
Client client = dcf.createClient("http://www.example.org/MyService/wsdl/myservice.wsdl");
//invoke method
Object[] res = client.invoke("myWSMethod", "Bubi");
//print the result
System.out.println("Response:\n" + res[0]);
But unfortunately it creates and compiles proxy classes runtime, hence requires JDK on the production machine. I have to avoid this, or at least I can't rely on it.
My question:
Is there another way to dinamically invoke any method of a web service in Java, without having a JDK at runtime and without generating "static" proxy classes? Maybe with a different library? Thanks!
I know this is a really old question but if you are still interested you could use soap-ws github project: https://github.com/reficio/soap-ws
Here you have a sample usage really simple:
Wsdl wsdl = Wsdl.parse("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL");
SoapBuilder builder = wsdl.binding()
.localPart("CurrencyConvertorSoap")
.find();
SoapOperation operation = builder.operation()
.soapAction("http://www.webserviceX.NET/ConversionRate")
.find();
Request request = builder.buildInputMessage(operation)
SoapClient client = SoapClient.builder()
.endpointUrl("http://www.webservicex.net/CurrencyConvertor.asmx")
.build();
String response = client.post(request);
As you can see it is really simple.
With CXF 3.x this could be possible with StaxDataBinding. Follow below steps to get the basics. Of course, this could be enhanced to your needs.
Create StaxDataBinding something like below. Note below code can be enhanced to your sophistication.
class StaxDataBinding extends AbstractInterceptorProvidingDataBinding {
private XMLStreamDataReader xsrReader;
private XMLStreamDataWriter xswWriter;
public StaxDataBinding() {
super();
this.xsrReader = new XMLStreamDataReader();
this.xswWriter = new XMLStreamDataWriter();
inInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inFaultInterceptors.add(new StaxInEndingInterceptor(Phase.POST_INVOKE));
inInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
inFaultInterceptors.add(RemoveStaxInEndingInterceptor.INSTANCE);
}
static class RemoveStaxInEndingInterceptor
extends AbstractPhaseInterceptor<Message> {
static final RemoveStaxInEndingInterceptor INSTANCE = new RemoveStaxInEndingInterceptor();
public RemoveStaxInEndingInterceptor() {
super(Phase.PRE_INVOKE);
addBefore(StaxInEndingInterceptor.class.getName());
}
public void handleMessage(Message message) throws Fault {
message.getInterceptorChain().remove(StaxInEndingInterceptor.INSTANCE);
}
}
public void initialize(Service service) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
SchemaCollection schemaCollection = serviceInfo.getXmlSchemaCollection();
if (schemaCollection.getXmlSchemas().length > 1) {
// Schemas are already populated.
continue;
}
new ServiceModelVisitor(serviceInfo) {
public void begin(MessagePartInfo part) {
if (part.getTypeQName() != null
|| part.getElementQName() != null) {
return;
}
part.setTypeQName(Constants.XSD_ANYTYPE);
}
}.walk();
}
}
#SuppressWarnings("unchecked")
public <T> DataReader<T> createReader(Class<T> cls) {
if (cls == XMLStreamReader.class) {
return (DataReader<T>) xsrReader;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedReaderFormats() {
return new Class[] { XMLStreamReader.class };
}
#SuppressWarnings("unchecked")
public <T> DataWriter<T> createWriter(Class<T> cls) {
if (cls == XMLStreamWriter.class) {
return (DataWriter<T>) xswWriter;
}
else {
throw new UnsupportedOperationException(
"The type " + cls.getName() + " is not supported.");
}
}
public Class<?>[] getSupportedWriterFormats() {
return new Class[] { XMLStreamWriter.class, Node.class };
}
public static class XMLStreamDataReader implements DataReader<XMLStreamReader> {
public Object read(MessagePartInfo part, XMLStreamReader input) {
return read(null, input, part.getTypeClass());
}
public Object read(QName name, XMLStreamReader input, Class<?> type) {
return input;
}
public Object read(XMLStreamReader reader) {
return reader;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String prop, Object value) {
}
}
public static class XMLStreamDataWriter implements DataWriter<XMLStreamWriter> {
private static final Logger LOG = LogUtils
.getL7dLogger(XMLStreamDataWriter.class);
public void write(Object obj, MessagePartInfo part, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
// WRITE YOUR LOGIC HOW you WANT TO HANDLE THE INPUT DATA
//BELOW CODE JUST CALLS toString() METHOD
if (part.isElement()) {
QName element = part.getElementQName();
writer.writeStartElement(element.getNamespaceURI(),
element.getLocalPart());
if (obj != null) {
writer.writeCharacters(obj.toString());
}
writer.writeEndElement();
}
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
public void write(Object obj, XMLStreamWriter writer) {
try {
if (!doWrite(obj, writer)) {
throw new UnsupportedOperationException("Data types of "
+ obj.getClass() + " are not supported.");
}
}
catch (XMLStreamException e) {
throw new Fault("COULD_NOT_READ_XML_STREAM", LOG, e);
}
}
private boolean doWrite(Object obj, XMLStreamWriter writer)
throws XMLStreamException {
if (obj instanceof XMLStreamReader) {
XMLStreamReader xmlStreamReader = (XMLStreamReader) obj;
StaxUtils.copy(xmlStreamReader, writer);
xmlStreamReader.close();
return true;
}
else if (obj instanceof XMLStreamWriterCallback) {
((XMLStreamWriterCallback) obj).write(writer);
return true;
}
return false;
}
public void setSchema(Schema s) {
}
public void setAttachments(Collection<Attachment> attachments) {
}
public void setProperty(String key, Object value) {
}
}
}
Prepare your input to match the expected input, something like below
private Object[] prepareInput(BindingOperationInfo operInfo, String[] paramNames,
String[] paramValues) {
List<Object> inputs = new ArrayList<Object>();
List<MessagePartInfo> parts = operInfo.getInput().getMessageParts();
if (parts != null && parts.size() > 0) {
for (MessagePartInfo partInfo : parts) {
QName element = partInfo.getElementQName();
String localPart = element.getLocalPart();
// whatever your input data you need to match data value for given element
// below code assumes names are paramNames variable and value in paramValues
for (int i = 0; i < paramNames.length; i++) {
if (paramNames[i].equals(localPart)) {
inputs.add(findParamValue(paramNames, paramValues, localPart));
}
}
}
}
return inputs.toArray();
}
Now set the proper data binding and pass the data
Bus bus = CXFBusFactory.getThreadDefaultBus();
WSDLServiceFactory sf = new WSDLServiceFactory(bus, wsdl);
sf.setAllowElementRefs(false);
Service svc = sf.create();
Client client = new ClientImpl(bus, svc, null,
SimpleEndpointImplFactory.getSingleton());
StaxDataBinding databinding = new StaxDataBinding();
svc.setDataBinding(databinding);
bus.getFeatures().add(new StaxDataBindingFeature());
BindingOperationInfo operInfo = ...//find the operation you need (see below)
Object[] inputs = prepareInput(operInfo, paramNames, paramValues);
client.invoke("operationname", inputs);
If needed you can match operation name something like below
private BindingOperationInfo findBindingOperation(Service service,
String operationName) {
for (ServiceInfo serviceInfo : service.getServiceInfos()) {
Collection<BindingInfo> bindingInfos = serviceInfo.getBindings();
for (BindingInfo bindingInfo : bindingInfos) {
Collection<BindingOperationInfo> operInfos = bindingInfo.getOperations();
for (BindingOperationInfo operInfo : operInfos) {
if (operInfo.getName().getLocalPart().equals(operationName)) {
if (operInfo.isUnwrappedCapable()) {
return operInfo.getUnwrappedOperation();
}
return operInfo;
}
}
}
}
return null;
}

making async calls using mvvm in silverlight

I am trying to make a call to a wcf service with my silverlight application and I am having some trouble understanding how the model returns the result back to the view model. Within my view model I have the following command:
public DelegateCommand GetSearchResultCommand
{
get
{
if (this._getSearchResultCommand == null)
this._getSearchResultCommand = new DelegateCommand(GetSearchResultCommandExecute, CanGetSearchResultsCommandExecute);
return this._getSearchResultCommand;
}
}
private void GetSearchResultCommandExecute(object parameter)
{
this.SearchResults = this._DataModel.GetSearchResults(this.SearchTerm);
}
/// <summary>
/// Bindable property for SearchResults
/// </summary>
public ObservableCollection<QueryResponse> SearchResults
{
get
{
return this._SearchResults;
}
private set
{
if (this._SearchResults == value)
return;
// Set the new value and notify
this._SearchResults = value;
this.NotifyPropertyChanged("SearchResults");
}
}
then within my model I have the following code
public ObservableCollection<QueryResponse> GetSearchResults(string searchQuery)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += new EventHandler<QueryCompletedEventArgs>(sc_QueryCompleted);
sc.QueryAsync(new Query { QueryText = searchQuery });
return LastSearchResults;
}
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
this.LastSearchResults = results;
}
When I insert breakpoints within the model I see where the query is being executed and a result is returned within the model (this.LastSearchResults = results) however I cannot seem to get this collection to update/ notify the view model of the result. I've generated and run a similar test using just a method and dummy class and it seems to work so I suspect the issue is due to the async call /threading. I have INotifyPropertyChanged within the ViewModel to sync the View and ViewModel. Do I need to also implement INotifyPropChng within the model as well? I'm new to mvvm so any help / example of how I should approach this would be appreciated.
Thank you,
UPDATE
In further testing I added INotifyPropertyChanged to the model and changed the Completed event as follows:
void sc_QueryCompleted(object sender, QueryCompletedEventArgs e)
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
//this.LastSearchResults = results;
SearchResults = results;
}
Placing a watch on Search Results I now see it is updated with results from teh WCF. My question is still around is this teh correct approach? It seems to work right now however I am curious if I am missing something else or if I should not be placing INotify within the Model.
Thank you,
I've found that it's best to encapsulate my WCF services in an additional layer of Service classes. This allows me to more easily Unit Test my ViewModels. There are several patterns when doing this, though this is the simplest I've used. The pattern is to create a method that matches the definition of the service call, though also contains an Action that can be invoked after the service call completes.
public class Service : IService
{
public void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply)
{
//return type cannot be void needs to be a collection
SearchClient sc = new SearchClient();
//******
//TODO: stubbed in placeholder for Endpoint Address used to retreive proxy address at runtime
// sc.Endpoint.Address = (clientProxy);
//******
sc.QueryCompleted += (s,e) =>
{
ObservableCollection<QueryResponse> results = new ObservableCollection<QueryResponse>();
results.Add(e.Result);
reply(results);
};
sc.QueryAsync(new Query { QueryText = searchQuery });
}
}
You can also provide an interface that your ViewModel can use. This makes Unit Testing even easier, though is optional.
public interface IService
{
void GetSearchResults(string searchQuery, Action<ObservableCollection<QueryResponse>> reply);
}
Your ViewModel would then look something like this:
public class MyViewModel : INotifyPropertyChanged
{
private IService _service;
public MyViewModel()
: this(new Service())
{ }
public MyViewModel(IService service)
{
_service = service;
SearchResults = new ObservableCollection<QueryResponse>();
}
private ObservableCollection<QueryResponse> _searchResults
public ObservableCollection<QueryResponse> SearchResults
{
get { return _searchResults; }
set
{
_searchResults = value;
NotifyPropertyChanged("SearchResults");
}
}
public void Search()
{
_service.GetSearchResults("abcd", results =>
{
SearchResults.AddRange(results);
});
}
protected void NotifyPropertyChanged(string property)
{
var handler = this.PropertyChanged;
if(handler != null)
handler(new PropertyChangedEventArgs(property));
}
}
An additional reason for encapsulating your service calls into another class like this is that it can provide a single place for such things as logging and error handling. That way your ViewModel itself doesn't need to take care of those things specifically related to the Service.
I would likely use something along the lines of:
public class ViewModel : INotifyPropertyChanged
{
private readonly IModel model;
private readonly DelegateCommand getSearchResultsCommand;
public DelegateCommand GetSearchResultsCommand
{
get { return getSearchResultsCommand; }
}
public ObservableCollection<QueryResponse> SearchResults
{
get { return model.SearchResults; }
}
public ViewModel(IModel model)
{
this.model = model;
this.model.SearchResultsRetrieved += new EventHandler(model_SearchResultsRetrieved);
this.getSearchResultsCommand = new DelegateCommand(model.GetSearchResultCommandExecute, model.CanGetSearchResultsCommandExecute);
}
private void model_SearchResultsRetrieved(object sender, EventArgs e)
{
this.NotifyPropertyChanged("SearchResults");
}
}
public interface IModel
{
event EventHandler SearchResultsRetrieved;
void GetSearchResultCommandExecute(object parameter);
bool CanGetSearchResultsCommandExecute(object parameter);
ObservableCollection<QueryResponse> SearchResults { get; }
}
With the SearchResultsRetrieved event being fired by the Model when its SearchResults collection has been filled with the appropriate data. I prefer to have custom events rather than implement INotifyPropertyChanged on my models, particularly if there are only one, or a few, events that need to be communicated to the viewmodel.

Silverlight Async Method Chaining (Possible gotchas?)

I am working on a 'proof of concept' Silverlight 4 project and am learning the way of THE ASYNC. I have stopped fighting the urge to implement some pseudo-synchronous smoke and mirrors technique. I am going to learn to stop worrying and love THE ASYNC.
Most of the time I just use a BusyIndicator while async methods are running and all is good but I have run into a few situations where I need to call methods sequentially. I put together this example and it works. But in my experience... if it works... there is something wrong with it.
When is this going to blow up in my face or steal my wife or date one of my daughters?
Is there a better way to do this?
The Code:
public class CustomPage : Page
{
static readonly object _AsyncMethodChain_Lock = new object();
private Dictionary<Action<object>, string> _AsyncMethodChain = new Dictionary<Action<object>, string>();
public Dictionary<Action<object>, string> AsyncMethodChain
{
get { lock (_AsyncMethodChain_Lock) { return this._AsyncMethodChain; } }
set { lock (_AsyncMethodChain_Lock) { this._AsyncMethodChain = value; } }
}
private void CustomPage_Loaded(object sender, RoutedEventArgs e)
{
if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
{
var user = this.SecurityProvider.UserObject as TimeKeeper.UserServiceReference.User;
if (user == null)
return;
this.AsyncMethodChain.Add(
data =>
{
var userServiceClient = new UserServiceClient();
userServiceClient.GetCompleted +=
(send, arg) =>
{
var userViewSource = this.Resources["userViewSource"] as CollectionViewSource;
userViewSource.Source = new List<UserServiceReference.User>(new UserServiceReference.User[1] { arg.Result });
userViewSource.View.MoveCurrentToPosition(0);
this.AsyncMethodChain.ExecuteNext(arg.Result.UserID, this.BusyIndicator);
};
userServiceClient.GetAsync(user.UserID);
},
"Loading user..."
);
this.AsyncMethodChain.Add(
data =>
{
var userID = (int)data;
var timeLogServiceClient = new TimeLogServiceClient();
timeLogServiceClient.FindByUserIDCompleted +=
(send, arg) =>
{
var timeLogViewSource = this.Resources["timeLogViewSource"] as CollectionViewSource;
timeLogViewSource.Source = arg.Result;
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
};
timeLogServiceClient.FindByUserIDAsync(userID);
},
"Loading time logs..."
);
this.AsyncMethodChain.ExecuteNext(null, this.BusyIndicator);
}
}
}
public static class Extensions
{
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data, BusyIndicator busyIndicator)
{
if (methods.Count <= 0)
{
busyIndicator.BusyContent = "";
busyIndicator.IsBusy = false;
return;
}
else
{
var method = methods.Keys.ToList<Action<object>>()[0];
busyIndicator.BusyContent = methods[method];
busyIndicator.IsBusy = true;
}
methods.ExecuteNext(data);
}
public static void ExecuteNext(this Dictionary<Action<object>, string> methods, object data)
{
var method = methods.Keys.ToList<Action<object>>()[0];
methods.Remove(method);
method(data);
}
}
What you have sine looks pretty good, but if you are still worried about the callsequence i would sugest that you create a new method in your webservice which will call the other four in whatever sequence you need them to and call this new method from your silverlight application.
I dont see the need for you to do that as your current implemenation is pretty good as well.

Resources