Scott Hanselman

Separating a Web Service's Implementation from the ASMX File

March 20, '05 Comments [5] Posted in ASP.NET | Web Services | DasBlog
Sponsored By

A fellow said recently that he wanted to build a "monster web service" with over 20 classes and over 20 methods (well, not THAT monster, but not Hello World). He said:

Is there any way to provide my consumers with a single end-point (.asmx) exposing these methods from several different class files? I can't see my dev team all working on a single ASMX file...what am I missing here?

It's easy to make the assumption that the ASMX file has some magic and that everything needs to go in it. But the ASMX is just an endpoint like an ASPX or ASMX. It gives IIS and ASP.NET something to think about, but it's just a broker - even less - it's a front.

DasBlog has a Web Services interface, thanks to Clemens and the crew before Omar and I, and here's the contents of it's EditService.asmx.cs:

[WebService(Namespace="urn:schemas-newtelligence-com:dasblog:edit-services")]

 public class EditService : EditServiceImplementation

 {

 }

That's it. Seriously. It lives in our main Web Project.  So, how does this work? Well, look at what the class it's derived from. It's not System.Web.Services.WebService (yet), it's EditServiceImplementation.

RULE: Don't mix your implementation with your presentation

A Web Services endpoint is arguably just a presentation of some logic. Hopefully that logic exists somewhere that's NOT the ASMX file. The ASMX file is just a way to call something somewhere else.

For example, here's part of the source for EditServiceImplementation.cs that's in a totally different assembly and project.

public class EditServiceImplementation : WebService

{

    [WebMethod]

    public string CreateEntry(Entry entry, string username, string password)

    {

        SiteConfig siteConfig = SiteConfig.GetSiteConfig();

        if (!siteConfig.EnableEditService)

        {

            throw new ServiceDisabledException();

        }

 

        if (SiteSecurity.Login(username, password).Role != "admin")

        {

            throw new Exception("Invalid Password");

        }

 

        // ensure that the entryId was filled in

        //

        if (entry.EntryId == null || entry.EntryId.Length == 0)

        {

            entry.EntryId = Guid.NewGuid().ToString();

        }

 

        ILoggingDataService logService = LoggingDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.LogDir));

        IBlogDataService dataService = BlogDataServiceFactory.GetService(Context.Server.MapPath(siteConfig.ContentDir), logService);

 

        SaveEntry(entry, "", null, siteConfig, logService, dataService);

 

        return entry.EntryId;

    }

    //SNIP...

This shows EditServiceImplementation (remember, NOT in the ASMX.cs file) deriving from WebService. It also shows the CreateEntry method that is marked as a [WebMethod] and exposed to the outside world.

Note that this method takes an Entry, a username and a password. Internally it checks to see if Web Services are enabled, tries to log the user in then calls the existing .NET API method "SaveEntry".

SaveEntry already existed. the CreateEntry WebMethod is a stateless rollup of Login and SaveEntry.

So, in this example:

WebService Request -> EditService.asmx -> EditService class, deriving from EditServiceImplementation -> Validate the User, etc -> Broker to existing SaveEntry API.

We leverage the existing dasBlog DataService, we easily create other endpoints (foo.asmx) in a number of ways, the implementation doesn't even live in the Web Project, and even then, the Implementation is ten lines of code.

Don't let your [perception] of the  ASMX file cramp your style. You can add a few small layers and not only make development of your Web Service easy, but also friendly for large development groups. Idea: If you're concerned about collision, folks can test and work on their own atomic endpoints before rolling everything up into one WSDL. Also, remember Contract First.

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

How to add a .REG file to your Registry silently

March 17, '05 Comments [5] Posted in Programming
Sponsored By

How to add a .REG file to your Registry silently? This question came up today. As is the case usually /s is for silent and /q is for quiet. One will usually work.

regedit /s foo.reg

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Rory and Scott design some software - TechEd - Revenge of the Sith

March 16, '05 Comments [10] Posted in TechEd | ASP.NET | Javascript | Speaking | Web Services
Sponsored By

Rory and I are REALLY talented software designers. We know all the acronyms and technical terms. DOS. WS-This and WS-That. ASP.NET. Packet. SOAP. PostBack. See?

Here's video number two in the ongoing saga. This is WAY better than the Star Wars Trilogy. WAY. BETTER.

Please spread it around and Trackback/Pingback it. If you can't view it below, you can download it here.

Remember, if you're blogging TechEd this year, make sure to register your blog at TechEd Bloggers.NET and get your content aggregated!


play video stop video indicatorhandleamount downloaded toggle sound launch in external player
Launch the streaming media file

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Yes, it WILL run differently under the Debugger

March 16, '05 Comments [1] Posted in ASP.NET | Web Services | Bugs
Sponsored By

