WebServices working!

Posts   
1  /  2  /  3  /  4
 
    
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 17-Oct-2005 21:17:56   

Frans,

I have sucessfully gotten the wsdl.exe in .NET 2.0 to generate the correct proxy using LLBLGen entities. I was wondering if you could include the code in this release since its pretty simple.

First off, you need two attributes above each entity that you want serializable. Actually i dont believe XMLRoot is needed, but i used it on mine and it worked, so i would prolly put it in there.

    
 [XmlSchemaProvider("GetEntitySchema")]
 [XmlRoot("CatalogEntity", Namespace = "http://yournamespace", IsNullable = true)]

Next you need to implement the static method GetEntitySchema.


        const string NS = "http://yournamespace/xml/serialization";
        
        public static XmlQualifiedName CatalogSchema(XmlSchemaSet xss)
        {
            XmlSchema xs = XmlSchema.Read(new StringReader(
              "<xs:schema id='CatalogSchema' targetNamespace='" + NS +
              "' elementFormDefault='qualified' xmlns='" + NS + "' xmlns:mstns='" +
              NS + "' xmlns:xs='http://www.w3.org/2001/XMLSchema'><xs:complexType " +
              "name='CatalogEntity'></xs:complexType></xs:schema>"), null);
            xss.XmlResolver = new XmlUrlResolver();
            xss.Add(xs);
            return new XmlQualifiedName("CatalogEntity", NS);
        }

Now i relealize this isnt a correct schema, but it does want i need it to do and seems to work perfectly. Now, this needs to be implemented on every entity that implements IXmlSerializable. In the code i past above, its from a generated CatalogEntity. The Attribute and GetEntitySchema is required becuase it gives the wsdl the name and namespace of the object. So when its generating its proxy it passes the name "CatalogEntity" with the namespace of "http://yournamespace/xml/serialization" to the schemaimportextension. If the attribute and GetEntitySchema function is missing the wsdl just assumes its a dataset, and never passes the info to the schemaimport extension so you dont get a chance to modify the return type for the client proxy.

Here is a SchemaImportExtension i created to replace client proxy generated junk with rich llblgen entities simple_smile This could easily be made into a llbgen template and it generated at the time of creating the entities but ill be happy if youjust include the attributes and schema function simple_smile Creating the schemaimport extension is a whole another ball game, so i just included this so you could better see whats going on.


public class SchemaImporter : SchemaImporterExtension {

        static SchemaImporter()
        {
        }

        public override string  ImportSchemaType(XmlSchemaType type, XmlSchemaObject context, XmlSchemas schemas, XmlSchemaImporter importer, CodeCompileUnit compileUnit, CodeNamespace mainNamespace, CodeGenerationOptions options, CodeDomProvider codeProvider)
        {
            return null;
        }

        public override string ImportSchemaType(string name, string ns, XmlSchemaObject context, XmlSchemas schemas, XmlSchemaImporter importer, CodeCompileUnit compileUnit, CodeNamespace mainNamespace, CodeGenerationOptions options, CodeDomProvider codeProvider)
        {

            if (ns.Equals("http://yournamespace/xml/serialization"))
            {
                switch (name)
                {
                        case "CatalogEntity":
                        {
                            compileUnit.ReferencedAssemblies.Add("yourassemblywhichcontainsllblgenentities.dll");
                            mainNamespace.Imports.Add(new CodeNamespaceImport            ("yournamespace.EntityClasses"));
                            return "CatalogEntity";
                        }
                }
            }
}
}


I hope i made this as clear as possible, but it seems to work beautifully and i have been passing lllbgen entities across web services all morning and its nice not having to manually edit the proxy every time its regenerated simple_smile

Im guessing this will take no longer then 30 minutes to add to the templates. I amnot familar with llblgen template editing yet, so i wanted to see if this is something you could add since im sure other will use this too.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 17-Oct-2005 22:36:21   

Great stuff! I'll try to merge the stuff tomorrow (tuesday) !! simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 18-Oct-2005 00:07:19   

WonderFul!

I made a small mistake on teh code i pasted, the attribute has teh function name "GetEntitySchema" , the function code i pasted has the name "CatalogSchema", change it to GetEntitySchema simple_smile

