Welcome to GASP Sign in | Join | Help

Hugo Batista

<wsa:RelatesTo>
Software Engineering
</wsa:RelatesTo>

News


  • All information or content in this site is provided "AS IS" with no warranties, and confer no rights. The views expressed by me do not necessarily reflect the official policy, position, or opinions of the company where I work.





Web Services: Reusing your class library web services - part II

If you read my last post about this subject, you can see I was cheating WebServiceHandlerFactory, by previously creating the .asmx file. Well, that requires file write permission for the process model user, something that is not very usual. So, today I'll present a better "cheating" approach.

First, if you didn't read the last post, you can find it here.

Remembering the problem, mainly I want to be able to serve web services, without having .asmx files. For example, if i want to share a web service between two solutions, i want to be able to distribute it inside a class library, and no need to deploy it inside the web service project.

After investigating a little bit about WebServiceHandlerFactory, I've seen that in the GetHandler method, the whole work is done by two components: the Web Service Parser, and the CoreGetHandler method. So basically, the Web Service Parser class parses the .asmx file, and obtains the correspondent web service type to be sent to CoreGetHandler method, which does all the reflection, wsdl, and blablabla work.

My first approach, was to try to implement all this work (the CoreGetHandler one) from scratch... well.. too painful! At least for an exercise! If i basically just need to cheat it and bypass the web service parser step, it was a pain to implement all the rest just because of that.. until i remembered something...

So CoreGetHandler is internal. That's a problem! If i wanted to call it, i couldn't... at least.. i couldn't normally do it, and so: i cheated!

Yes: I can call CoreGetHandler, by getting the method through reflection, and lately invoking it. Even if it is Internal.

Before showing the code, remember, don't try this at WORK! This code can break with any service pack update, or hotfix for the framework. So get out of this post and stop reading.


...(for your safety, stop reading this post) ...


You're still here, hã ? :)

So we have a dummy service called My Service, with an HelloWorld method. We have the factory implementing IHttpHandlerFactory, where our work is. And we have a nested class called ServiceMapping, which is used to map URLs to Services. This is a mapping like:

URL -> Type

where URL is the service URL, and type is the type of the service implementation.

Latter, inside our factory, we have a maps instance variable with an array of mappings (ServiceMapping), where we do our maps.

So, let's see the code:

 

.csharpcode { color: black; font-family: Courier New , Courier, Monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
using System;
using System.Reflection;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.UI;

    // 
    /// My dummy service, without asmx file
    /// 
    internal class MyService : System.Web.Services.WebService
    {
        [WebMethod()]
        public string HelloWorld()
        {
            return "hello world";
        }
    
    }

    /// 
    /// Represents a web service handler factory, but discovering the types on another way
    /// 
    public class WebServiceHandlerFactory2 : IHttpHandlerFactory
    {
        
        /// 
        /// Represents a mapping between a URL and a web service type
        /// 
        private class Service Mapping
        {
            /// 
            /// Creates a new  instance.
            /// 
            /// URL.
            /// Type.
            public Service Mapping(string URL, Type type)
            {
                this.Url = URL;
                this.Type = type;
            }
            public string URL = string.Empty;
            public Type Type = null;
            /// 
            /// Implicit operators the specified service mapping to a string, returning URL
            /// 
            /// Service mapping.
            /// 
            public static implicit operator string(Service Mapping service Mapping)
            {
                return serviceMapping.Url;
            }
        }

        /// 
        /// Maps go HERE!
        /// 
        private Service Mapping[] maps = new Service Mapping[]
            {
                new Service Mapping("/MyService.asmx", typeof(MyService)),    
            };

        
        //internal REAL handler factory
        IHttpHandlerFactory internalHandlerFactory  =new WebServiceHandlerFactory();
    
        /// 
        /// Gets the service type for the URL, according to maps
        /// 
        /// URL.
        /// 
        private Type GetServiceType(string URL)
        {
            //TODO: ei! this is slow if we're having lots of services.. 
//just an example! Consider a collection of some type of cache in real world!
for each(Service Mapping service Mapping in this.maps) { if (string.Compare(service Mapping, URL, true)==0) return serviceMapping.Type; } return null; } /// /// Gets the handler. /// /// Context. /// Request type. /// URL. /// Path translated. /// public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) { new AspNetHostingPermission(AspNetHostingPermissionLevel.Minimal).Demand(); //we'll jsut take out vpath applicationpath url, for simplicity string vPathUrl = context.Request.ApplicationPath!="/" ?
url.Replace(context.Request.ApplicationPath, string.Empty) : url;
            Type webServiceType = this.GetServiceType(vPathUrl);
            //if our solver couldn't find the type, we'll delegate the request to the .net framework
            if (webServiceType == null) webServiceType= WebServiceParser.GetCompiledType(pathTranslated, context);

            //this is where the trick goes... CoreGetHandler is INTERNAL .. so we're cheating
            System.Reflection.MethodInfo method = 
internalHandlerFactory.GetType().GetMethod("CoreGetHandler",BindingFlags.NonPublic | BindingFlags.Instance);
            return   (IHttpHandler) method.Invoke(internalHandlerFactory,
new object[]{ webServiceType, context, context.Request, context.Response} ) ; } /// /// Releases the handler. /// /// Handler. public void ReleaseHandler(IHttpHandler handler) { internalHandlerFactory.ReleaseHandler(handler); } }
 
Remember, now we have to configure our handler in web.config, like this:
 
httpHandlers>
            add verb="*" path="*.asmx" type="WebServiceHandlerFactory2, YourAssembly"  validate="false" />
httpHandlers>

Now, you can call MyService in http://yourdomain/yourvpath/MyService.asmx !! 

Again, this is a very "dirty" way of do it, but it was really fast to implement it and solve our problem, right ?  So you may ask, what's the power of this ? Well, what about to have a dynamic service broker ? Or if you want to dinamically generate your web services code and dinamically make them available, that is powerfull, no ??

Posted: Thursday, August 11, 2005 1:34 PM by Hugo Pais Batista

Comments

No Comments

Anonymous comments are disabled