It seems like an obvious thing to say, but I know that *I* totally forgot it. Running under the Debugger (not just when compiled in Debug Mode, but literally during Interactive Debugging) is a totally different universe than running 'normally.'

Recently my buddy Adam Cogan forwarded me an interesting thread that was going on at his company.

For an internal application, Mark Liu noticed when he called a Web Service in response to a WinForms event, say Key Down, but when that even had a 'partner event' on the way, like Key Up, that the Key Up would occur immediately, without waiting for the Synchronously-called Web Service to return.

He might see something like this in his call stack. Red is the event he expected to come later, and Purple is the Web Services call he was counting on blocking.

[<Non-user Code>]  
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.SelectLineRow
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.AddInventoryLine
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.AddInventoryLine
MySystem.windowsui.dll!MySystem.WindowsUI.Components.PointOfSale.ProductKeyPad.b_Click
[<Non-user Code>]  
MySystem.windowsui.dll!MySystem.WindowsUI.Components.HotKeyProvider.ContainerForm_KeyUp
[<Non-user Code>]  
MySystem.webservicewrapper.dll!MySystem.
  WebServiceWrapper.somekindofWebService.SomeService.calculate_Some

MySystem.webservicewrapper.dll!MySystem.
  WebServiceWrapper.somekindofWebServiceWrapper.CalculateLines
MySystem.windowsui.dll!MySystem.WindowsUI.Components.SomeCalculator.Line_RowChanging
MySystem.data.dll!MySystem.Data.SomeDataSet.SomeDataTable.OnRowChanging
[<Non-user Code>]  
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.AddNewInventoryOrderLine
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.AddInventoryLine
MySystem.windowsui.dll!MySystem.WindowsUI.Forms.PointOfSaleForm.AddInventoryLine
MySystem.windowsui.dll!MySystem.WindowsUI.Components.PointOfSale.ProductKeyPad.b_Click
[<Non-user Code>]  
MySystem.windowsui.dll!MySystem.WindowsUI.Components.HotKeyProvider.ContainerForm_KeyUp

Without arguing the merits of the desired ordering of events, it appeared to everyone involved that the hidden "this.Invoke" that was doing the Web Services call was releasing control to the calling thread and allowing the KeyUp event to show up immediately.

I said I know that SoapHttpClientProtocol wouldn't be doing such evil since it's likely just calling BeginInvoke itself and waiting on the response. My less than useful answer was "don't do that." I figured that the UI thread wasn't going to block, but I was wrong. We all went back and forth and there was some scratching and biting. I only cried once.

Then George Doubinski from SolutionsNet said (wisely):

I think the debate somewhat degraded and everyone has forgotten the basics – get the simplest test case. I was very suspicious about this event processing business in a first place. So I whipped a bit of code of my own. Guess what – the thread blocks as it should and there is no funny KeyUp business (not that I would personally hook anything to KeyUp anyway). Did a bit of digging with Reflector and confirmed that Invoke in the end calls BeginWebResponse which is exactly what BeginInvoke calls (the difference is the first one does not provide a callback and waits on event explicitly). Started debugging – oops – got the described behaviour. Eureka!

Ah yes, back to basics. The VS.NET Stack Window has a context menu item called "Show Non-User Code." When he did this, the newly illuminated call stack showed:

WindowsApplication1.exe!WindowsApplication1.Form1.Form1_KeyUp(System.Object sender = {WindowsApplication1.Form1}, System.Windows.Forms.KeyEventArgs e = {KeyData=Return}) Line 168        C#
<snip>
system.windows.forms.dll!ControlNativeWindow.OnMessage(System.Windows.Forms.Message m = {System.Windows.Forms.Message}) + 0x13 bytes 
system.windows.forms.dll!ControlNativeWindow.WndProc(System.Windows.Forms.Message m = {System.Windows.Forms.Message}) + 0xda bytes
system.windows.forms.dll!System.Windows.Forms.NativeWindow.
  DebuggableCallback(int hWnd = 1445384, int msg = 257, int wparam = 13, int lparam = -1071906815) + 0x3d bytes    
system.web.services.dll!System.Web.Services.UnsafeNativeMethods.OnSyncCallOut
  (System.Web.Services.Interop.INotifySink2 sink = {System.__ComObject}, System.Web.Services.Interop.CallId callId = {System.Web.Services.Interop.CallId}, int out_ppBuffer = 0, int inout_pBufferSize = 0) + 0x37 bytes 
system.web.services.dll!System.Web.Services.Protocols.WebClientProtocol.GetWebRequest
  (System.Uri uri = {System.Uri}) + 0xdd bytes        
<snip>
  system.web.services.dll!System.Web.Services.Protocols.SoapHttpClientProtocol.BeginInvoke