Also, is it possible for you to create a template to generate the SchemaImporterExtension. All you have to do is take the code i pasted above, and for every entity that is generated add a case statement for it, and of course add case statements for EntityCollection and any other entity that implements IXmlSerializable. This would make doing web services ALMOST effortless stuck_out_tongue_winking_eye

I am pretty sure i created a template to do this a while back when i first tried getting this to work but i seem to have lost it in a reformat. rage

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 18-Oct-2005 08:53:20   

That schema extension class, where is it located? In the same assembly as the generated code?

Frans Bouma | Lead developer LLBLGen Pro
Marcus avatar
Marcus
User
Posts: 747
Joined: 23-Apr-2004
# Posted on: 18-Oct-2005 12:54:33   

Wow this is a great addition!! smile

Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 18-Oct-2005 17:26:46   

The SchemaImportExtension can go anywhere really. In order for it to work though, you have to takea couple steps...

1.) Compile it into a library. (You could add it the generic adapter portion for instance, or any other class library project really. I created a seperate class library project with just the extension in it. But theres no reason really you couldnt add it to the generic adapter portion.) 2.) Sign the assembly 3.) Install the library into the GAC (from cmd.exe gacutil -i assemblyname.dll) 4.) Modify the machine.config to include the extension.

To be honest, its a pain in the butt, but it sure beats modifying the proxy every time its regenerated.

Example setting to add to the machine.config


    <system.xml.serialization>
        <schemaImporterExtensions>
            <add name="SchemaImporter" type="yournamespace.SchemaImporter, assemblyname, Version=1.0.0.0, Culture=neutral, PublicKeyToken=051e40d08c44d2e0" />
        </schemaImporterExtensions>
    </system.xml.serialization>

Now, its kinda finiky. So after you install the assembly into the GAC or modify the machine.config make sure you close VS.NET 2005 and restart it. I also was unable to get it to work if i didnt specify the PublicKeyToken in the machine.config. You can use the commands below to create a key file and get the PublicKeyToken for your assembly.

sn -k sn1.snk Generates and stores the key pair in the file 'sn1.snk'.

sn -p sn1.snk snpub1.snk Extracts the public key from the key pair file 'snk1.snk' and stores the public key in the file 'snpub1.snk'.

sn -t snpub1.snk Generates the Public Key Token from the Public Key.

SchemaImportExtension MSDN page http://msdn2.microsoft.com/en-us/library/ms163210(en-us,vs.80).aspx

Hope this is helps. I suppose i could write a small tutorial on the steps to do it specifically if needed.

Ryan

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 18-Oct-2005 17:54:39   

Thanks! simple_smile

I think I'll add the necessary code to the entities/runtime libs and will provide a template / tutorial for the extension class, as it really should be in its own assembly, as you said.

It could be I generate the class in the dbgeneric project so the user can just copy the class and create a separate assembly.

I hope to have it done by today, though I think it will be tomorrow. (although adding it all is not that big I think simple_smile ). I still have an hour to go before I pack the new beta.

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 18-Oct-2005 22:52:34   

Ok, i have found one problem so far... And i have no idea on how to fix this. But for some reason when using WSE 3.0 , it converts all < characters to < and all > characters to >

POCO objects go through wse 3.0 just fine. I suspect that becuase the object doesnt have a true schema being returned, that something along with way thinks its a string and encodes the string. So im working on actually returning a valid schema. See if that fixes it.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Oct-2005 15:49:05   

Is there a way to specify the schemaimporter assembly in the web.config of the service? It's of course not workable to have the extension added to the machine.config.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Oct-2005 16:23:12   

Ok got it working! simple_smile

I'll now see if I can use the attribute on EntityBase2

(edit) That doesn't seem to work, you have to have the attribute on the class you want to return.

Hmm. Well, I'll generate one file into the EntityClasses namespace, with partial classes for every entity with the attribute and the GetEntitySchema method. As it's a static method, I can't use a generic piece of code in the base class.

I'll then document how to create the schemaimporter and how to enable it. THough I think, the altering of machine.config is pretty bad, even though you only need it during development...

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Oct-2005 17:14:29   

It is however a lot of work to get this documented in full, and to get the stuff generated properly, as the schemaimporter has to be separated, and users have to be guided what to do. I also don't know if the schema is correct...

I'll postpone it till .NET 2.0 RTM is available on November 7th, where the .NET 2.0 runtime libs have to be re-released anyway. I've lost a lot of time this afternoon by searching for docs of beta2 classes which weren't there.

