Passing parameters for reference data

Topics: CAB & Smart Client Software Factory
Jun 10, 2004 at 2:55 AM
originally posted by: webman

Despite the thorough documentation on the Offline Application Block, I am having problems.

I have my web service set up to return data based on a parameter passed. How do I pass the parameter when using the OAB?

A normal call would be:
Dim myWS as new <web service>
Dim myObj as String
myObj = myWS.GetMeSomething(“1234”)

To clarify; I want to pass ‘1234’ through the OAB, and I cannot find out how I do this. Is this even possible? If it is not possible, then I can’t see any good use for the OAB, so I would think that it is just me missing something. I have posted a similar question to the MSDN managed newsgroup. I cannot find many resources for this, so I need some direction.

I have been beating my head against the wall on this. I can make it work when there is nothing to pass, but I cannot make it work when there is.

What do I do?

Thanks in advance,
Brad Simon
Jun 10, 2004 at 11:55 AM
originally posted by: webman

Thanks John,

I was working in that direction myself, but I could not get it to work.

I did everything you said, and in the extra argument in the ReferenceDataDefinition, I put in the variable that represented the string that I want to pass through. It didn't work, saying that it could not invoke the method. How do I pass that parameter in that extra argument? I guess I need a little hand holding on this one.

I know all about having work to do :).
Jun 10, 2004 at 10:09 PM
originally posted by: cidor00

Let me know if you need a working example making use of the 'associatedRequestData' overload on your ReferenceDataDefinition. ;-)

Andries Olivier
Jun 11, 2004 at 1:50 AM
originally posted by: webman

I would love to have a working example. That would be great.

Thanks
Jun 11, 2004 at 5:28 AM
originally posted by: JohnAskew

The type for the associatedRequestData argument (the 4th arg) is Object. I would assume that the string type is implicitly cast as an object for the call. I would have thought that there was a type problem for the argument you tried, and there may not have been an overloaded method to handle your type, but that is apparently not the case.

I am using C#, not VB, so perhaps the compiler messages are not the same?

Did you get a working demo from Andries?

Can you post yours or Andries code?
Jun 11, 2004 at 8:02 AM
originally posted by: webman

I have no code at this point.

I am using VB, and the message I get is:
Unable to invoke method: OfflineTesting.OfflineTesting.OnlineProxy.GetWorkExtraItems.

That is all it says.

I would have suspected that the string would have gone through fine, too, since all types are objects. What about when I have many params to pass?

I hope Andries code will shed light on what to do.
Jun 11, 2004 at 8:26 AM
originally posted by: JohnAskew

If you have many parameters to pass, you should put them all into an class and pass that one object.

**
You will need to Serializable C# or <Serializable> VB the object that you want to pass, since it is cached/queued. That is what I am working on right now for my classes. Here is a good link about serialization:

http://search.microsoft.com/search/results.aspx?qu=Run-time+Serialization&View=msdn&st=b&c=4&s=1&swc=4&qp=msdn%20magazine

This is a 3-part article by Jeffery Richter about Serialization. If your parameters are not too customized, you should be able to use the attribute <Serializable> for your class and be done with it in short order.
**

I see a duplication of the OfflineTesting namespace section; is this something the compiler put in place? Maybe you could try removing one of them. Perhaps the error message is trying to say "I can't find the method".

I don't know about you, but I am no .NET guru yet. Things change.

Remember to email rojacobs@microsoft.com and request this forum to be monitored. He is the product manager for most of the Application Blocks and should be able to delegate a Microsoft code sharpie to help us. I am still waiting too...

Jun 11, 2004 at 8:38 AM
originally posted by: webman

The duplicate OfflineTester is not it, I have successfully called an almost identical (different function name) in the same OnlineProxy Class.

I will try to set it up as an object to pass. thanks
Jun 11, 2004 at 10:42 AM
originally posted by: JohnAskew

webman,

I now have a working download that is seeded with an ID.
The one item that I added in addition to those things I posted yesterday is in my equivalent to the OnlineProxy method GetWorkItems. I have extracted the parameter object, in this case a Customer class object, from the payload.

bo = (Customer)refCacheDataPayload.DataDefinition.AssociatedRequestData;

This object is then passed into the WebMethod for data-filling.

MyWebService myService = new MyWebService();
myService.GetMyBusinessObject(bo);
refCacheDataPayload.UpdateDataToReturn(bo);

You will have to translate the C# to VB.

**
My modus operandi is for the AssociatedRequestData object parameter to be business objects from my class library. These classes each contain their own CRUD functionality - create, read, update, delete - and the WebService simply calls those methods. So my arguments will contain the results of the query and be downloaded back to me filled. Your scenario may be to pass the argument object up with parameters for a query and then return a dataset similar to the InsuranceClaims demo.

