Override host of webapi odata links - asp.net

I'm using WebAPI 2.2 and Microsoft.AspNet.OData 5.7.0 to create an OData service that supports paging.
When hosted in the production environment, the WebAPI lives on a server that is not exposed externally, hence the various links returned in the OData response such as the #odata.context and #odata.nextLink point to the internal IP address e.g. http://192.168.X.X/<AccountName>/api/... etc.
I've been able to modify the Request.ODataProperties().NextLink by implementing some logic in each and every ODataController method to replace the internal URL with an external URL like https://account-name.domain.com/api/..., but this is very inconvenient and it only fixes the NextLinks.
Is there some way to set an external host name at configuration time of the OData service? I've seen a property Request.ODataProperties().Path and wonder if it's possible to set a base path at the config.MapODataServiceRoute("odata", "odata", GetModel()); call, or in the GetModel() implementation using for instance the ODataConventionModelBuilder?
UPDATE: The best solution I've come up with so far, is to create a BaseODataController that overrides the Initialize method and checks whether the Request.RequestUri.Host.StartsWith("beginning-of-known-internal-IP-address") and then do a RequestUri rewrite like so:
var externalAddress = ConfigClient.Get().ExternalAddress; // e.g. https://account-name.domain.com
var account = ConfigClient.Get().Id; // e.g. AccountName
var uriToReplace = new Uri(new Uri("http://" + Request.RequestUri.Host), account);
string originalUri = Request.RequestUri.AbsoluteUri;
Request.RequestUri = new Uri(Request.RequestUri.AbsoluteUri.Replace(uriToReplace.AbsoluteUri, externalAddress));
string newUri = Request.RequestUri.AbsoluteUri;
this.GetLogger().Info($"Request URI was rewritten from {originalUri} to {newUri}");
This perfectly fixes the #odata.nextLink URLs for all controllers, but for some reason the #odata.context URLs still get the AccountName part (e.g. https://account-name.domain.com/AccountName/api/odata/$metadata#ControllerName) so they still don't work.

Rewriting the RequestUri is sufficient to affect #odata.nextLink values because the code that computes the next link depends on the RequestUri directly. The other #odata.xxx links are computed via a UrlHelper, which is somehow referencing the path from the original request URI. (Hence the AccountName you see in your #odata.context link. I've seen this behavior in my code, but I haven't been able to track down the source of the cached URI path.)
Rather than rewrite the RequestUri, we can solve the problem by creating a CustomUrlHelper class to rewrite OData links on the fly. The new GetNextPageLink method will handle #odata.nextLink rewrites, and the Link method override will handle all other rewrites.
public class CustomUrlHelper : System.Web.Http.Routing.UrlHelper
{
public CustomUrlHelper(HttpRequestMessage request) : base(request)
{ }
// Change these strings to suit your specific needs.
private static readonly string ODataRouteName = "ODataRoute"; // Must be the same as used in api config
private static readonly string TargetPrefix = "http://localhost:8080/somePathPrefix";
private static readonly int TargetPrefixLength = TargetPrefix.Length;
private static readonly string ReplacementPrefix = "http://www.contoso.com"; // Do not end with slash
// Helper method.
protected string ReplaceTargetPrefix(string link)
{
if (link.StartsWith(TargetPrefix))
{
if (link.Length == TargetPrefixLength)
{
link = ReplacementPrefix;
}
else if (link[TargetPrefixLength] == '/')
{
link = ReplacementPrefix + link.Substring(TargetPrefixLength);
}
}
return link;
}
public override string Link(string routeName, IDictionary<string, object> routeValues)
{
var link = base.Link(routeName, routeValues);
if (routeName == ODataRouteName)
{
link = this.ReplaceTargetPrefix(link);
}
return link;
}
public Uri GetNextPageLink(int pageSize)
{
return new Uri(this.ReplaceTargetPrefix(this.Request.GetNextPageLink(pageSize).ToString()));
}
}
Wire-up the CustomUrlHelper in the Initialize method of a base controller class.
public abstract class BaseODataController : ODataController
{
protected abstract int DefaultPageSize { get; }
protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var helper = new CustomUrlHelper(controllerContext.Request);
controllerContext.RequestContext.Url = helper;
controllerContext.Request.ODataProperties().NextLink = helper.GetNextPageLink(this.DefaultPageSize);
}
Note in the above that the page size will be the same for all actions in a given controller class. You can work around this limitation by moving the assignment of ODataProperties().NextLink to the body of a specific action method as follows:
var helper = this.RequestContext.Url as CustomUrlHelper;
this.Request.ODataProperties().NextLink = helper.GetNextPageLink(otherPageSize);