For teh people interested, you can generate an XmlWebservicesHelper class with the following structure:


using System;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;
using System.IO;

namespace <[Rootnamespace]>.EntityClasses
{
<[Foreach Entity]>
    [XmlSchemaProvider("GetEntitySchema")]
    public partial class <[CurrentEntityName]>Entity
    {
        /// <summary>
        /// Method which provides a schema for IXmlSerializable implementation so no proxies are generated.
        /// </summary>
        /// <param name="schemaSet">schema set which is the current schema for the type to produce</param>
        /// <returns></returns>
        public <[If IsSubtype]>new<[EndIf]> static XmlQualifiedName GetEntitySchema(XmlSchemaSet schemaSet)
        {
            string entityNamespace = "http://<[RootNamespace]>/xml/serialization";
            XmlSchema xs = XmlSchema.Read(
                new StringReader(
                    string.Format("<xs:schema id='<[CurrentEntityName]>EntitySchema' targetNamespace='{0}' elementFormDefault='qualified' xmlns='{0}' xmlns:mstns='{0}' xmlns:xs='http://www.w3.org/2001/XMLSchema'><xs:complexType name='<[CurrentEntityName]>Entity'></xs:complexType></xs:schema>",
                        entityNamespace)), null);
            schemaSet.XmlResolver = new XmlUrlResolver();
            schemaSet.Add(xs);
            return new XmlQualifiedName("<[CurrentEntityName]>Entity", entityNamespace);
        }
    }
<[NextForeach]>
}

namespace <[RootNamespace]>.HelperClasses
{
    [XmlSchemaProvider("GetEntityCollectionSchema")]
    public partial class EntityCollection
    {
        /// <summary>
        /// Method which provides a schema for IXmlSerializable implementation so no proxies are generated.
        /// </summary>
        /// <param name="schemaSet">schema set which is the current schema for the type to produce</param>
        /// <returns></returns>
        public static XmlQualifiedName GetEntityCollectionSchema(XmlSchemaSet schemaSet)
        {
            string entityNamespace = "http://<[RootNamespace]>/xml/serialization";
            XmlSchema xs = XmlSchema.Read(
                new StringReader(
                    string.Format("<xs:schema id='EntityCollectionSchema' targetNamespace='{0}' elementFormDefault='qualified' xmlns='{0}' xmlns:mstns='{0}' xmlns:xs='http://www.w3.org/2001/XMLSchema'><xs:complexType name='EntityCollection'></xs:complexType></xs:schema>",
                        entityNamespace)), null);
            schemaSet.XmlResolver = new XmlUrlResolver();
            schemaSet.Add(xs);
            return new XmlQualifiedName("EntityCollection", entityNamespace);
        }
    }
}

For the schema importer:


using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Schema;
using System.Xml.Serialization;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Xml.Serialization.Advanced;

namespace SchemaImporter<[RootNamespace]>
{
    public class SchemaImporter : SchemaImporterExtension
    {
        static SchemaImporter()
        {
        }

        public override string ImportSchemaType(XmlSchemaType type, XmlSchemaObject context, XmlSchemas schemas, XmlSchemaImporter importer, 
                CodeCompileUnit compileUnit, CodeNamespace mainNamespace, CodeGenerationOptions options, CodeDomProvider codeProvider)
        {
            return null;
        }

        public override string ImportSchemaType(string name, string ns, XmlSchemaObject context, XmlSchemas schemas, XmlSchemaImporter importer, 
            CodeCompileUnit compileUnit, CodeNamespace mainNamespace, CodeGenerationOptions options, CodeDomProvider codeProvider)
        {
            if (ns.Equals("http://<[RootNamespace]>/xml/serialization"))
            {
                switch (name)
                {
<[Foreach Entity]>
                    case "<[CurrentEntityName]>Entity":
                    {
                        compileUnit.ReferencedAssemblies.Add("<[RootNamespace]>.dll");
                        mainNamespace.Imports.Add(new CodeNamespaceImport("<[RootNamespace]>.EntityClasses"));
                        return "<[CurrentEntityName]>Entity";
                    }<[NextForeach]>
                    case "EntityCollection":
                    {
                        compileUnit.ReferencedAssemblies.Add("<[RootNamespace]>.dll");
                        mainNamespace.Imports.Add(new CodeNamespaceImport("<[RootNamespace]>.HelperClasses"));
                        return "EntityCollection";
                    }
                }
            }

            return null;
        }
    }
}

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 19-Oct-2005 17:21:57   

