ASMX not generating WSDL when using Custom IHttpHandlerFactory - asp.net

I am implementing a dynamic ASMX web service via a custom HttpHandler, and my web service has recently stopped generating WSDL automatically. When I use ?WSDL on the asmx url, I get the following error:
System.InvalidOperationException: XML Web service description was not found.
at System.Web.Services.Protocols.DiscoveryServerProtocol.WriteReturns(Object[] returnValues, Stream outputStream)
at System.Web.Services.Protocols.WebServiceHandler.WriteReturns(Object[] returnValues)
at System.Web.Services.Protocols.WebServiceHandler.Invoke()
This worked fine a while ago, so I'm wondering if there's a file permission problem somewhere.
A Google search does not return any reference to this particular situation.
I doubt that my code is relevant to the problem; it has not changed:
[WebService(Description = "...", Namespace = "...")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class MyWebService : System.Web.Services.WebService
{
[WebMethod]
void MyWebMethod() {}
}
public class VirtualWebServiceFactory : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
WebServiceHandlerFactory wshf = new WebServiceHandlerFactory();
MethodInfo coreGetHandler = wshf.GetType().GetMethod("CoreGetHandler", BindingFlags.NonPublic | BindingFlags.Instance);
IHttpHandler handler = (IHttpHandler)coreGetHandler.Invoke(wshf, new object[] { typeof(MyWebService), context, context.Request, context.Response });
return handler;
}
}
Decompiling System.Web.Services.Protocols.DiscoveryServerProtocol.WriteReturns() reveals that it looks up the XML service description in a dictionary created somewhere else.
I was hoping that someone familiar with the DiscoverServerProtocol etc. might know under what circumstances the XML service description might fail to be built.
The following works just fine:
ServiceDescriptionReflector reflector = new ServiceDescriptionReflector();
reflector.Reflect(typeof(MyWebService), "...");
Navigating to MyWebService.asmx shows all the functions and allows testing them. But using ?WSDL gives the exception above.

Just for fun, try making your WebMethod public.
However, the real answer is to not screw around with .NET Framework code that's not meant to be called. What's wrong with just calling GetHandler? What are you trying to accomplish here that can't be accomplished without messing around in the internals of an obscure class?

Hmm, after many months, found out that the URL was being re-written to include other parameters than the ?WSDL, which made the private function choke.

Related

Return type XmlDocument of an Asp.Net webservice changes to XmlNode when accessing the webmethod

I have a Web Method in Web service which is returning an XmlDocument. The Web service works fine when i am executing it and providing the necessary parameters.
I have created a proxy to this service in another application.proxy is created well and good.
Now the problem is,when i try to access the methods from that service its getting all the methods from the service but the return type of the method is showing as XmlNode instead of XmlDocument.
Let us say for example:
Service.asmx
public class DataService : System.Web.Services.WebService
{
[WebMethod]
public XmlDocument GetData(int ID)
{
//Code Here
}
}
Now i have one windows application which is using this service.
Created an object to the service through proxy.
DRService.DataService drService = new DRService.DataService();
Now i am trying to access the service methods.
drService.GetData(1)
The return type of the above method call should be XmlDocument but it is returning XmlNode as return type.
Any idea why the retun type is XmlNode?
This is the expected behavior.

How can I use a standard ASP.NET session object within ServiceStack service implementation

