5

I have the following API Controller:

public class TestController : ApiController
{
    [HttpPost]
    [APIAuthorizeAttribute]
    public IQueryable<Computers> ListOfComputersInFolder(Guid folderId)
    {
        return GetListOfComputersForFolder(folderId);
    } // End of ListOfComputersInFolder
} // End of TestController 

And the following is my basic APIAuthorizeAttribute.

public class APIAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
    public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
    {
        var Request = System.Web.HttpContext.Current.Request;
        var folderId = Request.RequestContext.RouteData.Values["folderId"] ?? Request.Params["folderId] as string;
        if(null == folderId)
        {
            folderId = actionContext.ControllerContext.RouteData.Values["folderId"];
        }

        base.OnAuthorization(actionContext);
    }
}

The problem that I'm having is that folderId is coming out null in the onAuthorize method. (I based the fetcher on this code).

It seems to me that this should be working, but I cannot seem to get it to. Any ideas on what I am doing wrong and how I should go about getting the posted parameter?

Edit: I tried reading the post data directly with the following:

using (StreamReader inputStream = new StreamReader(request.InputStream))
{
    output = inputStream.ReadToEnd();
}
request.InputStream.Position = 0;

Which gets me the post data in JSON format which I could then parse, but then my call never makes it though. I get the following exception in the Response:

  <h2>500 - Internal server error.</h2>
  <h3>There is a problem with the resource you are looking for, and it cannot be displayed.