Is there a way to specify the schemaimporter assembly in the web.config of the service? It's of course not workable to have the extension added to the machine.config.

I know why they chooose the machin.config but i cant figure out why the also didnt have it look in the web.config.

Yes, each entity must have the function and attribute.

As far as the schema importer, just generating the class is enough.

im still working on the schema.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Oct-2005 18:00:59   

Answer wrote:

Is there a way to specify the schemaimporter assembly in the web.config of the service? It's of course not workable to have the extension added to the machine.config.

I know why they chooose the machin.config but i cant figure out why the also didnt have it look in the web.config.

After a while it made sense though: the service is on the server, the wsdl.exe is not on the server, so the schemaimporter should be on the client, not on the server smile . Prettly logical, but without any documentation it's pretty hard (you must have had a really hard time figuring this all out) to figure it all out...

Yes, each entity must have the function and attribute. As far as the schema importer, just generating the class is enough. im still working on the schema.

I've provided teh templates (from my bare head, some syntax issues here and there might need correction) above. I'll postpone the full docs of this and the inclusion of templates for this till .NET 2.0 is released, as it will take me at least half a day to add it all, test it in vb.net and c# etc., and I don't have that time now (as the deadline is friday evening, so 1.0.2005.1 can be released on sunday)

Keep up the good work, I'll include it a.s.a.p. after the release into the final code (it can be done solely by templates so that's a good thing simple_smile ) and docs.simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 19-Oct-2005 19:04:02   

Yeah, i ahve spent a TON of time working on this. I couldnt get it to work the first day or two and i got frustrated with the serious lack of documentation so i started my project using NHibernate. For some reason i decided to give it another try, and i got it work in like a half hour. GO figure!

Ok, the schema doesnt matter! When using regular web services they work beautifully. When using WSE 3.0 it encodes the xml for some reason. Now i figured out that its something to do with the writer.writeraw(string xml) function that use you to create the xml for the entities.

If i implment IXmlSerializable myself on the entity (not entitybase2) and use


        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            writer.WriteRaw("<name>Ryan</name>");
        }

The WSE trace file looks liek this. As you can see its encoding the < and > characters.


          <GetAddressEntitiesResponse xmlns="http://namespace.com/xml/serialization">
            <GetAddressEntitiesResult>&lt;name&gt;Ryan&lt;/name&gt;</GetAddressEntitiesResult>
          </GetAddressEntitiesResponse>

Now if change IXmlSerializable to this


        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            writer.WriteElementString("name", "Ryan");
        }

The wse trace looks liek this. As you can see its NOT encoding it.


          <GetAddressEntitiesResponse xmlns="http://namespace.com/xml/serialization">
            <GetAddressEntitiesResult>
              <name>Ryan</name>
            </GetAddressEntitiesResult>
          </GetAddressEntitiesResponse>

im guessing this is a bug in WSE?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 19-Oct-2005 20:06:29   

that's really strange... because IXmlSerializable should just dump the xml into the writer, though, I do that, I don't create <name> strings as tags, so that's even more strange...

Also, I don't know of WSE is out of beta (I don't know anything about WSE actually wink )

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 19-Oct-2005 20:44:24   

SOLVED!

ok, two options. Either modify the IXmlSerializable.WriteXml method on Entitybase2...etc OR implement IXmlSerializable on every generated entity. Obvisouly the easiest way is to modify teh base classes.

to


        public virtual void WriteXml(XmlWriter writer)
        {
            string xmlOutput = string.Empty;
            WriteXml(XmlFormatAspect.Compact | XmlFormatAspect.MLTextInCDataBlocks | XmlFormatAspect.DatesInXmlDataType, out xmlOutput);
                        XmlReader reader = XmlReader.Create(new StringReader(xmlOutput));
            writer.WriteNode(reader, true);
        }

by using WriteNode it works over both standard web services and WSE. When you use writeraw() it does no checking and apparently in the WSE it treats it like a string and html encodes it. By using writenode it performs as expected!

Btw...this is all new to me. I've never used web services before nor WSE.

Waiting till nov 7+ isnt going to help me rage Can you include the code but just neglect the docs until later?

Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 19-Oct-2005 23:50:27   