HTH
Jun 11, 2004 at 10:50 AM
originally posted by: JohnAskew

The Payload class has an argument of type object that will carry anything up to the web service; that is internal to the block and you don't need to customize it. That is how your ID will be carried.

1) Have your application call a custom Controller.Download() method -- write one to suit your needs in your Controller class. This method will look just like the Controller.DownLoad() method in the Insurance demo app, but you will have customized the argument to pass data up to the web service.

2) Controller.Download() method takes the argument-data (e.g. an ID) for an arg. and calls a ServiceAgent.DoMyRequest() method that you will write. It will look like the ServiceAgent.GetWorkItems() method in the Insurance demo app. except that it will have your argument type whereas the demo's ServiceAgent.GetWorkItems() has none. Back to the Controller.Download() method: the ReferenceDataDefinition object has an overloaded method that takes 4 arguments instead of the one in the Insurance app. that only takes 3. That 4th argument is of type object, so it can fit anything, such as an ID. "ReferenceDataDefinition refDataDefinition = new ReferenceDataDefinition(cacheKey, absoluteExpirationTime, onlineProxyMethodContext, bo);" where bo is your argument, business object, or ID. That 4th argument is the object the Payload class carries between webservice and application.

    • I had mistakenly typed 'webservice' where I now put 'ServiceAgent'; sorry for the confusion.


3) Now the webservice will have the ID and can lookup data with it.

I will help you all I can, but I have to do work too. Let's gang up on Blaine! :D
HTH
Jun 11, 2004 at 11:21 AM
originally posted by: webman

I knew it was something simple!

Thanks a lot John.

You and I have the same type of project, the CRUD is in the BO, not the app or web service.

That is the fix
Jun 11, 2004 at 10:54 PM
originally posted by: cidor00

Did you manage to solve your problem?
Jun 12, 2004 at 1:41 AM
originally posted by: webman

Yes, the problem is solved.
Mar 11, 2005 at 3:55 PM
originally posted by: Taniwha

I am not getting this to work, so I would like to bring this thread back to life!

I understand now about the 4th parameter.
I understand it has to be a Business Object.
I do not understand how to convert a int or a string to a Business object?

To overcome that last issue I thought I would create a serializable object.

Serializable
public class StartUpParameters
{
public int CompanyID;
public StartUpParameters()
{
}
}

I placed this class in my Webservice.
On the client I instantiated this class and made it the Business object to send as a parameter.
When I test my app I now get an error message that says my client app does not recognise the class as serializable.

Can anyone please put up a working example of how this all works?

__Allan
Mar 12, 2005 at 7:58 AM
originally posted by: JohnAskew

3) When the data reaches the server via the web service, I suggest you simply feed it to a stored procedure to save onto your sql server. If you cannot standardize on a DB server and need to support many DB engines, you may elect to copy your lightweight XML data back into your myLibrary class (with CRUD capability) for saving to the DB.

-----------------------------------------------------
The code that I have written to make the Offline block work for my situation is not trivial to write. It is worth the effort.
It will take time to get familiar with how to interface with all the Offline block parts; again it is worth it.

FWIW, I am using datasets for downloads and have found that I gained time but lost some hands-on control over the data etc. Boxing/unboxing object data is probably the best way to implement downloads, if you have time.

I gotta get back to work...
HTH
Mar 12, 2005 at 8:04 AM
originally posted by: JohnAskew

2) You will need to box up your objects in lightweight XML wrapper classes for the physical upload to the web service server. This is when the data stored in the Queue gets extracted and the web service called for uploading. I do this in the OnlineProxy.cs class.