(string methodName = "listCountries", System.Object[] parameters = {Length=0}, System.AsyncCallback callback = <undefined value>, System.Object asyncState = <undefined value>) + 0x53 bytes    

The interesting stuff is of course the method DebuggableCallback appearing in the call stack. This is our first hint that something different is happening at Debug Time.

Here is an interesting snippet from GetWebRequest:

if (RemoteDebugger.IsClientCallOutEnabled())
{
   this.debugger = new RemoteDebugger();
   this.debugger.NotifyClientCallOut(request1);
   return request1;
}

Ah. What's IsClientCalloutEnabled?

internal static bool IsClientCallOutEnabled()
{
      bool flag1 = false;
      try
      {
            flag1 = (!CompModSwitches.DisableRemoteDebugging.Enabled && Debugger.IsAttached) && (RemoteDebugger.Connection != null);
      }
      catch (Exception){}      
      return flag1;
}

So, message pumping is neccessary for Remote Debugging to occur. At this point, it's voodoo.

Moral: Debugging under VS.NET is amazing, but it is a different code path than at runtime. A good reminder from George.

Sample: Mark Liu thoughtfully includes sample code.

Now playing: DJ Bolivia - Memory Leak - www.djbolivia.ca

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

A Boilerplate HttpHandler

March 16, '05 Comments [8] Posted in ASP.NET | HttpHandler
Sponsored By

I've been writing HttpHandlers lately for such things as Check Images and Statement Downloads. Remember, an HttpHandler is the kind of thing you want to use when an HttpRequest is going to return a file or image to the browser. Basically anything other than a standard page. True, you could use a page, remove all the HTML from the ASPX part, and return stuff in Page_Load, but that's not really what pages are for, right? Also, Pages are themselves an HttpHandler (returned by PageHandlerFactory) so why not write your own HttpHandler?

Here's a boilerplate template I like to use for an HttpHandler. IMHO, an HttpHandler should always return appropriate HTTP Status Codes like you see below. Make sure the semantics of the status code(s) you choose match what you're trying to express. 404 for Not Found, 403 for Forbidden, 500 for Holy Crap!, etc.

One day I'll be more organized and make an abstract base class to handle this kind of boilerplate stuff. For now, it's on my blog.

    1 public class MyHandler : IHttpHandler

    2 {

    3     private const string CONSTSOMEPARAM = "SomeParam";

    4 

    5     public MyHandler(){}

    6 

    7     public void ProcessRequest(HttpContext context)

    8     {

    9         // Don't allow this response to be cached by the browser.

   10         // Note, you MIGHT want to allow it to be cached, depending on what you're doing.

   11         context.Response.Cache.SetCacheability(HttpCacheability.NoCache);

   12         context.Response.Cache.SetNoStore();

   13         context.Response.Cache.SetExpires(DateTime.MinValue);

   14 

   15         if (ValidateParameters(context) == false)

   16         {   

   17             //Internal Server Error

   18             context.Response.StatusCode = 500;

   19             context.Response.End();

   20             return;

   21         }

   22 

   23         if (context.User.Identity.IsAuthenticated == false)

   24         {

   25             //Forbidden

   26             context.Response.StatusCode = 403;

   27             context.Response.End();

   28             return;

   29         }

   30 

   31         string someParam = context.Request.QueryString[CONSTSOMEPARAM];

   32 

   33         SomethingReponse response = SomeService.SomeImportantThing(someParam);

   34         if(response.Header == null || response.Header.Success == false)

   35         {

   36             //Whatever wasn't found

   37             context.Response.StatusCode = 404;

   38             context.Response.End();

   39             return;

   40         }

   41 

   42         context.Response.ContentType = "somespecific/mimetype";

   43 

   44         context.Response.BinaryWrite();

   45         //or

   46         context.Response.Write();

   47         //or

   48         context.Response.WriteFile();

   49         //or

   50         someImageStream.Save(context.Response.OutputStream);

   51     }

   52 

   53     public bool ValidateParameters(HttpContext context)

   54     {

   55         //Validate some stuff...true if cool, false if not

   56         return false;

   57     }

   58 

   59     /// <summary>

   60     /// True if this handler can be reused between calls. That's cool if you don't have any class instance data.

   61     /// False if you'd rather get a fresh one.

   62     /// </summary>

   63     public bool IsReusable

   64     {

   65         get

   66         {

   67             return true;

   68         }

   69     }

   70 }

HttpHandlers are nice because their endpoint (fancy word for URL) is configurable in your web.config.

<httpHandlers>
   <add verb="GET,POST,WHATEVER" path="SomeEndPoint.ashx" type="MyNamespace.MtHandler, MyAssemblyName" />
</httpHandlers>

 

 

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Sponsored By
Hosting By
Dedicated Windows Server Hosting by SherWeb

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.