Well ive taken the liberty to actually attempt and learn your generator. I had the hardest time with all the tasks etc...

but i managed to get it!

So i've created a template set for web services.

I guess just post here with your email if your interested.

Just a note, but until frans modifies the EntityBase2 class, passing a EntityBase2 object across a web service using WSE wont work. So if you plan on using WSE make sure you use the entity name, ie CatalogEntity.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 20-Oct-2005 08:34:16   

Answer wrote:

SOLVED!

ok, two options. Either modify the IXmlSerializable.WriteXml method on Entitybase2...etc OR implement IXmlSerializable on every generated entity. Obvisouly the easiest way is to modify teh base classes.

to


        public virtual void WriteXml(XmlWriter writer)
        {
            string xmlOutput = string.Empty;
            WriteXml(XmlFormatAspect.Compact | XmlFormatAspect.MLTextInCDataBlocks | XmlFormatAspect.DatesInXmlDataType, out xmlOutput);
                        XmlReader reader = XmlReader.Create(new StringReader(xmlOutput));
            writer.WriteNode(reader, true);
        }

by using WriteNode it works over both standard web services and WSE. When you use writeraw() it does no checking and apparently in the WSE it treats it like a string and html encodes it. By using writenode it performs as expected!

Btw...this is all new to me. I've never used web services before nor WSE.

Waiting till nov 7+ isnt going to help me rage Can you include the code but just neglect the docs until later?

Sure, as long as it works over standard webservices as well, but it does as you said simple_smile

I'll eventually create a conditional compile for 2.0, that's no problem.

The writeraw was a fix for an issue where 2 or more entities passed to or returned from a webmethod gave invalid xml.

Could elaborate a bit, if time permits, what was so hard to learn the generator stuff, so we can improve our docs? Thanks simple_smile

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 20-Oct-2005 08:46:52   

Hmm, i dont believe any of my quick test functions passed more then one parameter that was an LLBLGen entity. Ill have to check that out tomorow. I am going to start porting my Nhibernate code over to llblgen tomorow so it will be more tested by the end of the week. But so far everything i have thrown at it has passed.

As for the generator stuff, i think it was mostly a naming issue. Once i figured it out its actually quite simple and pretty nifty. I think creating a tutorial that takes you step by step on how the generator works with some sample templates would help a lot. Like maybe walk through how the generator works with adapter.

Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 20-Oct-2005 18:51:22   

So far everything i have thrown at it including passing more then one entity as a parameter works wonderfully in both standard WS and WSE enchanced WS smile

Im quite impressed with its speed too.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 20-Oct-2005 19:52:44   

simple_smile

I have to admit I forgot to add your change to the beta released today. Will do tomorrow

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-Oct-2005 12:08:23   

There's a problem.

Hierarchical entities returned from a webmethod, with polymorphism.

Say I have the webmethod: [WebMethod()] public CustomerEntity GetCustomer(string customerID) { ... }

now, what if I return a FemaleCustomerEntity (subtype of CustomerEntity) ? -> the client creates a CustomerEntity, because that's what the method's return type is, which means that there are just the customer fields, while the xml contains the FemaleCustomerEntity fields.

I don't have a clue how to solve this though, apparently, MS didn't think through this well enough, as is often the case when it comes to something related to Xml disappointed

Frans Bouma | Lead developer LLBLGen Pro
Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 21-Oct-2005 18:18:11   

Hmmm, yes that is an issue. But not one that is limited to just your entities. It would act the same way with POCO would it not? Infact, i know it would. Thats a downside to web services.

The only option i can think of to solve this would be to change the the GetSchema function for abstract classes so it returns something else, like maybe AbstractEntity. Then have a AbstractEntity class (generated of course) with a function of GetCustomer etc...

This AbstractEntity class would of course store the returned XML, and init the correct object.

its one extra step, but i cant really thinkg of any other way.

BTW....a microsoft support guy is looking into the writer.writeraw() problem.

MikeH
User
Posts: 3
Joined: 21-Oct-2005
# Posted on: 05-Nov-2005 00:02:12   

I'm just wondering: when we can expect this to be released? I'm REALLY looking forward to it! smile

Answer
User
Posts: 363
Joined: 28-Jun-2004
# Posted on: 05-Nov-2005 04:12:05   

Hopefully monday. I created the templates to do it already and made my own task generator. Works great.. simple_smile

1  /  2  /  3  /  4