I'm just getting started with ServiceStack and, as a test case, I am looking to rework an existing service which is built using standard ASP.Net handlers. I've managed to get it all working as I want it but have certain aspects which make use of the ASP.Net Session object.
I've tried adding IRequiresSessionState into the service interface:
public class SessionTestService : RestServiceBase<SessionTest>, IRequiresSessionState {
public override object OnPost(SessionTest request) {
// Here I want to use System.Web.HttpContext.Current.Session
}
}
The trouble is I can't seem to get it to work as the Session object is always null.
I've done a lot of Googling and have puzzled over https://github.com/mythz/ServiceStack/blob/master/tests/ServiceStack.WebHost.IntegrationTests/Services/Secure.cs and similar but I can't find any example code which does this (which surprises me). Can anyone explain why the above doesn't work and advise what I need to do to get it working?
Note: Ultimately I'll probably look to replace this with Redis or will try to remove any serverside session requirement, but I figured that I'd use the ASP.Net implementation for the time being, to get things working and to avoid reworking it more than is necessary at this point.
Using ServiceStack ISession
ServiceStack has a new ISession interface backed by ICacheClient that lets you share same ISession between MVC Controllers, ASP.NET base pages and ServiceStack's Web Services which share the same Cookie Id allowing you to freely share data between these web frameworks.
Note: ISession is a clean implementation that completely by-passes the existing ASP.NET session with ServiceStack's own components as described in ServiceStack's MVC PowerPack and explained in detail in the Sessions wiki page.
To easily make use of ServiceStack's Session (Cache & JSON Serializer) have your Controllers inherit from ServiceStackController (in MVC) or PageBase (in ASP.NET)
There is also new Authentication / Validation functionality added in ServiceStack which you can read about on the wiki:
Authentication and authorization
Validation
Using ASP.NET Session
Essentially ServiceStack is just a set of lightweight IHttpHandler's running on either an ASP.NET or HttpListener host. If hosted in IIS/ASP.NET (most common) it works like a normal ASP.NET request.
Nothing in ServiceStack accesses or affects the configured Caching and Session providers in the underlying ASP.NET application. If you want to enable it you would need to configure it as per normal in ASP.NET (i.e. outside of ServiceStack) see:
http://msdn.microsoft.com/en-us/library/ms178581.aspx
Once configured you can access the ASP.NET session inside a ServiceStack web service via the singleton:
HttpContext.Current.Session
Or alternatively via the underlying ASP.NET HttpRequest with:
var req = (HttpRequest)base.RequestContext.Get<IHttpRequest>().OriginalRequest;
var session = req.RequestContext.HttpContext.Session;
Although because of the mandatory reliance on XML config and degraded performance by default, I prefer to shun the use of ASP.NET's Session, instead opting to use the cleaner Cache Clients included with ServiceStack.
Basically the way Sessions work (ASP.NET included) is a cookie containing a unique id is added to the Response uniquely identifying the browser session. This id points to a matching Dictionary/Collection on the server which represents the browsers' Session.
The IRequiresSession interface you link to doesn't do anything by default, it simply is a way to signal to either a Custom Request Filter or base web service that this request needs to be authenticated (i.e. two places where you should put validation/authentication logic in ServiceStack).
Here's a Basic Auth implementation that looks to see if a web service is Secure and if so make sure they have authenticated.
Here's another authentication implementation that instead validates all services marked with an [Authenticate] attribute, and how to enable Authentication for your service by adding the Attribute on your Request DTO.
New Authentication Model in ServiceStack
The above implementation is apart of the multi-auth provider model included in the next version of ServiceStack. Here's the reference example showing how to register and configure the new Auth model in your application.
Authentication Strategies
The new Auth model is entirely an opt-in convenience as you can simply not use it and implement similar behaviour yourself using Request Filters or in base classes (by overriding OnBeforeExecute). In fact the new Auth services are not actually built-into ServiceStack per-se. The entire implementation lives in the optional ServiceStack.ServiceInterfaces project and implemented using Custom Request Filters.
Here are different Authentication strategies I've used over the years:
Mark services that need authentication with an [Attribute]. Likely the most idiomatic C# way, ideal when the session-id is passed via a Cookie.
Especially outside of a Web Context, sometimes using a more explicit IRequiresAuthentication interface is better as it provides strong-typed access to the User and SessionId required for Authentication.
You can just have a 1-liner to authenticate on each service that needs it - on an adhoc basis. A suitable approach when you have very few services requiring authentication.
That's a great and comprehensive answer by #mythz. However, when trying to access the ASP.NET session by HttpContext.Current.Session within a ServiceStack web service, it always returns null for me. That's because none of the HttpHandlers within ServiceStack are adorned with the IRequiresSessionState interface, so the .NET Framework does not provide us with the session object.
To get around this, I've implemented two new classes, both of which use the decorator pattern to provide us with what we need.
Firstly, a new IHttpHandler which requires session state. It wraps the IHttpHandler provided by ServiceStack and passes calls through to it...
public class SessionHandlerDecorator : IHttpHandler, IRequiresSessionState {
private IHttpHandler Handler { get; set; }
internal SessionHandlerDecorator(IHttpHandler handler) {
this.Handler = handler;
}
public bool IsReusable {
get { return Handler.IsReusable; }
}
public void ProcessRequest(HttpContext context) {
Handler.ProcessRequest(context);
}
}
Next, a new IHttpHandlerFactory which delegates the responsibility for generating the IHttpHandler to ServiceStack, before wrapping the returned handler in our new SessionHandlerDecorator...
public class SessionHttpHandlerFactory : IHttpHandlerFactory {
private readonly static ServiceStackHttpHandlerFactory factory = new ServiceStackHttpHandlerFactory();
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated) {
var handler = factory.GetHandler(context, requestType, url, pathTranslated);
return handler == null ? null : new SessionHandlerDecorator(handler);
}
public void ReleaseHandler(IHttpHandler handler) {
factory.ReleaseHandler(handler);
}
}
Then, it's just a matter of changing the type attributes in the handlers in Web.config to SessionHttpHandlerFactory instead of ServiceStack.WebHost.Endpoints.ServiceStackHttpHandlerFactory, ServiceStack, and your web services should now have the ASP.NET session avaialble to them.
Despite the above, I fully endorse the new ISession implementation provided by ServiceStack. However, in some cases, on a mature product, it just seems too big a job to replace all uses of the ASP.NET session with the new implementation, hence this workaround!
Thanks #Richard for your answer above. I am running a new version of service stack and they have removed the ServiceStackHttpFactory with HttpFactory. Instead of having
private readonly static ServiceStackHttpHandlerFactory factory = new ServiceStackHttpHandlerFactory();
You need to have
private static readonly HttpHandlerFactory Factory = new HttpHandlerFactory();
Here is updated code for this service
using ServiceStack;
using System.Web;
using System.Web.SessionState;
namespace MaryKay.eCommerce.Mappers.AMR.Host
{
public class SessionHttpHandlerFactory : IHttpHandlerFactory
{
private static readonly HttpHandlerFactory Factory = new HttpHandlerFactory();
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
var handler = Factory.GetHandler(context, requestType, url, pathTranslated);
return handler == null ? null : new SessionHandlerDecorator(handler);
}
public void ReleaseHandler(IHttpHandler handler)
{
Factory.ReleaseHandler(handler);
}
}
public class SessionHandlerDecorator : IHttpHandler, IRequiresSessionState
{
private IHttpHandler Handler { get; set; }
internal SessionHandlerDecorator(IHttpHandler handler)
{
Handler = handler;
}
public bool IsReusable
{
get { return Handler.IsReusable; }
}
public void ProcessRequest(HttpContext context)
{
Handler.ProcessRequest(context);
}
}
}
As of ServiceStack 4.5+ the HttpHandler can also support Async. Like so:
namespace FboOne.Services.Host
{
public class SessionHttpHandlerFactory : IHttpHandlerFactory
{
private static readonly HttpHandlerFactory Factory = new HttpHandlerFactory();
public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
var handler = Factory.GetHandler(context, requestType, url, pathTranslated);
return handler == null ? null : new SessionHandlerDecorator((IHttpAsyncHandler)handler);
}
public void ReleaseHandler(IHttpHandler handler)
{
Factory.ReleaseHandler(handler);
}
}
public class SessionHandlerDecorator : IHttpAsyncHandler, IRequiresSessionState
{
private IHttpAsyncHandler Handler { get; set; }
internal SessionHandlerDecorator(IHttpAsyncHandler handler)
{
Handler = handler;
}
public bool IsReusable
{
get { return Handler.IsReusable; }
}
public void ProcessRequest(HttpContext context)
{
Handler.ProcessRequest(context);
}
public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
return Handler.BeginProcessRequest(context, cb, extraData);
}
public void EndProcessRequest(IAsyncResult result)
{
Handler.EndProcessRequest(result);
}
}
}

