.NET Development
HttpClient Post AntiforgeryToken + model
Gravatar is a globally recognized avatar based on your email address. HttpClient Post AntiforgeryToken + model
  David
  All
  Feb 28, 2017 @ 12:45pm

Hi,

I have a class library that targets .NET Standard. If necessary, I can switch to .NET Framework. Currently, I am generating the message content with a List, to generate a FormUrlEncodedContent. This way, I am able to pass the Antiforgery token, along with other values. I post this to an MVC app. This works fine. I want to expand this to serialize a model, along with the Antiforgery token.

I can serialize the model and add it to the KeyValuePair, along with the Antifogery token. I have an MVC action that receives a single paramter, of type matching the model type, and name matching the KeyValuePair name. It includes a [ValidateAntiforgeryToken] annotation.

If I trace the code, the controller validates the Antifogery token. The Request.Form shows both the Antiforgery token and the serialized model. However, the received parameter has null values.

If I add [FromBody] to the controller action, I am unable to trace into the action. The HttpClient.PostAsync returns: 415 - Unsupported Media Type.

The default ContentType from the FormUrlEncodedContent is application/x-www-form-urlencoded. Changing that to application/json, return an error 400 - Bad Request, whether I specify [FromBody] or not. I tried adding the Antiforgery token as a JSON string, but I have to have a name for both the Key and the Value. Thus far that does not work.

Any ideas? Originally, I serialized the model, encrypted it, and added it as a string. That is a possibility, but I would prefer to not have to do that.

Thanks,

David

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  Rick Strahl
  David
  Feb 28, 2017 @ 02:13pm

Have you fired up Fiddler to see what you're actually sending to the server? My guess is there's something wrong with the client content type going up. It should application/x-www-form-urlencoded - are you setting that explicitly? Because HttpClient doesn't automatically do it.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  David
  Rick Strahl
  Feb 28, 2017 @ 03:12pm

Rick,

  1. Post Content without model:

var pairs = new List<KeyValuePair<string, string>>();
pairs.Add(new KeyValuePair<string, string>(antiforgeryTokenName, antiforgeryTokenValue));
if (paramValue.Length > 0) pairs.Add(new KeyValuePair<string, string>(paramName, paramValue));
var content = new FormUrlEncodedContent(pairs);

This creates conent, with ContentType = application/x-www-form-urlencoded.
I call that with HttpClient.PostAsync();

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<List<QCItemsLookup>> GetQCItems()
{
    return await _context.QCItemsLookup.ToListAsync();
}

I can call an MVC controller action. It validates the Antiforgery token and passes the string parameter fine.

  1. Post Content with model:

var pairs = new List<KeyValuePair<string, string>>();
pairs.Add(new KeyValuePair<string, string>(antiforgeryTokenName, antiforgeryTokenValue));
if (paramValue.Length > 0) pairs.Add(new KeyValuePair<string, string>(paramName, JsonConvert.SerializeObject(paramValue)));
var content = new FormUrlEncodedContent(pairs);

This is when I have a problem.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<bool> StartQCRequest(RequestFileType fileType)
{
    *** I removed the code for simplicity.  I can set a code break here.
}

From code break:

HttpContext.Request.ContentType = "application/x-www-form-urlencoded"
HttpContext.Request.Form:
Keys = 2
{[__RequestVerificationToken, CfDJ8DRE1h6Y_k5Cqaz8RP82ZNJpIBFzhCVnoryfK_xORtSJ2YNm7TnCzOPxmxAr7YSed13AUhZ-xe70OyRHfo730J9zaywD6bsBCjwYTXIAna3gIN56rVnPKIUBEJQvyIw9_PPBBW8uHWeMYGDbLdhv2oP539YiaqD1VIzSYTV_5XSnMbqEwmH719vbdDTMb1cN8g]}
{[fileType, {"RequestID":155,"FileType":"R"}]}

Variable fileType
fileType.FileType = null
fileType.RequestID = 0

***** It is passing the model as JSON, but it is not deserializing to the action variable.

With [FromBody] <- Causes "Unsupported Media Type" error

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<bool> StartQCRequest([FromBody]RequestFileType fileType)
{
    *** I removed the code for simplicity.  I can set a code break here.
}
var result = await ApiClient.PostAsync(endpoint, content);
result.StatusCode = 415
result.ReasonPhrase = "Unsupported Media Type"

Manually setting ContentType: In both cases, I tried setting the ContentType to application/json content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); In both cases I get a "Bad Request" error. I, also, tried adding the Antiforgery token to the KeyValuePair formatted as a JSON string, but that does not work either.

Thanks,

Davd

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  Rick Strahl
  David
  Mar 1, 2017 @ 01:14am

Ok - you're posting to an API endpoint, but Web API doesn't support form encoded content mapping. Web API supports only XML and JSON out of the box.

If you're posting form encoded content the easiest way might be to post to an MVC action which can do the UrlEncoded content mapping.

You might search around and see if somebody has built a Web API UrlEncoding message formatter. Taking a quick look around I don't see one though.

The other option you have is just capturing hte output as a string and then picking out the values out of the request explicitly and mapping them to your object.

Personally I would probably go the MVC route. It'll porter better to .NET Core that way anyway.

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  David
  Rick Strahl
  Mar 1, 2017 @ 09:11am

Rick,

It is an MVC app that I am using like a Web API. The allows me to use Microsoft's Antiforgery cookie and token. Posting form encoded content isn't a problem, when I just post simple strings. I have a problem when I post a simple string (the Antiforgery token) and a model serialized as json. I am able to return a model from the MVC app to the client. I just have trouble passing Antiforgery + model from the client to the server.

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  David
  Rick Strahl
  Mar 1, 2017 @ 10:51am

Antiforgery + serialized model + form encoded content works if I modify my controller action:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<bool> StartQCRequest(RequestFileType fileType)
        {
            foreach (var form in Request.Form)
            {
                if (form.Key == "fileType")
                {
                    fileType = JsonConvert.DeserializeObject<RequestFileType>(form.Value);
                }
            }
            return await QualityControl.Start(_context, fileType.RequestID, fileType.FileType, User.EmployeeID(), true);
        }

This is a possible workaround. However, I would like to be able to bind the model to the POST, without having to manually deserialize the model. Forcing ContentType to application/json does not seem to work.

Gravatar is a globally recognized avatar based on your email address. re: HttpClient Post AntiforgeryToken + model
  Rick Strahl
  David
  Mar 2, 2017 @ 04:06pm

Well Request.Form() will always work in MVC.

Looking at your data:

{[fileType, {"RequestID":155,"FileType":"R"}]}

that might be the problem. What does that map to? Array of object? There's no way to deserialize that into anything useful.

Really the best way to debug stuff like this is to open Fiddler and compare the trace that you get from the your form submission and what you're trying to send from the httpclient request.

+++ Rick ---

© 1996-2017