at System.Json.JXmlToJsonValueConverter.JXMLToJsonValue(Stream jsonStream, Byte[] jsonBytes)\u000d\u000a   at System.Net.Http.Formatting.JsonMediaTypeFormatter.<>c__DisplayClass7.<OnReadFromStreamAsync>b__6()\u000d\u000a   at System.Net.Http.Internal.TaskHelpers.RunSynchronously[TResult](Func`1 func, CancellationToken cancellationToken)"}

EDIT: In the end, it seems like this could possibly be a bug with the combination of ApiController, System.Web.Http.AuthorizeAttribute and HttpPost (it does work when using HttpGet). A bug report has been submitted.

Community
  • 1
  • 1
Kyle
  • 17,317
  • 32
  • 140
  • 246

4 Answers4

7

The AuthorizeAttribute should have an AuthorizationContext parameter rather than a HttpActionContext one, from that you should be able to access the RouteData e.g.

public override void OnAuthorization(AuthorizationContext filterContext)
{
    var folderId = filterContext.RouteData.Values["folderId"];
    ...
}

Update

Noticed you are using ApiController and as such using Http.AuthorizeAttribute (explains why you don't have an AuthorizationContext). In that case, you can get the RouteData via the action context e.g.

var folderId = actionContext.Request.GetRouteData().Values["folderId"];
James
  • 80,725
  • 18
  • 167
  • 237
  • 3
    Note that because this is an [APIController](http://msdn.microsoft.com/en-us/library/system.web.http.apicontroller(v=vs.108).aspx) and not a [Controller](http://msdn.microsoft.com/en-us/library/system.web.mvc.controller(v=vs.108).aspx), I use the `System.Web.Http.AuthorizeAttribute` and not the `System.Web.Mvc.AuthorizeAttribute`. I tried accessing its `ControllerContext.RouteData.Values["folderId"] and it still returned null. – Kyle Aug 31 '12 at 14:01
  • thanks I took a look at this as well, but still no luck. The `actionContext.Request` class is a [HttpRequestMessage](http://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessage_methods) and does not contain a `GetRouteData` method. Also if I look at: actionContext.ControllerContext.RouteData.Values it only contains two keys: controller and action. I think my best choice of action would be to read the post data from the inputStream. I gave this a try and confirmed that I can see my post data, but the seems a bit hacky, so I'm going to keep looking for now. – Kyle Aug 31 '12 at 14:31
  • @Zenox the [GetRouteData](http://msdn.microsoft.com/en-us/library/system.net.http.httprequestmessageextensions.getroutedata(v=vs.108).aspx) is an extension method of `HttpRequestMessage`, make sure you are including a reference to the `System.Web.Http` namespace. – James Aug 31 '12 at 14:49
  • ah, thanks. Adding the namespace did add the extension method, but the `keys` property only contains 'controller' and 'action'. From what I am seeing, I think that the `OnAuthorization` method must be called before the framework has populated the rest of the values. – Kyle Aug 31 '12 at 14:59
  • @Zenox The data should be in there somewhere, I can't remember off the top of my head. Possibly the `DataTokens` collection? – James Aug 31 '12 at 15:01
  • I checked the `actionContext.ControllerContext.RouteData.Route.DataTokens` collection and its empty. I'm fairly confident i've walked the entire path of the `actionContext` instance now and cannot find the folderId anywhere. I may start looking for an alternative method to accomplish what I am looking for. Thanks for the help though. – Kyle Aug 31 '12 at 16:09
  • @Zenox I have a custom `AuthorizeAttribute` which in the `OnAuthorization` method access route data via `actionContext.ControllerContext.RouteData.Values` therefore I can't understand why yours doesn't work. MVC4 is still in Beta so there is potential for it to be a bug, but doesn't make sense that it works for me and not you. – James Aug 31 '12 at 16:18
  • 1
    are you using `HttpPost`? Just to test, I switch to `HttpGet` and now I am able to get the data via `System.Web.HttpContext.Current.Request["folderId"]`. – Kyle Aug 31 '12 at 16:45
  • @Zenox ah that's what it must be, I am using `HttpGet`. Weird why you can't get post parameters though. – James Aug 31 '12 at 16:47
1

I have also encountered this problem.

To work around it I wrote the following method which I call from within the OnAuthorization method:

private static object GetValueFromActionContext(HttpActionContext actionContext, string key)
{
    var queryNameValuePairs = actionContext.Request.GetQueryNameValuePairs();

    var value = queryNameValuePairs
        .Where(pair => pair.Key.Equals(key, StringComparison.OrdinalIgnoreCase))
        .Select(pair => pair.Value)
        .FirstOrDefault();

    var methodInfo = ((ReflectedHttpActionDescriptor) (actionContext.ActionDescriptor)).MethodInfo;
    var parameters = methodInfo.GetParameters();
    var parameterType =
        parameters.Single(p => p.Name.Equals(key, StringComparison.OrdinalIgnoreCase)).ParameterType;

    var converter = TypeDescriptor.GetConverter(parameterType);

    return converter.ConvertFromString(value);
}

This code makes the following assumptions:

  • The key you are extracting matches an argument name on the action method.

  • The parameter type you are obtaining will have a converter valid for the type.

  • You are not using any custom binding or formatting on the parameter.

In the scenario that I am using the code I am only expecting simple types such as Guid, Boolean, String etc and could be customised as per your requirements.

The extension method GetQueryNameValuePairs is part of the System.Net.Http.HttpRequestMessageExtensions class and will read querystring / form data.

Example use:

object folderId = GetValueFromActionContext(actionContext, "folderId");
jim.taylor.1974
  • 3,493
  • 1
  • 17
  • 11
1

If the request's contenttype is application/json;charset=utf-8

The API action can retrieve the Post Data as follow:

Stream stream = actionContext.Request.Content.ReadAsStreamAsync().Result;
Encoding encoding = Encoding.UTF8;
stream.Position = 0;
string responseData = "";

using (StreamReader reader = new StreamReader(stream, encoding))
{
    responseData = reader.ReadToEnd().ToString();
}

var dic = JsonConvert.DeserializeObject<IDictionary<string, string>>(responseData);
Adrian Sanguineti
  • 2,455
  • 1
  • 27
  • 29
0

You can give this extension method a try: (this is an excerpt of working code)

public static string GetParameter(this RequestContext requestContext, string key)
{
    if (key == null) throw new ArgumentNullException("key");

    var lowKey = key.ToLower();

    return requestContext.RouteData.Values.ContainsKey(lowKey) &&
           requestContext.RouteData.Values[lowKey] != null
               ? requestContext.RouteData.Values[lowKey].ToString()
               : requestContext.HttpContext.Request.Params[lowKey];
}

I agree with James' answer, you have to access the request context via the actionContext in this scenario.

Shelakel
  • 1,070
  • 9
  • 16