Can ASP.NET Routing be used to create "clean" URLs for .ashx (IHttpHander) handlers?

I have some REST services using plain old IHttpHandlers. I'd like to generate cleaner URLs, so that I don't have the .ashx in the path. Is there a way to use ASP.NET routing to create routes that map to ashx handlers? I've seen these types of routes previously:
// Route to an aspx page
RouteTable.Routes.MapPageRoute("route-name",
"some/path/{arg}",
"~/Pages/SomePage.aspx");
// Route for a WCF service
RouteTable.Routes.Add(new ServiceRoute("Services/SomeService",
new WebServiceHostFactory(),
typeof(SomeService)));
Trying to use RouteTable.Routes.MapPageRoute() generates an error (that the handler does not derive from Page). System.Web.Routing.RouteBase only seems to have 2 derived classes: ServiceRoute for services, and DynamicDataRoute for MVC. I'm not sure what MapPageRoute() does (Reflector doesn't show the method body, it just shows "Performance critical to inline this type of method across NGen image boundaries").
I see that RouteBase is not sealed, and has a relatively simple interface:
public abstract RouteData GetRouteData(HttpContextBase httpContext);
public abstract VirtualPathData GetVirtualPath(RequestContext requestContext,
RouteValueDictionary values);
So perhaps I can make my own HttpHandlerRoute. I'll give that a shot, but if anyone knows of an existing or built-in way of mapping routes to IHttpHandlers, that would be great.
Ok, I've been figuring this out since I originally asked the question, and I finally have a solution that does just what I want. A bit of up front explanation is due, however. IHttpHandler is a very basic interface:
bool IsReusable { get; }
void ProcessRequest(HttpContext context)
There is no built in property for accessing the route data, and the route data also can't be found in the context or the request. A System.Web.UI.Page object has a RouteData property , ServiceRoutes do all the work of interpreting your UriTemplates and passing the values to the correct method internally, and ASP.NET MVC provides its own way of accessing the route data. Even if you had a RouteBase that (a) determined if the incoming url was a match for your route and (b) parsed the url to extract all of the individual values to be used from within your IHttpHandler, there's no easy way to pass that route data to your IHttpHandler. If you want to keep your IHttpHandler "pure", so to speak, it takes responsibility for dealing with the url, and how to extract any values from it. The RouteBase implementation in this case is only used to determine if your IHttpHandler should be used at all.
One problem remains, however. Once the RouteBase determines that the incoming url is a match for your route, it passes off to an IRouteHandler, which creates the instances of the IHttpHandler you want to handle your request. But, once you're in your IHttpHandler, the value of context.Request.CurrentExecutionFilePath is misleading. It's the url that came from the client, minus the query string. So it's not the path to your .ashx file. And, any parts of your route that are constant (such as the name of the method) will be part of that execution file path value. This can be a problem if you use UriTemplates within your IHttpHandler to determine which specific method within your IHttpHandler should handing the request.
Example: If you had a .ashx handler at /myApp/services/myHelloWorldHandler.ashx
And you had this route that mapped to the handler: "services/hello/{name}"
And you navigated to this url, trying to call the SayHello(string name) method of your handler:
http://localhost/myApp/services/hello/SayHello/Sam
Then your CurrentExecutionFilePath would be: /myApp/services/hello/Sam. It includes parts of the route url, which is a problem. You want the execution file path to match your route url. The below implementations of RouteBase and IRouteHandler deal with this problem.
Before I paste the 2 classes, here's a very simple usage example. Note that these implementations of RouteBase and IRouteHandler will actually work for IHttpHandlers that don't even have a .ashx file, which is pretty convenient.
// A "headless" IHttpHandler route (no .ashx file required)
RouteTable.Routes.Add(new GenericHandlerRoute<HeadlessService>("services/headless"));
That will cause all incoming urls that match the "services/headless" route to be handed off to a new instance of the HeadlessService IHttpHandler (HeadlessService is just an example in this case. It would be whatever IHttpHandler implementation you wanted to pass off to).
Ok, so here are the routing class implementations, comments and all:
/// <summary>
/// For info on subclassing RouteBase, check Pro Asp.NET MVC Framework, page 252.
/// Google books link: http://books.google.com/books?id=tD3FfFcnJxYC&pg=PA251&lpg=PA251&dq=.net+RouteBase&source=bl&ots=IQhFwmGOVw&sig=0TgcFFgWyFRVpXgfGY1dIUc0VX4&hl=en&ei=z61UTMKwF4aWsgPHs7XbAg&sa=X&oi=book_result&ct=result&resnum=6&ved=0CC4Q6AEwBQ#v=onepage&q=.net%20RouteBase&f=false
///
/// It explains how the asp.net runtime will call GetRouteData() for every route in the route table.
/// GetRouteData() is used for inbound url matching, and should return null for a negative match (the current requests url doesn't match the route).
/// If it does match, it returns a RouteData object describing the handler that should be used for that request, along with any data values (stored in RouteData.Values) that
/// that handler might be interested in.
///
/// The book also explains that GetVirtualPath() (used for outbound url generation) is called for each route in the route table, but that is not my experience,
/// as mine used to simply throw a NotImplementedException, and that never caused a problem for me. In my case, I don't need to do outbound url generation,
/// so I don't have to worry about it in any case.
/// </summary>
/// <typeparam name="T"></typeparam>
public class GenericHandlerRoute<T> : RouteBase where T : IHttpHandler, new()
{
public string RouteUrl { get; set; }
public GenericHandlerRoute(string routeUrl)
{
RouteUrl = routeUrl;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
// See if the current request matches this route's url
string baseUrl = httpContext.Request.CurrentExecutionFilePath;
int ix = baseUrl.IndexOf(RouteUrl);
if (ix == -1)
// Doesn't match this route. Returning null indicates to the asp.net runtime that this route doesn't apply for the current request.
return null;
baseUrl = baseUrl.Substring(0, ix + RouteUrl.Length);
// This is kind of a hack. There's no way to access the route data (or even the route url) from an IHttpHandler (which has a very basic interface).
// We need to store the "base" url somewhere, including parts of the route url that are constant, like maybe the name of a method, etc.
// For instance, if the route url "myService/myMethod/{myArg}", and the request url were "http://localhost/myApp/myService/myMethod/argValue",
// the "current execution path" would include the "myServer/myMethod" as part of the url, which is incorrect (and it will prevent your UriTemplates from matching).
// Since at this point in the exectuion, we know the route url, we can calculate the true base url (excluding all parts of the route url).
// This means that any IHttpHandlers that use this routing mechanism will have to look for the "__baseUrl" item in the HttpContext.Current.Items bag.
// TODO: Another way to solve this would be to create a subclass of IHttpHandler that has a BaseUrl property that can be set, and only let this route handler
// work with instances of the subclass. Perhaps I can just have RestHttpHandler have that property. My reticence is that it would be nice to have a generic
// route handler that works for any "plain ol" IHttpHandler (even though in this case, you have to use the "global" base url that's stored in HttpContext.Current.Items...)
// Oh well. At least this works for now.
httpContext.Items["__baseUrl"] = baseUrl;
GenericHandlerRouteHandler<T> routeHandler = new GenericHandlerRouteHandler<T>();
RouteData rdata = new RouteData(this, routeHandler);
return rdata;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
// This route entry doesn't generate outbound Urls.
return null;
}
}
public class GenericHandlerRouteHandler<T> : IRouteHandler where T : IHttpHandler, new()
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new T();
}
}
I know this answer has been quite long winded, but it was not an easy problem to solve. The core logic was easy enough, the trick was to somehow make your IHttpHandler aware of the "base url", so that it could properly determine what parts of the url belong to the route, and what parts are actual arguments for the service call.
These classes will be used in my upcoming C# REST library, RestCake. I hope that my path down the routing rabbit hole will help anyone else who decides to RouteBase, and do cool stuff with IHttpHandlers.
I actually like Joel's solution better, as it doesn't require you to know the type of handler while you're trying to setup your routes. I'd upvote it, but alas, I haven't the reputation required.
I actually found a solution which I feel is better than both mentioned. The original source code I derived my example from can be found linked here http://weblogs.asp.net/leftslipper/archive/2009/10/07/introducing-smartyroute-a-smarty-ier-way-to-do-routing-in-asp-net-applications.aspx.
This is less code, type agnostic, and fast.
public class HttpHandlerRoute : IRouteHandler {
private String _VirtualPath = null;
public HttpHandlerRoute(String virtualPath) {
_VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
IHttpHandler httpHandler = (IHttpHandler)BuildManager.CreateInstanceFromVirtualPath(_VirtualPath, typeof(IHttpHandler));
return httpHandler;
}
}
And a rough example of use
String handlerPath = "~/UploadHandler.ashx";
RouteTable.Routes.Add(new Route("files/upload", new HttpHandlerRoute(handlerPath)));
EDIT: I just edited this code because I had some issues with the old one. If you're using the old version please update.
This thread is a bit old but I just re-wrote some of the code here to do the same thing but on a more elegant way, using an extension method.
I'm using this on ASP.net Webforms, and I like to have the ashx files on a folder and being able to call them either using routing or a normal request.
So I pretty much grabbed shellscape's code and made an extension method that does the trick. At the end I felt that I should also support passing the IHttpHandler object instead of its Url, so I wrote and overload of the MapHttpHandlerRoute method for that.
namespace System.Web.Routing
{
public class HttpHandlerRoute<T> : IRouteHandler where T: IHttpHandler
{
private String _virtualPath = null;
public HttpHandlerRoute(String virtualPath)
{
_virtualPath = virtualPath;
}
public HttpHandlerRoute() { }
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return Activator.CreateInstance<T>();
}
}
public class HttpHandlerRoute : IRouteHandler
{
private String _virtualPath = null;
public HttpHandlerRoute(String virtualPath)
{
_virtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
if (!string.IsNullOrEmpty(_virtualPath))
{
return (IHttpHandler)System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(IHttpHandler));
}
else
{
throw new InvalidOperationException("HttpHandlerRoute threw an error because the virtual path to the HttpHandler is null or empty.");
}
}
}
public static class RoutingExtension
{
public static void MapHttpHandlerRoute(this RouteCollection routes, string routeName, string routeUrl, string physicalFile, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null)
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute(physicalFile));
routes.Add(routeName, route);
}
public static void MapHttpHandlerRoute<T>(this RouteCollection routes, string routeName, string routeUrl, RouteValueDictionary defaults = null, RouteValueDictionary constraints = null) where T : IHttpHandler
{
var route = new Route(routeUrl, defaults, constraints, new HttpHandlerRoute<T>());
routes.Add(routeName, route);
}
}
}
I'm putting it inside the same namespace of all the native routing objects so it will be automatically available.
So to use this you just have to call:
// using the handler url
routes.MapHttpHandlerRoute("DoSomething", "Handlers/DoSomething", "~/DoSomething.ashx");
Or
// using the type of the handler
routes.MapHttpHandlerRoute<MyHttpHanler>("DoSomething", "Handlers/DoSomething");
Enjoy,
Alex
Yeah, I noticed that, too. Perhaps there is a built-in ASP.NET way to do this, but the trick for me was to create a new class derived from IRouteHandler:
using System;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.Routing;
namespace MyNamespace
{
class GenericHandlerRouteHandler : IRouteHandler
{
private string _virtualPath;
private Type _handlerType;
private static object s_lock = new object();
public GenericHandlerRouteHandler(string virtualPath)
{
_virtualPath = virtualPath;
}
#region IRouteHandler Members
public System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
ResolveHandler();
IHttpHandler handler = (IHttpHandler)Activator.CreateInstance(_handlerType);
return handler;
}
#endregion
private void ResolveHandler()
{
if (_handlerType != null)
return;
lock (s_lock)
{
// determine physical path of ashx
string path = _virtualPath.Replace("~/", HttpRuntime.AppDomainAppPath);
if (!File.Exists(path))
throw new FileNotFoundException("Generic handler " + _virtualPath + " could not be found.");
// parse the class name out of the .ashx file
// unescaped reg-ex: (?<=Class=")[a-zA-Z\.]*
string className;
Regex regex = new Regex("(?<=Class=\")[a-zA-Z\\.]*");
using (var sr = new StreamReader(path))
{
string str = sr.ReadToEnd();
Match match = regex.Match(str);
if (match == null)
throw new InvalidDataException("Could not determine class name for generic handler " + _virtualPath);
className = match.Value;
}
// get the class type from the name
Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly asm in asms)
{
_handlerType = asm.GetType(className);
if (_handlerType != null)
break;
}
if (_handlerType == null)
throw new InvalidDataException("Could not find type " + className + " in any loaded assemblies.");
}
}
}
}
To create a route for an .ashx:
IRouteHandler routeHandler = new GenericHandlerRouteHandler("~/somehandler.ashx");
Route route = new Route("myroute", null, null, null, routeHandler);
RouteTable.Routes.Add(route);
The code above may need to be enhanced to work with your route arguments, but it's starting point. Comments welcome.
All of these answers are very good. I love the simplicity of Mr. Meacham's GenericHandlerRouteHandler<T> class. It is a great idea to eliminate an unnecessary reference to a virtual path if you know the specific HttpHandler class. The GenericHandlerRoute<T> class is not needed, however. The existing Route class which derives from RouteBase already handles all of the complexity of route matching, parameters, etc., so we can just use it along with GenericHandlerRouteHandler<T>.
Below is a combined version with a real-life usage example that includes route parameters.
First are the route handlers. There are two included, here -- both with the same class name, but one that is generic and uses type information to create an instance of the specific HttpHandler as in Mr. Meacham's usage, and one that uses a virtual path and BuildManager to create an instance of the appropriate HttpHandler as in shellscape's usage. The good news is that .NET allows both to live side by side just fine, so we can just use whichever we want and can switch between them as we wish.
using System.Web;
using System.Web.Compilation;
using System.Web.Routing;
public class HttpHandlerRouteHandler<T> : IRouteHandler where T : IHttpHandler, new() {
public HttpHandlerRouteHandler() { }
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return new T();
}
}
public class HttpHandlerRouteHandler : IRouteHandler {
private string _VirtualPath;
public HttpHandlerRouteHandler(string virtualPath) {
this._VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext) {
return (IHttpHandler) BuildManager.CreateInstanceFromVirtualPath(this._VirtualPath, typeof(IHttpHandler));
}
}
Let's assume that we created an HttpHandler that streams documents to users from a resource outside our virtual folder, maybe even from a database, and that we want to fool the user's browser into believing that we are directly serving a specific file rather than simply providing a download (i.e., allow the browser's plug-ins to handle the file rather than forcing the user to save the file). The HttpHandler may expect a document id with which to locate the document to provide, and may expect a file name to provide to the browser -- one that may differ from the file name used on the server.
The following shows the registration of the route used to accomplish this with a DocumentHandler HttpHandler:
routes.Add("Document", new Route("document/{documentId}/{*fileName}", new HttpHandlerRouteHandler<DocumentHandler>()));
I used {*fileName} rather than just {fileName} to allow the fileName parameter to act as an optional catch-all parameter.
To create a URL for a file served by this HttpHandler, we can add the following static method to a class where such a method would be appropriate, such as in the HttpHandler class, itself:
public static string GetFileUrl(int documentId, string fileName) {
string mimeType = null;
try { mimeType = MimeMap.GetMimeType(Path.GetExtension(fileName)); }
catch { }
RouteValueDictionary documentRouteParameters = new RouteValueDictionary { { "documentId", documentId.ToString(CultureInfo.InvariantCulture) }
, { "fileName", DocumentHandler.IsPassThruMimeType(mimeType) ? fileName : string.Empty } };
return RouteTable.Routes.GetVirtualPath(null, "Document", documentRouteParameters).VirtualPath;
}
I omitted the definitions of MimeMap and and IsPassThruMimeType to keep this example simple. But these are intended to determine whether or not specific file types should provide their file names directly in the URL, or rather in a Content-Disposition HTTP header. Some file extensions could be blocked by IIS or URL Scan, or could cause code to execute that might cause problems for users -- especially if the source of the file is another user who is malicious. You could replace this logic with some other filtering logic, or omit such logic entirely if you are not exposed to this type of risk.
Since in this particular example the file name may be omitted from the URL, then, obviously, we must retrieve the file name from somewhere. In this particular example, the file name can be retrieved by performing a look-up using document id, and including a file name in the URL is intended solely to improve the user's experience. So, the DocumentHandler HttpHandler can determine if a file name was provided in the URL, and if it was not, then it can simply add a Content-Disposition HTTP header to the response.
Staying on topic, the important part of the above code block is the usage of RouteTable.Routes.GetVirtualPath() and the routing parameters to generate a URL from the Route object that we created during the route registration process.
Here's a watered-down version of the DocumentHandler HttpHandler class (much omitted for the sake of clarity). You can see that this class uses route parameters to retrieve the document id and the file name when it can; otherwise, it will attempt to retrieve the document id from a query string parameter (i.e., assuming that routing was not used).
public void ProcessRequest(HttpContext context) {
try {
context.Response.Clear();
// Get the requested document ID from routing data, if routed. Otherwise, use the query string.
bool isRouted = false;
int? documentId = null;
string fileName = null;
RequestContext requestContext = context.Request.RequestContext;
if (requestContext != null && requestContext.RouteData != null) {
documentId = Utility.ParseInt32(requestContext.RouteData.Values["documentId"] as string);
fileName = Utility.Trim(requestContext.RouteData.Values["fileName"] as string);
isRouted = documentId.HasValue;
}
// Try the query string if no documentId obtained from route parameters.
if (!isRouted) {
documentId = Utility.ParseInt32(context.Request.QueryString["id"]);
fileName = null;
}
if (!documentId.HasValue) { // Bad request
// Response logic for bad request omitted for sake of simplicity
return;
}
DocumentDetails documentInfo = ... // Details of loading this information omitted
if (context.Response.IsClientConnected) {
string fileExtension = string.Empty;
try { fileExtension = Path.GetExtension(fileName ?? documentInfo.FileName); } // Use file name provided in URL, if provided, to get the extension.
catch { }
// Transmit the file to the client.
FileInfo file = new FileInfo(documentInfo.StoragePath);
using (FileStream fileStream = file.OpenRead()) {
// If the file size exceeds the threshold specified in the system settings, then we will send the file to the client in chunks.
bool mustChunk = fileStream.Length > Math.Max(SystemSettings.Default.MaxBufferedDownloadSize * 1024, DocumentHandler.SecondaryBufferSize);
// WARNING! Do not ever set the following property to false!
// Doing so causes each chunk sent by IIS to be of the same size,
// even if a chunk you are writing, such as the final chunk, may
// be shorter than the rest, causing extra bytes to be written to
// the stream.
context.Response.BufferOutput = true;
context.Response.ContentType = MimeMap.GetMimeType(fileExtension);
context.Response.AddHeader("Content-Length", fileStream.Length.ToString(CultureInfo.InvariantCulture));
if ( !isRouted
|| string.IsNullOrWhiteSpace(fileName)
|| string.IsNullOrWhiteSpace(fileExtension)) { // If routed and a file name was provided in the route, then the URL will appear to point directly to a file, and no file name header is needed; otherwise, add the header.
context.Response.AddHeader("Content-Disposition", string.Format("attachment; filename={0}", HttpUtility.UrlEncode(documentInfo.FileName)));
}
int bufferSize = DocumentHandler.SecondaryBufferSize;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, bufferSize)) > 0 && context.Response.IsClientConnected) {
context.Response.OutputStream.Write(buffer, 0, bytesRead);
if (mustChunk) {
context.Response.Flush();
}
}
}
}
}
catch (Exception e) {
// Error handling omitted from this example.
}
}
This example uses some additional custom classes, such as a Utility class to simplify some trivial tasks. But hopefully you can weed through that. The only really important part in this class with regard to the current topic, of course, is the retrieval of the route parameters from context.Request.RequestContext.RouteData. But I've seen several posts elsewhere asking how to stream large files using an HttpHandler without chewing up server memory, so it seemed like a good idea to combine examples.