The answer by lencharest is promising, but I found an improvement on his method. Rather than using the UrlHelper, I created a class derived from System.Net.Http.DelegatingHandler. This class is inserted (first) into the message handling pipeline and thus has a crack at altering the incoming HttpRequestMessage. It's an improvement over the above solution because in addition to altering the controller-specific URLs (as the UrlHelper does, e,g, https://data.contoso.com/odata/MyController), it also alters the url that appears as the xml:base in the OData service document (e.g., https://data.contoso.com/odata).
My particular application was to host an OData service behind a proxy server, and I wanted all the URLs presented by the server to be the externally-visible URLs, not the internally-visible ones. And, I didn't want to have to rely on annotations for this; I wanted it to be fully automatic.
The message handler looks like this:
public class BehindProxyMessageHandler : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
var builder = new UriBuilder(request.RequestUri);
var visibleHost = builder.Host;
var visibleScheme = builder.Scheme;
var visiblePort = builder.Port;
if (request.Headers.Contains("X-Forwarded-Host"))
{
string[] forwardedHosts = request.Headers.GetValues("X-Forwarded-Host").First().Split(new char[] { ',' });
visibleHost = forwardedHosts[0].Trim();
}
if (request.Headers.Contains("X-Forwarded-Proto"))
{
visibleScheme = request.Headers.GetValues("X-Forwarded-Proto").First();
}
if (request.Headers.Contains("X-Forwarded-Port"))
{
try
{
visiblePort = int.Parse(request.Headers.GetValues("X-Forwarded-Port").First());
}
catch (Exception)
{ }
}
builder.Host = visibleHost;
builder.Scheme = visibleScheme;
builder.Port = visiblePort;
request.RequestUri = builder.Uri;
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
You wire the handler up in WebApiConfig.cs:
config.Routes.MapODataServiceRoute(
routeName: "odata",
routePrefix: "odata",
model: builder.GetEdmModel(),
pathHandler: new DefaultODataPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);
config.MessageHandlers.Insert(0, new BehindProxyMessageHandler());

There is another solution, but it overrides url for the entire context.
What I'd like to suggest is:
Create owin middleware and override Host and Scheme properties inside
Register the middleware as the first one
Here is an example of middleware
public class RewriteUrlMiddleware : OwinMiddleware
{
public RewriteUrlMiddleware(OwinMiddleware next)
: base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
context.Request.Host = new HostString(Settings.Default.ProxyHost);
context.Request.Scheme = Settings.Default.ProxyScheme;
await Next.Invoke(context);
}
}
ProxyHost is the host you want to have. Example: test.com
ProxyScheme is the scheme you want: Example: https
Example of middleware registration
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(RewriteUrlMiddleware));
var config = new HttpConfiguration();
WebApiConfig.Register(config);
app.UseWebApi(config);
}
}

A couple of years later, using ASP.NET Core, I figured that the easiest way to apply it in my service was to just create a filter that masquerades the host name. (AppConfig is a custom configuration class that contains the host name, among other things.)
public class MasqueradeHostFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
var appConfig = context.HttpContext.RequestServices.GetService<AppConfig>();
if (!string.IsNullOrEmpty(appConfig?.MasqueradeHost))
context.HttpContext.Request.Host = new HostString(appConfig.MasqueradeHost);
}
}
Apply the filter to the controller base class.
[MasqueradeHostFilter]
public class AppODataController : ODataController
{
}
The result is a nicely formatted output:
{ "#odata.context":"https://app.example.com/odata/$metadata" }
Just my two cents.

Using system.web.odata 6.0.0.0.
Setting the NextLink property too soon is problematic. Every reply will then have a nextLink in it. The last page should of course be free of such decorations.
http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793048 says:
URLs present in a payload (whether request or response) MAY be
represented as relative URLs.
One way that I hope will work is to override EnableQueryAttribute:
public class myEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
var result = base.ApplyQuery(queryable, queryOptions);
var nextlink = queryOptions.Request.ODataProperties().NextLink;
if (nextlink != null)
queryOptions.Request.ODataProperties().NextLink = queryOptions.Request.RequestUri.MakeRelativeUri(nextlink);
return result;
}
}
ApplyQuery() is where the "overflow" is detected. It basically asks for pagesize+1 rows and will set NextLink if the result set contains more than pagesize rows.
At this point it is relatively easy to rewrite NextLink to a relative URL.
The downside is that every odata method must now be adorned with the new myEnableQuery attribute:
[myEnableQuery]
public async Task<IHttpActionResult> Get(ODataQueryOptions<TElement> options)
{
...
}
and other URLs embedded elsewhere remains problematic. odata.context remains a problem. I want to avoid playing with the request URL, because I fail to see how that is maintainable over time.