#region SetBusinessObject()
/// <summary>
/// Call the web service for updating the data to the server.
/// </summary>
/// <param name="refDataPayload"></param>
public Payload SetBusinessObject(Payload refDataPayload)
{
if(refDataPayload.RequestData is myLibrary.BusinessObject)
{
Type myType = refDataPayload.RequestData.GetType();
switch(myType.Name)
{
case "Account":
{
myLibrary.Account myAccount = (myLibrary.Account) refDataPayload.RequestData;
mySynchronization.WSAccount SyncAccount = new mySynchronization.WSAccount();

PrepareAccountSynchronization(ref SyncAccount, ref myAccount);

mySynchronization.BusinessObjectService sfaWebService = new mySynchronization.BusinessObjectService();
myWebService.Credentials = System.Net.CredentialCache.DefaultCredentials;
refDataPayload.Results = myWebService.SetBusinessObject(SyncAccount);
break;
}

.................etc...

#region PrepareAccountSynchronization()
/// <summary>
/// This method simply boxes Account business object into an XML wrapper class for transport.
/// The binary part of our business objects will not be accepted by the web service (XML only data), so we use proxy classes.
/// The
/// </summary>
/// <param name="SyncAccount"></param>
/// <param name="myAccount"></param>
internal void PrepareAccountSynchronization(
ref mySynchronization.WS_Account SyncAccount,
ref myLibrary.Account myAccount)
{
if(!Object.Equals(SyncAccount, null) && !Object.Equals(myAccount, null))
{
try
{
SyncAccount.AccountID = myAccount.AccountID;
SyncAccount.Name = myAccount.Name;
SyncAccount.CMCUST = myAccount.CMCUST;
SyncAccount.CMPACT = myAccount.CMPACT;
SyncAccount.TerritoryID = myAccount.TerritoryID;
SyncAccount.URL = myAccount.URL;
SyncAccount.Notes = myAccount.Notes;

// ROLLUP NESTED CLASSES CONTAINED IN THIS CLASS; A.K.A. THE OBJECT GRAPH
if(myAccount.Contacts.Count > 0)
{
int iCount = myAccount.Contacts.Count;
SyncAccount.Contacts = new mySynchronization.WS_ContactiCount;
for(int i = 0; i < iCount; i++)
{
myLibrary.Contact aContact = myAccount.Contactsi;
SyncAccount.Contactsi = new mySynchronization.WS_Contact();
PrepareContactSynchronization(ref SyncAccount.Contactsi, ref aContact);
}
}

.......................etc...
Mar 12, 2005 at 8:29 AM
originally posted by: JohnAskew

Taniwha:

Let me first say that I have a rich client with MSDE for my laptop data, aside from the Offline block storage databases (QueueDatabase & CacheDatabase), so I only use the Queue to stage data uploads for an online upload opportunity.

What follows will generate more questions than answers, I would imagine...

To answer your question about serialization:

1) You must include binary Serialization for your objects, using interface ISerializable, as shown below.
When the objects are sent up to the web service, the offline block pulls the request into the QueueDatabase.QueueData table as a binary stream; which is why you must support binary serialization.
ISerializable gives you tight control over what is stored.

Example:

Serializable
public class StartUpParameters : ISerializable
{
public int CompanyID;


#region Serialization

//Deserialization constructor.
protected StartUpParameters (SerializationInfo info, StreamingContext ctxt): base()
{
// Get the set of serializable members for our class and base classes
Type thisType = this.GetType();
MemberInfo[] mi = FormatterServices.GetSerializableMembers(thisType, ctxt);

// Deserialize the base class's fields from the info object -- YOU DONT HAVE ONE COMMENT OUT
for(Int32 i = 0; i < mi.Length; i++)
{
// don't deserialize fields for this class
if(mii.DeclaringType == thisType) continue;
// to ease coding, treat the member as a FieldInfo object FieldInfo fi = (FieldInfo)mii
// set the field to the deserialized value
fi.SetValue(this, info.GetValue(fi.Name, fi.FieldType));
}

//Get the values from info and assign them to the appropriate properties CompanyID = (int)info.GetValue("CompanyID", typeof(int));

//Name = (string)info.GetValue("Name", typeof(string)); // etc...
}

//Serialization function.
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
// Get the set of Serializable members for our class and base classes
Type thisType = this.GetType();
MemberInfo[] mi = FormatterServices.GetSerializableMembers(thisType, ctxt);

// Serialize the base class's fields to the info object
for(Int32 i = 0; i < mi.Length; i++)
{
if(mii.DeclaringType == thisType) continue;
info.AddValue(mii.Name, ((FieldInfo)mii).GetValue(this));
}

// Serialize the desired values for this class
info.AddValue("CompanyID", CompanyID);

//info.AddValue("Name", Name); // etc...

}
#endregion

public StartUpParameters()
{
}
}
Mar 14, 2005 at 2:36 PM
originally posted by: Taniwha

>> I do not understand how to convert a int or a string to a Business object?

So after some more testing I found that a string or a int (which I was trying to use) is a valid parameter to send to the ReferenceDataDefinition object. the translation to an Object is automated.

The part that made it work though was that I had to change the call in the OlineProxy.cs that when like this:
refCacheDataPayload.UpdateDataToReturn(insuranceClaimsObj.DownloadWorkItems());

because I was trying to send an int I changed the line to read:
refCacheDataPayload.UpdateDataToReturn(insuranceClaimsObj.DownloadWorkItems( (int) refCacheDataPayload.DataDefinition.AssociatedRequestData));

I now am able to send a int or string parameter to my web service.

__Allan
Mar 14, 2005 at 6:05 PM
originally posted by: Taniwha

John,

Thank-you for your prompt reply. I'm still crawling here so bare with me?

I've accomplished sending a int or string value to my WebMethod and it all works fine. Sending an object still has me stumped?

The serializable class we have worked on togeather StartUpParameters - where is this class defined ( application, webservice or share library)?
I've been trying to define it in my webservice

In the OnLineProxy.cs I am declaring my BO as our seralizable class.
However passing this parameter to the WebMethod is proving difficult.

What is the paramater type that the WebMethod is expecting.

Would you mind quickly answering the above question and anything else that you think I'm missing so I can get past this point and move on?

TIA

__Allan
Mar 15, 2005 at 4:31 AM
originally posted by: JohnAskew

Put your classes for transport in an assembly that both the Web Service project and your client-side application can share. I put them in a Library.

OnLineProxy.cs is client-side only. That is where I box my library classes into their XML-wrapper proxies.

When you reference the assembly (library dll) from your web service, you will use a Web Service XML-based wrapper class as proxies for your library classes, at least I did. These XML wrapper classes are paper-thin, include only the data for your class, this is because XML is text only, not binary. The wrapper classes are defined in the Web Service project's Reference.cs. I don't remember this morning how/when that file gets auto-generated, it must be when I referenced that shared library assembly from the web service project; it was long ago and I have not had any coffee yet this morning. I remember having to rename the classes to prevent name clashes on the client-side via the [System.XML.Serialization.XMLTypeAttribute("WS_BusinessObject", Namespace="[url:http://mywebsite.com]")]. These attributes are also auto-generated when the Reference.cs file is built, but the name prefix ("WS_") you need to steer during that file generation.

My Web Service methods expect parameters based on whether an upload or download of data is requested. As I mentioned before, I send my objects straight up to the server's web service as my base BusinessObject (actually as its XML-wrapper proxy class). For downloads, I send only a User object (also derived from my bo base class, and also its XML-wrapper proxy class). Downloads are shipped as datasets, Uploads just get a bool return.

Have a good day.
Mar 15, 2005 at 10:15 AM
originally posted by: Taniwha

John,

>> OnLineProxy.cs is client-side only. That is where I box my library classes into their XML-wrapper proxies.

I need more clarity at this point. The serizable class we've created - that is what I am sending as a parameter. Is that enought or does this class need to be boxed into a XML-wrapper first and if so how?

__Allan
Mar 15, 2005 at 10:48 AM
originally posted by: JohnAskew

The binary serialization is to put objects into the QueueDatabase.QueueData table. That is the only place we need binary serialization.

Now, still on the client-side, the Offline block, when online, pops records off the Queue and uploads them to the server. This is the point where we intervene and box the objects as XML proxy classes. This is done in OnlineProxy.cs on the client-side. Web Services only take XML.

Keep me informed...
Mar 15, 2005 at 11:52 AM
originally posted by: Taniwha

John,

I need to ask the same question again as the answer was not clear.

You remember we have this serialized class we designed last week

Serializable
public class StartUpParameters : ISerializable
{
public int CompanyID;
etc, etc
}

Now the QUESTION is simply this. How do I send the StartUpParameters class to the Web Service?


  • ServiceAgent Class
Now in my ServiceAgent class I am sending the StartUpParameters class as the 4th parameter in the ReferenceDataDefinition.

*OnlineProxy
I am trying to make my call and I put
refCacheDataPayload.UpdateDataToReturn(proxy.DownloadClientConfiguration( (StartUpParameters) refCacheDataPayload.DataDefinition.AssociatedRequestData ));

*WebService
WebMethod
public ClientConfigurationDataSet DownloadClientConfiguration( StartUpParameters sup){


Would you expect this to work or is there a process I am missing out on.

__Allan
Mar 15, 2005 at 12:46 PM
originally posted by: JohnAskew

2) You will need to box up your objects in lightweight XML wrapper classes for the physical upload to the web service server. This is when the data stored in the Queue gets extracted and the web service called for uploading. I do this in the OnlineProxy.cs class.

---- above is from an earlier post, look back at the code in that post too.

Taniwha:
The binary object is stored in the Queue only. That same object must be converted to an XML class before using as a parameter in ReferenceDataDefinition. This conversion should occur in OnlineProxy.cs, since that is the point where the binary object is deserialized back into its original object shape and needs to be turned into an XML class.

You must put your business object classes in a neutral project that you can reference from the client-side Windows Form project, and the server-side Web Service project. This is necessary.

When you Add Reference in your Web Service project, adding your business object library, Reference.cs will have XML classes generated for you.

    • After Add Reference in your Web Service for the library, you will Add Web Reference in your Windows Form project the web service URL. When you add this reference, the Windows Form project will have its own 'Reference.cs' generated from the 'Reference.cs' on the Web Service project. This will contain the classes you will use to feed the 4th parameter, the XML wrapper classes for you business object heirarchy.

You will succeed. Nothing worthwhile comes easy.