Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

URL rewrite in ASP.NET 4.5 and Web API

We've got a long-running ASP.NET web-forms application that was born in the .NET 1.1/IIS6 days. We're now on .NET4.5/IIS7 but we've done nothing with MVC.

We provide a catalog to customers and give them a URL they can use:

www.ourhost.com/customername

Using a custom IHttpModule we developed we pull 'customername' out of the URL to find the customer in the database. That customer's ID is then stored in the page's context* and used by virtually all the pages on the site to customize content for that customer. After this process, the above URL would be rewritten and processed as

www.ourhost.com/index.aspx

with index.aspx having access to the customer's ID via its context and it can do its thing.

This works great and we support several thousand customers with it. the rewriting logic is fairly complex because it validates customer accounts, redirects to a 'uh oh' page if the customer is invalid and to a different 'find a dealer' page if the customer has not paid, etc. etc.

Now I'd like to build some Web API controllers and MVC-style rewriting has me worried. I see many examples where rewriting happens to make URL's like this work:

www.ourhost.com/api/{controller}

but I still need these web api 'calls' to happen in the context of a customer. Our pages are getting more sophisticated with JSON/AJAX async calls but in answering those calls I still need customer context. I would like the URL's to be

www.ourhost.com/customername/api/{controller}

But I am stumped as to how to configure routing to do this and have it play nicely with our IHttpModule.

Is this even possible?

*UPDATE: When I say 'stored in the page context' I mean the HttpContext associated with each web request that includes a dictionary where I can store some page/request-specific data.

like image 786
n8wrl Avatar asked Nov 03 '22 23:11

n8wrl


1 Answers

There are two parts of the answer to your issue that I can see.

Maintaining the User Info across multiple requests Generally an MVC API application will be stateless, that is you do not retain the current users session state between requests. Well that is what I have learned or been preached many times when writing RESTFul APIs.

That been said, you can enable session state in MVC Web API by adding the following to your global.asax.cs

    protected void Application_PostAuthorizeRequest()
    {
        // To enable session state in the WebAPI.
        System.Web.HttpContext.Current.SetSessionStateBehavior(System.Web.SessionState.SessionStateBehavior.Required);
    }

Authorising A Customer in the Request As you have shown in the Request URL you could add the customer name, then capture that and pass it to the same routine that your current http module calls to authorise on request. You could do this with an MVC Filter.

First do a similar URL Pattern to capture your customers name in the WebApiConfig.cs, something like so;

        config.Routes.MapHttpRoute(
            name: "WithCustomerApi",
            routeTemplate: "api/{customername}/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

Then add an ActionFilter to your API Controller which processes each request, checks current session info and if needed calls your authorise/customer lookup code and then saves to session state for later use. Or if no good info from customer can send to a new MVC route

So you will add an attribute something like so;

[WebApiAuthentication]
public class BaseApiController : ApiController
{
}

Then create an action filter that might look like this (note I have not tested this, just done for a pattern of how to).

public class WebApiAuthenticationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var routeData = actionContext.ControllerContext.Request.GetRouteData();
        var currentContext = HttpContext.Current;

        if (routeData.Route.RouteTemplate.Contains("customername"))
        {
            try
            {
                var authenticated = currentContext.Request.IsAuthenticated;
                if (!authenticated)
                {
                    var customer = routeData.Values["customername"];
                    // do something with customer here and then put into session or cache
                    currentContext.Session.Add("CustomerName", customer);
                }
            }
            catch (Exception exception)
            {
                var error = exception.Message;
                // We dont like the request
                actionContext.Response = new HttpResponseMessage(HttpStatusCode.BadRequest);
            }
        }
        else
        {
            // No customer name specified, send bad request, not found, what have you ... you *could* potentially redirect but we are in API so it probably a service request rather than a user
            actionContext.Response = new HttpResponseMessage(HttpStatusCode.NotFound);
        }

    }
}

If you create a new MVC 5 Web API Application and add in these extras and put the filter on the default values controller like so you should be able to see this running as demo of a possible solution.

This will echo the customer name back if all works ok.

[WebApiAuthentication]
public class ValuesController : ApiController
{
    // GET api/values
    public IEnumerable<string> Get()
    {
        var session = HttpContext.Current.Session;

        if (session != null)
        {
            return new string[] {"session is present", "customer is", session["CustomerName"].ToString()};
        }

        return new string[] { "value1", "value2" };
    }

}

I offer this up as a possible solution as I say, there are religious arguments about storing session and authorising in an API but those are not the question. Hope that helps, Steve

like image 176
Wortho Avatar answered Nov 15 '22 11:11

Wortho