Your question boils down to controlling the service root URI from within the service itself. My first thought was to look for a hook on the media type formatters used to serialize responses. ODataMediaTypeFormatter.MessageWriterSettings.PayloadBaseUri and ODataMediaTypeFormatter.MessageWriterSettings.ODataUri.ServiceRoot are both settable properties that suggest a solution. Unfortunately, ODataMediaTypeFormatter resets these properties on every call to WriteToStreamAsync.
The work-around is not obvious, but if you dig through the source code you'll eventually reach a call to IODataPathHandler.Link. A path handler is an OData extension point, so you can create a custom path handler that always returns an absolute URI which begins with the service root you desire.
public class CustomPathHandler : DefaultODataPathHandler
{
private const string ServiceRoot = "http://example.com/";
public override string Link(ODataPath path)
{
return ServiceRoot + base.Link(path);
}
}
And then register that path handler during service configuration.
// config is an instance of HttpConfiguration
config.MapODataServiceRoute(
routeName: "ODataRoute",
routePrefix: null,
model: builder.GetEdmModel(),
pathHandler: new CustomPathHandler(),
routingConventions: ODataRoutingConventions.CreateDefault()
);

Related

How create custom authorization attribute for checking a role and url path in Asp.Net Core?

I want to create a custom authorization attribute for checking the role and url path.
I've find the way for doing it in the Asp.Net Core using the Policy-Based-Authorization but I've tried implement it but I can't get the HttpContext with incomming url.
AuthorizationHandlerContext hasn't access to HttpContext probable.
How can I get current HttpContext with the url path? Is it possible to do that or with another way?
I've tried this code for creating the custom Policy:
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var path = //Here I need get current url path for example - /api/posts/4545411
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
I want to create following:
[Authorize(Policy="RoleUrlValidation")] //Get ClientId from Url and check User's roles
public class PostsController : Controller
{
public ActionResult Get()
{
}
}
The policy approach is the right one. Only bit you missed is, that you can use Dependency Injection in the Handlers.
public class RoleUrlValidationHandler : AuthorizationHandler<RoleUrlValidationRequirement>
{
private readonly IHttpContextAccessor contextAccessor;
public RoleUrlValidationHandler(IHttpContextAccessor contextAccessor)
{
this.contextAccessor = contextAccessor;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleUrlValidationRequirement requirement)
{
var httpContext = contextAccessor.HttpContext;
var path = httpContext.Request.Path;
var pathPart = path.Split('/');
var clientId = pathPart[3];
if (context.User.IsInRole(clientId))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
You also may have to register the IHttpContextAccessor as its not registered by default.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
Extra bits:
Consider using var routeData = httpContext.GetRouteData() instead of using path.Split('/') for reading values from it so you can easily read the parameter values from the route.
Try like this:
((DefaultHttpContext)context.Resource).Request.Path.Value

Setting OData result Page Size at runtime in WebAPI app

I am writing an ASP.NET Web API 2 web service using OdataControllers I have found out how to set page size using the PageSize Property of the EnableQueryAttribute. I want to allow the users of my web service to set the page size in the app.config and then have the application read this setting and set the page size. The problem is that using the attribute requires Page Size be set to a compile time constant.
Usage of attribute:
[EnableQuery(PageSize = 10)]
public IHttpActionResult GetProducts()
{
return repo.GetProducts();
}
One proposed solution I have seen is to construct the EnableQueryAttribute and set it on the HTTPConfiguration config object like this
int customSize = ReadPageSizeSettingFromConfigFile();
var attr = new EnableQueryAttribute { PageSize = customSize };
config.AddODataQueryFilter(attr);
but this doesn't actually work. The HttpConfiguration's Filter collection remains empty.
A comment on another post (buried in a list of comments) suggested removing all the EnableQuery attributes on the controllers but that has no effect either. Since the EnableQuery attribute replaced the older Queryable attribute I am wondering if this is a Microsoft problem.
This question has been asked and not answered before: How limit OData results in a WebAPI
All help is greatly appreciated.
You can use $top and $skip to achieve your goal, if client want pagesize is 10, and want the second page:
localhost/odata/Customers?$top=10&$skip=10
About dynamically set the pagesize:
public class MyEnableQueryAttribute : EnableQueryAttribute
{
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
int pagesize = xxx;
var result = queryOptions.ApplyTo(queryable, new ODataQuerySettings { PageSize = pagesize });
return result;
}
}
and put this attribute in your controller method.
You can set config for MaxTop in webApiConfig resgister method
public static class WebApiConfig{
public static void Register(HttpConfiguration config){
config.Select().Expand().Filter().OrderBy().MaxTop(100).count() // you can change max page size here
}
}
I was able to accomplish this by creating a new attribute (I called it ConfigurableEnableQueryAttribute) that extends the enable query attribute. In the constructor for that attribute, load your config file and set any settings you are interested in in the base. Personally I loop through all settings provided in an "OData" section of my appsettings, and if there are matching settings in the EnableQuery attribute, I cast them to the specified type and supply them, but you can only look for specific settings if you want.
My attribute:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ConfigurableEnableQueryAttribute : EnableQueryAttribute
{
public ConfigurableEnableQueryAttribute()
{
var builder = new ConfigurationBuilder().AddJsonFile("appsettings.json");
var configuration = builder.Build();
var configProps = configuration.GetSection("OData").GetChildren();
var baseProps = typeof(EnableQueryAttribute).GetProperties();
foreach (var configProp in configProps)
{
var baseProp = baseProps.FirstOrDefault(x => x.Name.Equals(configProp.Key));
if (baseProp != null)
{
baseProp.SetValue(this, Convert.ChangeType(configProp.Value, baseProp.PropertyType));
}
}
}
}
and then in the controller
[HttpGet]
[ConfigurableEnableQuery]
public IQueryable<T> Get()
{
return _context.Set<T>().AsQueryable();
}

Asp.Net Web Api - attribute for not binding/formatting a parameter?

I have a method on an ApiController that looks like this:
public IEnumerable<Items> GetSlideSets() {
IServiceClass serviceClass = new ServiceClass();
//...
Yes, I am aware that this is not good design but I'm addressing this issue in a different iteration.
At a certain point in my application I need to call this functionality from within the project itself so I thought I could simply reuse the controller (and why not, I can pluck it out of my IoC container). The only problem is that in this case, I need to inject my own implementation of IServiceClass, easy enough:
public IEnumerable<Items> GetSlideSets(IServiceClass serviceClass = null) {
serviceClass = serviceClass ?? new ServiceClass();
//...
Except now I am getting errors when calling this via a regular Api call Optionalparameter 'serviceClass' is not supported by FormatterParameterBinding.
I know that there are various attributes that control bindings. Is there one that I can put on the parameter to say it shouldn't bind.
Like others have mentioned, it's probably a better idea to inject the dependency in the constructor.
But if you really must avoid binding an action parameter, there isn't a built-in attribute but you can create one pretty easily. Here's what it could look like:
public class DontBindAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor parameter)
{
return new DontBindParameterBinding(parameter);
}
private class DontBindParameterBinding : HttpParameterBinding
{
public DontBindParameterBinding(HttpParameterDescriptor parameter) : base(parameter)
{
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext, CancellationToken cancellationToken)
{
actionContext.ActionArguments.Add(Descriptor.ParameterName, Descriptor.DefaultValue);
var completedTaskSource = new TaskCompletionSource<object>();
completedTaskSource.SetResult(null);
return completedTaskSource.Task;
}
}
}
You just need to apply the attribute to the parameter afterwards:
public IEnumerable<Items> GetSlideSets([DontBind] IServiceClass serviceClass = null)

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.

Unit testing http handlers?

My current project based in Asp .net makes considerable use of Http handlers to process various requests? So, is there any way by which I can test the functionality of each of the handlers using unit test cases? We are using Nunit and Moq framework to facilitate unit testing.
I think these blog entries from a while back are relevant:
http://www.kongsli.net/nblog/2009/05/03/aspnet-35-improving-testability-with-systemwebabstractions/
http://www.kongsli.net/nblog/2009/05/28/testability-with-systemwebabstractions-and-no-mock-framework/
See example #2 in the first post for an example on how to unit test an HttpHandler.
If you dont care about unit tests and want something quick and dirty you can use Fiddler
if you want a more integrated approach (Unit testing) you can use the WebRequest and WebResponse.
You sure can, although I haven't done myself "in anger".
Use a System.Net.WebClient to make HTTP calls against your handlers, and evaluate what comes back, that will allow you to test the public facing interface of the handler.
In this example I've hard-coded my target, and I'm using a method on the WebClient that will return a string.
The WebClient also gives you access to the ResponseHeaders, Encoding and other useful 'webby' stuff; you can also upload info as well.
using System.Net;
namespace UnitTestHttpHandler
{
public class TestHarness
{
public static string GetString()
{
WebClient myWebClient = new WebClient();
return myWebClient.DownloadString("http://localhost/Morphfolia.Web/ContentList.ashx");
}
}
}
You can then use the TestHarness to call the target HttpHandler and verify the results in your tests (or use a better approach to your testing if you know one - I'm not a unit testing guru).
[TestMethod]
public void TestMethod1()
{
string x = UnitTestHttpHandler.TestHarness.GetString();
Assert.IsTrue(x.Length > 5);
}
The default interface for IHttpHandler is not testable because the param for ProcessRequest(HttpContext context) is not mockable.
This answer is inspired by this post.
To make your IHttpHandler implementation testable you must first make a small change so we can use the mockable HttpContextBase:
From:
class YourHttpHandler : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
{
/* Your handler implementation */
}
}
To:
class YourHttpHandler : IHttpHandler
{
public bool IsReusable => true;
public void ProcessRequest(HttpContext context)
=> ProcessRequest(new HttpContextWrapper(context));
public virtual void ProcessRequest(HttpContextBase context)
{
/* Your handler implementation */
}
}
Your functionality needs to be moved from ProcessRequest(HttpContext context) to ProcessRequest(HttpContextBase context).
The ProcessRequest(HttpContextBase context) method can now be called in your tests with a mock object to verify the functionality within.
It's useful the create a helper class that can be used to quickly instantiate YourHttpHandler, send a mocked request, and get access to the response.
public class YourHttpHandlerTester : YourHttpHandler
{
public class Response
{
public HttpResponseBase HttpResponse { get; }
public string Body { get; }
public Response(HttpResponseBase httpResponse, string body)
{
HttpResponseBase = httpResponse;
Body = body;
}
}
public Response ProcessRequest(HttpRequestBase httpRequest)
{
var memoryStream = new MemoryStream();
var httpResponse = CreateHttpResponse(memoryStream);
var httpContext = CreateHttpContext(httpRequest, httpResponse);
base.ProcessRequest(httpContext);
var response = CreateResponse(httpResponse, memoryStream);
return response;
}
protected virtual HttpResponseBase CreateHttpResponse(MemoryStream memoryStream)
{
var httpResponseBaseMock = new Moq.Mock<HttpResponseBase>();
httpResponseBaseMock.Setup(x => x.OutputStream).Returns(memoryStream);
return httpResponseBaseMock.Object;
}
protected virtual HttpContextBase CreateHttpContext(HttpRequestBase httpRequest, HttpResponseBase httpResponse)
{
var httpContextBaseMock = new Moq.Mock<HttpContextBase>();
httpContextBaseMock.Setup(x => x.Request).Returns(httpRequest);
httpContextBaseMock.Setup(x => x.Response).Returns(httpResponse);
return httpContextBaseMock.Object;
}
protected virtual Response CreateResponse(HttpResponseBase httpResponse, MemoryStream memoryStream)
{
memoryStream.Position = 0;
var body = new StreamReader(memoryStream).ReadToEnd();
var response = new Response(httpResponse, body);
return response;
}
}
This class can be used to quickly create readable tests:
[Fact]
public void Test()
{
var httpHandler = new YourHttpHandlerTester();
var request = CreateHttpRequestFromString("test");
var response = testHandler.ProcessRequest(request);
Assert.NotEmpty(response.Body);
}
public HttpRequestBase CreateHttpRequestFromString(string body)
{
var httpRequestBaseMock = new Moq.Mock<HttpRequestBase>();
var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(body)))
httpRequestBaseMock.Setup(x => x.InputStream).Returns(stream);
return httpRequestBaseMock.Object;
}
YourHttpHandlerTester has plenty of virtual methods that can be overridden as necessary.
Likewise, the Response class can be improved so that it exposes methods from HttpResponseBase like the http status code, headers, and anything else you may want to check.
You can do INTEGRATION testing of the handler using the methods mentioned in the other answers, to do UNIT testing you will need to create some interfaces and extract the core functionality out of the handler, as well as create some mock objects.
You won't be able to unit test ALL parts of it because it relies upon outside resources (those you'll be mocking) - but that's fine, thats why we HAVE integration testing.
If you want to test the communication between your handlers and the Web UI then yes, integration testing is the way to go for that. In order to unit test your logic, could you not instead separate your business logic into other classes (I'd use a separate assembly for the business layer) and mock / unit test these classes instead outside of your presentation layer?
Once you have a structured (and unit tested) business layer that has been separated from the presentation layer your handlers can simply instantiate your concretes and invoke the provided methods. Once this is done, you can then move onto integration testing as your business logic will have been unit tested.

Resources