Asp.Net 3.5 Routing to Webservice?

I was looking for a way to route http://www.example.com/WebService.asmx to http://www.example.com/service/ using only the ASP.NET 3.5 Routing framework without needing to configure the IIS server.
Until now I have done what most tutorials told me, added a reference to the routing assembly, configured stuff in the web.config, added this to the Global.asax:
protected void Application_Start(object sender, EventArgs e)
{
RouteCollection routes = RouteTable.Routes;
routes.Add(
"WebService",
new Route("service/{*Action}", new WebServiceRouteHandler())
);
}
...created this class:
public class WebServiceRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
// What now?
}
}
...and the problem is right there, I don't know what to do. The tutorials and guides I've read use routing for pages, not webservices. Is this even possible?
Ps: The route handler is working, I can visit /service/ and it throws the NotImplementedException I left in the GetHttpHandler method.
Just thought I would round off this question with a bit more of a detailed solution based on the answer given by Markives that worked for me.
Firstly here is the route handler class which takes the virtual directory to your WebService as its constructor param.
public class WebServiceRouteHandler : IRouteHandler
{
private string _VirtualPath;
public WebServiceRouteHandler(string virtualPath)
{
_VirtualPath = virtualPath;
}
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new WebServiceHandlerFactory().GetHandler(HttpContext.Current,
"*",
_VirtualPath,
HttpContext.Current.Server.MapPath(_VirtualPath));
}
}
and the actual usage of this class within the routey bit of Global.asax
routes.Add("SOAP",
new Route("soap", new WebServiceRouteHandler("~/Services/SoapQuery.asmx")));
This is for anyone else who wants to do the above. I found it incredibly difficult to find the information.
In the GetHttpHandler(byVal requestContext as RequestContext) as IHttpHandler Implements IRouteHandler.GetHttpHandler method (my version of the above)
This is for Webforms 3.5 by the way (mine's in VB).
You cant use the usual BuildManager.CreateInstanceFromVirtualPath() method to invoke your web servce that's only for things that implement iHttpHandler which .asmx doesn't. Instead you need to:
Return New WebServiceHandlerFactory().GetHandler(
HttpContext.Current, "*", "/VirtualPathTo/myWebService.asmx",
HttpContext.Current.Server.MapPath("/VirtualPathTo/MyWebService.aspx"))
The MSDN documentation says that the 3rd parameter should be the RawURL, passing HttpContext.Current.Request.RawURL doesn't work, but passing the virtual path to the .asmx file instead works great.
I use this functionality so that my webservice can be called by any Website configured in anyway (even a virtual directory) that points (in IIS) to my application can call the application web service using something like "http://url/virtualdirectory/anythingelse/WebService" and the routing will always route this to my .asmx file.
You need to return an object that implements IHttpHandler, that takes care of your request.
You can check out this article on how to implement a webservice using that interface: http://mikehadlow.blogspot.com/2007/03/writing-raw-web-service-using.html
But this is probably closer to what you want http://forums.asp.net/p/1013552/1357951.aspx (There is a link, but it requires registration, so I didnt test)

Help me understand web methods?

I have a method on a page marked with the webmethod and scriptmethod tags..
The method returns a collection of objects to a jquery function as JSON data with no hassles and without me having to manually serialize it.
I am now trying to recreate that same method using a HTTPHandler and was wondering why i have to now manually serialize the data.
What makes the webmethod different?
Because an HTTP handler (kind of) sits above the ASP WebForms Stack, you are totally responsible for the workings and output of the handler.
You can utilise (almost) anything you can get your hands on within the .NET framework, but for sure, an HTTPHandler will be more work than an off-the-shelf solution provided by ASP.NET.
The ASP.NET page handler is only one
type of handler. ASP.NET comes with
several other built-in handlers such
as the Web service handler for .asmx
files.
You can create custom HTTP handlers
when you want special handling that
you can identify using file name
extensions in your application
See http://msdn.microsoft.com/en-us/library/ms227675(VS.85).aspx
Web method provides you a connection between your c# class and Js file. Nowadays Using Json is a best way to get the return message in a smart format for a js function or anywhere in js file.
Regards
For lesser work:
Move your method to an ASMX (Web Service):
You will benefit the built-in serialization provided by the ScriptService:
namespace WS{
[System.web.Script.Services.ScriptService()]
[System.Web.Services.WebService(Namespace:="http://tempuri.org/")]
public class WebService1 : System.Web.Services.WebService
{
[WebMethod]
public Person GetDummyPerson()
{
Person p = new Person();
p.Name = "John Wayne";
p.Age = 20;
}
[WebMethod]
public IList GetPersonsByAge(int age)
{
//do actual data retrieval
List result = new List();
result.add(new Person());
result.add(new Person());
return result;
}
}
class Person
{
String Name;
int Age;
}
}
On the client side:
WS.GetDummyPerson(function(p){
alert(p.Name + "-->" + p.Age);
});
WS.GetPersonsByAge(10,function(list){
for(var i=0;i<list.length;i++)
{
document.write(list[i].Name + "==>" + list[i].Age);
}
});

Resources